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; } } } }