Lean  $LEAN_TAG$
MarketImpactSlippageModel.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Linq;
23 using QuantConnect.Data;
25 using MathNet.Numerics.Statistics;
26 
28 {
29  /// <summary>
30  /// Slippage model that mimic the effect brought by market impact,
31  /// i.e. consume the volume listed in the order book
32  /// </summary>
33  /// <remark>Almgren, R., Thum, C., Hauptmann, E., and Li, H. (2005).
34  /// Direct estimation of equity market impact. Risk, 18(7), 58-62.
35  /// Available from: https://www.ram-ai.com/sites/default/files/2022-06/costestim.pdf</remark>
36  /// <remark>The default parameters are calibrated around 2 decades ago,
37  /// the trading time effect is not accounted (volume near market open/close is larger),
38  /// the market regime is not taken into account,
39  /// and the market environment does not have many market makers at that time,
40  /// so it is recommend to recalibrate with reference to the original paper.</remark>
42  {
43  private readonly IAlgorithm _algorithm;
44  private readonly bool _nonNegative;
45  private readonly double _latency;
46  private readonly double _impactTime;
47  private readonly double _alpha;
48  private readonly double _beta;
49  private readonly double _gamma;
50  private readonly double _eta;
51  private readonly double _delta;
52  private readonly Random _random;
53  private SymbolData _symbolData;
54 
55  /// <summary>
56  /// Instantiate a new instance of MarketImpactSlippageModel
57  /// </summary>
58  /// <param name="algorithm">IAlgorithm instance</param>
59  /// <param name="nonNegative">Indicator whether only non-negative slippage allowed</param>
60  /// <param name="latency">Time between order submitted and filled, in seconds(s)</param>
61  /// <param name="impactTime">Time between order filled and new equilibrium established, in second(s)</param>
62  /// <param name="alpha">Exponent of the permanent impact function</param>
63  /// <param name="beta">Exponent of the temporary impact function</param>
64  /// <param name="gamma">Coefficient of the permanent impact function</param>
65  /// <param name="eta">Coefficient of the temporary impact function</param>
66  /// <param name="delta">Liquidity scaling factor for permanent impact</param>
67  /// <param name="randomSeed">Random seed for generating gaussian noise</param>
68  public MarketImpactSlippageModel(IAlgorithm algorithm, bool nonNegative = true, double latency = 0.075d,
69  double impactTime = 1800d, double alpha = 0.891d, double beta = 0.600d,
70  double gamma = 0.314d, double eta = 0.142d, double delta = 0.267d,
71  int randomSeed = 50)
72  {
73  if (latency <= 0)
74  {
75  throw new Exception("Latency cannot be less than or equal to 0.");
76  }
77  if (impactTime <= 0)
78  {
79  throw new Exception("impactTime cannot be less than or equal to 0.");
80  }
81 
82  _algorithm = algorithm;
83  _nonNegative = nonNegative;
84  _latency = latency;
85  _impactTime = impactTime;
86  _alpha = alpha;
87  _beta = beta;
88  _gamma = gamma;
89  _eta = eta;
90  _delta = delta;
91  _random = new(randomSeed);
92  }
93 
94  /// <summary>
95  /// Slippage Model. Return a decimal cash slippage approximation on the order.
96  /// </summary>
97  public decimal GetSlippageApproximation(Security asset, Order order)
98  {
99  if (asset.Type == SecurityType.Forex || asset.Type == SecurityType.Cfd)
100  {
101  throw new Exception($"Asset of {asset.Type} is not supported as MarketImpactSlippageModel requires volume data");
102  }
103 
104  if (_symbolData == null)
105  {
106  _symbolData = new SymbolData(_algorithm, asset, _latency, _impactTime);
107  }
108 
109  if (_symbolData.AverageVolume == 0d)
110  {
111  return 0m;
112  }
113 
114  // normalized volume of execution
115  var nu = (double)order.AbsoluteQuantity / _symbolData.ExecutionTime / _symbolData.AverageVolume;
116  // liquidity adjustment for temporary market impact, if any
117  var liquidityAdjustment = asset.Fundamentals.HasFundamentalData && asset.Fundamentals.CompanyProfile.SharesOutstanding != default ?
118  Math.Pow(asset.Fundamentals.CompanyProfile.SharesOutstanding / _symbolData.AverageVolume, _delta) :
119  1d;
120  // noise adjustment factor
121  var noise = _symbolData.Sigma * Math.Sqrt(_symbolData.ImpactTime);
122 
123  // permanent market impact
124  var permanentImpact = _symbolData.Sigma * _symbolData.ExecutionTime * G(nu) * liquidityAdjustment + SampleGaussian() * noise;
125  // temporary market impact
126  var temporaryImpact = _symbolData.Sigma * H(nu) + SampleGaussian() * noise;
127  // realized market impact
128  var realizedImpact = temporaryImpact + permanentImpact * 0.5d;
129 
130  // estimate the slippage by temporary impact
131  return SlippageFromImpactEstimation(realizedImpact) * asset.Price;
132  }
133 
134  /// <summary>
135  /// The permanent market impact function
136  /// </summary>
137  /// <param name="absoluteOrderQuantity">The absolute, normalized order quantity</param>
138  /// <return>Unadjusted permanent market impact factor</return>
139  private double G(double absoluteOrderQuantity)
140  {
141  return _gamma * Math.Pow(absoluteOrderQuantity, _alpha);
142  }
143 
144  /// <summary>
145  /// The temporary market impact function
146  /// </summary>
147  /// <param name="absoluteOrderQuantity">The absolute, normalized order quantity</param>
148  /// <return>Unadjusted temporary market impact factor</return>
149  private double H(double absoluteOrderQuantity)
150  {
151  return _eta * Math.Pow(absoluteOrderQuantity, _beta);
152  }
153 
154  /// <summary>
155  /// Estimate the slippage size from impact
156  /// </summary>
157  /// <param name="impact">The market impact of the order</param>
158  /// <return>Slippage estimation</return>
159  private decimal SlippageFromImpactEstimation(double impact)
160  {
161  // The percentage of impact that an order is averagely being affected is random from 0.0 to 1.0
162  var ultimateSlippage = (impact * _random.NextDouble()).SafeDecimalCast();
163  // Impact at max can be the asset's price
164  ultimateSlippage = Math.Min(ultimateSlippage, 1m);
165 
166  if (_nonNegative)
167  {
168  return Math.Max(0m, ultimateSlippage);
169  }
170 
171  return ultimateSlippage;
172  }
173 
174  private double SampleGaussian(double location = 0d, double scale = 1d)
175  {
176  var randomVariable1 = 1 - _random.NextDouble();
177  var randomVariable2 = 1 - _random.NextDouble();
178 
179  var deviation = Math.Sqrt(-2.0 * Math.Log(randomVariable1)) * Math.Cos(2.0 * Math.PI * randomVariable2);
180  return deviation * scale + location;
181  }
182  }
183 
184  internal class SymbolData
185  {
186  private readonly IAlgorithm _algorithm;
187  private readonly Symbol _symbol;
188  private readonly TradeBarConsolidator _consolidator;
189  private readonly RollingWindow<decimal> _volumes = new(10);
190  private readonly RollingWindow<decimal> _prices = new(252);
191 
192  public double Sigma { get; internal set; }
193 
194  public double AverageVolume { get; internal set; }
195 
196  public double ExecutionTime { get; internal set; }
197 
198  public double ImpactTime { get; internal set; }
199 
200  public SymbolData(IAlgorithm algorithm, Security asset, double latency, double impactTime)
201  {
202  _algorithm = algorithm;
203  _symbol = asset.Symbol;
204 
205  _consolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
206  _consolidator.DataConsolidated += OnDataConsolidated;
207  algorithm.SubscriptionManager.AddConsolidator(_symbol, _consolidator);
208 
209  var configs = algorithm
212  .GetSubscriptionDataConfigs(_symbol, includeInternalConfigs: true);
213  var configToUse = configs.Where(x => x.TickType == TickType.Trade).First();
214 
215  var historyRequestFactory = new HistoryRequestFactory(algorithm);
216  var historyRequest = historyRequestFactory.CreateHistoryRequest(configToUse,
217  algorithm.Time - TimeSpan.FromDays(370),
218  algorithm.Time,
219  algorithm.Securities[_symbol].Exchange.Hours,
220  Resolution.Daily);
221  foreach (var bar in algorithm.HistoryProvider.GetHistory(new List<HistoryRequest> { historyRequest }, algorithm.TimeZone))
222  {
223  _consolidator.Update(bar.Bars[_symbol]);
224  }
225 
226  // execution time is defined as time difference between order submission and filling here,
227  // default with 75ms latency (https://www.interactivebrokers.com/download/salesPDFs/10-PDF0513.pdf)
228  // it should be in unit of "trading days", so we need to divide by normal trade day's length
229  var normalTradeDayLength = asset.Exchange.Hours.RegularMarketDuration.TotalDays;
230  ExecutionTime = TimeSpan.FromSeconds(latency).TotalDays / normalTradeDayLength;
231  // expected valid time for impact
232  var adjustedImpactTime = TimeSpan.FromSeconds(impactTime).TotalDays / normalTradeDayLength;
233  ImpactTime = ExecutionTime + adjustedImpactTime;
234  }
235 
236  public void OnDataConsolidated(object _, TradeBar bar)
237  {
238  _prices.Add(bar.Close);
239  _volumes.Add(bar.Volume);
240 
241  if (_prices.Samples < 2)
242  {
243  return;
244  }
245 
246  var rocp = new double[_prices.Samples - 1];
247  for (var i = 0; i < _prices.Samples - 1; i++)
248  {
249  if (_prices[i + 1] == 0) continue;
250 
251  var roc = (_prices[i] - _prices[i + 1]) / _prices[i + 1];
252  rocp[i] = (double)roc;
253  }
254 
255  var variance = rocp.Variance();
256  Sigma = Math.Sqrt(variance);
257  AverageVolume = (double)_volumes.Average();
258  }
259 
260  public void Dispose()
261  {
262  _prices.Reset();
263  _volumes.Reset();
264 
265  _consolidator.DataConsolidated -= OnDataConsolidated;
266  _algorithm.SubscriptionManager.RemoveConsolidator(_symbol, _consolidator);
267  }
268  }
269 }