32 using System.Collections.Generic;
36 using System.Threading.Tasks;
49 private dynamic _pandas;
52 private static bool _isPythonNotebook;
62 var isPython = PyModule.FromString(Guid.NewGuid().ToString(),
65 " def IsPythonNotebook():\n" +
66 " return (IPython.get_ipython() != None)\n" +
68 " print('No IPython installed')\n" +
69 " def IsPythonNotebook():\n" +
70 " return false\n").GetAttr(
"IsPythonNotebook").Invoke();
71 isPython.TryConvert(out _isPythonNotebook);
77 _isPythonNotebook =
false;
78 Logging.Log.Error(
"QuantBook failed to determine Notebook kernel language");
83 Logging.Log.Trace($
"QuantBook started; Is Python: {_isPythonNotebook}");
96 _pandas = Py.Import(
"pandas");
105 if (newYorkTime.Hour >= hourThreshold)
139 systemHandlers.LeanManager.Initialize(systemHandlers,
143 systemHandlers.LeanManager.SetAlgorithm(
this);
146 algorithmHandlers.DataPermissionsManager.Initialize(algorithmPacket);
148 algorithmHandlers.ObjectStore.Initialize(algorithmPacket.UserId,
149 algorithmPacket.ProjectId,
150 algorithmPacket.UserToken,
154 PersistenceIntervalSeconds = -1,
155 StorageLimit = Config.GetValue(
"storage-limit", 10737418240L),
156 StorageFileCount = Config.GetInt(
"storage-file-count", 10000),
157 StoragePermissions = (FileAccess) Config.GetInt(
"storage-permissions", (int)FileAccess.ReadWrite)
162 _dataProvider = algorithmHandlers.DataProvider;
168 symbolPropertiesDataBase,
176 new UniverseSelection(
this, securityService, algorithmHandlers.DataPermissionsManager, algorithmHandlers.DataProvider),
182 algorithmHandlers.DataPermissionsManager));
184 var mapFileProvider = algorithmHandlers.MapFileProvider;
190 algorithmHandlers.DataProvider,
193 algorithmHandlers.FactorFileProvider,
196 algorithmHandlers.DataPermissionsManager,
204 optionChainProvider.Initialize(initParameters);
206 futureChainProvider.Initialize(initParameters);
213 catch (Exception exception)
215 throw new Exception(
"QuantBook.Main(): " + exception);
227 [Obsolete(
"Please use the 'UniverseHistory()' API")]
228 public PyObject
GetFundamental(PyObject input,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
234 var fundamentalData = GetAllFundamental(symbols, selector, start, end);
238 var data =
new PyDict();
239 foreach (var day
in fundamentalData.OrderBy(x => x.Key))
241 var orderedValues = day.Value.OrderBy(x => x.Key.ID.ToString()).ToList();
242 var columns = orderedValues.Select(x => x.Key.ID.ToString());
243 var values = orderedValues.Select(x => x.Value);
244 var row = _pandas.Series(values, columns);
245 data.SetItem(day.Key.ToPython(), row);
248 return _pandas.DataFrame.from_dict(data, orient:
"index");
260 [Obsolete(
"Please use the 'UniverseHistory()' API")]
261 public IEnumerable<DataDictionary<dynamic>>
GetFundamental(IEnumerable<Symbol> symbols,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
263 var data = GetAllFundamental(symbols, selector, start, end);
265 foreach (var kvp
in data.OrderBy(kvp => kvp.Key))
267 yield
return kvp.Value;
279 [Obsolete(
"Please use the 'UniverseHistory()' API")]
280 public IEnumerable<DataDictionary<dynamic>>
GetFundamental(
Symbol symbol,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
282 var list =
new List<Symbol>
298 [Obsolete(
"Please use the 'UniverseHistory()' API")]
299 public IEnumerable<DataDictionary<dynamic>>
GetFundamental(IEnumerable<string> tickers,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
301 var list =
new List<Symbol>();
302 foreach (var ticker
in tickers)
318 [Obsolete(
"Please use the 'UniverseHistory()' API")]
319 public dynamic
GetFundamental(
string ticker,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
323 if (_isPythonNotebook)
329 var list =
new List<Symbol>
349 bool fillForward =
true,
bool extendedMarketHours =
false)
351 symbol = GetOptionSymbolForHistoryRequest(symbol, targetOption, resolution, fillForward);
353 return OptionHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
367 [Obsolete(
"Please use the 'OptionHistory()' API")]
369 bool fillForward =
true,
bool extendedMarketHours =
false)
371 return OptionHistory(symbol, targetOption, start, end, resolution, fillForward, extendedMarketHours);
385 bool fillForward =
true,
bool extendedMarketHours =
false)
387 if (!end.HasValue || end.Value == start)
389 end = start.AddDays(1);
393 symbol = GetOptionSymbolForHistoryRequest(symbol,
null, resolution, fillForward);
395 IEnumerable<Symbol> symbols;
405 .DefaultIfEmpty(
null)
409 resolutionToUseForUnderlying = universe.UniverseSettings.Resolution;
416 extendedMarketHours: extendedMarketHours);
426 extendedMarketHours: extendedMarketHours);
431 extendedMarketHours: extendedMarketHours);
435 var allSymbols =
new HashSet<Symbol>();
438 foreach (var (date, chainData, underlyingData) in GetChainHistory<OptionUniverse>(option, start, end.Value, extendedMarketHours))
440 if (underlyingData is not
null)
442 optionFilterUniverse.Refresh(chainData, underlyingData, underlyingData.EndTime);
443 allSymbols.UnionWith(option.ContractFilter.Filter(optionFilterUniverse).Select(x => x.Symbol));
448 symbols = allSymbols.Concat(
new[] { symbol.
Underlying });
453 symbols =
new List<Symbol>{ symbol };
456 return new OptionHistory(
History(symbols, start, end.Value, resolution, fillForward, extendedMarketHours));
469 [Obsolete(
"Please use the 'OptionHistory()' API")]
471 bool fillForward =
true,
bool extendedMarketHours =
false)
473 return OptionHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
487 bool fillForward =
true,
bool extendedMarketHours =
false)
489 if (!end.HasValue || end.Value == start)
491 end = start.AddDays(1);
494 var allSymbols =
new HashSet<Symbol>();
500 foreach (var (date, chainData, underlyingData) in GetChainHistory<FutureUniverse>(future, start, end.Value, extendedMarketHours))
502 allSymbols.UnionWith(future.ContractFilter.Filter(
new FutureFilterUniverse(chainData, date)).Select(x => x.Symbol));
508 allSymbols.
Add(symbol);
511 return new FutureHistory(
History(allSymbols, start, end.Value, resolution, fillForward, extendedMarketHours));
524 [Obsolete(
"Please use the 'FutureHistory()' API")]
526 bool fillForward =
true,
bool extendedMarketHours =
false)
528 return FutureHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
540 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
543 var history =
History(
new[] { symbol }, period, resolution);
556 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
559 var history =
History(
new[] { symbol }, period, resolution);
572 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
575 var history =
History(
new[] { symbol }, period, resolution);
589 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
592 var history =
History(
new[] { symbol }, span, resolution);
606 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
609 var history =
History(
new[] { symbol }, span, resolution);
623 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
626 var history =
History(
new[] { symbol }, span, resolution);
641 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
644 var history =
History(
new[] { symbol }, start, end, resolution);
659 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
662 var history =
History(
new[] { symbol }, start, end, resolution);
677 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
680 var history =
History(
new[] { symbol }, start, end, resolution);
694 public IEnumerable<IEnumerable<T2>>
UniverseHistory<T1, T2>(DateTime start, DateTime? end =
null, Func<IEnumerable<T2>, IEnumerable<Symbol>> func =
null,
IDateRule dateRule =
null)
698 var universeSymbol = ((
BaseDataCollection)typeof(T1).GetBaseDataInstance()).UniverseSymbol();
700 var symbols =
new[] { universeSymbol };
701 var endDate = end ?? DateTime.UtcNow.Date;
703 var history = GetDataTypedHistory<BaseDataCollection>(requests).Select(x => x.Values.Single());
705 HashSet<Symbol> filteredSymbols =
null;
706 Func<BaseDataCollection, IEnumerable<T2>> castDataPoint = dataPoint =>
708 var castedType = dataPoint.Data.OfType<T2>();
711 var selection = func(castedType);
714 filteredSymbols = selection.ToHashSet();
716 return castedType.Where(x => filteredSymbols ==
null || filteredSymbols.Contains(x.Symbol));
724 Func<BaseDataCollection, DateTime> getTime = datapoint => datapoint.EndTime.Date;
727 return PerformSelection<IEnumerable<T2>,
BaseDataCollection>(history, castDataPoint, getTime, start, endDate, dateRule);
740 return RunUniverseSelection(universe, start, end, dateRule);
756 public PyObject
UniverseHistory(PyObject universe, DateTime start, DateTime? end =
null, PyObject func =
null,
IDateRule dateRule =
null,
757 bool flatten =
false)
759 if (universe.TryConvert<
Universe>(out var convertedUniverse))
763 throw new ArgumentException($
"When providing a universe, the selection func argument isn't supported. Please provider a universe or a type and a func");
765 var filteredUniverseSelectionData = RunUniverseSelection(convertedUniverse, start, end, dateRule);
767 return GetDataFrame(filteredUniverseSelectionData, flatten);
770 if (universe.TryConvert<Type>(out var convertedType) && convertedType.IsAssignableTo(typeof(
BaseDataCollection)))
772 var endDate = end ?? DateTime.UtcNow.Date;
773 var universeSymbol = ((
BaseDataCollection)convertedType.GetBaseDataInstance()).UniverseSymbol();
776 return History(universe, universeSymbol, start, endDate, flatten: flatten);
780 var history =
History(requests);
782 return GetDataFrame(GetFilteredSlice(history, func, start, endDate, dateRule), flatten, convertedType);
785 throw new ArgumentException($
"Failed to convert given universe {universe}. Please provider a valid {nameof(Universe)}");
795 var dictBenchmark =
new SortedDictionary<DateTime, double>();
796 var dictEquity =
new SortedDictionary<DateTime, double>();
797 var dictPL =
new SortedDictionary<DateTime, double>();
801 var result =
new PyDict();
806 var df = ((dynamic)dataFrame).dropna();
807 dictBenchmark = GetDictionaryFromSeries((PyObject)df[
"benchmark"]);
808 dictEquity = GetDictionaryFromSeries((PyObject)df[
"equity"]);
809 dictPL = GetDictionaryFromSeries((PyObject)df[
"equity"].pct_change());
811 catch (PythonException e)
813 result.SetItem(
"Runtime Error", e.Message.ToPython());
818 var equity =
new SortedDictionary<DateTime, decimal>(dictEquity.ToDictionary(kvp => kvp.Key, kvp => (decimal)kvp.Value));
819 var profitLoss =
new SortedDictionary<DateTime, decimal>(dictPL.ToDictionary(kvp => kvp.Key, kvp =>
double.IsNaN(kvp.Value) ? 0 : (decimal)kvp.Value));
822 var listBenchmark = CalculateDailyRateOfChange(dictBenchmark);
823 var listPerformance = CalculateDailyRateOfChange(dictEquity);
826 var startingCapital = Convert.ToDecimal(dictEquity.FirstOrDefault().Value);
835 result.SetItem(
"Average Win (%)", Convert.ToDouble(stats.AverageWinRate * 100).ToPython());
836 result.SetItem(
"Average Loss (%)", Convert.ToDouble(stats.AverageLossRate * 100).ToPython());
837 result.SetItem(
"Compounding Annual Return (%)", Convert.ToDouble(stats.CompoundingAnnualReturn * 100m).ToPython());
838 result.SetItem(
"Drawdown (%)", Convert.ToDouble(stats.Drawdown * 100).ToPython());
839 result.SetItem(
"Expectancy", Convert.ToDouble(stats.Expectancy).ToPython());
840 result.SetItem(
"Net Profit (%)", Convert.ToDouble(stats.TotalNetProfit * 100).ToPython());
841 result.SetItem(
"Sharpe Ratio", Convert.ToDouble(stats.SharpeRatio).ToPython());
842 result.SetItem(
"Win Rate (%)", Convert.ToDouble(stats.WinRate * 100).ToPython());
843 result.SetItem(
"Loss Rate (%)", Convert.ToDouble(stats.LossRate * 100).ToPython());
844 result.SetItem(
"Profit-Loss Ratio", Convert.ToDouble(stats.ProfitLossRatio).ToPython());
845 result.SetItem(
"Alpha", Convert.ToDouble(stats.Alpha).ToPython());
846 result.SetItem(
"Beta", Convert.ToDouble(stats.Beta).ToPython());
847 result.SetItem(
"Annual Standard Deviation", Convert.ToDouble(stats.AnnualStandardDeviation).ToPython());
848 result.SetItem(
"Annual Variance", Convert.ToDouble(stats.AnnualVariance).ToPython());
849 result.SetItem(
"Information Ratio", Convert.ToDouble(stats.InformationRatio).ToPython());
850 result.SetItem(
"Tracking Error", Convert.ToDouble(stats.TrackingError).ToPython());
851 result.SetItem(
"Treynor Ratio", Convert.ToDouble(stats.TreynorRatio).ToPython());
860 private IEnumerable<T> GetChainHistory<T>(
Symbol canonicalSymbol, DateTime date, out
BaseData underlyingData)
866 extendedMarketHours:
false, marketHoursEntry.DataTimeZone);
867 var start = startInExchangeTz.ConvertTo(marketHoursEntry.ExchangeHours.TimeZone,
TimeZone);
868 var end = date.ConvertTo(marketHoursEntry.ExchangeHours.TimeZone,
TimeZone);
869 var universeData =
History<T>(canonicalSymbol, start, end).SingleOrDefault();
871 if (universeData is not
null)
873 underlyingData = universeData.Underlying;
874 return universeData.Data.Cast<
T>();
877 underlyingData =
null;
878 return Enumerable.Empty<
T>();
884 private IEnumerable<(DateTime Date, IEnumerable<T> ChainData,
BaseData UnderlyingData)> GetChainHistory<T>(
885 Security security, DateTime start, DateTime end,
bool extendedMarketHours)
890 var universeData = GetChainHistory<T>(security.Symbol, date, out var underlyingData);
891 yield
return (date, universeData, underlyingData);
898 private IEnumerable<Slice> GetFilteredSlice(IEnumerable<Slice> history, dynamic func, DateTime start, DateTime end,
IDateRule dateRule =
null)
900 HashSet<Symbol> filteredSymbols =
null;
901 Func<Slice, Slice> processSlice = slice =>
906 using PyObject selection = func(filteredData.SelectMany(baseData => baseData.Data));
907 if (!selection.TryConvert<
object>(out var result) || !ReferenceEquals(result,
Universe.
Unchanged))
909 filteredSymbols = ((
Symbol[])selection.AsManagedObject(typeof(
Symbol[]))).ToHashSet();
912 return new Slice(slice.Time, filteredData.Where(x => {
913 if (filteredSymbols == null)
917 x.Data =
new List<BaseData>(x.Data.Where(dataPoint => filteredSymbols.Contains(dataPoint.Symbol)));
922 Func<Slice, DateTime> getTime = slice => slice.Time.Date;
923 return PerformSelection<Slice, Slice>(history, processSlice, getTime, start, end, dateRule);
929 private IEnumerable<BaseDataCollection> RunUniverseSelection(
Universe universe, DateTime start, DateTime? end =
null,
IDateRule dateRule =
null)
931 var endDate = end ?? DateTime.UtcNow.Date;
932 var history =
History(universe, start, endDate);
934 HashSet<Symbol> filteredSymbols =
null;
935 Func<BaseDataCollection, BaseDataCollection> processDataPoint = dataPoint =>
941 filteredSymbols = selection.ToHashSet();
943 dataPoint.Data = dataPoint.Data.Where(x => filteredSymbols ==
null || filteredSymbols.Contains(x.Symbol)).ToList();
947 Func<BaseDataCollection, DateTime> getTime = dataPoint => dataPoint.EndTime.Date;
949 return PerformSelection<BaseDataCollection, BaseDataCollection>(history, processDataPoint, getTime, start, endDate, dateRule);
957 private SortedDictionary<DateTime, double> GetDictionaryFromSeries(PyObject series)
959 var dictionary =
new SortedDictionary<DateTime, double>();
961 var pyDict =
new PyDict(((dynamic)series).to_dict());
962 foreach (PyObject item
in pyDict.Items())
964 var key = (DateTime)item[0].AsManagedObject(typeof(DateTime));
965 var value = (double)item[1].AsManagedObject(typeof(
double));
966 dictionary.Add(key, value);
977 private List<double> CalculateDailyRateOfChange(IDictionary<DateTime, double> dictionary)
979 var daily = dictionary.GroupBy(kvp => kvp.Key.Date)
980 .ToDictionary(x => x.Key, v => v.LastOrDefault().Value)
983 var rocp =
new double[daily.Length];
984 for (var i = 1; i < daily.Length; i++)
986 rocp[i] = (daily[i] - daily[i - 1]) / daily[i - 1];
990 return rocp.ToList();
999 private object GetPropertyValue(
object baseData,
string fullName)
1001 foreach (var name
in fullName.Split(
'.'))
1003 if (baseData ==
null)
return null;
1006 var info = baseData.GetType().GetProperty(name);
1008 baseData = info?.GetValue(baseData,
null);
1021 private Dictionary<DateTime, DataDictionary<dynamic>> GetAllFundamental(IEnumerable<Symbol> symbols,
string selector, DateTime? start =
null, DateTime? end =
null)
1025 var endTime = end.HasValue ? (DateTime) end : DateTime.UtcNow.Date;
1028 var data =
new Dictionary<DateTime, DataDictionary<dynamic>>();
1031 foreach (var symbol
in symbols)
1037 var time = currentData.EndTime;
1038 object dataPoint = currentData;
1039 if (!
string.IsNullOrWhiteSpace(selector))
1041 dataPoint = GetPropertyValue(currentData, selector);
1048 if (!data.TryGetValue(time, out var dataAtTime))
1052 dataAtTime.Add(currentData.Symbol, dataPoint);
1058 private Symbol GetOptionSymbolForHistoryRequest(Symbol symbol,
string targetOption,
Resolution? resolution,
bool fillForward)
1061 if (!symbol.SecurityType.IsOption())
1063 var option = AddOption(symbol, targetOption, resolution, symbol.ID.Market, fillForward);
1067 if (symbol.SecurityType ==
SecurityType.Future && symbol.IsCanonical())
1069 throw new ArgumentException(
"The Future Symbol provided is a canonical Symbol (i.e. a Symbol representing all Futures), which is not supported at this time. " +
1070 "If you are using the Symbol accessible from `AddFuture(...)`, use the Symbol from `AddFutureContract(...)` instead. " +
1071 "You can use `qb.FutureOptionChainProvider(canonicalFuture, datetime)` to get a list of futures contracts for a given date, and add them to your algorithm with `AddFutureContract(symbol, Resolution)`.");
1073 if (symbol.SecurityType ==
SecurityType.Future && !symbol.IsCanonical())
1075 option.SetFilter(universe => universe.Strikes(-10, +10));
1078 symbol = option.Symbol;
1084 private static void RecycleMemory()
1086 Task.Delay(TimeSpan.FromSeconds(20)).ContinueWith(_ =>
1088 if (Logging.Log.DebuggingEnabled)
1090 Logging.Log.Debug($
"QuantBook.RecycleMemory(): running...");
1096 }, TaskScheduler.Current);
1099 protected static IEnumerable<T1> PerformSelection<T1, T2>(
1100 IEnumerable<T2> history,
1101 Func<T2, T1> processDataPointFunction,
1102 Func<T2, DateTime> getTime,
1107 if (dateRule ==
null)
1109 foreach(var dataPoint
in history)
1111 yield
return processDataPointFunction(dataPoint);
1117 var targetDatesQueue =
new Queue<DateTime>(dateRule.GetDates(start, endDate));
1118 T2 previousDataPoint =
default;
1119 foreach (var dataPoint
in history)
1121 var dataPointWasProcessed =
false;
1126 while (targetDatesQueue.TryPeek(out var targetDate) && getTime(dataPoint) >= targetDate)
1128 if (getTime(dataPoint) == targetDate)
1130 yield
return processDataPointFunction(dataPoint);
1134 dataPointWasProcessed =
true;
1138 if (!
Equals(previousDataPoint,
default(T2)))
1140 yield
return processDataPointFunction(previousDataPoint);
1144 previousDataPoint =
default;
1146 targetDatesQueue.Dequeue();
1149 if (!dataPointWasProcessed)
1151 previousDataPoint = dataPoint;