| Overall Statistics |
|
Total Trades 5088 Average Win 0.17% Average Loss -0.33% Compounding Annual Return 114.443% Drawdown 6.300% Expectancy 0.095 Net Profit 118.059% Sharpe Ratio 3.675 Loss Rate 28% Win Rate 72% Profit-Loss Ratio 0.53 Alpha 1.32 Beta -41.643 Annual Standard Deviation 0.174 Annual Variance 0.03 Information Ratio 3.58 Tracking Error 0.174 Treynor Ratio -0.015 Total Fees $19158.71 |
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Orders.Fills;
using QuantConnect.Securities;
using System;
using System.Linq;
namespace QuantConnect.Algorithm.CSharp
{
public sealed class ImmediateOptimisticStopFillModel : ImmediateFillModel
{
/// <summary>
/// Default limit order fill model in the base security class.
/// </summary>
/// <param name="asset">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill information detailing the average price and quantity filled.</returns>
/// <seealso cref="StopMarketFill(Security, StopMarketOrder)"/>
/// <seealso cref="MarketFill(Security, MarketOrder)"/>
public override OrderEvent LimitFill(Security asset, LimitOrder order)
{
//Initialise;
var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
var fill = new OrderEvent(order, utcTime, 0);
//If its cancelled don't need anymore checks:
if (order.Status == OrderStatus.Canceled) return fill;
//Get the range of prices in the last bar:
var prices = GetPrices(asset, order.Direction);
//-> Valid Live/Model Order:
switch (order.Direction)
{
case OrderDirection.Buy:
//Buy limit seeks lowest price
if (prices.Low < order.LimitPrice)
{
//Set order fill:
fill.Status = OrderStatus.Filled;
// fill at the worse price this bar or the limit price, this allows far out of the money limits
// to be executed properly
fill.FillPrice = order.LimitPrice;
}
break;
case OrderDirection.Sell:
//Sell limit seeks highest price possible
if (prices.High > order.LimitPrice)
{
fill.Status = OrderStatus.Filled;
// fill at the worse price this bar or the limit price, this allows far out of the money limits
// to be executed properly
fill.FillPrice = order.LimitPrice;
}
break;
}
// assume the order completely filled
if (fill.Status == OrderStatus.Filled)
{
fill.FillQuantity = order.Quantity;
fill.OrderFee = asset.FeeModel.GetOrderFee(asset, order);
}
return fill;
}
/// <summary>
/// Default stop fill model implementation in base class security. (Stop Market Order Type)
/// </summary>
/// <param name="asset">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill information detailing the average price and quantity filled.</returns>
/// <seealso cref="MarketFill(Security, MarketOrder)"/>
/// <seealso cref="SecurityTransactionModel.LimitFill"/>
public override OrderEvent StopMarketFill(Security asset, StopMarketOrder order)
{
//Default order event to return.
var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
var fill = new OrderEvent(order, utcTime, 0);
// make sure the exchange is open before filling
if (!IsExchangeOpen(asset)) return fill;
//If its cancelled don't need anymore checks:
if (order.Status == OrderStatus.Canceled) return fill;
//Get the range of prices in the last bar:
var prices = GetPrices(asset, order.Direction);
//Calculate the model slippage: e.g. 0.01c
var slip = asset.SlippageModel.GetSlippageApproximation(asset, order);
//Check if the Stop Order was filled: opposite to a limit order
switch (order.Direction)
{
case OrderDirection.Sell:
//-> 1.1 Sell Stop: If Price below setpoint, Sell:
if (prices.Low < order.StopPrice)
{
fill.Status = OrderStatus.Filled;
fill.FillPrice = order.StopPrice - slip;
}
break;
case OrderDirection.Buy:
//-> 1.2 Buy Stop: If Price Above Setpoint, Buy:
if (prices.High > order.StopPrice)
{
fill.Status = OrderStatus.Filled;
fill.FillPrice = order.StopPrice + slip;
}
break;
}
// assume the order completely filled
if (fill.Status == OrderStatus.Filled)
{
fill.FillQuantity = order.Quantity;
fill.OrderFee = asset.FeeModel.GetOrderFee(asset, order);
}
return fill;
}
/// <summary>
/// Get the minimum and maximum price for this security in the last bar:
/// </summary>
/// <param name="asset">Security asset we're checking</param>
/// <param name="direction">The order direction, decides whether to pick bid or ask</param>
private Prices GetPrices(Security asset, OrderDirection direction)
{
var low = asset.Low;
var high = asset.High;
var open = asset.Open;
var close = asset.Close;
var current = asset.Price;
if (direction == OrderDirection.Hold)
{
return new Prices(current, open, high, low, close);
}
// Only fill with data types we are subscribed to
var subscriptionTypes = asset.Subscriptions.Select(x => x.Type).ToList();
// Tick
var tick = asset.Cache.GetData<Tick>();
if (subscriptionTypes.Contains(typeof(Tick)) && tick != null)
{
var price = direction == OrderDirection.Sell ? tick.BidPrice : tick.AskPrice;
if (price != 0m)
{
return new Prices(price, 0, 0, 0, 0);
}
// If the ask/bid spreads are not available for ticks, try the price
price = tick.Price;
if (price != 0m)
{
return new Prices(price, 0, 0, 0, 0);
}
}
// Quote
var quoteBar = asset.Cache.GetData<QuoteBar>();
if (subscriptionTypes.Contains(typeof(QuoteBar)) && quoteBar != null)
{
var bar = direction == OrderDirection.Sell ? quoteBar.Bid : quoteBar.Ask;
if (bar != null)
{
return new Prices(bar);
}
}
// Trade
var tradeBar = asset.Cache.GetData<TradeBar>();
if (subscriptionTypes.Contains(typeof(TradeBar)) && tradeBar != null)
{
return new Prices(tradeBar);
}
return new Prices(current, open, high, low, close);
}
/// <summary>
/// Determines if the exchange is open using the current time of the asset
/// </summary>
private static bool IsExchangeOpen(Security asset)
{
if (!asset.Exchange.DateTimeIsOpen(asset.LocalTime))
{
// if we're not open at the current time exactly, check the bar size, this handle large sized bars (hours/days)
var currentBar = asset.GetLastData();
if (asset.LocalTime.Date != currentBar.EndTime.Date || !asset.Exchange.IsOpenDuringBar(currentBar.Time, currentBar.EndTime, false))
{
return false;
}
}
return true;
}
private class Prices
{
public readonly decimal Current;
public readonly decimal Open;
public readonly decimal High;
public readonly decimal Low;
public readonly decimal Close;
public Prices(IBar bar)
: this(bar.Close, bar.Open, bar.High, bar.Low, bar.Close)
{
}
public Prices(decimal current, decimal open, decimal high, decimal low, decimal close)
{
Current = current;
Open = open == 0 ? current : open;
High = high == 0 ? current : high;
Low = low == 0 ? current : low;
Close = close == 0 ? current : close;
}
}
}
}using QuantConnect.Securities;
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Orders;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
namespace QuantConnect.Algorithm.CSharp
{
public partial class GapReversalAlgorithm : QCAlgorithm
{
private const decimal _minGapSize = 0.25m / 100;
private const Resolution _resolution = Resolution.Minute;
private const decimal _leverage = 4;
private const decimal _maxAllocations = 3;
private const decimal _maxLongShortImbalance = 100000; //1
private const decimal _allocationSize = 0.25m;
private const int _universeSize = 100;
private const decimal _simSlippage = 0;//0.063m / 100;
private readonly Dictionary<Symbol, Tradable> _tradables = new Dictionary<Symbol, Tradable>();
private SlippageMeasurement _slippageMeasurement;
private IFillModel _fillModel;
private ISlippageModel _slippageModel;
private decimal _allocationsToday;
private decimal _longShortBalanceToday;
private static GapReversalAlgorithm _instance;
public override void Initialize()
{
SetCash(100000);
SetStartDate(2017, 1, 1);
//SetEndDate(2017, 1, 1);
_instance = this;
_slippageMeasurement = new SlippageMeasurement(this, 50);
_fillModel = new ImmediateOptimisticStopFillModel();
var slippageModel = new OrderTypeDependentSlippageModel(new ConstantSlippageModel(0));
slippageModel.MarketOrderSlippage = new ConstantSlippageModel(_simSlippage);
slippageModel.MarketOnCloseOrderSlippage = new ConstantSlippageModel(_simSlippage);
slippageModel.MarketOnOpenOrderSlippage = new ConstantSlippageModel(_simSlippage);
slippageModel.StopMarketOrderSlippage = new ConstantSlippageModel(_simSlippage);
_slippageModel = slippageModel;
if (LiveMode)
_createUniverse = false;
var someSecurity = AddEquity("SPY", _resolution, leverage: _leverage);
Schedule.On(DateRules.EveryDay(someSecurity.Symbol), TimeRules.BeforeMarketClose(someSecurity.Symbol, 10), ClosePositions);
Schedule.On(DateRules.EveryDay(someSecurity.Symbol), TimeRules.AfterMarketOpen(someSecurity.Symbol, -1), PrepareForNewDay);
if (LiveMode)
{
Schedule.On(DateRules.EveryDay(someSecurity.Symbol), TimeRules.Every(TimeSpan.FromMinutes(1)), PrintSlippage);
}
SetUpUniverse();
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
foreach (var change in changes.RemovedSecurities)
{
_tradables.Remove(change.Symbol);
}
foreach (var change in changes.AddedSecurities)
{
if (!_tradables.ContainsKey(change.Symbol))
{
var tradable = new Tradable(change);
_tradables[change.Symbol] = tradable;
}
}
}
public override void OnEndOfAlgorithm()
{
_slippageMeasurement.Process();
Log(_slippageMeasurement.ToString());
}
public override void OnEndOfDay()
{
if (LiveMode)
{
_slippageMeasurement.Process();
Log(_slippageMeasurement.ToString());
}
}
private void PrintSlippage()
{
_slippageMeasurement.Process();
SetRuntimeStatistic("Slippage %", _slippageMeasurement.GetMeanSlippage().ToString("P3"));
SetRuntimeStatistic("Slippage SD", _slippageMeasurement.GetStandardDeviationOfSlippage().ToString("P3"));
}
public override void OnData(Slice slice)
{
foreach (var bar in slice.Bars)
{
Tradable tradable;
if (_tradables.TryGetValue(bar.Key, out tradable))
{
tradable.OnData(bar.Value, false);
}
}
_slippageMeasurement.Process();
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
Tradable tradable;
if (_tradables.TryGetValue(orderEvent.Symbol, out tradable))
{
tradable.OnOrderEventAsync(orderEvent);
}
}
private void PrepareForNewDay()
{
_allocationsToday = 0;
_longShortBalanceToday = 0;
}
private decimal RequestAllocationToday(int dir)
{
if (_allocationsToday >= _maxAllocations)
return 0;
if (Math.Abs(_longShortBalanceToday + dir) > _maxLongShortImbalance)
return 0;
_longShortBalanceToday += dir;
_allocationsToday += _allocationSize;
if (LiveMode)
{
_instance.SetRuntimeStatistic("Alloc", _allocationsToday);
_instance.SetRuntimeStatistic("Bal", _longShortBalanceToday);
}
return _allocationSize;
}
private void ClosePositions()
{
foreach (var tradable in _tradables.Values)
tradable.ExitBeforeClose();
Liquidate();
}
private class Tradable
{
public readonly Security Security;
public Symbol Symbol { get { return Security.Symbol; } }
private readonly RollingWindow<Gap> _gaps = new RollingWindow<Gap>(2);
private readonly StandardDeviationOverflowSafe _std = new StandardDeviationOverflowSafe(60 * 7 * 2);
private decimal _allocation;
private bool _hasPosition;
private bool _canEnter = false;
private sealed class Gap
{
public readonly decimal Open;
public readonly decimal PrevClose;
public decimal Change
{
get { return Open / PrevClose - 1; }
}
public Gap(decimal open, decimal close)
{
Open = open;
PrevClose = close;
}
}
public Tradable(Security sec)
{
Security = sec;
sec.FillModel = _instance._fillModel;
sec.SlippageModel = _instance._slippageModel;
foreach (var bar in _instance.History(Symbol, TimeSpan.FromDays(5), _resolution))
{
OnData(bar, true);
}
}
public Tradable(string ticker)
: this(_instance.AddEquity(ticker, _resolution, leverage: _leverage))
{
}
private decimal _lastClose;
private DateTime _lastBar;
private bool _anyBar;
public void OnData(TradeBar bar, bool warmup)
{
if (!_anyBar)
{
_anyBar = true;
}
if (bar.Time.Day != _lastBar.Day || _lastBar == DateTime.MinValue)
{
_lastBar = bar.Time;
OnNewDay(bar, warmup);
}
_std.Update(_instance.Time, bar.Close);
bool gapClosed = false;
int dir = -Math.Sign(_gaps[0].Change);
if (dir > 0)
{
if (bar.Close >= _gaps[0].PrevClose)
{
gapClosed = true;
}
}
else if (dir < 0)
{
if (bar.Close <= _gaps[0].PrevClose)
{
gapClosed = true;
}
}
/*if (_hasPosition)
{
if (gapClosed)
{
_hasPosition = false;
_instance.Liquidate(Symbol);
//TODO: relinquish allocation?
}
}
else*/
if (!_hasPosition && _canEnter)
{
if (gapClosed)
_canEnter = false;
if (_canEnter && _allocation == 0)
{
_canEnter = false;
TryEnter(dir, bar.Close);
}
}
_lastClose = bar.Close;
}
private decimal _targetLevel;
private decimal _stopLevel;
private OrderTicket _entryTicket;
private OrderTicket _stopTicket;
private OrderTicket _exitTicket;
private void TryEnter(int dir, decimal price)
{
decimal proposedStop = price - dir * _std * 3;
if (!(proposedStop >= price * 0.75m && proposedStop <= price / 0.75m))
return; //also catches NaN
_allocation = _instance.RequestAllocationToday(dir);
if (_allocation <= 0)
return;
_hasPosition = true;
_targetLevel = _gaps[0].PrevClose;
_stopLevel = proposedStop;
//_instance.SetHoldings(Symbol, dir * _allocation);
var qty = _instance.CalculateOrderQuantity(Symbol, dir * _allocation);
if (qty == 0)
return;
//decimal limitPrice = price * (1 - dir * _minGapSize);
if (_instance.LiveMode)
_instance.Log("Enter: " + qty + " " + Symbol.ToString() + " at " + _targetLevel + " stopped at " + _stopLevel);
_entryTicket = _instance.MarketOrder(Symbol, qty, true, "Entry");
_instance._slippageMeasurement.NewOrder(_entryTicket);
}
public void OnOrderEventAsync(OrderEvent ev)
{
if (ev.Status == OrderStatus.Filled)
{
if (_entryTicket != null && ev.OrderId == _entryTicket.OrderId)
{
var qty = -ev.FillQuantity;
_exitTicket = _instance.LimitOrder(Symbol, qty, _targetLevel, "Exit");
_instance._slippageMeasurement.NewOrder(_exitTicket);
_stopTicket = _instance.StopMarketOrder(Symbol, qty, _stopLevel, "Stop");
_instance._slippageMeasurement.NewOrder(_stopTicket);
}
else if (_exitTicket != null && ev.OrderId == _exitTicket.OrderId)
{
if (_stopTicket != null)
_stopTicket.Cancel();
}
else if (_stopTicket != null && ev.OrderId == _stopTicket.OrderId)
{
if (_exitTicket != null)
_exitTicket.Cancel();
}
}
_instance._slippageMeasurement.OnOrderEventAsync(ev);
}
private void OnNewDay(TradeBar bar, bool warmup)
{
if (_lastClose == 0)
_lastClose = bar.Open;
_gaps.Add(new Gap(bar.Open, _lastClose));
if (_gaps.IsReady && !warmup)
{
//hardcoded rule now for testing
var change1 = _gaps[0].Change;
var change2 = _gaps[1].Change;
_canEnter = Math.Sign(change1) == Math.Sign(change2);
if (_canEnter)
{
_canEnter = Math.Abs(change1 + change2) > 2 * _minGapSize &&
Math.Abs(change1) > _minGapSize && Math.Abs(change2) > _minGapSize;
}
}
}
public void ExitBeforeClose()
{
try
{
if (_hasPosition)
{
//_instance.Liquidate(Symbol);
}
}
finally
{
_canEnter = false;
_hasPosition = false;
_allocation = 0;
_entryTicket = null;
_stopTicket = null;
_exitTicket = null;
}
}
}
}
}/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using QuantConnect.Indicators;
using System;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This indicator computes the n-period population standard deviation.
/// </summary>
public class StandardDeviationOverflowSafe : Variance
{
/// <summary>
/// Initializes a new instance of the StandardDeviation class with the specified period.
///
/// Evaluates the standard deviation of samples in the lookback period.
/// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset.
/// </summary>
/// <param name="period">The sample size of the standard deviation</param>
public StandardDeviationOverflowSafe(int period)
: this("STD" + period, period)
{
}
/// <summary>
/// Initializes a new instance of the StandardDeviation class with the specified name and period.
///
/// Evaluates the standard deviation of samples in the lookback period.
/// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset.
/// </summary>
/// <param name="name">The name of this indicator</param>
/// <param name="period">The sample size of the standard deviation</param>
public StandardDeviationOverflowSafe(string name, int period)
: base(name, period)
{
}
/// <summary>
/// Gets a flag indicating when this indicator is ready and fully initialized
/// </summary>
public override bool IsReady
{
get { return Samples >= Period; }
}
/// <summary>
/// Computes the next value of this indicator from the given state
/// </summary>
/// <param name="input">The input given to the indicator</param>
/// <param name="window">The window for the input history</param>
/// <returns>A new value for this indicator</returns>
protected override decimal ComputeNextValue(IReadOnlyWindow<IndicatorDataPoint> window, IndicatorDataPoint input)
{
double val = Math.Sqrt((double)base.ComputeNextValue(window, input));
return (decimal)val.Clamp(_min, _max);
}
private static readonly double _max = (double)decimal.MaxValue * 0.01;
private static readonly double _min = (double)decimal.MinValue * 0.01;
}
}using System;
using System.Collections.Generic;
using System.Reflection;
namespace QuantConnect.Algorithm.CSharp
{
public static class Xtend
{
public static FieldInfo GetPrivateField(Type type, string name)
{
return type.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
}
public static FieldInfo GetPrivateField<MyType>(this MyType dummy, string name)
{
return GetPrivateField(typeof(MyType), name);
}
public static object GetPrivateFieldValue(Type type, object instance, string name)
{
var fieldInfo = GetPrivateField(type, name);
if (fieldInfo == null)
throw new InvalidOperationException("Unable to find field with name " + name);
return fieldInfo.GetValue(instance);
}
public static object GetPrivateFieldValue<MyType>(this MyType instance, string name)
{
return GetPrivateFieldValue(typeof(MyType), instance, name);
}
public static T Clamp<T>(this T val, T min, T max) where T : IComparable
{
XMath.Clamp(ref val, min, max);
return val;
}
public static void Shuffle<T>(this IList<T> list, Random rng)
{
int n = list.Count;
while (n > 1)
{
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
public static int Argmax(this IList<int> list)
{
int maxIndex = list.Count - 1;
var max = list[maxIndex];
for (int index = list.Count - 2; index >= 0; --index)
{
var x = list[index];
if (x > max)
{
maxIndex = index;
max = x;
}
}
return maxIndex;
}
public static int Argmin(this IList<int> list)
{
int minIndex = list.Count - 1;
var min = list[minIndex];
for (int index = list.Count - 2; index >= 0; --index)
{
var x = list[index];
if (x < min)
{
minIndex = index;
min = x;
}
}
return minIndex;
}
public static int Argmax(this IList<double> list)
{
int maxIndex = list.Count - 1;
var max = list[maxIndex];
for (int index = list.Count - 2; index >= 0; --index)
{
var x = list[index];
if (x > max)
{
maxIndex = index;
max = x;
}
}
return maxIndex;
}
public static int Argmin(this IList<double> list)
{
int minIndex = list.Count - 1;
var min = list[minIndex];
for (int index = list.Count - 2; index >= 0; --index)
{
var x = list[index];
if (x < min)
{
minIndex = index;
min = x;
}
}
return minIndex;
}
}
}using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QuantConnect.Algorithm.CSharp
{
public static class XMath
{
public static bool IsPowerOfTwo(ulong x)
{
return (x & (x - 1)) == 0;
}
public static int Log2(int v)
{
int r = 0xFFFF - v >> 31 & 0x10;
v >>= r;
int shift = 0xFF - v >> 31 & 0x8;
v >>= shift;
r |= shift;
shift = 0xF - v >> 31 & 0x4;
v >>= shift;
r |= shift;
shift = 0x3 - v >> 31 & 0x2;
v >>= shift;
r |= shift;
r |= (v >> 1);
return r;
}
public static void Clamp<T>(ref T val, T min, T max) where T : IComparable
{
if (min.CompareTo(val) > 0)
val = min;
else if (max.CompareTo(val) < 0)
val = max;
}
}
}using MathNet.Numerics.Statistics;
using QuantConnect.Indicators;
using QuantConnect.Orders;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace QuantConnect.Algorithm.CSharp
{
sealed class SlippageMeasurement
{
private const int _numOrderTypes = 7;
private readonly QCAlgorithm _algo;
private sealed class OrderInfo
{
public OrderType Type;
public int Dir;
public decimal PriceAtIssue;
public decimal VolumeFilled;
public decimal DollarVolumeFilled;
public decimal AverageFillPrice
{
get
{
return DollarVolumeFilled / VolumeFilled;
}
}
public decimal SlippageInPrice
{
get
{
return -Dir * (AverageFillPrice - PriceAtIssue);
}
}
public decimal SlippageAsFraction
{
get { return SlippageInPrice / PriceAtIssue; }
}
}
private readonly ConcurrentDictionary<int, OrderInfo> _orders = new ConcurrentDictionary<int, OrderInfo>();
private readonly ConcurrentQueue<OrderEvent> _orderEvents = new ConcurrentQueue<OrderEvent>();
private readonly RollingWindow<OrderInfo>[] _statWindow;
public SlippageMeasurement(QCAlgorithm algo, int windowSize)
{
_algo = algo;
_statWindow = new RollingWindow<OrderInfo>[(_numOrderTypes + 1) * 3 + 1]; //(orderType + anyOrderType) * (direction + anyDirection) + any
for (int i = 0; i < _statWindow.Length; ++i)
{
_statWindow[i] = new RollingWindow<OrderInfo>(windowSize);
}
}
private RollingWindow<OrderInfo> GetStatWindow(int orderType = -1, int dir = 0)
{
if (orderType < -1)
throw new ArgumentException("orderType");
if (orderType < 0 && dir < 0)
return _statWindow[0];
return _statWindow[1 + (1 + orderType) * 3 + (1 + dir)];
}
public void NewOrder(Order order)
{
if (order == null)
throw new ArgumentNullException("order");
decimal priceAtIssue = _algo.Securities[order.Symbol].Price;
if (order is StopMarketOrder)
priceAtIssue = ((StopMarketOrder)order).StopPrice;
else if (order is StopLimitOrder)
priceAtIssue = ((StopLimitOrder)order).LimitPrice;
else if (order is LimitOrder)
priceAtIssue = ((LimitOrder)order).LimitPrice;
NewOrder(order.Id, order.Direction == OrderDirection.Buy? 1 : -1, priceAtIssue, order.Type);
}
public void NewOrder(OrderTicket order)
{
if (order == null)
throw new ArgumentNullException("order");
decimal priceAtIssue = _algo.Securities[order.Symbol].Price;
if (order.OrderType == OrderType.StopMarket)
{
priceAtIssue = order.SubmitRequest.StopPrice;
}
else if (order.OrderType == OrderType.Limit || order.OrderType == OrderType.StopLimit)
{
priceAtIssue = order.SubmitRequest.LimitPrice;
}
NewOrder(order.OrderId, Math.Sign(order.Quantity), priceAtIssue, order.OrderType);
}
public void NewOrder(int orderId, int direction, decimal priceAtIssue, OrderType type)
{
if ((int)type >= _numOrderTypes)
throw new ArgumentException("Order type " + type.ToString() + " has too high enum value", "type");
_orders[orderId] = new OrderInfo()
{
Type = type,
Dir = direction,
PriceAtIssue = priceAtIssue,
};
}
private void OnFill(int orderId, decimal quantity, decimal actualPrice)
{
OrderInfo orderInfo;
if (_orders.TryGetValue(orderId, out orderInfo))
{
orderInfo.VolumeFilled += quantity;
orderInfo.DollarVolumeFilled += quantity * actualPrice;
}
}
private void OnOrderClosed(int orderId)
{
OrderInfo orderInfo;
if (_orders.TryRemove(orderId, out orderInfo))
{
if (orderInfo.VolumeFilled > 0)
{
RecordOrder(orderInfo);
}
}
}
public void OnOrderEventAsync(OrderEvent ev)
{
_orderEvents.Enqueue(ev);
}
public void Process()
{
OrderEvent ev;
while (_orderEvents.TryDequeue(out ev))
{
if (ev.Status == OrderStatus.PartiallyFilled || ev.Status == OrderStatus.Filled)
{
OnFill(ev.OrderId, ev.AbsoluteFillQuantity, ev.FillPrice);
}
if (ev.Status.IsClosed())
{
OnOrderClosed(ev.OrderId);
}
}
}
private void RecordOrder(OrderInfo orderInfo)
{
GetStatWindow().Add(orderInfo);
GetStatWindow((int)orderInfo.Type).Add(orderInfo);
GetStatWindow(-1, orderInfo.Dir).Add(orderInfo);
GetStatWindow((int)orderInfo.Type, orderInfo.Dir).Add(orderInfo);
}
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendLine("Slippage % Mean VW Std:");
PrintStatLine("All", builder, GetStatWindow());
PrintStatLine("Buy", builder, GetStatWindow(-1, 1));
PrintStatLine("Sell", builder, GetStatWindow(-1, -1));
for (int i = 0; i < _numOrderTypes; ++i)
{
var type = (OrderType)i;
PrintStatLine(type.ToString(), builder, GetStatWindow(i));
}
for (int i = 0; i < _numOrderTypes; ++i)
{
var type = (OrderType)i;
for (int dir = -1; dir < 1; ++dir)
{
if (dir == 0)
dir = 1;
PrintStatLine(type.ToString() + (dir == 1? " Buy" : " Sell"), builder, GetStatWindow(i, dir));
}
}
return builder.ToString();
}
private static double GetMean(IEnumerable<OrderInfo> window)
{
return window.Select(x => (double)x.SlippageAsFraction).Average();
}
private static double GetVolumeWeighted(IEnumerable<OrderInfo> window)
{
double totalDollarVolume = window.Sum(x => (double)x.DollarVolumeFilled);
double volumeWeighted = totalDollarVolume > 0 ? window.Select(x => (double)x.SlippageAsFraction * (double)x.DollarVolumeFilled).Sum() / totalDollarVolume : 0;
return volumeWeighted;
}
private static double GetStandardDeviation(IEnumerable<OrderInfo> window)
{
return window.Select(x => (double)x.SlippageAsFraction).StandardDeviation();
}
private static void PrintStatLine(string caption, StringBuilder builder, IEnumerable<OrderInfo> window)
{
if (!window.Any())
return;
builder.Append(caption);
builder.Append(": ");
builder.Append(GetMean(window).ToString("P3"));
builder.Append(" ");
builder.Append(GetVolumeWeighted(window).ToString("P3"));
builder.Append(" ");
builder.Append(GetStandardDeviation(window).ToString("P3"));
builder.AppendLine();
}
public double GetMeanSlippage()
{
return GetMean(GetStatWindow());
}
public double GetVolumeWeightedSlippage()
{
return GetVolumeWeighted(GetStatWindow());
}
public double GetStandardDeviationOfSlippage()
{
return GetStandardDeviation(GetStatWindow());
}
}
}using QuantConnect.Orders.Slippage;
using System;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
class OrderTypeDependentSlippageModel : ISlippageModel
{
public ISlippageModel LimitOrderSlippage { get; set; }
public ISlippageModel MarketOrderSlippage { get; set; }
public ISlippageModel MarketOnCloseOrderSlippage { get; set; }
public ISlippageModel MarketOnOpenOrderSlippage { get; set; }
public ISlippageModel StopLimitOrderSlippage { get; set; }
public ISlippageModel StopMarketOrderSlippage { get; set; }
public OrderTypeDependentSlippageModel(ISlippageModel defaultModel)
{
LimitOrderSlippage = defaultModel;
MarketOrderSlippage = defaultModel;
MarketOnCloseOrderSlippage = defaultModel;
MarketOnOpenOrderSlippage = defaultModel;
StopLimitOrderSlippage = defaultModel;
StopMarketOrderSlippage = defaultModel;
}
public void SetMarketSlippageModel(ISlippageModel model)
{
MarketOrderSlippage = model;
MarketOnCloseOrderSlippage = model;
MarketOnOpenOrderSlippage = model;
StopMarketOrderSlippage = model;
}
public void SetLimitSlippageModel(ISlippageModel model)
{
LimitOrderSlippage = model;
StopLimitOrderSlippage = model;
}
public decimal GetSlippageApproximation(Security asset, Order order)
{
switch (order.Type)
{
case OrderType.Limit:
return LimitOrderSlippage.GetSlippageApproximation(asset, order);
case OrderType.Market:
return MarketOrderSlippage.GetSlippageApproximation(asset, order);
case OrderType.MarketOnClose:
return MarketOnCloseOrderSlippage.GetSlippageApproximation(asset, order);
case OrderType.MarketOnOpen:
return MarketOnOpenOrderSlippage.GetSlippageApproximation(asset, order);
case OrderType.StopLimit:
return StopLimitOrderSlippage.GetSlippageApproximation(asset, order);
case OrderType.StopMarket:
return StopMarketOrderSlippage.GetSlippageApproximation(asset, order);
default:
throw new NotImplementedException("Slippage specialization not implemented for order type " + order.Type.ToString());
}
}
}
}using QuantConnect.Securities;
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using MathNet.Numerics.Statistics;
namespace QuantConnect.Algorithm.CSharp
{
public partial class GapReversalAlgorithm : QCAlgorithm
{
//if false, load from external list file even during backtest
private bool _createUniverse = true;
private const int _universeWindow = 120;
private readonly Dictionary<Symbol, UniverseData> _universeData = new Dictionary<QuantConnect.Symbol, UniverseData>();
private void SetUpUniverse()
{
UniverseSettings.MinimumTimeInUniverse = TimeSpan.FromDays(1);
UniverseSettings.Leverage = _leverage;
UniverseSettings.Resolution = _resolution;
if (_createUniverse)
AddUniverse(CoarseUniverseSelection);
else
throw new Exception("Unimplemented");
}
private IEnumerable<Symbol> CoarseUniverseSelection(IEnumerable<CoarseFundamental> coarse)
{
var eligible = coarse
.Where(x => x.HasFundamentalData);
//the eligible tickers are the ones we analyze
foreach (var fundamental in eligible)
{
UniverseData data;
if (!_universeData.TryGetValue(fundamental.Symbol, out data))
{
data = new UniverseData(fundamental.Symbol);
_universeData[fundamental.Symbol] = data;
}
data.AnalyzeCoarse(fundamental);
}
var finalFilter = eligible.Select(x => _universeData[x.Symbol])
.Where(x => x.LastPrice > 30 && x.LastPrice < 200) //due to IB fee structure
.Where(x => x.AverageDollarVolume > 100000)
.OrderByDescending(x => x.Rank)
.Select(x => x.Symbol);
Debug("Max coarse universe size " + finalFilter.Count());
return finalFilter.Take(_universeSize);
}
private class UniverseData
{
public Symbol Symbol { get; private set; }
public double Rank { get; private set; }
public decimal LastPrice { get; private set; }
public SimpleMovingAverage AverageDollarVolume { get; private set; }
private readonly RollingWindow<double> _prices;
public UniverseData(Symbol sym)
{
Symbol = sym;
AverageDollarVolume = new SimpleMovingAverage(_universeWindow);
_prices = new RollingWindow<double>(_universeWindow + 1);
}
public void AnalyzeCoarse(CoarseFundamental coarse)
{
LastPrice = coarse.Price;
AverageDollarVolume.Update(_instance.Time, coarse.DollarVolume);
_prices.Add((double)coarse.Price);
var spearman = _prices.Count > 2? Correlation.Spearman(_prices.Take(_prices.Count - 1), _prices.Skip(1)) : 0;
Rank = -spearman;
}
}
}
}