Lean  $LEAN_TAG$
QuantBook.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 Python.Runtime;
19 using QuantConnect.Data;
30 using QuantConnect.Util;
31 using System;
32 using System.Collections.Generic;
33 using System.IO;
34 using System.Linq;
35 using QuantConnect.Packets;
36 using System.Threading.Tasks;
41 
42 namespace QuantConnect.Research
43 {
44  /// <summary>
45  /// Provides access to data for quantitative analysis
46  /// </summary>
47  public class QuantBook : QCAlgorithm
48  {
49  private dynamic _pandas;
50  private IDataCacheProvider _dataCacheProvider;
51  private IDataProvider _dataProvider;
52  private static bool _isPythonNotebook;
53 
54  static QuantBook()
55  {
56  //Determine if we are in a Python Notebook
57  try
58  {
59  PythonEngine.Initialize();
60  using (Py.GIL())
61  {
62  var isPython = PyModule.FromString(Guid.NewGuid().ToString(),
63  "try:\n" +
64  " import IPython\n" +
65  " def IsPythonNotebook():\n" +
66  " return (IPython.get_ipython() != None)\n" +
67  "except:\n" +
68  " print('No IPython installed')\n" +
69  " def IsPythonNotebook():\n" +
70  " return false\n").GetAttr("IsPythonNotebook").Invoke();
71  isPython.TryConvert(out _isPythonNotebook);
72  }
73  }
74  catch
75  {
76  //Default to false
77  _isPythonNotebook = false;
78  Logging.Log.Error("QuantBook failed to determine Notebook kernel language");
79  }
80 
81  RecycleMemory();
82 
83  Logging.Log.Trace($"QuantBook started; Is Python: {_isPythonNotebook}");
84  }
85 
86  /// <summary>
87  /// <see cref = "QuantBook" /> constructor.
88  /// Provides access to data for quantitative analysis
89  /// </summary>
90  public QuantBook() : base()
91  {
92  try
93  {
94  using (Py.GIL())
95  {
96  _pandas = Py.Import("pandas");
97  }
98 
99  // Issue #4892 : Set start time relative to NY time
100  // when the data is available from the previous day
101  var newYorkTime = DateTime.UtcNow.ConvertFromUtc(TimeZones.NewYork);
102  var hourThreshold = Config.GetInt("qb-data-hour", 9);
103 
104  // If it is after our hour threshold; then we can use today
105  if (newYorkTime.Hour >= hourThreshold)
106  {
107  SetStartDate(newYorkTime);
108  }
109  else
110  {
111  SetStartDate(newYorkTime - TimeSpan.FromDays(1));
112  }
113 
114  // Sets PandasConverter
116 
117  // Reset our composer; needed for re-creation of QuantBook
119  var composer = Composer.Instance;
120  Config.Reset();
121 
122  // Create our handlers with our composer instance
123  var systemHandlers = LeanEngineSystemHandlers.FromConfiguration(composer);
124  // init the API
125  systemHandlers.Initialize();
126  var algorithmHandlers = LeanEngineAlgorithmHandlers.FromConfiguration(composer, researchMode: true);
127 ;
128 
129  var algorithmPacket = new BacktestNodePacket
130  {
131  UserToken = Globals.UserToken,
132  UserId = Globals.UserId,
134  OrganizationId = Globals.OrganizationID,
135  Version = Globals.Version
136  };
137 
138  ProjectId = algorithmPacket.ProjectId;
139  systemHandlers.LeanManager.Initialize(systemHandlers,
140  algorithmHandlers,
141  algorithmPacket,
142  new AlgorithmManager(false));
143  systemHandlers.LeanManager.SetAlgorithm(this);
144 
145 
146  algorithmHandlers.DataPermissionsManager.Initialize(algorithmPacket);
147 
148  algorithmHandlers.ObjectStore.Initialize(algorithmPacket.UserId,
149  algorithmPacket.ProjectId,
150  algorithmPacket.UserToken,
151  new Controls
152  {
153  // if <= 0 we disable periodic persistence and make it synchronous
154  PersistenceIntervalSeconds = -1,
155  StorageLimit = Config.GetValue("storage-limit", 10737418240L),
156  StorageFileCount = Config.GetInt("storage-file-count", 10000),
157  StoragePermissions = (FileAccess) Config.GetInt("storage-permissions", (int)FileAccess.ReadWrite)
158  });
159  SetObjectStore(algorithmHandlers.ObjectStore);
160 
161  _dataCacheProvider = new ZipDataCacheProvider(algorithmHandlers.DataProvider);
162  _dataProvider = algorithmHandlers.DataProvider;
163 
164  var symbolPropertiesDataBase = SymbolPropertiesDatabase.FromDataFolder();
165  var registeredTypes = new RegisteredSecurityDataTypesProvider();
166  var securityService = new SecurityService(Portfolio.CashBook,
168  symbolPropertiesDataBase,
169  this,
170  registeredTypes,
172  algorithm: this);
173  Securities.SetSecurityService(securityService);
175  new DataManager(new NullDataFeed(),
176  new UniverseSelection(this, securityService, algorithmHandlers.DataPermissionsManager, algorithmHandlers.DataProvider),
177  this,
178  TimeKeeper,
180  false,
181  registeredTypes,
182  algorithmHandlers.DataPermissionsManager));
183 
184  var mapFileProvider = algorithmHandlers.MapFileProvider;
188  null,
189  null,
190  algorithmHandlers.DataProvider,
191  _dataCacheProvider,
192  mapFileProvider,
193  algorithmHandlers.FactorFileProvider,
194  null,
195  true,
196  algorithmHandlers.DataPermissionsManager,
197  ObjectStore,
198  Settings
199  )
200  );
201 
202  var initParameters = new ChainProviderInitializeParameters(mapFileProvider, HistoryProvider);
203  var optionChainProvider = new BacktestingOptionChainProvider();
204  optionChainProvider.Initialize(initParameters);
205  var futureChainProvider = new BacktestingFutureChainProvider();
206  futureChainProvider.Initialize(initParameters);
207  SetOptionChainProvider(new CachingOptionChainProvider(optionChainProvider));
208  SetFutureChainProvider(new CachingFutureChainProvider(futureChainProvider));
209 
211  SetDeploymentTarget(Config.GetValue("deployment-target", DeploymentTarget.LocalPlatform));
212  }
213  catch (Exception exception)
214  {
215  throw new Exception("QuantBook.Main(): " + exception);
216  }
217  }
218 
219  /// <summary>
220  /// Python implementation of GetFundamental, get fundamental data for input symbols or tickers
221  /// </summary>
222  /// <param name="input">The symbols or tickers to retrieve fundamental data for</param>
223  /// <param name="selector">Selects a value from the Fundamental data to filter the request output</param>
224  /// <param name="start">The start date of selected data</param>
225  /// <param name="end">The end date of selected data</param>
226  /// <returns>pandas DataFrame</returns>
227  [Obsolete("Please use the 'UniverseHistory()' API")]
228  public PyObject GetFundamental(PyObject input, string selector = null, DateTime? start = null, DateTime? end = null)
229  {
230  //Covert to symbols
231  var symbols = PythonUtil.ConvertToSymbols(input);
232 
233  //Fetch the data
234  var fundamentalData = GetAllFundamental(symbols, selector, start, end);
235 
236  using (Py.GIL())
237  {
238  var data = new PyDict();
239  foreach (var day in fundamentalData.OrderBy(x => x.Key))
240  {
241  var orderedValues = day.Value.OrderBy(x => x.Key.ID.ToString()).ToList();
242  var columns = orderedValues.Select(x => x.Key.ID.ToString());
243  var values = orderedValues.Select(x => x.Value);
244  var row = _pandas.Series(values, columns);
245  data.SetItem(day.Key.ToPython(), row);
246  }
247 
248  return _pandas.DataFrame.from_dict(data, orient:"index");
249  }
250  }
251 
252  /// <summary>
253  /// Get fundamental data from given symbols
254  /// </summary>
255  /// <param name="symbols">The symbols to retrieve fundamental data for</param>
256  /// <param name="selector">Selects a value from the Fundamental data to filter the request output</param>
257  /// <param name="start">The start date of selected data</param>
258  /// <param name="end">The end date of selected data</param>
259  /// <returns>Enumerable collection of DataDictionaries, one dictionary for each day there is data</returns>
260  [Obsolete("Please use the 'UniverseHistory()' API")]
261  public IEnumerable<DataDictionary<dynamic>> GetFundamental(IEnumerable<Symbol> symbols, string selector = null, DateTime? start = null, DateTime? end = null)
262  {
263  var data = GetAllFundamental(symbols, selector, start, end);
264 
265  foreach (var kvp in data.OrderBy(kvp => kvp.Key))
266  {
267  yield return kvp.Value;
268  }
269  }
270 
271  /// <summary>
272  /// Get fundamental data for a given symbol
273  /// </summary>
274  /// <param name="symbol">The symbol to retrieve fundamental data for</param>
275  /// <param name="selector">Selects a value from the Fundamental data to filter the request output</param>
276  /// <param name="start">The start date of selected data</param>
277  /// <param name="end">The end date of selected data</param>
278  /// <returns>Enumerable collection of DataDictionaries, one Dictionary for each day there is data.</returns>
279  [Obsolete("Please use the 'UniverseHistory()' API")]
280  public IEnumerable<DataDictionary<dynamic>> GetFundamental(Symbol symbol, string selector = null, DateTime? start = null, DateTime? end = null)
281  {
282  var list = new List<Symbol>
283  {
284  symbol
285  };
286 
287  return GetFundamental(list, selector, start, end);
288  }
289 
290  /// <summary>
291  /// Get fundamental data for a given set of tickers
292  /// </summary>
293  /// <param name="tickers">The tickers to retrieve fundamental data for</param>
294  /// <param name="selector">Selects a value from the Fundamental data to filter the request output</param>
295  /// <param name="start">The start date of selected data</param>
296  /// <param name="end">The end date of selected data</param>
297  /// <returns>Enumerable collection of DataDictionaries, one dictionary for each day there is data.</returns>
298  [Obsolete("Please use the 'UniverseHistory()' API")]
299  public IEnumerable<DataDictionary<dynamic>> GetFundamental(IEnumerable<string> tickers, string selector = null, DateTime? start = null, DateTime? end = null)
300  {
301  var list = new List<Symbol>();
302  foreach (var ticker in tickers)
303  {
304  list.Add(QuantConnect.Symbol.Create(ticker, SecurityType.Equity, Market.USA));
305  }
306 
307  return GetFundamental(list, selector, start, end);
308  }
309 
310  /// <summary>
311  /// Get fundamental data for a given ticker
312  /// </summary>
313  /// <param name="symbol">The symbol to retrieve fundamental data for</param>
314  /// <param name="selector">Selects a value from the Fundamental data to filter the request output</param>
315  /// <param name="start">The start date of selected data</param>
316  /// <param name="end">The end date of selected data</param>
317  /// <returns>Enumerable collection of DataDictionaries, one Dictionary for each day there is data.</returns>
318  [Obsolete("Please use the 'UniverseHistory()' API")]
319  public dynamic GetFundamental(string ticker, string selector = null, DateTime? start = null, DateTime? end = null)
320  {
321  //Check if its Python; PythonNet likes to convert the strings, but for python we want the DataFrame as the return object
322  //So we must route the function call to the Python version.
323  if (_isPythonNotebook)
324  {
325  return GetFundamental(ticker.ToPython(), selector, start, end);
326  }
327 
328  var symbol = QuantConnect.Symbol.Create(ticker, SecurityType.Equity, Market.USA);
329  var list = new List<Symbol>
330  {
331  symbol
332  };
333 
334  return GetFundamental(list, selector, start, end);
335  }
336 
337  /// <summary>
338  /// Gets <see cref="OptionHistory"/> object for a given symbol, date and resolution
339  /// </summary>
340  /// <param name="symbol">The symbol to retrieve historical option data for</param>
341  /// <param name="targetOption">The target option ticker. This is useful when the option ticker does not match the underlying, e.g. SPX index and the SPXW weekly option. If null is provided will use underlying</param>
342  /// <param name="start">The history request start time</param>
343  /// <param name="end">The history request end time. Defaults to 1 day if null</param>
344  /// <param name="resolution">The resolution to request</param>
345  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
346  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
347  /// <returns>A <see cref="OptionHistory"/> object that contains historical option data.</returns>
348  public OptionHistory OptionHistory(Symbol symbol, string targetOption, DateTime start, DateTime? end = null, Resolution? resolution = null,
349  bool fillForward = true, bool extendedMarketHours = false)
350  {
351  symbol = GetOptionSymbolForHistoryRequest(symbol, targetOption, resolution, fillForward);
352 
353  return OptionHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
354  }
355 
356  /// <summary>
357  /// Gets <see cref="OptionHistory"/> object for a given symbol, date and resolution
358  /// </summary>
359  /// <param name="symbol">The symbol to retrieve historical option data for</param>
360  /// <param name="targetOption">The target option ticker. This is useful when the option ticker does not match the underlying, e.g. SPX index and the SPXW weekly option. If null is provided will use underlying</param>
361  /// <param name="start">The history request start time</param>
362  /// <param name="end">The history request end time. Defaults to 1 day if null</param>
363  /// <param name="resolution">The resolution to request</param>
364  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
365  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
366  /// <returns>A <see cref="OptionHistory"/> object that contains historical option data.</returns>
367  [Obsolete("Please use the 'OptionHistory()' API")]
368  public OptionHistory GetOptionHistory(Symbol symbol, string targetOption, DateTime start, DateTime? end = null, Resolution? resolution = null,
369  bool fillForward = true, bool extendedMarketHours = false)
370  {
371  return OptionHistory(symbol, targetOption, start, end, resolution, fillForward, extendedMarketHours);
372  }
373 
374  /// <summary>
375  /// Gets <see cref="OptionHistory"/> object for a given symbol, date and resolution
376  /// </summary>
377  /// <param name="symbol">The symbol to retrieve historical option data for</param>
378  /// <param name="start">The history request start time</param>
379  /// <param name="end">The history request end time. Defaults to 1 day if null</param>
380  /// <param name="resolution">The resolution to request</param>
381  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
382  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
383  /// <returns>A <see cref="OptionHistory"/> object that contains historical option data.</returns>
384  public OptionHistory OptionHistory(Symbol symbol, DateTime start, DateTime? end = null, Resolution? resolution = null,
385  bool fillForward = true, bool extendedMarketHours = false)
386  {
387  if (!end.HasValue || end.Value == start)
388  {
389  end = start.AddDays(1);
390  }
391 
392  // Load a canonical option Symbol if the user provides us with an underlying Symbol
393  symbol = GetOptionSymbolForHistoryRequest(symbol, null, resolution, fillForward);
394 
395  IEnumerable<Symbol> symbols;
396  if (symbol.IsCanonical())
397  {
398  // canonical symbol, lets find the contracts
399  var option = Securities[symbol] as Option;
400  if (!Securities.ContainsKey(symbol.Underlying))
401  {
402  var resolutionToUseForUnderlying = resolution ?? SubscriptionManager.SubscriptionDataConfigService
404  .Select(x => (Resolution?)x.Resolution)
405  .DefaultIfEmpty(null)
406  .Min();
407  if (!resolutionToUseForUnderlying.HasValue && UniverseManager.TryGetValue(symbol, out var universe))
408  {
409  resolutionToUseForUnderlying = universe.UniverseSettings.Resolution;
410  }
411 
412  if (symbol.Underlying.SecurityType == SecurityType.Equity)
413  {
414  // only add underlying if not present
415  AddEquity(symbol.Underlying.Value, resolutionToUseForUnderlying, fillForward: fillForward,
416  extendedMarketHours: extendedMarketHours);
417  }
418  else if (symbol.Underlying.SecurityType == SecurityType.Index)
419  {
420  // only add underlying if not present
421  AddIndex(symbol.Underlying.Value, resolutionToUseForUnderlying, fillForward: fillForward);
422  }
423  else if (symbol.Underlying.SecurityType == SecurityType.Future && symbol.Underlying.IsCanonical())
424  {
425  AddFuture(symbol.Underlying.ID.Symbol, resolutionToUseForUnderlying, fillForward: fillForward,
426  extendedMarketHours: extendedMarketHours);
427  }
428  else if (symbol.Underlying.SecurityType == SecurityType.Future)
429  {
430  AddFutureContract(symbol.Underlying, resolutionToUseForUnderlying, fillForward: fillForward,
431  extendedMarketHours: extendedMarketHours);
432  }
433  }
434 
435  var allSymbols = new HashSet<Symbol>();
436  var optionFilterUniverse = new OptionFilterUniverse(option);
437 
438  foreach (var (date, chainData, underlyingData) in GetChainHistory<OptionUniverse>(option, start, end.Value, extendedMarketHours))
439  {
440  if (underlyingData is not null)
441  {
442  optionFilterUniverse.Refresh(chainData, underlyingData, underlyingData.EndTime);
443  allSymbols.UnionWith(option.ContractFilter.Filter(optionFilterUniverse).Select(x => x.Symbol));
444  }
445  }
446 
447  var distinctSymbols = allSymbols.Distinct().Select(x => new OptionUniverse() { Symbol = x, Time = start });
448  symbols = allSymbols.Concat(new[] { symbol.Underlying });
449  }
450  else
451  {
452  // the symbol is a contract
453  symbols = new List<Symbol>{ symbol };
454  }
455 
456  return new OptionHistory(History(symbols, start, end.Value, resolution, fillForward, extendedMarketHours));
457  }
458 
459  /// <summary>
460  /// Gets <see cref="OptionHistory"/> object for a given symbol, date and resolution
461  /// </summary>
462  /// <param name="symbol">The symbol to retrieve historical option data for</param>
463  /// <param name="start">The history request start time</param>
464  /// <param name="end">The history request end time. Defaults to 1 day if null</param>
465  /// <param name="resolution">The resolution to request</param>
466  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
467  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
468  /// <returns>A <see cref="OptionHistory"/> object that contains historical option data.</returns>
469  [Obsolete("Please use the 'OptionHistory()' API")]
470  public OptionHistory GetOptionHistory(Symbol symbol, DateTime start, DateTime? end = null, Resolution? resolution = null,
471  bool fillForward = true, bool extendedMarketHours = false)
472  {
473  return OptionHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
474  }
475 
476  /// <summary>
477  /// Gets <see cref="FutureHistory"/> object for a given symbol, date and resolution
478  /// </summary>
479  /// <param name="symbol">The symbol to retrieve historical future data for</param>
480  /// <param name="start">The history request start time</param>
481  /// <param name="end">The history request end time. Defaults to 1 day if null</param>
482  /// <param name="resolution">The resolution to request</param>
483  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
484  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
485  /// <returns>A <see cref="FutureHistory"/> object that contains historical future data.</returns>
486  public FutureHistory FutureHistory(Symbol symbol, DateTime start, DateTime? end = null, Resolution? resolution = null,
487  bool fillForward = true, bool extendedMarketHours = false)
488  {
489  if (!end.HasValue || end.Value == start)
490  {
491  end = start.AddDays(1);
492  }
493 
494  var allSymbols = new HashSet<Symbol>();
495  if (symbol.IsCanonical())
496  {
497  // canonical symbol, lets find the contracts
498  var future = Securities[symbol] as Future;
499 
500  foreach (var (date, chainData, underlyingData) in GetChainHistory<FutureUniverse>(future, start, end.Value, extendedMarketHours))
501  {
502  allSymbols.UnionWith(future.ContractFilter.Filter(new FutureFilterUniverse(chainData, date)).Select(x => x.Symbol));
503  }
504  }
505  else
506  {
507  // the symbol is a contract
508  allSymbols.Add(symbol);
509  }
510 
511  return new FutureHistory(History(allSymbols, start, end.Value, resolution, fillForward, extendedMarketHours));
512  }
513 
514  /// <summary>
515  /// Gets <see cref="FutureHistory"/> object for a given symbol, date and resolution
516  /// </summary>
517  /// <param name="symbol">The symbol to retrieve historical future data for</param>
518  /// <param name="start">The history request start time</param>
519  /// <param name="end">The history request end time. Defaults to 1 day if null</param>
520  /// <param name="resolution">The resolution to request</param>
521  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
522  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
523  /// <returns>A <see cref="FutureHistory"/> object that contains historical future data.</returns>
524  [Obsolete("Please use the 'FutureHistory()' API")]
525  public FutureHistory GetFutureHistory(Symbol symbol, DateTime start, DateTime? end = null, Resolution? resolution = null,
526  bool fillForward = true, bool extendedMarketHours = false)
527  {
528  return FutureHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
529  }
530 
531  /// <summary>
532  /// Gets the historical data of an indicator for the specified symbol. The exact number of bars will be returned.
533  /// The symbol must exist in the Securities collection.
534  /// </summary>
535  /// <param name="symbol">The symbol to retrieve historical data for</param>
536  /// <param name="periods">The number of bars to request</param>
537  /// <param name="resolution">The resolution to request</param>
538  /// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
539  /// <returns>pandas.DataFrame of historical data of an indicator</returns>
540  [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
541  public PyObject Indicator(IndicatorBase<IndicatorDataPoint> indicator, Symbol symbol, int period, Resolution? resolution = null, Func<IBaseData, decimal> selector = null)
542  {
543  var history = History(new[] { symbol }, period, resolution);
544  return IndicatorHistory(indicator, history, selector).DataFrame;
545  }
546 
547  /// <summary>
548  /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
549  /// The symbol must exist in the Securities collection.
550  /// </summary>
551  /// <param name="symbol">The symbol to retrieve historical data for</param>
552  /// <param name="periods">The number of bars to request</param>
553  /// <param name="resolution">The resolution to request</param>
554  /// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
555  /// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
556  [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
557  public PyObject Indicator(IndicatorBase<IBaseDataBar> indicator, Symbol symbol, int period, Resolution? resolution = null, Func<IBaseData, IBaseDataBar> selector = null)
558  {
559  var history = History(new[] { symbol }, period, resolution);
560  return IndicatorHistory(indicator, history, selector).DataFrame;
561  }
562 
563  /// <summary>
564  /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
565  /// The symbol must exist in the Securities collection.
566  /// </summary>
567  /// <param name="symbol">The symbol to retrieve historical data for</param>
568  /// <param name="periods">The number of bars to request</param>
569  /// <param name="resolution">The resolution to request</param>
570  /// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
571  /// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
572  [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
573  public PyObject Indicator(IndicatorBase<TradeBar> indicator, Symbol symbol, int period, Resolution? resolution = null, Func<IBaseData, TradeBar> selector = null)
574  {
575  var history = History(new[] { symbol }, period, resolution);
576  return IndicatorHistory(indicator, history, selector).DataFrame;
577  }
578 
579  /// <summary>
580  /// Gets the historical data of an indicator for the specified symbol. The exact number of bars will be returned.
581  /// The symbol must exist in the Securities collection.
582  /// </summary>
583  /// <param name="indicator">Indicator</param>
584  /// <param name="symbol">The symbol to retrieve historical data for</param>
585  /// <param name="span">The span over which to retrieve recent historical data</param>
586  /// <param name="resolution">The resolution to request</param>
587  /// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
588  /// <returns>pandas.DataFrame of historical data of an indicator</returns>
589  [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
590  public PyObject Indicator(IndicatorBase<IndicatorDataPoint> indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func<IBaseData, decimal> selector = null)
591  {
592  var history = History(new[] { symbol }, span, resolution);
593  return IndicatorHistory(indicator, history, selector).DataFrame;
594  }
595 
596  /// <summary>
597  /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
598  /// The symbol must exist in the Securities collection.
599  /// </summary>
600  /// <param name="indicator">Indicator</param>
601  /// <param name="symbol">The symbol to retrieve historical data for</param>
602  /// <param name="span">The span over which to retrieve recent historical data</param>
603  /// <param name="resolution">The resolution to request</param>
604  /// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
605  /// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
606  [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
607  public PyObject Indicator(IndicatorBase<IBaseDataBar> indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func<IBaseData, IBaseDataBar> selector = null)
608  {
609  var history = History(new[] { symbol }, span, resolution);
610  return IndicatorHistory(indicator, history, selector).DataFrame;
611  }
612 
613  /// <summary>
614  /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
615  /// The symbol must exist in the Securities collection.
616  /// </summary>
617  /// <param name="indicator">Indicator</param>
618  /// <param name="symbol">The symbol to retrieve historical data for</param>
619  /// <param name="span">The span over which to retrieve recent historical data</param>
620  /// <param name="resolution">The resolution to request</param>
621  /// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
622  /// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
623  [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
624  public PyObject Indicator(IndicatorBase<TradeBar> indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func<IBaseData, TradeBar> selector = null)
625  {
626  var history = History(new[] { symbol }, span, resolution);
627  return IndicatorHistory(indicator, history, selector).DataFrame;
628  }
629 
630  /// <summary>
631  /// Gets the historical data of an indicator for the specified symbol. The exact number of bars will be returned.
632  /// The symbol must exist in the Securities collection.
633  /// </summary>
634  /// <param name="indicator">Indicator</param>
635  /// <param name="symbol">The symbol to retrieve historical data for</param>
636  /// <param name="start">The start time in the algorithm's time zone</param>
637  /// <param name="end">The end time in the algorithm's time zone</param>
638  /// <param name="resolution">The resolution to request</param>
639  /// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
640  /// <returns>pandas.DataFrame of historical data of an indicator</returns>
641  [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
642  public PyObject Indicator(IndicatorBase<IndicatorDataPoint> indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func<IBaseData, decimal> selector = null)
643  {
644  var history = History(new[] { symbol }, start, end, resolution);
645  return IndicatorHistory(indicator, history, selector).DataFrame;
646  }
647 
648  /// <summary>
649  /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
650  /// The symbol must exist in the Securities collection.
651  /// </summary>
652  /// <param name="indicator">Indicator</param>
653  /// <param name="symbol">The symbol to retrieve historical data for</param>
654  /// <param name="start">The start time in the algorithm's time zone</param>
655  /// <param name="end">The end time in the algorithm's time zone</param>
656  /// <param name="resolution">The resolution to request</param>
657  /// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
658  /// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
659  [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
660  public PyObject Indicator(IndicatorBase<IBaseDataBar> indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func<IBaseData, IBaseDataBar> selector = null)
661  {
662  var history = History(new[] { symbol }, start, end, resolution);
663  return IndicatorHistory(indicator, history, selector).DataFrame;
664  }
665 
666  /// <summary>
667  /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
668  /// The symbol must exist in the Securities collection.
669  /// </summary>
670  /// <param name="indicator">Indicator</param>
671  /// <param name="symbol">The symbol to retrieve historical data for</param>
672  /// <param name="start">The start time in the algorithm's time zone</param>
673  /// <param name="end">The end time in the algorithm's time zone</param>
674  /// <param name="resolution">The resolution to request</param>
675  /// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
676  /// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
677  [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
678  public PyObject Indicator(IndicatorBase<TradeBar> indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func<IBaseData, TradeBar> selector = null)
679  {
680  var history = History(new[] { symbol }, start, end, resolution);
681  return IndicatorHistory(indicator, history, selector).DataFrame;
682  }
683 
684  /// <summary>
685  /// Will return the universe selection data and will optionally perform selection
686  /// </summary>
687  /// <typeparam name="T1">The universe selection universe data type, for example Fundamentals</typeparam>
688  /// <typeparam name="T2">The selection data type, for example Fundamental</typeparam>
689  /// <param name="start">The start date</param>
690  /// <param name="end">Optionally the end date, will default to today</param>
691  /// <param name="func">Optionally the universe selection function</param>
692  /// <param name="dateRule">Date rule to apply for the history data</param>
693  /// <returns>Enumerable of universe selection data for each date, filtered if the func was provided</returns>
694  public IEnumerable<IEnumerable<T2>> UniverseHistory<T1, T2>(DateTime start, DateTime? end = null, Func<IEnumerable<T2>, IEnumerable<Symbol>> func = null, IDateRule dateRule = null)
695  where T1 : BaseDataCollection
696  where T2 : IBaseData
697  {
698  var universeSymbol = ((BaseDataCollection)typeof(T1).GetBaseDataInstance()).UniverseSymbol();
699 
700  var symbols = new[] { universeSymbol };
701  var endDate = end ?? DateTime.UtcNow.Date;
702  var requests = CreateDateRangeHistoryRequests(new[] { universeSymbol }, typeof(T1), start, endDate);
703  var history = GetDataTypedHistory<BaseDataCollection>(requests).Select(x => x.Values.Single());
704 
705  HashSet<Symbol> filteredSymbols = null;
706  Func<BaseDataCollection, IEnumerable<T2>> castDataPoint = dataPoint =>
707  {
708  var castedType = dataPoint.Data.OfType<T2>();
709  if (func != null)
710  {
711  var selection = func(castedType);
712  if (!ReferenceEquals(selection, Universe.Unchanged))
713  {
714  filteredSymbols = selection.ToHashSet();
715  }
716  return castedType.Where(x => filteredSymbols == null || filteredSymbols.Contains(x.Symbol));
717  }
718  else
719  {
720  return castedType;
721  }
722  };
723 
724  Func<BaseDataCollection, DateTime> getTime = datapoint => datapoint.EndTime.Date;
725 
726 
727  return PerformSelection<IEnumerable<T2>, BaseDataCollection>(history, castDataPoint, getTime, start, endDate, dateRule);
728  }
729 
730  /// <summary>
731  /// Will return the universe selection data and will optionally perform selection
732  /// </summary>
733  /// <param name="universe">The universe to fetch the data for</param>
734  /// <param name="start">The start date</param>
735  /// <param name="end">Optionally the end date, will default to today</param>
736  /// <param name="dateRule">Date rule to apply for the history data</param>
737  /// <returns>Enumerable of universe selection data for each date, filtered if the func was provided</returns>
738  public IEnumerable<IEnumerable<BaseData>> UniverseHistory(Universe universe, DateTime start, DateTime? end = null, IDateRule dateRule = null)
739  {
740  return RunUniverseSelection(universe, start, end, dateRule);
741  }
742 
743  /// <summary>
744  /// Will return the universe selection data and will optionally perform selection
745  /// </summary>
746  /// <param name="universe">The universe to fetch the data for</param>
747  /// <param name="start">The start date</param>
748  /// <param name="end">Optionally the end date, will default to today</param>
749  /// <param name="func">Optionally the universe selection function</param>
750  /// <param name="dateRule">Date rule to apply for the history data</param>
751  /// <param name="flatten">Whether to flatten the resulting data frame.
752  /// For universe data, the each row represents a day of data, and the data is stored in a list in a cell of the data frame.
753  /// If flatten is true, the resulting data frame will contain one row per universe constituent,
754  /// and each property of the constituent will be a column in the data frame.</param>
755  /// <returns>Enumerable of universe selection data for each date, filtered if the func was provided</returns>
756  public PyObject UniverseHistory(PyObject universe, DateTime start, DateTime? end = null, PyObject func = null, IDateRule dateRule = null,
757  bool flatten = false)
758  {
759  if (universe.TryConvert<Universe>(out var convertedUniverse))
760  {
761  if (func != null)
762  {
763  throw new ArgumentException($"When providing a universe, the selection func argument isn't supported. Please provider a universe or a type and a func");
764  }
765  var filteredUniverseSelectionData = RunUniverseSelection(convertedUniverse, start, end, dateRule);
766 
767  return GetDataFrame(filteredUniverseSelectionData, flatten);
768  }
769  // for backwards compatibility
770  if (universe.TryConvert<Type>(out var convertedType) && convertedType.IsAssignableTo(typeof(BaseDataCollection)))
771  {
772  var endDate = end ?? DateTime.UtcNow.Date;
773  var universeSymbol = ((BaseDataCollection)convertedType.GetBaseDataInstance()).UniverseSymbol();
774  if (func == null)
775  {
776  return History(universe, universeSymbol, start, endDate, flatten: flatten);
777  }
778 
779  var requests = CreateDateRangeHistoryRequests(new[] { universeSymbol }, convertedType, start, endDate);
780  var history = History(requests);
781 
782  return GetDataFrame(GetFilteredSlice(history, func, start, endDate, dateRule), flatten, convertedType);
783  }
784 
785  throw new ArgumentException($"Failed to convert given universe {universe}. Please provider a valid {nameof(Universe)}");
786  }
787 
788  /// <summary>
789  /// Gets Portfolio Statistics from a pandas.DataFrame with equity and benchmark values
790  /// </summary>
791  /// <param name="dataFrame">pandas.DataFrame with the information required to compute the Portfolio statistics</param>
792  /// <returns><see cref="PortfolioStatistics"/> object wrapped in a <see cref="PyDict"/> with the portfolio statistics.</returns>
793  public PyDict GetPortfolioStatistics(PyObject dataFrame)
794  {
795  var dictBenchmark = new SortedDictionary<DateTime, double>();
796  var dictEquity = new SortedDictionary<DateTime, double>();
797  var dictPL = new SortedDictionary<DateTime, double>();
798 
799  using (Py.GIL())
800  {
801  var result = new PyDict();
802 
803  try
804  {
805  // Converts the data from pandas.DataFrame into dictionaries keyed by time
806  var df = ((dynamic)dataFrame).dropna();
807  dictBenchmark = GetDictionaryFromSeries((PyObject)df["benchmark"]);
808  dictEquity = GetDictionaryFromSeries((PyObject)df["equity"]);
809  dictPL = GetDictionaryFromSeries((PyObject)df["equity"].pct_change());
810  }
811  catch (PythonException e)
812  {
813  result.SetItem("Runtime Error", e.Message.ToPython());
814  return result;
815  }
816 
817  // Convert the double into decimal
818  var equity = new SortedDictionary<DateTime, decimal>(dictEquity.ToDictionary(kvp => kvp.Key, kvp => (decimal)kvp.Value));
819  var profitLoss = new SortedDictionary<DateTime, decimal>(dictPL.ToDictionary(kvp => kvp.Key, kvp => double.IsNaN(kvp.Value) ? 0 : (decimal)kvp.Value));
820 
821  // Gets the last value of the day of the benchmark and equity
822  var listBenchmark = CalculateDailyRateOfChange(dictBenchmark);
823  var listPerformance = CalculateDailyRateOfChange(dictEquity);
824 
825  // Gets the startting capital
826  var startingCapital = Convert.ToDecimal(dictEquity.FirstOrDefault().Value);
827 
828  // call method to set tradingDayPerYear for Algorithm (use: backwards compatibility)
830 
831  // Compute portfolio statistics
832  var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital, RiskFreeInterestRateModel,
834 
835  result.SetItem("Average Win (%)", Convert.ToDouble(stats.AverageWinRate * 100).ToPython());
836  result.SetItem("Average Loss (%)", Convert.ToDouble(stats.AverageLossRate * 100).ToPython());
837  result.SetItem("Compounding Annual Return (%)", Convert.ToDouble(stats.CompoundingAnnualReturn * 100m).ToPython());
838  result.SetItem("Drawdown (%)", Convert.ToDouble(stats.Drawdown * 100).ToPython());
839  result.SetItem("Expectancy", Convert.ToDouble(stats.Expectancy).ToPython());
840  result.SetItem("Net Profit (%)", Convert.ToDouble(stats.TotalNetProfit * 100).ToPython());
841  result.SetItem("Sharpe Ratio", Convert.ToDouble(stats.SharpeRatio).ToPython());
842  result.SetItem("Win Rate (%)", Convert.ToDouble(stats.WinRate * 100).ToPython());
843  result.SetItem("Loss Rate (%)", Convert.ToDouble(stats.LossRate * 100).ToPython());
844  result.SetItem("Profit-Loss Ratio", Convert.ToDouble(stats.ProfitLossRatio).ToPython());
845  result.SetItem("Alpha", Convert.ToDouble(stats.Alpha).ToPython());
846  result.SetItem("Beta", Convert.ToDouble(stats.Beta).ToPython());
847  result.SetItem("Annual Standard Deviation", Convert.ToDouble(stats.AnnualStandardDeviation).ToPython());
848  result.SetItem("Annual Variance", Convert.ToDouble(stats.AnnualVariance).ToPython());
849  result.SetItem("Information Ratio", Convert.ToDouble(stats.InformationRatio).ToPython());
850  result.SetItem("Tracking Error", Convert.ToDouble(stats.TrackingError).ToPython());
851  result.SetItem("Treynor Ratio", Convert.ToDouble(stats.TreynorRatio).ToPython());
852 
853  return result;
854  }
855  }
856 
857  /// <summary>
858  /// Get's the universe data for the specified date
859  /// </summary>
860  private IEnumerable<T> GetChainHistory<T>(Symbol canonicalSymbol, DateTime date, out BaseData underlyingData)
861  where T : BaseChainUniverseData
862  {
863  // Use this GetEntry extension method since it's data type dependent, so we get the correct entry for the option universe
864  var marketHoursEntry = MarketHoursDatabase.GetEntry(canonicalSymbol, new[] { typeof(T) });
865  var startInExchangeTz = QuantConnect.Time.GetStartTimeForTradeBars(marketHoursEntry.ExchangeHours, date, QuantConnect.Time.OneDay, 1,
866  extendedMarketHours: false, marketHoursEntry.DataTimeZone);
867  var start = startInExchangeTz.ConvertTo(marketHoursEntry.ExchangeHours.TimeZone, TimeZone);
868  var end = date.ConvertTo(marketHoursEntry.ExchangeHours.TimeZone, TimeZone);
869  var universeData = History<T>(canonicalSymbol, start, end).SingleOrDefault();
870 
871  if (universeData is not null)
872  {
873  underlyingData = universeData.Underlying;
874  return universeData.Data.Cast<T>();
875  }
876 
877  underlyingData = null;
878  return Enumerable.Empty<T>();
879  }
880 
881  /// <summary>
882  /// Helper method to get option/future chain historical data for a given date range
883  /// </summary>
884  private IEnumerable<(DateTime Date, IEnumerable<T> ChainData, BaseData UnderlyingData)> GetChainHistory<T>(
885  Security security, DateTime start, DateTime end, bool extendedMarketHours)
886  where T : BaseChainUniverseData
887  {
888  foreach (var date in QuantConnect.Time.EachTradeableDay(security, start.Date, end.Date, extendedMarketHours))
889  {
890  var universeData = GetChainHistory<T>(security.Symbol, date, out var underlyingData);
891  yield return (date, universeData, underlyingData);
892  }
893  }
894 
895  /// <summary>
896  /// Helper method to perform selection on the given data and filter it
897  /// </summary>
898  private IEnumerable<Slice> GetFilteredSlice(IEnumerable<Slice> history, dynamic func, DateTime start, DateTime end, IDateRule dateRule = null)
899  {
900  HashSet<Symbol> filteredSymbols = null;
901  Func<Slice, Slice> processSlice = slice =>
902  {
903  var filteredData = slice.AllData.OfType<BaseDataCollection>();
904  using (Py.GIL())
905  {
906  using PyObject selection = func(filteredData.SelectMany(baseData => baseData.Data));
907  if (!selection.TryConvert<object>(out var result) || !ReferenceEquals(result, Universe.Unchanged))
908  {
909  filteredSymbols = ((Symbol[])selection.AsManagedObject(typeof(Symbol[]))).ToHashSet();
910  }
911  }
912  return new Slice(slice.Time, filteredData.Where(x => {
913  if (filteredSymbols == null)
914  {
915  return true;
916  }
917  x.Data = new List<BaseData>(x.Data.Where(dataPoint => filteredSymbols.Contains(dataPoint.Symbol)));
918  return true;
919  }), slice.UtcTime);
920  };
921 
922  Func<Slice, DateTime> getTime = slice => slice.Time.Date;
923  return PerformSelection<Slice, Slice>(history, processSlice, getTime, start, end, dateRule);
924  }
925 
926  /// <summary>
927  /// Helper method to perform selection on the given data and filter it using the given universe
928  /// </summary>
929  private IEnumerable<BaseDataCollection> RunUniverseSelection(Universe universe, DateTime start, DateTime? end = null, IDateRule dateRule = null)
930  {
931  var endDate = end ?? DateTime.UtcNow.Date;
932  var history = History(universe, start, endDate);
933 
934  HashSet<Symbol> filteredSymbols = null;
935  Func<BaseDataCollection, BaseDataCollection> processDataPoint = dataPoint =>
936  {
937  var utcTime = dataPoint.EndTime.ConvertToUtc(universe.Configuration.ExchangeTimeZone);
938  var selection = universe.SelectSymbols(utcTime, dataPoint);
939  if (!ReferenceEquals(selection, Universe.Unchanged))
940  {
941  filteredSymbols = selection.ToHashSet();
942  }
943  dataPoint.Data = dataPoint.Data.Where(x => filteredSymbols == null || filteredSymbols.Contains(x.Symbol)).ToList();
944  return dataPoint;
945  };
946 
947  Func<BaseDataCollection, DateTime> getTime = dataPoint => dataPoint.EndTime.Date;
948 
949  return PerformSelection<BaseDataCollection, BaseDataCollection>(history, processDataPoint, getTime, start, endDate, dateRule);
950  }
951 
952  /// <summary>
953  /// Converts a pandas.Series into a <see cref="SortedDictionary{DateTime, Double}"/>
954  /// </summary>
955  /// <param name="series">pandas.Series to be converted</param>
956  /// <returns><see cref="SortedDictionary{DateTime, Double}"/> with pandas.Series information</returns>
957  private SortedDictionary<DateTime, double> GetDictionaryFromSeries(PyObject series)
958  {
959  var dictionary = new SortedDictionary<DateTime, double>();
960 
961  var pyDict = new PyDict(((dynamic)series).to_dict());
962  foreach (PyObject item in pyDict.Items())
963  {
964  var key = (DateTime)item[0].AsManagedObject(typeof(DateTime));
965  var value = (double)item[1].AsManagedObject(typeof(double));
966  dictionary.Add(key, value);
967  }
968 
969  return dictionary;
970  }
971 
972  /// <summary>
973  /// Calculates the daily rate of change
974  /// </summary>
975  /// <param name="dictionary"><see cref="IDictionary{DateTime, Double}"/> with prices keyed by time</param>
976  /// <returns><see cref="List{Double}"/> with daily rate of change</returns>
977  private List<double> CalculateDailyRateOfChange(IDictionary<DateTime, double> dictionary)
978  {
979  var daily = dictionary.GroupBy(kvp => kvp.Key.Date)
980  .ToDictionary(x => x.Key, v => v.LastOrDefault().Value)
981  .Values.ToArray();
982 
983  var rocp = new double[daily.Length];
984  for (var i = 1; i < daily.Length; i++)
985  {
986  rocp[i] = (daily[i] - daily[i - 1]) / daily[i - 1];
987  }
988  rocp[0] = 0;
989 
990  return rocp.ToList();
991  }
992 
993  /// <summary>
994  /// Gets a value of a property
995  /// </summary>
996  /// <param name="baseData">Object with the desired property</param>
997  /// <param name="fullName">Property name</param>
998  /// <returns>Property value</returns>
999  private object GetPropertyValue(object baseData, string fullName)
1000  {
1001  foreach (var name in fullName.Split('.'))
1002  {
1003  if (baseData == null) return null;
1004 
1005  // TODO this is expensive and can be cached
1006  var info = baseData.GetType().GetProperty(name);
1007 
1008  baseData = info?.GetValue(baseData, null);
1009  }
1010 
1011  return baseData;
1012  }
1013 
1014  /// <summary>
1015  /// Get all fundamental data for given symbols
1016  /// </summary>
1017  /// <param name="symbols">The symbols to retrieve fundamental data for</param>
1018  /// <param name="start">The start date of selected data</param>
1019  /// <param name="end">The end date of selected data</param>
1020  /// <returns>DataDictionary of Enumerable IBaseData</returns>
1021  private Dictionary<DateTime, DataDictionary<dynamic>> GetAllFundamental(IEnumerable<Symbol> symbols, string selector, DateTime? start = null, DateTime? end = null)
1022  {
1023  //SubscriptionRequest does not except nullable DateTimes, so set a startTime and endTime
1024  var startTime = start.HasValue ? (DateTime)start : QuantConnect.Time.Start;
1025  var endTime = end.HasValue ? (DateTime) end : DateTime.UtcNow.Date;
1026 
1027  //Collection to store our results
1028  var data = new Dictionary<DateTime, DataDictionary<dynamic>>();
1029 
1030  //Get all data for each symbol and fill our dictionary
1031  foreach (var symbol in symbols)
1032  {
1033  var exchangeHours = MarketHoursDatabase.GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
1034  foreach (var date in QuantConnect.Time.EachTradeableDayInTimeZone(exchangeHours, startTime, endTime, TimeZones.NewYork))
1035  {
1036  var currentData = new Fundamental(date, symbol);
1037  var time = currentData.EndTime;
1038  object dataPoint = currentData;
1039  if (!string.IsNullOrWhiteSpace(selector))
1040  {
1041  dataPoint = GetPropertyValue(currentData, selector);
1042  if (BaseFundamentalDataProvider.IsNone(dataPoint))
1043  {
1044  dataPoint = null;
1045  }
1046  }
1047 
1048  if (!data.TryGetValue(time, out var dataAtTime))
1049  {
1050  dataAtTime = data[time] = new DataDictionary<dynamic>(time);
1051  }
1052  dataAtTime.Add(currentData.Symbol, dataPoint);
1053  }
1054  }
1055  return data;
1056  }
1057 
1058  private Symbol GetOptionSymbolForHistoryRequest(Symbol symbol, string targetOption, Resolution? resolution, bool fillForward)
1059  {
1060  // Load a canonical option Symbol if the user provides us with an underlying Symbol
1061  if (!symbol.SecurityType.IsOption())
1062  {
1063  var option = AddOption(symbol, targetOption, resolution, symbol.ID.Market, fillForward);
1064 
1065  // Allow 20 strikes from the money for futures. No expiry filter is applied
1066  // so that any future contract provided will have data returned.
1067  if (symbol.SecurityType == SecurityType.Future && symbol.IsCanonical())
1068  {
1069  throw new ArgumentException("The Future Symbol provided is a canonical Symbol (i.e. a Symbol representing all Futures), which is not supported at this time. " +
1070  "If you are using the Symbol accessible from `AddFuture(...)`, use the Symbol from `AddFutureContract(...)` instead. " +
1071  "You can use `qb.FutureOptionChainProvider(canonicalFuture, datetime)` to get a list of futures contracts for a given date, and add them to your algorithm with `AddFutureContract(symbol, Resolution)`.");
1072  }
1073  if (symbol.SecurityType == SecurityType.Future && !symbol.IsCanonical())
1074  {
1075  option.SetFilter(universe => universe.Strikes(-10, +10));
1076  }
1077 
1078  symbol = option.Symbol;
1079  }
1080 
1081  return symbol;
1082  }
1083 
1084  private static void RecycleMemory()
1085  {
1086  Task.Delay(TimeSpan.FromSeconds(20)).ContinueWith(_ =>
1087  {
1088  if (Logging.Log.DebuggingEnabled)
1089  {
1090  Logging.Log.Debug($"QuantBook.RecycleMemory(): running...");
1091  }
1092 
1093  GC.Collect();
1094 
1095  RecycleMemory();
1096  }, TaskScheduler.Current);
1097  }
1098 
1099  protected static IEnumerable<T1> PerformSelection<T1, T2>(
1100  IEnumerable<T2> history,
1101  Func<T2, T1> processDataPointFunction,
1102  Func<T2, DateTime> getTime,
1103  DateTime start,
1104  DateTime endDate,
1105  IDateRule dateRule = null)
1106  {
1107  if (dateRule == null)
1108  {
1109  foreach(var dataPoint in history)
1110  {
1111  yield return processDataPointFunction(dataPoint);
1112  }
1113 
1114  yield break;
1115  }
1116 
1117  var targetDatesQueue = new Queue<DateTime>(dateRule.GetDates(start, endDate));
1118  T2 previousDataPoint = default;
1119  foreach (var dataPoint in history)
1120  {
1121  var dataPointWasProcessed = false;
1122 
1123  // If the datapoint date is greater than the target date on the top, process the last
1124  // datapoint and remove target dates from the queue until the target date on the top is
1125  // greater than the current datapoint date
1126  while (targetDatesQueue.TryPeek(out var targetDate) && getTime(dataPoint) >= targetDate)
1127  {
1128  if (getTime(dataPoint) == targetDate)
1129  {
1130  yield return processDataPointFunction(dataPoint);
1131 
1132  // We use each data point just once, this is, we cannot return the same datapoint
1133  // twice
1134  dataPointWasProcessed = true;
1135  }
1136  else
1137  {
1138  if (!Equals(previousDataPoint, default(T2)))
1139  {
1140  yield return processDataPointFunction(previousDataPoint);
1141  }
1142  }
1143 
1144  previousDataPoint = default;
1145  // Search the next target date
1146  targetDatesQueue.Dequeue();
1147  }
1148 
1149  if (!dataPointWasProcessed)
1150  {
1151  previousDataPoint = dataPoint;
1152  }
1153  }
1154  }
1155  }
1156 }