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  /// <summary>
116  /// Updates the market capacity for any Symbols that require a market update.
117  /// Sometimes, after the specified <seealso cref="_snapshotPeriod"/>, we
118  /// take a "snapshot" (point-in-time capacity) of the portfolio's capacity.
119  ///
120  /// This result will be written into the Algorithm Statistics via the <see cref="BacktestingResultHandler"/>
121  /// </summary>
122  public void UpdateMarketCapacity(bool forceProcess)
123  {
124  for (var i = _monitoredSymbolCapacity.Count - 1; i >= 0; --i)
125  {
126  var capacity = _monitoredSymbolCapacity[i];
127  if (capacity.UpdateMarketCapacity())
128  {
129  _monitoredSymbolCapacity.RemoveAt(i);
130  _monitoredSymbolCapacitySet.Remove(capacity);
131  }
132  }
133 
134  var utcDate = _algorithm.UtcTime.Date;
135  if (forceProcess || utcDate >= _nextSnapshotDate && _capacityBySymbol.Count != 0)
136  {
137  var totalPortfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
138  var totalSaleVolume = _capacityBySymbol.Values
139  .Sum(s => s.SaleVolume);
140 
141  if (totalPortfolioValue == 0 || _capacityBySymbol.Count == 0)
142  {
143  return;
144  }
145 
146  var smallestAsset = _capacityBySymbol.Values
147  .OrderBy(c => c.MarketCapacityDollarVolume)
148  .First();
149 
150  _smallestAssetSymbol = smallestAsset.Security.Symbol;
151 
152  // When there is no trading, rely on the portfolio holdings
153  var percentageOfSaleVolume = totalSaleVolume != 0
154  ? smallestAsset.SaleVolume / totalSaleVolume
155  : 0;
156 
157  var buyingPowerUsed = smallestAsset.Security.MarginModel.GetReservedBuyingPowerForPosition(new ReservedBuyingPowerForPositionParameters(smallestAsset.Security))
158  .AbsoluteUsedBuyingPower * smallestAsset.Security.Leverage;
159 
160  var percentageOfHoldings = buyingPowerUsed / totalPortfolioValue;
161 
162  var scalingFactor = Math.Max(percentageOfSaleVolume, percentageOfHoldings);
163  var dailyMarketCapacityDollarVolume = smallestAsset.MarketCapacityDollarVolume / smallestAsset.Trades;
164 
165  var newCapacity = scalingFactor == 0
166  ? _capacity.Value
167  : dailyMarketCapacityDollarVolume / scalingFactor;
168 
169  // Weight our capacity based on previous value if we have one
170  if (_capacity.Value != 0)
171  {
172  newCapacity = (0.33m * newCapacity) + (_capacity.Value * 0.66m);
173  }
174 
175  // Set our new capacity value
176  Capacity = newCapacity;
177 
178  foreach (var capacity in _capacityBySymbol.Select(pair => pair.Value).ToList())
179  {
180  if (!capacity.ShouldRemove())
181  {
182  capacity.Reset();
183  continue;
184  }
185 
186  // we remove non invested and non tradable (delisted, deselected) securities this will allow the 'smallestAsset'
187  // to be changing between snapshots, and avoid the collections to grow
188  _capacityBySymbol.Remove(capacity.Security.Symbol);
189  _monitoredSymbolCapacity.Remove(capacity);
190  _monitoredSymbolCapacitySet.Remove(capacity);
191  }
192 
193  _nextSnapshotDate = utcDate + _snapshotPeriod;
194  }
195  }
196  }
197 }