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