Lean  $LEAN_TAG$
QCAlgorithm.Python.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 QuantConnect.Data;
20 using System;
22 using NodaTime;
23 using System.Collections.Generic;
24 using QuantConnect.Python;
25 using Python.Runtime;
28 using System.Linq;
31 using QuantConnect.Util;
32 
33 namespace QuantConnect.Algorithm
34 {
35  public partial class QCAlgorithm
36  {
37  private readonly Dictionary<IntPtr, PythonIndicator> _pythonIndicators = new Dictionary<IntPtr, PythonIndicator>();
38 
39  /// <summary>
40  /// PandasConverter for this Algorithm
41  /// </summary>
42  public virtual PandasConverter PandasConverter { get; private set; }
43 
44  /// <summary>
45  /// Sets pandas converter
46  /// </summary>
47  public void SetPandasConverter()
48  {
50  }
51 
52  /// <summary>
53  /// AddData a new user defined data source, requiring only the minimum config options.
54  /// The data is added with a default time zone of NewYork (Eastern Daylight Savings Time).
55  /// This method is meant for custom data types that require a ticker, but have no underlying Symbol.
56  /// Examples of data sources that meet this criteria are U.S. Treasury Yield Curve Rates and Trading Economics data
57  /// </summary>
58  /// <param name="type">Data source type</param>
59  /// <param name="ticker">Key/Ticker for data</param>
60  /// <param name="resolution">Resolution of the data</param>
61  /// <returns>The new <see cref="Security"/></returns>
62  [DocumentationAttribute(AddingData)]
63  public Security AddData(PyObject type, string ticker, Resolution? resolution = null)
64  {
65  return AddData(type, ticker, resolution, null, false, 1m);
66  }
67 
68  /// <summary>
69  /// AddData a new user defined data source, requiring only the minimum config options.
70  /// The data is added with a default time zone of NewYork (Eastern Daylight Savings Time).
71  /// This adds a Symbol to the `Underlying` property in the custom data Symbol object.
72  /// Use this method when adding custom data with a ticker from the past, such as "AOL"
73  /// before it became "TWX", or if you need to filter using custom data and place trades on the
74  /// Symbol associated with the custom data.
75  /// </summary>
76  /// <param name="type">Data source type</param>
77  /// <param name="underlying">The underlying symbol for the custom data</param>
78  /// <param name="resolution">Resolution of the data</param>
79  /// <returns>The new <see cref="Security"/></returns>
80  /// <remarks>
81  /// We include three optional unused object parameters so that pythonnet chooses the intended method
82  /// correctly. Previously, calling the overloaded method that accepts a string would instead call this method.
83  /// Adding the three unused parameters makes it choose the correct method when using a string or Symbol. This is
84  /// due to pythonnet's method precedence, as viewable here: https://github.com/QuantConnect/pythonnet/blob/9e29755c54e6008cb016e3dd9d75fbd8cd19fcf7/src/runtime/methodbinder.cs#L215
85  /// </remarks>
86  [DocumentationAttribute(AddingData)]
87  public Security AddData(PyObject type, Symbol underlying, Resolution? resolution = null)
88  {
89  return AddData(type, underlying, resolution, null, false, 1m);
90  }
91 
92  /// <summary>
93  /// AddData a new user defined data source, requiring only the minimum config options.
94  /// This method is meant for custom data types that require a ticker, but have no underlying Symbol.
95  /// Examples of data sources that meet this criteria are U.S. Treasury Yield Curve Rates and Trading Economics data
96  /// </summary>
97  /// <param name="type">Data source type</param>
98  /// <param name="ticker">Key/Ticker for data</param>
99  /// <param name="resolution">Resolution of the Data Required</param>
100  /// <param name="timeZone">Specifies the time zone of the raw data</param>
101  /// <param name="fillForward">When no data available on a tradebar, return the last data that was generated</param>
102  /// <param name="leverage">Custom leverage per security</param>
103  /// <returns>The new <see cref="Security"/></returns>
104  [DocumentationAttribute(AddingData)]
105  public Security AddData(PyObject type, string ticker, Resolution? resolution, DateTimeZone timeZone, bool fillForward = false, decimal leverage = 1.0m)
106  {
107  return AddData(type.CreateType(), ticker, resolution, timeZone, fillForward, leverage);
108  }
109 
110  /// <summary>
111  /// AddData a new user defined data source, requiring only the minimum config options.
112  /// This adds a Symbol to the `Underlying` property in the custom data Symbol object.
113  /// Use this method when adding custom data with a ticker from the past, such as "AOL"
114  /// before it became "TWX", or if you need to filter using custom data and place trades on the
115  /// Symbol associated with the custom data.
116  /// </summary>
117  /// <param name="type">Data source type</param>
118  /// <param name="underlying">The underlying symbol for the custom data</param>
119  /// <param name="resolution">Resolution of the Data Required</param>
120  /// <param name="timeZone">Specifies the time zone of the raw data</param>
121  /// <param name="fillForward">When no data available on a tradebar, return the last data that was generated</param>
122  /// <param name="leverage">Custom leverage per security</param>
123  /// <returns>The new <see cref="Security"/></returns>
124  /// <remarks>
125  /// We include three optional unused object parameters so that pythonnet chooses the intended method
126  /// correctly. Previously, calling the overloaded method that accepts a string would instead call this method.
127  /// Adding the three unused parameters makes it choose the correct method when using a string or Symbol. This is
128  /// due to pythonnet's method precedence, as viewable here: https://github.com/QuantConnect/pythonnet/blob/9e29755c54e6008cb016e3dd9d75fbd8cd19fcf7/src/runtime/methodbinder.cs#L215
129  /// </remarks>
130  [DocumentationAttribute(AddingData)]
131  public Security AddData(PyObject type, Symbol underlying, Resolution? resolution, DateTimeZone timeZone, bool fillForward = false, decimal leverage = 1.0m)
132  {
133  return AddData(type.CreateType(), underlying, resolution, timeZone, fillForward, leverage);
134  }
135 
136  /// <summary>
137  /// AddData a new user defined data source, requiring only the minimum config options.
138  /// This method is meant for custom data types that require a ticker, but have no underlying Symbol.
139  /// Examples of data sources that meet this criteria are U.S. Treasury Yield Curve Rates and Trading Economics data
140  /// </summary>
141  /// <param name="dataType">Data source type</param>
142  /// <param name="ticker">Key/Ticker for data</param>
143  /// <param name="resolution">Resolution of the Data Required</param>
144  /// <param name="timeZone">Specifies the time zone of the raw data</param>
145  /// <param name="fillForward">When no data available on a tradebar, return the last data that was generated</param>
146  /// <param name="leverage">Custom leverage per security</param>
147  /// <returns>The new <see cref="Security"/></returns>
148  [DocumentationAttribute(AddingData)]
149  public Security AddData(Type dataType, string ticker, Resolution? resolution, DateTimeZone timeZone, bool fillForward = false, decimal leverage = 1.0m)
150  {
151  // NOTE: Invoking methods on BaseData w/out setting the symbol may provide unexpected behavior
152  var baseInstance = dataType.GetBaseDataInstance();
153  if (!baseInstance.RequiresMapping())
154  {
155  var symbol = new Symbol(
156  SecurityIdentifier.GenerateBase(dataType, ticker, Market.USA, baseInstance.RequiresMapping()),
157  ticker);
158  return AddDataImpl(dataType, symbol, resolution, timeZone, fillForward, leverage);
159  }
160  // If we need a mappable ticker and we can't find one in the SymbolCache, throw
161  Symbol underlying;
162  if (!SymbolCache.TryGetSymbol(ticker, out underlying))
163  {
164  throw new InvalidOperationException($"The custom data type {dataType.Name} requires mapping, but the provided ticker is not in the cache. " +
165  $"Please add this custom data type using a Symbol or perform this call after " +
166  $"a Security has been added using AddEquity, AddForex, AddCfd, AddCrypto, AddFuture, AddOption or AddSecurity. " +
167  $"An example use case can be found in CustomDataAddDataRegressionAlgorithm");
168  }
169 
170  return AddData(dataType, underlying, resolution, timeZone, fillForward, leverage);
171  }
172 
173  /// <summary>
174  /// AddData a new user defined data source, requiring only the minimum config options.
175  /// This adds a Symbol to the `Underlying` property in the custom data Symbol object.
176  /// Use this method when adding custom data with a ticker from the past, such as "AOL"
177  /// before it became "TWX", or if you need to filter using custom data and place trades on the
178  /// Symbol associated with the custom data.
179  /// </summary>
180  /// <param name="dataType">Data source type</param>
181  /// <param name="underlying"></param>
182  /// <param name="resolution">Resolution of the Data Required</param>
183  /// <param name="timeZone">Specifies the time zone of the raw data</param>
184  /// <param name="fillForward">When no data available on a tradebar, return the last data that was generated</param>
185  /// <param name="leverage">Custom leverage per security</param>
186  /// <returns>The new <see cref="Security"/></returns>
187  /// <remarks>
188  /// We include three optional unused object parameters so that pythonnet chooses the intended method
189  /// correctly. Previously, calling the overloaded method that accepts a string would instead call this method.
190  /// Adding the three unused parameters makes it choose the correct method when using a string or Symbol. This is
191  /// due to pythonnet's method precedence, as viewable here: https://github.com/QuantConnect/pythonnet/blob/9e29755c54e6008cb016e3dd9d75fbd8cd19fcf7/src/runtime/methodbinder.cs#L215
192  /// </remarks>
193  [DocumentationAttribute(AddingData)]
194  public Security AddData(Type dataType, Symbol underlying, Resolution? resolution = null, DateTimeZone timeZone = null, bool fillForward = false, decimal leverage = 1.0m)
195  {
196  var symbol = QuantConnect.Symbol.CreateBase(dataType, underlying, underlying.ID.Market);
197  return AddDataImpl(dataType, symbol, resolution, timeZone, fillForward, leverage);
198  }
199 
200  /// <summary>
201  /// AddData a new user defined data source including symbol properties and exchange hours,
202  /// all other vars are not required and will use defaults.
203  /// This overload reflects the C# equivalent for custom properties and market hours
204  /// </summary>
205  /// <param name="type">Data source type</param>
206  /// <param name="ticker">Key/Ticker for data</param>
207  /// <param name="properties">The properties of this new custom data</param>
208  /// <param name="exchangeHours">The Exchange hours of this symbol</param>
209  /// <param name="resolution">Resolution of the Data Required</param>
210  /// <param name="fillForward">When no data available on a tradebar, return the last data that was generated</param>
211  /// <param name="leverage">Custom leverage per security</param>
212  /// <returns>The new <see cref="Security"/></returns>
213  [DocumentationAttribute(AddingData)]
214  public Security AddData(PyObject type, string ticker, SymbolProperties properties, SecurityExchangeHours exchangeHours, Resolution? resolution = null, bool fillForward = false, decimal leverage = 1.0m)
215  {
216  // Get the right key for storage of base type symbols
217  var dataType = type.CreateType();
218  var key = SecurityIdentifier.GenerateBaseSymbol(dataType, ticker);
219 
220  // Add entries to our Symbol Properties DB and MarketHours DB
221  SetDatabaseEntries(key, properties, exchangeHours);
222 
223  // Then add the data
224  return AddData(dataType, ticker, resolution, null, fillForward, leverage);
225  }
226 
227  /// <summary>
228  /// Creates and adds a new Future Option contract to the algorithm.
229  /// </summary>
230  /// <param name="futureSymbol">The Future canonical symbol (i.e. Symbol returned from <see cref="AddFuture"/>)</param>
231  /// <param name="optionFilter">Filter to apply to option contracts loaded as part of the universe</param>
232  /// <returns>The new Option security, containing a Future as its underlying.</returns>
233  /// <exception cref="ArgumentException">The symbol provided is not canonical.</exception>
234  [DocumentationAttribute(AddingData)]
235  public void AddFutureOption(Symbol futureSymbol, PyObject optionFilter)
236  {
237  Func<OptionFilterUniverse, OptionFilterUniverse> optionFilterUniverse;
238  if (!optionFilter.TryConvertToDelegate(out optionFilterUniverse))
239  {
240  throw new ArgumentException("Option contract universe filter provided is not a function");
241  }
242 
243  AddFutureOption(futureSymbol, optionFilterUniverse);
244  }
245 
246  /// <summary>
247  /// Adds the provided final Symbol with/without underlying set to the algorithm.
248  /// This method is meant for custom data types that require a ticker, but have no underlying Symbol.
249  /// Examples of data sources that meet this criteria are U.S. Treasury Yield Curve Rates and Trading Economics data
250  /// </summary>
251  /// <param name="dataType">Data source type</param>
252  /// <param name="symbol">Final symbol that includes underlying (if any)</param>
253  /// <param name="resolution">Resolution of the Data required</param>
254  /// <param name="timeZone">Specifies the time zone of the raw data</param>
255  /// <param name="fillForward">When no data available on a tradebar, return the last data that was generated</param>
256  /// <param name="leverage">Custom leverage per security</param>
257  /// <returns>The new <see cref="Security"/></returns>
258  private Security AddDataImpl(Type dataType, Symbol symbol, Resolution? resolution, DateTimeZone timeZone, bool fillForward, decimal leverage)
259  {
260  var alias = symbol.ID.Symbol;
261  SymbolCache.Set(alias, symbol);
262 
263  if (timeZone != null)
264  {
265  // user set time zone
266  MarketHoursDatabase.SetEntryAlwaysOpen(symbol.ID.Market, alias, SecurityType.Base, timeZone);
267  }
268 
269  //Add this new generic data as a tradeable security:
271  dataType,
272  symbol,
273  resolution,
274  fillForward,
275  isCustomData: true,
276  extendedMarketHours: true);
277  var security = Securities.CreateSecurity(symbol, config, leverage, addToSymbolCache: false);
278 
279  return AddToUserDefinedUniverse(security, new List<SubscriptionDataConfig> { config });
280  }
281 
282  /// <summary>
283  /// Creates a new universe and adds it to the algorithm. This is for coarse fundamental US Equity data and
284  /// will be executed on day changes in the NewYork time zone (<see cref="TimeZones.NewYork"/>)
285  /// </summary>
286  /// <param name="pyObject">Defines an initial coarse selection</param>
287  [DocumentationAttribute(Universes)]
288  public Universe AddUniverse(PyObject pyObject)
289  {
290  Func<IEnumerable<Fundamental>, object> fundamentalSelector;
291  Universe universe;
292 
293  if (pyObject.TryCreateType(out var type))
294  {
295  return AddUniverse(pyObject, null, null);
296  }
297  // TODO: to be removed when https://github.com/QuantConnect/pythonnet/issues/62 is solved
298  else if(pyObject.TryConvert(out universe))
299  {
300  return AddUniverse(universe);
301  }
302  else if (pyObject.TryConvert(out universe, allowPythonDerivative: true))
303  {
304  return AddUniverse(new UniversePythonWrapper(pyObject));
305  }
306  else if (pyObject.TryConvertToDelegate(out fundamentalSelector))
307  {
308  return AddUniverse(FundamentalUniverse.USA(fundamentalSelector));
309  }
310  else
311  {
312  using (Py.GIL())
313  {
314  throw new ArgumentException($"QCAlgorithm.AddUniverse: {pyObject.Repr()} is not a valid argument.");
315  }
316  }
317  }
318 
319  /// <summary>
320  /// Creates a new universe and adds it to the algorithm. This is for coarse and fine fundamental US Equity data and
321  /// will be executed on day changes in the NewYork time zone (<see cref="TimeZones.NewYork"/>)
322  /// </summary>
323  /// <param name="pyObject">Defines an initial coarse selection or a universe</param>
324  /// <param name="pyfine">Defines a more detailed selection with access to more data</param>
325  [DocumentationAttribute(Universes)]
326  public Universe AddUniverse(PyObject pyObject, PyObject pyfine)
327  {
328  Func<IEnumerable<CoarseFundamental>, object> coarseFunc;
329  Func<IEnumerable<FineFundamental>, object> fineFunc;
330 
331  try
332  {
333  // this is due to a pythonNet limitation even if defining 'AddUniverse(IDateRule, PyObject)'
334  // it will chose this method instead
335  IDateRule dateRule;
336  using (Py.GIL())
337  {
338  dateRule = pyObject.As<IDateRule>();
339  }
340 
341  if (pyfine.TryConvertToDelegate(out coarseFunc))
342  {
343  return AddUniverse(dateRule, coarseFunc.ConvertToUniverseSelectionSymbolDelegate());
344  }
345  }
346  catch (InvalidCastException)
347  {
348  // pass
349  }
350 
351  if (pyObject.TryCreateType(out var type))
352  {
353  return AddUniverse(pyObject, null, pyfine);
354  }
355  else if (pyObject.TryConvert(out Universe universe) && pyfine.TryConvertToDelegate(out fineFunc))
356  {
357  return AddUniverse(universe, fineFunc.ConvertToUniverseSelectionSymbolDelegate());
358  }
359  else if (pyObject.TryConvertToDelegate(out coarseFunc) && pyfine.TryConvertToDelegate(out fineFunc))
360  {
361  return AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate(),
362  fineFunc.ConvertToUniverseSelectionSymbolDelegate());
363  }
364  else
365  {
366  using (Py.GIL())
367  {
368  throw new ArgumentException($"QCAlgorithm.AddUniverse: {pyObject.Repr()} or {pyfine.Repr()} is not a valid argument.");
369  }
370  }
371  }
372 
373  /// <summary>
374  /// Creates a new universe and adds it to the algorithm. This can be used to return a list of string
375  /// symbols retrieved from anywhere and will loads those symbols under the US Equity market.
376  /// </summary>
377  /// <param name="name">A unique name for this universe</param>
378  /// <param name="resolution">The resolution this universe should be triggered on</param>
379  /// <param name="pySelector">Function delegate that accepts a DateTime and returns a collection of string symbols</param>
380  [DocumentationAttribute(Universes)]
381  public Universe AddUniverse(string name, Resolution resolution, PyObject pySelector)
382  {
383  var selector = pySelector.ConvertToDelegate<Func<DateTime, object>>();
384  return AddUniverse(name, resolution, selector.ConvertToUniverseSelectionStringDelegate());
385  }
386 
387  /// <summary>
388  /// Creates a new universe and adds it to the algorithm. This can be used to return a list of string
389  /// symbols retrieved from anywhere and will loads those symbols under the US Equity market.
390  /// </summary>
391  /// <param name="name">A unique name for this universe</param>
392  /// <param name="pySelector">Function delegate that accepts a DateTime and returns a collection of string symbols</param>
393  [DocumentationAttribute(Universes)]
394  public Universe AddUniverse(string name, PyObject pySelector)
395  {
396  var selector = pySelector.ConvertToDelegate<Func<DateTime, object>>();
397  return AddUniverse(name, selector.ConvertToUniverseSelectionStringDelegate());
398  }
399 
400  /// <summary>
401  /// Creates a new user defined universe that will fire on the requested resolution during market hours.
402  /// </summary>
403  /// <param name="securityType">The security type of the universe</param>
404  /// <param name="name">A unique name for this universe</param>
405  /// <param name="resolution">The resolution this universe should be triggered on</param>
406  /// <param name="market">The market of the universe</param>
407  /// <param name="universeSettings">The subscription settings used for securities added from this universe</param>
408  /// <param name="pySelector">Function delegate that accepts a DateTime and returns a collection of string symbols</param>
409  [DocumentationAttribute(Universes)]
410  public Universe AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
411  {
412  var selector = pySelector.ConvertToDelegate<Func<DateTime, object>>();
413  return AddUniverse(securityType, name, resolution, market, universeSettings, selector.ConvertToUniverseSelectionStringDelegate());
414  }
415 
416  /// <summary>
417  /// Creates a new universe and adds it to the algorithm. This will use the default universe settings
418  /// specified via the <see cref="UniverseSettings"/> property. This universe will use the defaults
419  /// of SecurityType.Equity, Resolution.Daily, Market.USA, and UniverseSettings
420  /// </summary>
421  /// <param name="T">The data type</param>
422  /// <param name="name">A unique name for this universe</param>
423  /// <param name="selector">Function delegate that performs selection on the universe data</param>
424  [DocumentationAttribute(Universes)]
425  public Universe AddUniverse(PyObject T, string name, PyObject selector)
426  {
427  return AddUniverse(T.CreateType(), null, name, null, null, null, selector);
428  }
429 
430  /// <summary>
431  /// Creates a new universe and adds it to the algorithm. This will use the default universe settings
432  /// specified via the <see cref="UniverseSettings"/> property. This universe will use the defaults
433  /// of SecurityType.Equity, Market.USA and UniverseSettings
434  /// </summary>
435  /// <param name="T">The data type</param>
436  /// <param name="name">A unique name for this universe</param>
437  /// <param name="resolution">The expected resolution of the universe data</param>
438  /// <param name="selector">Function delegate that performs selection on the universe data</param>
439  [DocumentationAttribute(Universes)]
440  public Universe AddUniverse(PyObject T, string name, Resolution resolution, PyObject selector)
441  {
442  return AddUniverse(T.CreateType(), null, name, resolution, null, null, selector);
443  }
444 
445  /// <summary>
446  /// Creates a new universe and adds it to the algorithm. This will use the default universe settings
447  /// specified via the <see cref="UniverseSettings"/> property. This universe will use the defaults
448  /// of SecurityType.Equity, and Market.USA
449  /// </summary>
450  /// <param name="T">The data type</param>
451  /// <param name="name">A unique name for this universe</param>
452  /// <param name="resolution">The expected resolution of the universe data</param>
453  /// <param name="universeSettings">The settings used for securities added by this universe</param>
454  /// <param name="selector">Function delegate that performs selection on the universe data</param>
455  [DocumentationAttribute(Universes)]
456  public Universe AddUniverse(PyObject T, string name, Resolution resolution, UniverseSettings universeSettings, PyObject selector)
457  {
458  return AddUniverse(T.CreateType(), null, name, resolution, null, universeSettings, selector);
459  }
460 
461  /// <summary>
462  /// Creates a new universe and adds it to the algorithm. This will use the default universe settings
463  /// specified via the <see cref="UniverseSettings"/> property. This universe will use the defaults
464  /// of SecurityType.Equity, Resolution.Daily, and Market.USA
465  /// </summary>
466  /// <param name="T">The data type</param>
467  /// <param name="name">A unique name for this universe</param>
468  /// <param name="universeSettings">The settings used for securities added by this universe</param>
469  /// <param name="selector">Function delegate that performs selection on the universe data</param>
470  [DocumentationAttribute(Universes)]
471  public Universe AddUniverse(PyObject T, string name, UniverseSettings universeSettings, PyObject selector)
472  {
473  return AddUniverse(T.CreateType(), null, name, null, null, universeSettings, selector);
474  }
475 
476  /// <summary>
477  /// Creates a new universe and adds it to the algorithm. This will use the default universe settings
478  /// specified via the <see cref="UniverseSettings"/> property.
479  /// </summary>
480  /// <param name="T">The data type</param>
481  /// <param name="securityType">The security type the universe produces</param>
482  /// <param name="name">A unique name for this universe</param>
483  /// <param name="resolution">The expected resolution of the universe data</param>
484  /// <param name="market">The market for selected symbols</param>
485  /// <param name="selector">Function delegate that performs selection on the universe data</param>
486  [DocumentationAttribute(Universes)]
487  public Universe AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, PyObject selector)
488  {
489  return AddUniverse(T.CreateType(), securityType, name, resolution, market, null, selector);
490  }
491 
492  /// <summary>
493  /// Creates a new universe and adds it to the algorithm
494  /// </summary>
495  /// <param name="T">The data type</param>
496  /// <param name="securityType">The security type the universe produces</param>
497  /// <param name="name">A unique name for this universe</param>
498  /// <param name="resolution">The expected resolution of the universe data</param>
499  /// <param name="market">The market for selected symbols</param>
500  /// <param name="universeSettings">The subscription settings to use for newly created subscriptions</param>
501  /// <param name="selector">Function delegate that performs selection on the universe data</param>
502  [DocumentationAttribute(Universes)]
503  public Universe AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject selector)
504  {
505  return AddUniverse(T.CreateType(), securityType, name, resolution, market, universeSettings, selector);
506  }
507 
508  /// <summary>
509  /// Creates a new universe and adds it to the algorithm
510  /// </summary>
511  /// <param name="dataType">The data type</param>
512  /// <param name="securityType">The security type the universe produces</param>
513  /// <param name="name">A unique name for this universe</param>
514  /// <param name="resolution">The expected resolution of the universe data</param>
515  /// <param name="market">The market for selected symbols</param>
516  /// <param name="universeSettings">The subscription settings to use for newly created subscriptions</param>
517  /// <param name="pySelector">Function delegate that performs selection on the universe data</param>
518  [DocumentationAttribute(Universes)]
519  public Universe AddUniverse(Type dataType, SecurityType? securityType = null, string name = null, Resolution? resolution = null, string market = null, UniverseSettings universeSettings = null, PyObject pySelector = null)
520  {
521  if (market.IsNullOrEmpty())
522  {
523  market = Market.USA;
524  }
525  securityType ??= SecurityType.Equity;
526  Func<IEnumerable<BaseData>, IEnumerable<Symbol>> wrappedSelector = null;
527  if (pySelector != null)
528  {
529  var selector = pySelector.ConvertToDelegate<Func<IEnumerable<IBaseData>, object>>();
530  wrappedSelector = baseDatas =>
531  {
532  var result = selector(baseDatas);
533  if (ReferenceEquals(result, Universe.Unchanged))
534  {
535  return Universe.Unchanged;
536  }
537  return ((object[])result).Select(x => x is Symbol symbol ? symbol : QuantConnect.Symbol.Create((string)x, securityType.Value, market, baseDataType: dataType));
538  };
539  }
540  return AddUniverseSymbolSelector(dataType, name, resolution, market, universeSettings, wrappedSelector);
541  }
542 
543  /// <summary>
544  /// Creates a new universe selection model and adds it to the algorithm. This universe selection model will chain to the security
545  /// changes of a given <see cref="Universe"/> selection output and create a new <see cref="OptionChainUniverse"/> for each of them
546  /// </summary>
547  /// <param name="universe">The universe we want to chain an option universe selection model too</param>
548  /// <param name="optionFilter">The option filter universe to use</param>
549  [DocumentationAttribute(Universes)]
550  public void AddUniverseOptions(PyObject universe, PyObject optionFilter)
551  {
552  Func<OptionFilterUniverse, OptionFilterUniverse> convertedOptionChain;
553  Universe universeToChain;
554 
555  if (universe.TryConvert(out universeToChain) && optionFilter.TryConvertToDelegate(out convertedOptionChain))
556  {
557  AddUniverseOptions(universeToChain, convertedOptionChain);
558  }
559  else
560  {
561  using (Py.GIL())
562  {
563  throw new ArgumentException($"QCAlgorithm.AddChainedEquityOptionUniverseSelectionModel: {universe.Repr()} or {optionFilter.Repr()} is not a valid argument.");
564  }
565  }
566  }
567 
568  /// <summary>
569  /// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates
570  /// from the consolidator.
571  /// </summary>
572  /// <param name="symbol">The symbol to register against</param>
573  /// <param name="indicator">The indicator to receive data from the consolidator</param>
574  /// <param name="resolution">The resolution at which to send data to the indicator, null to use the same resolution as the subscription</param>
575  /// <param name="selector">Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)</param>
576  [DocumentationAttribute(Indicators)]
577  [DocumentationAttribute(ConsolidatingData)]
578  public void RegisterIndicator(Symbol symbol, PyObject indicator, Resolution? resolution = null, PyObject selector = null)
579  {
580  RegisterIndicator(symbol, indicator, ResolveConsolidator(symbol, resolution), selector);
581  }
582 
583  /// <summary>
584  /// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates
585  /// from the consolidator.
586  /// </summary>
587  /// <param name="symbol">The symbol to register against</param>
588  /// <param name="indicator">The indicator to receive data from the consolidator</param>
589  /// <param name="resolution">The resolution at which to send data to the indicator, null to use the same resolution as the subscription</param>
590  /// <param name="selector">Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)</param>
591  [DocumentationAttribute(Indicators)]
592  [DocumentationAttribute(ConsolidatingData)]
593  public void RegisterIndicator(Symbol symbol, PyObject indicator, TimeSpan? resolution = null, PyObject selector = null)
594  {
595  RegisterIndicator(symbol, indicator, ResolveConsolidator(symbol, resolution), selector);
596  }
597 
598  /// <summary>
599  /// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates
600  /// from the consolidator.
601  /// </summary>
602  /// <param name="symbol">The symbol to register against</param>
603  /// <param name="indicator">The indicator to receive data from the consolidator</param>
604  /// <param name="pyObject">The python object that it is trying to register with, could be consolidator or a timespan</param>
605  /// <param name="selector">Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)</param>
606  [DocumentationAttribute(Indicators)]
607  [DocumentationAttribute(ConsolidatingData)]
608  public void RegisterIndicator(Symbol symbol, PyObject indicator, PyObject pyObject, PyObject selector = null)
609  {
610  // First check if this is just a regular IDataConsolidator
611  IDataConsolidator dataConsolidator;
612  if (pyObject.TryConvert(out dataConsolidator))
613  {
614  RegisterIndicator(symbol, indicator, dataConsolidator, selector);
615  return;
616  }
617 
618  try
619  {
620  dataConsolidator = new DataConsolidatorPythonWrapper(pyObject);
621  }
622  catch
623  {
624  // Finally, since above didn't work, just try it as a timespan
625  // Issue #4668 Fix
626  using (Py.GIL())
627  {
628  try
629  {
630  // tryConvert does not work for timespan
631  TimeSpan? timeSpan = pyObject.As<TimeSpan>();
632  if (timeSpan != default(TimeSpan))
633  {
634  RegisterIndicator(symbol, indicator, timeSpan, selector);
635  return;
636  }
637  }
638  catch (Exception e)
639  {
640  throw new ArgumentException("Invalid third argument, should be either a valid consolidator or timedelta object. The following exception was thrown: ", e);
641  }
642  }
643  }
644 
645  RegisterIndicator(symbol, indicator, dataConsolidator, selector);
646  }
647 
648  /// <summary>
649  /// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates
650  /// from the consolidator.
651  /// </summary>
652  /// <param name="symbol">The symbol to register against</param>
653  /// <param name="indicator">The indicator to receive data from the consolidator</param>
654  /// <param name="consolidator">The consolidator to receive raw subscription data</param>
655  /// <param name="selector">Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)</param>
656  [DocumentationAttribute(Indicators)]
657  [DocumentationAttribute(ConsolidatingData)]
658  public void RegisterIndicator(Symbol symbol, PyObject indicator, IDataConsolidator consolidator, PyObject selector = null)
659  {
660  // TODO: to be removed when https://github.com/QuantConnect/pythonnet/issues/62 is solved
661  IndicatorBase<IndicatorDataPoint> indicatorDataPoint;
662  IndicatorBase<IBaseDataBar> indicatorDataBar;
663  IndicatorBase<TradeBar> indicatorTradeBar;
664 
665  if (indicator.TryConvert(out indicatorDataPoint))
666  {
667  RegisterIndicator(symbol, indicatorDataPoint, consolidator, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
668  return;
669  }
670  else if (indicator.TryConvert(out indicatorDataBar))
671  {
672  RegisterIndicator(symbol, indicatorDataBar, consolidator, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
673  return;
674  }
675  else if (indicator.TryConvert(out indicatorTradeBar))
676  {
677  RegisterIndicator(symbol, indicatorTradeBar, consolidator, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
678  return;
679  }
680 
681  RegisterIndicator(symbol, WrapPythonIndicator(indicator), consolidator, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
682  }
683 
684  /// <summary>
685  /// Warms up a given indicator with historical data
686  /// </summary>
687  /// <param name="symbol">The symbol whose indicator we want</param>
688  /// <param name="indicator">The indicator we want to warm up</param>
689  /// <param name="resolution">The resolution</param>
690  /// <param name="selector">Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)</param>
691  [DocumentationAttribute(Indicators)]
692  [DocumentationAttribute(HistoricalData)]
693  public void WarmUpIndicator(Symbol symbol, PyObject indicator, Resolution? resolution = null, PyObject selector = null)
694  {
695  // TODO: to be removed when https://github.com/QuantConnect/pythonnet/issues/62 is solved
696 
697  if (indicator.TryConvert(out IndicatorBase<IndicatorDataPoint> indicatorDataPoint))
698  {
699  WarmUpIndicator(symbol, indicatorDataPoint, resolution, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
700  return;
701  }
702  if (indicator.TryConvert(out IndicatorBase<IBaseDataBar> indicatorDataBar))
703  {
704  WarmUpIndicator(symbol, indicatorDataBar, resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
705  return;
706  }
707  if (indicator.TryConvert(out IndicatorBase<TradeBar> indicatorTradeBar))
708  {
709  WarmUpIndicator(symbol, indicatorTradeBar, resolution, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
710  return;
711  }
712 
713  WarmUpIndicator(symbol, WrapPythonIndicator(indicator), resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
714  }
715 
716  /// <summary>
717  /// Plot a chart using string series name, with value.
718  /// </summary>
719  /// <param name="series">Name of the plot series</param>
720  /// <param name="pyObject">PyObject with the value to plot</param>
721  /// <seealso cref="Plot(string,decimal)"/>
722  [DocumentationAttribute(Charting)]
723  public void Plot(string series, PyObject pyObject)
724  {
725  using (Py.GIL())
726  {
727  if (pyObject.TryConvert(out IndicatorBase indicator, true))
728  {
729  Plot(series, indicator);
730  }
731  else
732  {
733  try
734  {
735  var value = (((dynamic)pyObject).Value as PyObject).GetAndDispose<decimal>();
736  Plot(series, value);
737  }
738  catch
739  {
740  var pythonType = pyObject.GetPythonType().Repr();
741  throw new ArgumentException($"QCAlgorithm.Plot(): The last argument should be a QuantConnect Indicator object, {pythonType} was provided.");
742  }
743  }
744  }
745  }
746 
747  /// <summary>
748  /// Plots the value of each indicator on the chart
749  /// </summary>
750  /// <param name="chart">The chart's name</param>
751  /// <param name="first">The first indicator to plot</param>
752  /// <param name="second">The second indicator to plot</param>
753  /// <param name="third">The third indicator to plot</param>
754  /// <param name="fourth">The fourth indicator to plot</param>
755  /// <seealso cref="Plot(string,string,decimal)"/>
756  [DocumentationAttribute(Charting)]
757  public void Plot(string chart, Indicator first, Indicator second = null, Indicator third = null, Indicator fourth = null)
758  {
759  Plot(chart, new[] { first, second, third, fourth }.Where(x => x != null).ToArray());
760  }
761 
762  /// <summary>
763  /// Plots the value of each indicator on the chart
764  /// </summary>
765  /// <param name="chart">The chart's name</param>
766  /// <param name="first">The first indicator to plot</param>
767  /// <param name="second">The second indicator to plot</param>
768  /// <param name="third">The third indicator to plot</param>
769  /// <param name="fourth">The fourth indicator to plot</param>
770  /// <seealso cref="Plot(string,string,decimal)"/>
771  [DocumentationAttribute(Charting)]
772  public void Plot(string chart, BarIndicator first, BarIndicator second = null, BarIndicator third = null, BarIndicator fourth = null)
773  {
774  Plot(chart, new[] { first, second, third, fourth }.Where(x => x != null).ToArray());
775  }
776 
777  /// <summary>
778  /// Plots the value of each indicator on the chart
779  /// </summary>
780  /// <param name="chart">The chart's name</param>
781  /// <param name="first">The first indicator to plot</param>
782  /// <param name="second">The second indicator to plot</param>
783  /// <param name="third">The third indicator to plot</param>
784  /// <param name="fourth">The fourth indicator to plot</param>
785  /// <seealso cref="Plot(string,string,decimal)"/>
786  [DocumentationAttribute(Charting)]
787  public void Plot(string chart, TradeBarIndicator first, TradeBarIndicator second = null, TradeBarIndicator third = null, TradeBarIndicator fourth = null)
788  {
789  Plot(chart, new[] { first, second, third, fourth }.Where(x => x != null).ToArray());
790  }
791 
792  /// <summary>
793  /// Automatically plots each indicator when a new value is available
794  /// </summary>
795  [DocumentationAttribute(Charting)]
796  [DocumentationAttribute(Indicators)]
797  public void PlotIndicator(string chart, PyObject first, PyObject second = null, PyObject third = null, PyObject fourth = null)
798  {
799  var array = GetIndicatorArray(first, second, third, fourth);
800  PlotIndicator(chart, array[0], array[1], array[2], array[3]);
801  }
802 
803  /// <summary>
804  /// Automatically plots each indicator when a new value is available
805  /// </summary>
806  [DocumentationAttribute(Charting)]
807  [DocumentationAttribute(Indicators)]
808  public void PlotIndicator(string chart, bool waitForReady, PyObject first, PyObject second = null, PyObject third = null, PyObject fourth = null)
809  {
810  var array = GetIndicatorArray(first, second, third, fourth);
811  PlotIndicator(chart, waitForReady, array[0], array[1], array[2], array[3]);
812  }
813 
814  /// <summary>
815  /// Creates a new FilteredIdentity indicator for the symbol The indicator will be automatically
816  /// updated on the symbol's subscription resolution
817  /// </summary>
818  /// <param name="symbol">The symbol whose values we want as an indicator</param>
819  /// <param name="selector">Selects a value from the BaseData, if null defaults to the .Value property (x => x.Value)</param>
820  /// <param name="filter">Filters the IBaseData send into the indicator, if null defaults to true (x => true) which means no filter</param>
821  /// <param name="fieldName">The name of the field being selected</param>
822  /// <returns>A new FilteredIdentity indicator for the specified symbol and selector</returns>
823  [DocumentationAttribute(Indicators)]
824  public FilteredIdentity FilteredIdentity(Symbol symbol, PyObject selector = null, PyObject filter = null, string fieldName = null)
825  {
826  var resolution = GetSubscription(symbol).Resolution;
827  return FilteredIdentity(symbol, resolution, selector, filter, fieldName);
828  }
829 
830  /// <summary>
831  /// Creates a new FilteredIdentity indicator for the symbol The indicator will be automatically
832  /// updated on the symbol's subscription resolution
833  /// </summary>
834  /// <param name="symbol">The symbol whose values we want as an indicator</param>
835  /// <param name="resolution">The desired resolution of the data</param>
836  /// <param name="selector">Selects a value from the BaseData, if null defaults to the .Value property (x => x.Value)</param>
837  /// <param name="filter">Filters the IBaseData send into the indicator, if null defaults to true (x => true) which means no filter</param>
838  /// <param name="fieldName">The name of the field being selected</param>
839  /// <returns>A new FilteredIdentity indicator for the specified symbol and selector</returns>
840  [DocumentationAttribute(Indicators)]
841  public FilteredIdentity FilteredIdentity(Symbol symbol, Resolution resolution, PyObject selector = null, PyObject filter = null, string fieldName = null)
842  {
843  var name = CreateIndicatorName(symbol, fieldName ?? "close", resolution);
844  var pyselector = PythonUtil.ToFunc<IBaseData, IBaseDataBar>(selector);
845  var pyfilter = PythonUtil.ToFunc<IBaseData, bool>(filter);
846  var filteredIdentity = new FilteredIdentity(name, pyfilter);
847  RegisterIndicator(symbol, filteredIdentity, resolution, pyselector);
848  return filteredIdentity;
849  }
850 
851  /// <summary>
852  /// Creates a new FilteredIdentity indicator for the symbol The indicator will be automatically
853  /// updated on the symbol's subscription resolution
854  /// </summary>
855  /// <param name="symbol">The symbol whose values we want as an indicator</param>
856  /// <param name="resolution">The desired resolution of the data</param>
857  /// <param name="selector">Selects a value from the BaseData, if null defaults to the .Value property (x => x.Value)</param>
858  /// <param name="filter">Filters the IBaseData send into the indicator, if null defaults to true (x => true) which means no filter</param>
859  /// <param name="fieldName">The name of the field being selected</param>
860  /// <returns>A new FilteredIdentity indicator for the specified symbol and selector</returns>
861  [DocumentationAttribute(Indicators)]
862  public FilteredIdentity FilteredIdentity(Symbol symbol, TimeSpan resolution, PyObject selector = null, PyObject filter = null, string fieldName = null)
863  {
864  var name = $"{symbol}({fieldName ?? "close"}_{resolution.ToStringInvariant(null)})";
865  var pyselector = PythonUtil.ToFunc<IBaseData, IBaseDataBar>(selector);
866  var pyfilter = PythonUtil.ToFunc<IBaseData, bool>(filter);
867  var filteredIdentity = new FilteredIdentity(name, pyfilter);
868  RegisterIndicator(symbol, filteredIdentity, ResolveConsolidator(symbol, resolution), pyselector);
869  return filteredIdentity;
870  }
871 
872  /// <summary>
873  /// Gets the historical data for the specified symbol. The exact number of bars will be returned.
874  /// The symbol must exist in the Securities collection.
875  /// </summary>
876  /// <param name="tickers">The symbols to retrieve historical data for</param>
877  /// <param name="periods">The number of bars to request</param>
878  /// <param name="resolution">The resolution to request</param>
879  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
880  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
881  /// <param name="dataMappingMode">The contract mapping mode to use for the security history request</param>
882  /// <param name="dataNormalizationMode">The price scaling mode to use for the securities history</param>
883  /// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
884  /// For example, 0 will use the front month, 1 will use the back month contract</param>
885  /// <returns>A python dictionary with pandas DataFrame containing the requested historical data</returns>
886  [DocumentationAttribute(HistoricalData)]
887  public PyObject History(PyObject tickers, int periods, Resolution? resolution = null, bool? fillForward = null,
888  bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null, DataNormalizationMode? dataNormalizationMode = null,
889  int? contractDepthOffset = null)
890  {
891  if (tickers.TryConvert<Universe>(out var universe))
892  {
893  resolution ??= universe.Configuration.Resolution;
894  var requests = CreateBarCountHistoryRequests(new[] { universe.Symbol }, universe.DataType, periods, resolution, fillForward, extendedMarketHours,
895  dataMappingMode, dataNormalizationMode, contractDepthOffset);
896  // we pass in 'BaseDataCollection' type so we clean up the dataframe if we can
897  return GetDataFrame(History(requests.Where(x => x != null)), typeof(BaseDataCollection));
898  }
899  if (tickers.TryCreateType(out var type))
900  {
901  var requests = CreateBarCountHistoryRequests(Securities.Keys, type, periods, resolution, fillForward, extendedMarketHours,
902  dataMappingMode, dataNormalizationMode, contractDepthOffset);
903  return GetDataFrame(History(requests.Where(x => x != null)), type);
904  }
905 
906  var symbols = tickers.ConvertToSymbolEnumerable();
907  return GetDataFrame(History(symbols, periods, resolution, fillForward, extendedMarketHours, dataMappingMode, dataNormalizationMode,
908  contractDepthOffset));
909  }
910 
911  /// <summary>
912  /// Gets the historical data for the specified symbols over the requested span.
913  /// The symbols must exist in the Securities collection.
914  /// </summary>
915  /// <param name="tickers">The symbols to retrieve historical data for</param>
916  /// <param name="span">The span over which to retrieve recent historical data</param>
917  /// <param name="resolution">The resolution to request</param>
918  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
919  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
920  /// <param name="dataMappingMode">The contract mapping mode to use for the security history request</param>
921  /// <param name="dataNormalizationMode">The price scaling mode to use for the securities history</param>
922  /// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
923  /// For example, 0 will use the front month, 1 will use the back month contract</param>
924  /// <returns>A python dictionary with pandas DataFrame containing the requested historical data</returns>
925  [DocumentationAttribute(HistoricalData)]
926  public PyObject History(PyObject tickers, TimeSpan span, Resolution? resolution = null, bool? fillForward = null,
927  bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null, DataNormalizationMode? dataNormalizationMode = null,
928  int? contractDepthOffset = null)
929  {
930  return History(tickers, Time - span, Time, resolution, fillForward, extendedMarketHours, dataMappingMode, dataNormalizationMode, contractDepthOffset);
931  }
932 
933  /// <summary>
934  /// Gets the historical data for the specified symbols between the specified dates. The symbols must exist in the Securities collection.
935  /// </summary>
936  /// <param name="tickers">The symbols to retrieve historical data for</param>
937  /// <param name="start">The start time in the algorithm's time zone</param>
938  /// <param name="end">The end time in the algorithm's time zone</param>
939  /// <param name="resolution">The resolution to request</param>
940  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
941  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
942  /// <param name="dataMappingMode">The contract mapping mode to use for the security history request</param>
943  /// <param name="dataNormalizationMode">The price scaling mode to use for the securities history</param>
944  /// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
945  /// For example, 0 will use the front month, 1 will use the back month contract</param>
946  /// <returns>A python dictionary with a pandas DataFrame containing the requested historical data</returns>
947  [DocumentationAttribute(HistoricalData)]
948  public PyObject History(PyObject tickers, DateTime start, DateTime end, Resolution? resolution = null, bool? fillForward = null,
949  bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null, DataNormalizationMode? dataNormalizationMode = null,
950  int? contractDepthOffset = null)
951  {
952  if (tickers.TryConvert<Universe>(out var universe))
953  {
954  resolution ??= universe.Configuration.Resolution;
955  var requests = CreateDateRangeHistoryRequests(new[] { universe.Symbol }, universe.DataType, start, end, resolution, fillForward, extendedMarketHours,
956  dataMappingMode, dataNormalizationMode, contractDepthOffset);
957  // we pass in 'BaseDataCollection' type so we clean up the dataframe if we can
958  return GetDataFrame(History(requests.Where(x => x != null)), typeof(BaseDataCollection));
959  }
960  if (tickers.TryCreateType(out var type))
961  {
962  var requests = CreateDateRangeHistoryRequests(Securities.Keys, type, start, end, resolution, fillForward, extendedMarketHours,
963  dataMappingMode, dataNormalizationMode, contractDepthOffset);
964  return GetDataFrame(History(requests.Where(x => x != null)), type);
965  }
966 
967  var symbols = tickers.ConvertToSymbolEnumerable();
968  return GetDataFrame(History(symbols, start, end, resolution, fillForward, extendedMarketHours, dataMappingMode,
969  dataNormalizationMode, contractDepthOffset));
970  }
971 
972  /// <summary>
973  /// Gets the historical data for the specified symbols between the specified dates. The symbols must exist in the Securities collection.
974  /// </summary>
975  /// <param name="type">The data type of the symbols</param>
976  /// <param name="tickers">The symbols to retrieve historical data for</param>
977  /// <param name="start">The start time in the algorithm's time zone</param>
978  /// <param name="end">The end time in the algorithm's time zone</param>
979  /// <param name="resolution">The resolution to request</param>
980  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
981  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
982  /// <param name="dataMappingMode">The contract mapping mode to use for the security history request</param>
983  /// <param name="dataNormalizationMode">The price scaling mode to use for the securities history</param>
984  /// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
985  /// For example, 0 will use the front month, 1 will use the back month contract</param>
986  /// <returns>pandas.DataFrame containing the requested historical data</returns>
987  [DocumentationAttribute(HistoricalData)]
988  public PyObject History(PyObject type, PyObject tickers, DateTime start, DateTime end, Resolution? resolution = null,
989  bool? fillForward = null, bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null,
990  DataNormalizationMode? dataNormalizationMode = null, int? contractDepthOffset = null)
991  {
992  var symbols = tickers.ConvertToSymbolEnumerable();
993  var requestedType = type.CreateType();
994  var requests = CreateDateRangeHistoryRequests(symbols, requestedType, start, end, resolution, fillForward, extendedMarketHours,
995  dataMappingMode, dataNormalizationMode, contractDepthOffset);
996  return GetDataFrame(History(requests.Where(x => x != null)), requestedType);
997  }
998 
999  /// <summary>
1000  /// Gets the historical data for the specified symbols. The exact number of bars will be returned for
1001  /// each symbol. This may result in some data start earlier/later than others due to when various
1002  /// exchanges are open. The symbols must exist in the Securities collection.
1003  /// </summary>
1004  /// <param name="type">The data type of the symbols</param>
1005  /// <param name="tickers">The symbols to retrieve historical data for</param>
1006  /// <param name="periods">The number of bars to request</param>
1007  /// <param name="resolution">The resolution to request</param>
1008  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
1009  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
1010  /// <param name="dataMappingMode">The contract mapping mode to use for the security history request</param>
1011  /// <param name="dataNormalizationMode">The price scaling mode to use for the securities history</param>
1012  /// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
1013  /// For example, 0 will use the front month, 1 will use the back month contract</param>
1014  /// <returns>pandas.DataFrame containing the requested historical data</returns>
1015  [DocumentationAttribute(HistoricalData)]
1016  public PyObject History(PyObject type, PyObject tickers, int periods, Resolution? resolution = null, bool? fillForward = null,
1017  bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null, DataNormalizationMode? dataNormalizationMode = null,
1018  int? contractDepthOffset = null)
1019  {
1020  var symbols = tickers.ConvertToSymbolEnumerable();
1021  var requestedType = type.CreateType();
1022  CheckPeriodBasedHistoryRequestResolution(symbols, resolution, requestedType);
1023 
1024  var requests = CreateBarCountHistoryRequests(symbols, requestedType, periods, resolution, fillForward, extendedMarketHours,
1025  dataMappingMode, dataNormalizationMode, contractDepthOffset);
1026 
1027  return GetDataFrame(History(requests.Where(x => x != null)), requestedType);
1028  }
1029 
1030  /// <summary>
1031  /// Gets the historical data for the specified symbols over the requested span.
1032  /// The symbols must exist in the Securities collection.
1033  /// </summary>
1034  /// <param name="type">The data type of the symbols</param>
1035  /// <param name="tickers">The symbols to retrieve historical data for</param>
1036  /// <param name="span">The span over which to retrieve recent historical data</param>
1037  /// <param name="resolution">The resolution to request</param>
1038  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
1039  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
1040  /// <param name="dataMappingMode">The contract mapping mode to use for the security history request</param>
1041  /// <param name="dataNormalizationMode">The price scaling mode to use for the securities history</param>
1042  /// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
1043  /// For example, 0 will use the front month, 1 will use the back month contract</param>
1044  /// <returns>pandas.DataFrame containing the requested historical data</returns>
1045  [DocumentationAttribute(HistoricalData)]
1046  public PyObject History(PyObject type, PyObject tickers, TimeSpan span, Resolution? resolution = null, bool? fillForward = null,
1047  bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null, DataNormalizationMode? dataNormalizationMode = null,
1048  int? contractDepthOffset = null)
1049  {
1050  return History(type, tickers, Time - span, Time, resolution, fillForward, extendedMarketHours, dataMappingMode, dataNormalizationMode,
1051  contractDepthOffset);
1052  }
1053 
1054  /// <summary>
1055  /// Gets the historical data for the specified symbols between the specified dates. The symbols must exist in the Securities collection.
1056  /// </summary>
1057  /// <param name="type">The data type of the symbols</param>
1058  /// <param name="symbol">The symbol to retrieve historical data for</param>
1059  /// <param name="start">The start time in the algorithm's time zone</param>
1060  /// <param name="end">The end time in the algorithm's time zone</param>
1061  /// <param name="resolution">The resolution to request</param>
1062  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
1063  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
1064  /// <param name="dataMappingMode">The contract mapping mode to use for the security history request</param>
1065  /// <param name="dataNormalizationMode">The price scaling mode to use for the securities history</param>
1066  /// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
1067  /// For example, 0 will use the front month, 1 will use the back month contract</param>
1068  /// <returns>pandas.DataFrame containing the requested historical data</returns>
1069  [DocumentationAttribute(HistoricalData)]
1070  public PyObject History(PyObject type, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, bool? fillForward = null,
1071  bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null, DataNormalizationMode? dataNormalizationMode = null,
1072  int? contractDepthOffset = null)
1073  {
1074  return History(type.CreateType(), symbol, start, end, resolution, fillForward, extendedMarketHours, dataMappingMode,
1075  dataNormalizationMode, contractDepthOffset);
1076  }
1077 
1078  /// <summary>
1079  /// Gets the historical data for the specified symbols between the specified dates. The symbols must exist in the Securities collection.
1080  /// </summary>
1081  /// <param name="type">The data type of the symbols</param>
1082  /// <param name="symbol">The symbol to retrieve historical data for</param>
1083  /// <param name="start">The start time in the algorithm's time zone</param>
1084  /// <param name="end">The end time in the algorithm's time zone</param>
1085  /// <param name="resolution">The resolution to request</param>
1086  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
1087  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
1088  /// <param name="dataMappingMode">The contract mapping mode to use for the security history request</param>
1089  /// <param name="dataNormalizationMode">The price scaling mode to use for the securities history</param>
1090  /// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
1091  /// For example, 0 will use the front month, 1 will use the back month contract</param>
1092  /// <returns>pandas.DataFrame containing the requested historical data</returns>
1093  private PyObject History(Type type, Symbol symbol, DateTime start, DateTime end, Resolution? resolution, bool? fillForward,
1094  bool? extendedMarketHours, DataMappingMode? dataMappingMode, DataNormalizationMode? dataNormalizationMode,
1095  int? contractDepthOffset)
1096  {
1097  var requests = CreateDateRangeHistoryRequests(new[] { symbol }, type, start, end, resolution, fillForward,
1098  extendedMarketHours, dataMappingMode, dataNormalizationMode, contractDepthOffset);
1099  if (requests.IsNullOrEmpty())
1100  {
1101  throw new ArgumentException($"No history data could be fetched. " +
1102  $"This could be due to the specified security not being of the requested type. Symbol: {symbol} Requested Type: {type.Name}");
1103  }
1104 
1105  return GetDataFrame(History(requests), type);
1106  }
1107 
1108  /// <summary>
1109  /// Gets the historical data for the specified symbols. The exact number of bars will be returned for
1110  /// each symbol. This may result in some data start earlier/later than others due to when various
1111  /// exchanges are open. The symbols must exist in the Securities collection.
1112  /// </summary>
1113  /// <param name="type">The data type of the symbols</param>
1114  /// <param name="symbol">The symbol to retrieve historical data for</param>
1115  /// <param name="periods">The number of bars to request</param>
1116  /// <param name="resolution">The resolution to request</param>
1117  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
1118  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
1119  /// <param name="dataMappingMode">The contract mapping mode to use for the security history request</param>
1120  /// <param name="dataNormalizationMode">The price scaling mode to use for the securities history</param>
1121  /// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
1122  /// For example, 0 will use the front month, 1 will use the back month contract</param>
1123  /// <returns>pandas.DataFrame containing the requested historical data</returns>
1124  [DocumentationAttribute(HistoricalData)]
1125  public PyObject History(PyObject type, Symbol symbol, int periods, Resolution? resolution = null, bool? fillForward = null,
1126  bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null, DataNormalizationMode? dataNormalizationMode = null,
1127  int? contractDepthOffset = null)
1128  {
1129  var managedType = type.CreateType();
1130  resolution = GetResolution(symbol, resolution, managedType);
1131  CheckPeriodBasedHistoryRequestResolution(new[] { symbol }, resolution, managedType);
1132 
1133  var marketHours = GetMarketHours(symbol, managedType);
1134  var start = _historyRequestFactory.GetStartTimeAlgoTz(symbol, periods, resolution.Value, marketHours.ExchangeHours,
1135  marketHours.DataTimeZone, extendedMarketHours);
1136  return History(managedType, symbol, start, Time, resolution, fillForward, extendedMarketHours, dataMappingMode, dataNormalizationMode,
1137  contractDepthOffset);
1138  }
1139 
1140  /// <summary>
1141  /// Gets the historical data for the specified symbols over the requested span.
1142  /// The symbols must exist in the Securities collection.
1143  /// </summary>
1144  /// <param name="type">The data type of the symbols</param>
1145  /// <param name="symbol">The symbol to retrieve historical data for</param>
1146  /// <param name="span">The span over which to retrieve recent historical data</param>
1147  /// <param name="resolution">The resolution to request</param>
1148  /// <param name="fillForward">True to fill forward missing data, false otherwise</param>
1149  /// <param name="extendedMarketHours">True to include extended market hours data, false otherwise</param>
1150  /// <param name="dataMappingMode">The contract mapping mode to use for the security history request</param>
1151  /// <param name="dataNormalizationMode">The price scaling mode to use for the securities history</param>
1152  /// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
1153  /// For example, 0 will use the front month, 1 will use the back month contract</param>
1154  /// <returns>pandas.DataFrame containing the requested historical data</returns>
1155  [DocumentationAttribute(HistoricalData)]
1156  public PyObject History(PyObject type, Symbol symbol, TimeSpan span, Resolution? resolution = null, bool? fillForward = null,
1157  bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null, DataNormalizationMode? dataNormalizationMode = null,
1158  int? contractDepthOffset = null)
1159  {
1160  return History(type, symbol, Time - span, Time, resolution, fillForward, extendedMarketHours, dataMappingMode, dataNormalizationMode,
1161  contractDepthOffset);
1162  }
1163 
1164  /// <summary>
1165  /// Sets the specified function as the benchmark, this function provides the value of
1166  /// the benchmark at each date/time requested
1167  /// </summary>
1168  /// <param name="benchmark">The benchmark producing function</param>
1169  [DocumentationAttribute(TradingAndOrders)]
1170  [DocumentationAttribute(SecuritiesAndPortfolio)]
1171  [DocumentationAttribute(Indicators)]
1172  public void SetBenchmark(PyObject benchmark)
1173  {
1174  using (Py.GIL())
1175  {
1176  var pyBenchmark = PythonUtil.ToFunc<DateTime, decimal>(benchmark);
1177  if (pyBenchmark != null)
1178  {
1179  SetBenchmark(pyBenchmark);
1180  return;
1181  }
1182  SetBenchmark((Symbol)benchmark.AsManagedObject(typeof(Symbol)));
1183  }
1184  }
1185 
1186  /// <summary>
1187  /// Sets the brokerage to emulate in backtesting or paper trading.
1188  /// This can be used to set a custom brokerage model.
1189  /// </summary>
1190  /// <param name="model">The brokerage model to use</param>
1191  [DocumentationAttribute(Modeling)]
1192  public void SetBrokerageModel(PyObject model)
1193  {
1194  IBrokerageModel brokerageModel;
1195  if (!model.TryConvert(out brokerageModel))
1196  {
1197  brokerageModel = new BrokerageModelPythonWrapper(model);
1198  }
1199 
1200  SetBrokerageModel(brokerageModel);
1201  }
1202 
1203  /// <summary>
1204  /// Sets the implementation used to handle messages from the brokerage.
1205  /// The default implementation will forward messages to debug or error
1206  /// and when a <see cref="BrokerageMessageType.Error"/> occurs, the algorithm
1207  /// is stopped.
1208  /// </summary>
1209  /// <param name="handler">The message handler to use</param>
1210  [DocumentationAttribute(Modeling)]
1211  [DocumentationAttribute(Logging)]
1212  public void SetBrokerageMessageHandler(PyObject handler)
1213  {
1214  if (!handler.TryConvert(out IBrokerageMessageHandler brokerageMessageHandler))
1215  {
1216  brokerageMessageHandler = new BrokerageMessageHandlerPythonWrapper(handler);
1217  }
1218 
1219  SetBrokerageMessageHandler(brokerageMessageHandler);
1220  }
1221 
1222  /// <summary>
1223  /// Sets the risk free interest rate model to be used in the algorithm
1224  /// </summary>
1225  /// <param name="model">The risk free interest rate model to use</param>
1226  [DocumentationAttribute(Modeling)]
1227  public void SetRiskFreeInterestRateModel(PyObject model)
1228  {
1230  }
1231 
1232  /// <summary>
1233  /// Sets the security initializer function, used to initialize/configure securities after creation
1234  /// </summary>
1235  /// <param name="securityInitializer">The security initializer function or class</param>
1236  [DocumentationAttribute(AddingData)]
1237  [DocumentationAttribute(Modeling)]
1238  public void SetSecurityInitializer(PyObject securityInitializer)
1239  {
1240  var securityInitializer1 = PythonUtil.ToAction<Security>(securityInitializer);
1241  if (securityInitializer1 != null)
1242  {
1243  SetSecurityInitializer(securityInitializer1);
1244  return;
1245  }
1246 
1247  SetSecurityInitializer(new SecurityInitializerPythonWrapper(securityInitializer));
1248  }
1249 
1250  /// <summary>
1251  /// Downloads the requested resource as a <see cref="string"/>.
1252  /// The resource to download is specified as a <see cref="string"/> containing the URI.
1253  /// </summary>
1254  /// <param name="address">A string containing the URI to download</param>
1255  /// <param name="headers">Defines header values to add to the request</param>
1256  /// <returns>The requested resource as a <see cref="string"/></returns>
1257  [DocumentationAttribute(AddingData)]
1258  [DocumentationAttribute(MachineLearning)]
1259  public string Download(string address, PyObject headers) => Download(address, headers, null, null);
1260 
1261  /// <summary>
1262  /// Downloads the requested resource as a <see cref="string"/>.
1263  /// The resource to download is specified as a <see cref="string"/> containing the URI.
1264  /// </summary>
1265  /// <param name="address">A string containing the URI to download</param>
1266  /// <param name="headers">Defines header values to add to the request</param>
1267  /// <param name="userName">The user name associated with the credentials</param>
1268  /// <param name="password">The password for the user name associated with the credentials</param>
1269  /// <returns>The requested resource as a <see cref="string"/></returns>
1270  [DocumentationAttribute(AddingData)]
1271  [DocumentationAttribute(MachineLearning)]
1272  public string Download(string address, PyObject headers, string userName, string password)
1273  {
1274  var dict = new Dictionary<string, string>();
1275 
1276  if (headers != null)
1277  {
1278  using (Py.GIL())
1279  {
1280  // In python algorithms, headers must be a python dictionary
1281  // In order to convert it into a C# Dictionary
1282  if (PyDict.IsDictType(headers))
1283  {
1284  using var iterator = headers.GetIterator();
1285  foreach (PyObject pyKey in iterator)
1286  {
1287  var key = (string)pyKey.AsManagedObject(typeof(string));
1288  var value = (string)headers.GetItem(pyKey).AsManagedObject(typeof(string));
1289  dict.Add(key, value);
1290  }
1291  }
1292  else
1293  {
1294  throw new ArgumentException($"QCAlgorithm.Fetch(): Invalid argument. {headers.Repr()} is not a dict");
1295  }
1296  }
1297  }
1298  return Download(address, dict, userName, password);
1299  }
1300 
1301  /// <summary>
1302  /// Send a debug message to the web console:
1303  /// </summary>
1304  /// <param name="message">Message to send to debug console</param>
1305  /// <seealso cref="Log(PyObject)"/>
1306  /// <seealso cref="Error(PyObject)"/>
1307  [DocumentationAttribute(Logging)]
1308  public void Debug(PyObject message)
1309  {
1310  Debug(message.ToSafeString());
1311  }
1312 
1313  /// <summary>
1314  /// Send a string error message to the Console.
1315  /// </summary>
1316  /// <param name="message">Message to display in errors grid</param>
1317  /// <seealso cref="Debug(PyObject)"/>
1318  /// <seealso cref="Log(PyObject)"/>
1319  [DocumentationAttribute(Logging)]
1320  public void Error(PyObject message)
1321  {
1322  Error(message.ToSafeString());
1323  }
1324 
1325  /// <summary>
1326  /// Added another method for logging if user guessed.
1327  /// </summary>
1328  /// <param name="message">String message to log.</param>
1329  /// <seealso cref="Debug(PyObject)"/>
1330  /// <seealso cref="Error(PyObject)"/>
1331  [DocumentationAttribute(Logging)]
1332  public void Log(PyObject message)
1333  {
1334  Log(message.ToSafeString());
1335  }
1336 
1337  /// <summary>
1338  /// Terminate the algorithm after processing the current event handler.
1339  /// </summary>
1340  /// <param name="message">Exit message to display on quitting</param>
1341  [DocumentationAttribute(Logging)]
1342  public void Quit(PyObject message)
1343  {
1344  Quit(message.ToSafeString());
1345  }
1346 
1347  /// <summary>
1348  /// Registers the <paramref name="handler"/> to receive consolidated data for the specified symbol
1349  /// </summary>
1350  /// <param name="symbol">The symbol who's data is to be consolidated</param>
1351  /// <param name="period">The consolidation period</param>
1352  /// <param name="handler">Data handler receives new consolidated data when generated</param>
1353  /// <returns>A new consolidator matching the requested parameters with the handler already registered</returns>
1354  [DocumentationAttribute(ConsolidatingData)]
1355  public IDataConsolidator Consolidate(Symbol symbol, Resolution period, PyObject handler)
1356  {
1357  return Consolidate(symbol, period.ToTimeSpan(), null, handler);
1358  }
1359 
1360  /// <summary>
1361  /// Registers the <paramref name="handler"/> to receive consolidated data for the specified symbol
1362  /// </summary>
1363  /// <param name="symbol">The symbol who's data is to be consolidated</param>
1364  /// <param name="period">The consolidation period</param>
1365  /// <param name="tickType">The tick type of subscription used as data source for consolidator. Specify null to use first subscription found.</param>
1366  /// <param name="handler">Data handler receives new consolidated data when generated</param>
1367  /// <returns>A new consolidator matching the requested parameters with the handler already registered</returns>
1368  [DocumentationAttribute(ConsolidatingData)]
1369  public IDataConsolidator Consolidate(Symbol symbol, Resolution period, TickType? tickType, PyObject handler)
1370  {
1371  return Consolidate(symbol, period.ToTimeSpan(), tickType, handler);
1372  }
1373 
1374  /// <summary>
1375  /// Registers the <paramref name="handler"/> to receive consolidated data for the specified symbol
1376  /// </summary>
1377  /// <param name="symbol">The symbol who's data is to be consolidated</param>
1378  /// <param name="period">The consolidation period</param>
1379  /// <param name="handler">Data handler receives new consolidated data when generated</param>
1380  /// <returns>A new consolidator matching the requested parameters with the handler already registered</returns>
1381  [DocumentationAttribute(ConsolidatingData)]
1382  public IDataConsolidator Consolidate(Symbol symbol, TimeSpan period, PyObject handler)
1383  {
1384  return Consolidate(symbol, period, null, handler);
1385  }
1386 
1387  /// <summary>
1388  /// Registers the <paramref name="handler"/> to receive consolidated data for the specified symbol
1389  /// </summary>
1390  /// <param name="symbol">The symbol who's data is to be consolidated</param>
1391  /// <param name="period">The consolidation period</param>
1392  /// <param name="tickType">The tick type of subscription used as data source for consolidator. Specify null to use first subscription found.</param>
1393  /// <param name="handler">Data handler receives new consolidated data when generated</param>
1394  /// <returns>A new consolidator matching the requested parameters with the handler already registered</returns>
1395  [DocumentationAttribute(ConsolidatingData)]
1396  public IDataConsolidator Consolidate(Symbol symbol, TimeSpan period, TickType? tickType, PyObject handler)
1397  {
1398  // resolve consolidator input subscription
1399  var type = GetSubscription(symbol, tickType).Type;
1400 
1401  if (type == typeof(TradeBar))
1402  {
1403  return Consolidate(symbol, period, tickType, handler.ConvertToDelegate<Action<TradeBar>>());
1404  }
1405 
1406  if (type == typeof(QuoteBar))
1407  {
1408  return Consolidate(symbol, period, tickType, handler.ConvertToDelegate<Action<QuoteBar>>());
1409  }
1410 
1411  return Consolidate(symbol, period, tickType, handler.ConvertToDelegate<Action<BaseData>>());
1412  }
1413 
1414  /// <summary>
1415  /// Registers the <paramref name="handler"/> to receive consolidated data for the specified symbol
1416  /// </summary>
1417  /// <param name="symbol">The symbol who's data is to be consolidated</param>
1418  /// <param name="calendar">The consolidation calendar</param>
1419  /// <param name="handler">Data handler receives new consolidated data when generated</param>
1420  /// <returns>A new consolidator matching the requested parameters with the handler already registered</returns>
1421  [DocumentationAttribute(ConsolidatingData)]
1422  public IDataConsolidator Consolidate(Symbol symbol, Func<DateTime, CalendarInfo> calendar, PyObject handler)
1423  {
1424  return Consolidate(symbol, calendar, null, handler);
1425  }
1426 
1427  /// <summary>
1428  /// Schedules the provided training code to execute immediately
1429  /// </summary>
1430  /// <param name="trainingCode">The training code to be invoked</param>
1431  [DocumentationAttribute(MachineLearning)]
1432  [DocumentationAttribute(ScheduledEvents)]
1433  public ScheduledEvent Train(PyObject trainingCode)
1434  {
1435  return Schedule.TrainingNow(trainingCode);
1436  }
1437 
1438  /// <summary>
1439  /// Schedules the training code to run using the specified date and time rules
1440  /// </summary>
1441  /// <param name="dateRule">Specifies what dates the event should run</param>
1442  /// <param name="timeRule">Specifies the times on those dates the event should run</param>
1443  /// <param name="trainingCode">The training code to be invoked</param>
1444  [DocumentationAttribute(MachineLearning)]
1445  [DocumentationAttribute(ScheduledEvents)]
1446  public ScheduledEvent Train(IDateRule dateRule, ITimeRule timeRule, PyObject trainingCode)
1447  {
1448  return Schedule.Training(dateRule, timeRule, trainingCode);
1449  }
1450 
1451  /// <summary>
1452  /// Registers the <paramref name="handler"/> to receive consolidated data for the specified symbol
1453  /// </summary>
1454  /// <param name="symbol">The symbol who's data is to be consolidated</param>
1455  /// <param name="calendar">The consolidation calendar</param>
1456  /// <param name="tickType">The tick type of subscription used as data source for consolidator. Specify null to use first subscription found.</param>
1457  /// <param name="handler">Data handler receives new consolidated data when generated</param>
1458  /// <returns>A new consolidator matching the requested parameters with the handler already registered</returns>
1459  [DocumentationAttribute(ConsolidatingData)]
1460  public IDataConsolidator Consolidate(Symbol symbol, Func<DateTime, CalendarInfo> calendar, TickType? tickType, PyObject handler)
1461  {
1462  // resolve consolidator input subscription
1463  var type = GetSubscription(symbol, tickType).Type;
1464 
1465  if (type == typeof(TradeBar))
1466  {
1467  return Consolidate(symbol, calendar, tickType, handler.ConvertToDelegate<Action<TradeBar>>());
1468  }
1469 
1470  if (type == typeof(QuoteBar))
1471  {
1472  return Consolidate(symbol, calendar, tickType, handler.ConvertToDelegate<Action<QuoteBar>>());
1473  }
1474 
1475  return Consolidate(symbol, calendar, tickType, handler.ConvertToDelegate<Action<BaseData>>());
1476  }
1477 
1478  /// <summary>
1479  /// Gets indicator base type
1480  /// </summary>
1481  /// <param name="type">Indicator type</param>
1482  /// <returns>Indicator base type</returns>
1483  private Type GetIndicatorBaseType(Type type)
1484  {
1485  if (type.BaseType == typeof(object))
1486  {
1487  return type;
1488  }
1489  return GetIndicatorBaseType(type.BaseType);
1490  }
1491 
1492  /// <summary>
1493  /// Converts the sequence of PyObject objects into an array of dynamic objects that represent indicators of the same type
1494  /// </summary>
1495  /// <returns>Array of dynamic objects with indicator</returns>
1496  private dynamic[] GetIndicatorArray(PyObject first, PyObject second = null, PyObject third = null, PyObject fourth = null)
1497  {
1498  using (Py.GIL())
1499  {
1500  var array = new[] {first, second, third, fourth}
1501  .Select(
1502  x =>
1503  {
1504  if (x == null) return null;
1505 
1506  Type type;
1507  return x.GetPythonType().TryConvert(out type)
1508  ? x.AsManagedObject(type)
1509  : WrapPythonIndicator(x);
1510  }
1511  ).ToArray();
1512 
1513  var types = array.Where(x => x != null).Select(x => GetIndicatorBaseType(x.GetType())).Distinct();
1514 
1515  if (types.Count() > 1)
1516  {
1517  throw new Exception("QCAlgorithm.GetIndicatorArray(). All indicators must be of the same type: data point, bar or tradebar.");
1518  }
1519 
1520  return array;
1521  }
1522  }
1523 
1524  /// <summary>
1525  /// Wraps a custom python indicator and save its reference to _pythonIndicators dictionary
1526  /// </summary>
1527  /// <param name="pyObject">The python implementation of <see cref="IndicatorBase{IBaseDataBar}"/></param>
1528  /// <returns><see cref="PythonIndicator"/> that wraps the python implementation</returns>
1529  private PythonIndicator WrapPythonIndicator(PyObject pyObject)
1530  {
1531  PythonIndicator pythonIndicator;
1532 
1533  if (!_pythonIndicators.TryGetValue(pyObject.Handle, out pythonIndicator))
1534  {
1535  pyObject.TryConvert(out pythonIndicator);
1536  pythonIndicator?.SetIndicator(pyObject);
1537 
1538  if (pythonIndicator == null)
1539  {
1540  pythonIndicator = new PythonIndicator(pyObject);
1541  }
1542 
1543  // Save to prevent future additions
1544  _pythonIndicators.Add(pyObject.Handle, pythonIndicator);
1545  }
1546 
1547  return pythonIndicator;
1548  }
1549 
1550  protected PyObject GetDataFrame(IEnumerable<Slice> data, Type dataType = null)
1551  {
1552  var history = PandasConverter.GetDataFrame(RemoveMemoizing(data), dataType);
1553  return TryCleanupCollectionDataFrame(dataType, history);
1554  }
1555 
1556  protected PyObject GetDataFrame<T>(IEnumerable<T> data)
1557  where T : IBaseData
1558  {
1559  var history = PandasConverter.GetDataFrame(RemoveMemoizing(data));
1560  return TryCleanupCollectionDataFrame(typeof(T), history);
1561  }
1562 
1563  private IEnumerable<T> RemoveMemoizing<T>(IEnumerable<T> data)
1564  {
1565  var memoizingEnumerable = data as MemoizingEnumerable<T>;
1566  if (memoizingEnumerable != null)
1567  {
1568  // we don't need the internal buffer which will just generate garbage, so we disable it
1569  // the user will only have access to the final pandas data frame object
1570  memoizingEnumerable.Enabled = false;
1571  }
1572  return data;
1573  }
1574 
1575  private PyObject TryCleanupCollectionDataFrame(Type dataType, PyObject history)
1576  {
1577  if (dataType != null && dataType.IsAssignableTo(typeof(BaseDataCollection)))
1578  {
1579  // clear out the first symbol level since it doesn't make sense, it's the universe generic symbol
1580  // let's directly return the data property which is where all the data points are in a BaseDataCollection, save the user some pain
1581  dynamic dynamic = history;
1582  using (Py.GIL())
1583  {
1584  if (!dynamic.empty)
1585  {
1586  using PyObject columns = dynamic.columns;
1587  if (columns.As<string[]>().Contains("data"))
1588  {
1589  history = dynamic["data"];
1590  }
1591  else
1592  {
1593  dynamic.index = dynamic.index.droplevel("symbol");
1594  history = dynamic;
1595  }
1596  }
1597  }
1598  }
1599  return history;
1600  }
1601  }
1602 }