Lean  $LEAN_TAG$
CapacityEstimate.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 
17 using System;
18 using System.Linq;
19 using QuantConnect.Util;
20 using QuantConnect.Orders;
23 using System.Collections.Generic;
24 
25 namespace QuantConnect
26 {
27  /// <summary>
28  /// Estimates dollar volume capacity of algorithm (in account currency) using all Symbols in the portfolio.
29  /// </summary>
30  /// <remarks>
31  /// Any mention of dollar volume is volume in account currency, but "dollar volume" is used
32  /// to maintain consistency with financial terminology and our use
33  /// case of having alphas measured capacity be in USD.
34  /// </remarks>
35  public class CapacityEstimate
36  {
37  private readonly IAlgorithm _algorithm;
38  private readonly Dictionary<Symbol, SymbolCapacity> _capacityBySymbol;
39  private List<SymbolCapacity> _monitoredSymbolCapacity;
40  // We use multiple collections to avoid having to perform an O(n) lookup whenever
41  // we're wanting to check whether a particular SymbolData instance is being "monitored",
42  // but still want to preserve indexing via an integer index
43  // (monitored meaning it is currently aggregating market dollar volume for its capacity calculation).
44  // For integer indexing, we use the List above, v.s. for lookup we use this HashSet.
45  private HashSet<SymbolCapacity> _monitoredSymbolCapacitySet;
46  private DateTime _nextSnapshotDate;
47  private TimeSpan _snapshotPeriod;
48  private Symbol _smallestAssetSymbol;
49 
50  /// <summary>
51  /// Private capacity member, We wrap this value type because it's being
52  /// read and written by multiple threads.
53  /// </summary>
54  private ReferenceWrapper<decimal> _capacity;
55 
56  /// <summary>
57  /// The total capacity of the strategy at a point in time
58  /// </summary>
59  public decimal Capacity
60  {
61  // Round our capacity to the nearest 1000
62  get => _capacity.Value.DiscretelyRoundBy(1000.00m);
63  private set => _capacity = new ReferenceWrapper<decimal>(value);
64  }
65 
66  /// <summary>
67  /// Provide a reference to the lowest capacity symbol used in scaling down the capacity for debugging.
68  /// </summary>
69  public Symbol LowestCapacityAsset => _smallestAssetSymbol;
70 
71  /// <summary>
72  /// Initializes an instance of the class.
73  /// </summary>
74  /// <param name="algorithm">Used to get data at the current time step and access the portfolio state</param>
75  public CapacityEstimate(IAlgorithm algorithm)
76  {
77  _algorithm = algorithm;
78  _capacityBySymbol = new Dictionary<Symbol, SymbolCapacity>();
79  _monitoredSymbolCapacity = new List<SymbolCapacity>();
80  _monitoredSymbolCapacitySet = new HashSet<SymbolCapacity>();
81  // Set the minimum snapshot period to one day, but use algorithm start/end if the algo runtime is less than seven days
82  _snapshotPeriod = TimeSpan.FromDays(Math.Max(Math.Min((_algorithm.EndDate - _algorithm.StartDate).TotalDays - 1, 7), 1));
83  _nextSnapshotDate = _algorithm.StartDate + _snapshotPeriod;
84  _capacity = new ReferenceWrapper<decimal>(0);
85  }
86 
87  /// <summary>
88  /// Processes an order whenever it's encountered so that we can calculate the capacity
89  /// </summary>
90  /// <param name="orderEvent">Order event to use to calculate capacity</param>
91  public void OnOrderEvent(OrderEvent orderEvent)
92  {
93  if (orderEvent.Status != OrderStatus.Filled && orderEvent.Status != OrderStatus.PartiallyFilled)
94  {
95  return;
96  }
97 
98  SymbolCapacity symbolCapacity;
99  if (!_capacityBySymbol.TryGetValue(orderEvent.Symbol, out symbolCapacity))
100  {
101  symbolCapacity = new SymbolCapacity(_algorithm, orderEvent.Symbol);
102  _capacityBySymbol[orderEvent.Symbol] = symbolCapacity;
103  }
104 
105  symbolCapacity.OnOrderEvent(orderEvent);
106  if (_monitoredSymbolCapacitySet.Contains(symbolCapacity))
107  {
108  return;
109  }
110 
111  _monitoredSymbolCapacity.Add(symbolCapacity);
112  _monitoredSymbolCapacitySet.Add(symbolCapacity);
113  }
114 
115  #pragma warning disable CS1574
116  /// <summary>
117  /// Updates the market capacity for any Symbols that require a market update.
118  /// Sometimes, after the specified <seealso cref="_snapshotPeriod"/>, we
119  /// take a "snapshot" (point-in-time capacity) of the portfolio's capacity.
120  ///
121  /// This result will be written into the Algorithm Statistics via the <see cref="BacktestingResultHandler"/>
122  /// </summary>
123  #pragma warning restore CS1574
124  public void UpdateMarketCapacity(bool forceProcess)
125  {
126  for (var i = _monitoredSymbolCapacity.Count - 1; i >= 0; --i)
127  {
128  var capacity = _monitoredSymbolCapacity[i];
129  if (capacity.UpdateMarketCapacity())
130  {
131  _monitoredSymbolCapacity.RemoveAt(i);
132  _monitoredSymbolCapacitySet.Remove(capacity);
133  }
134  }
135 
136  var utcDate = _algorithm.UtcTime.Date;
137  if (forceProcess || utcDate >= _nextSnapshotDate && _capacityBySymbol.Count != 0)
138  {
139  var totalPortfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
140  var totalSaleVolume = _capacityBySymbol.Values
141  .Sum(s => s.SaleVolume);
142 
143  if (totalPortfolioValue == 0 || _capacityBySymbol.Count == 0)
144  {
145  return;
146  }
147 
148  var smallestAsset = _capacityBySymbol.Values
149  .OrderBy(c => c.MarketCapacityDollarVolume)
150  .First();
151 
152  _smallestAssetSymbol = smallestAsset.Security.Symbol;
153 
154  // When there is no trading, rely on the portfolio holdings
155  var percentageOfSaleVolume = totalSaleVolume != 0
156  ? smallestAsset.SaleVolume / totalSaleVolume
157  : 0;
158 
159  var buyingPowerUsed = smallestAsset.Security.MarginModel.GetReservedBuyingPowerForPosition(new ReservedBuyingPowerForPositionParameters(smallestAsset.Security))
160  .AbsoluteUsedBuyingPower * smallestAsset.Security.Leverage;
161 
162  var percentageOfHoldings = buyingPowerUsed / totalPortfolioValue;
163 
164  var scalingFactor = Math.Max(percentageOfSaleVolume, percentageOfHoldings);
165  var dailyMarketCapacityDollarVolume = smallestAsset.MarketCapacityDollarVolume / smallestAsset.Trades;
166 
167  var newCapacity = scalingFactor == 0
168  ? _capacity.Value
169  : dailyMarketCapacityDollarVolume / scalingFactor;
170 
171  // Weight our capacity based on previous value if we have one
172  if (_capacity.Value != 0)
173  {
174  newCapacity = (0.33m * newCapacity) + (_capacity.Value * 0.66m);
175  }
176 
177  // Set our new capacity value
178  Capacity = newCapacity;
179 
180  foreach (var capacity in _capacityBySymbol.Select(pair => pair.Value).ToList())
181  {
182  if (!capacity.ShouldRemove())
183  {
184  capacity.Reset();
185  continue;
186  }
187 
188  // we remove non invested and non tradable (delisted, deselected) securities this will allow the 'smallestAsset'
189  // to be changing between snapshots, and avoid the collections to grow
190  _capacityBySymbol.Remove(capacity.Security.Symbol);
191  _monitoredSymbolCapacity.Remove(capacity);
192  _monitoredSymbolCapacitySet.Remove(capacity);
193  }
194 
195  _nextSnapshotDate = utcDate + _snapshotPeriod;
196  }
197  }
198  }
199 }