32 using System.Collections.Generic;
36 using System.Threading.Tasks;
41 using System.Collections;
50 private dynamic _pandas;
53 private static bool _isPythonNotebook;
63 var isPython = PyModule.FromString(Guid.NewGuid().ToString(),
66 " def IsPythonNotebook():\n" +
67 " return (IPython.get_ipython() != None)\n" +
69 " print('No IPython installed')\n" +
70 " def IsPythonNotebook():\n" +
71 " return false\n").GetAttr(
"IsPythonNotebook").Invoke();
72 isPython.TryConvert(out _isPythonNotebook);
78 _isPythonNotebook =
false;
79 Logging.Log.Error(
"QuantBook failed to determine Notebook kernel language");
84 Logging.Log.Trace($
"QuantBook started; Is Python: {_isPythonNotebook}");
97 _pandas = Py.Import(
"pandas");
106 if (newYorkTime.Hour >= hourThreshold)
140 systemHandlers.LeanManager.Initialize(systemHandlers,
144 systemHandlers.LeanManager.SetAlgorithm(
this);
147 algorithmHandlers.DataPermissionsManager.Initialize(algorithmPacket);
149 algorithmHandlers.ObjectStore.Initialize(algorithmPacket.UserId,
150 algorithmPacket.ProjectId,
151 algorithmPacket.UserToken,
155 PersistenceIntervalSeconds = -1,
156 StorageLimit = Config.GetValue(
"storage-limit", 10737418240L),
157 StorageFileCount = Config.GetInt(
"storage-file-count", 10000),
158 StoragePermissions = (FileAccess) Config.GetInt(
"storage-permissions", (int)FileAccess.ReadWrite)
163 _dataProvider = algorithmHandlers.DataProvider;
169 symbolPropertiesDataBase,
177 new UniverseSelection(
this, securityService, algorithmHandlers.DataPermissionsManager, algorithmHandlers.DataProvider),
183 algorithmHandlers.DataPermissionsManager));
185 var mapFileProvider = algorithmHandlers.MapFileProvider;
191 algorithmHandlers.DataProvider,
194 algorithmHandlers.FactorFileProvider,
197 algorithmHandlers.DataPermissionsManager,
209 catch (Exception exception)
211 throw new Exception(
"QuantBook.Main(): " + exception);
223 [Obsolete(
"Please use the 'UniverseHistory()' API")]
224 public PyObject
GetFundamental(PyObject input,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
230 var fundamentalData = GetAllFundamental(symbols, selector, start, end);
234 var data =
new PyDict();
235 foreach (var day
in fundamentalData.OrderBy(x => x.Key))
237 var orderedValues = day.Value.OrderBy(x => x.Key.ID.ToString()).ToList();
238 var columns = orderedValues.Select(x => x.Key.ID.ToString());
239 var values = orderedValues.Select(x => x.Value);
240 var row = _pandas.Series(values, columns);
241 data.SetItem(day.Key.ToPython(), row);
244 return _pandas.DataFrame.from_dict(data, orient:
"index");
256 [Obsolete(
"Please use the 'UniverseHistory()' API")]
257 public IEnumerable<DataDictionary<dynamic>>
GetFundamental(IEnumerable<Symbol> symbols,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
259 var data = GetAllFundamental(symbols, selector, start, end);
261 foreach (var kvp
in data.OrderBy(kvp => kvp.Key))
263 yield
return kvp.Value;
275 [Obsolete(
"Please use the 'UniverseHistory()' API")]
276 public IEnumerable<DataDictionary<dynamic>>
GetFundamental(
Symbol symbol,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
278 var list =
new List<Symbol>
294 [Obsolete(
"Please use the 'UniverseHistory()' API")]
295 public IEnumerable<DataDictionary<dynamic>>
GetFundamental(IEnumerable<string> tickers,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
297 var list =
new List<Symbol>();
298 foreach (var ticker
in tickers)
314 [Obsolete(
"Please use the 'UniverseHistory()' API")]
315 public dynamic
GetFundamental(
string ticker,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
319 if (_isPythonNotebook)
325 var list =
new List<Symbol>
345 bool fillForward =
true,
bool extendedMarketHours =
false)
347 symbol = GetOptionSymbolForHistoryRequest(symbol, targetOption, resolution, fillForward);
349 return OptionHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
363 [Obsolete(
"Please use the 'OptionHistory()' API")]
365 bool fillForward =
true,
bool extendedMarketHours =
false)
367 return OptionHistory(symbol, targetOption, start, end, resolution, fillForward, extendedMarketHours);
381 bool fillForward =
true,
bool extendedMarketHours =
false)
383 if (!end.HasValue || end.Value == start)
385 end = start.AddDays(1);
389 symbol = GetOptionSymbolForHistoryRequest(symbol,
null, resolution, fillForward);
391 IEnumerable<Symbol> symbols;
401 .DefaultIfEmpty(
null)
405 resolutionToUseForUnderlying = universe.UniverseSettings.Resolution;
412 extendedMarketHours: extendedMarketHours);
422 extendedMarketHours: extendedMarketHours);
427 extendedMarketHours: extendedMarketHours);
430 var allSymbols =
new List<Symbol>();
431 for (var date = start; date < end; date = date.AddDays(1))
433 if (option.Exchange.DateIsOpen(date, extendedMarketHours: extendedMarketHours))
445 symbols = base.History(symbol.
Underlying, start, end.
Value, resolution)
449 optionFilterUniverse.Refresh(distinctSymbols, x, x.EndTime);
450 return option.ContractFilter.Filter(optionFilterUniverse).Select(x => x.Symbol);
452 .Distinct().Concat(
new[] { symbol.
Underlying });
457 symbols =
new List<Symbol>{ symbol };
460 return new OptionHistory(
History(symbols, start, end.Value, resolution, fillForward, extendedMarketHours));
473 [Obsolete(
"Please use the 'OptionHistory()' API")]
475 bool fillForward =
true,
bool extendedMarketHours =
false)
477 return OptionHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
491 bool fillForward =
true,
bool extendedMarketHours =
false)
493 if (!end.HasValue || end.Value == start)
495 end = start.AddDays(1);
498 var allSymbols =
new HashSet<Symbol>();
504 for (var date = start; date < end; date = date.AddDays(1))
506 if (future.Exchange.DateIsOpen(date, extendedMarketHours))
517 allSymbols.Add(symbol);
520 return new FutureHistory(
History(allSymbols, start, end.Value, resolution, fillForward, extendedMarketHours));
533 [Obsolete(
"Please use the 'FutureHistory()' API")]
535 bool fillForward =
true,
bool extendedMarketHours =
false)
537 return FutureHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
549 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
552 var history =
History(
new[] { symbol }, period, resolution);
565 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
568 var history =
History(
new[] { symbol }, period, resolution);
581 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
584 var history =
History(
new[] { symbol }, period, resolution);
598 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
601 var history =
History(
new[] { symbol }, span, resolution);
615 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
618 var history =
History(
new[] { symbol }, span, resolution);
632 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
635 var history =
History(
new[] { symbol }, span, resolution);
650 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
653 var history =
History(
new[] { symbol }, start, end, resolution);
668 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
671 var history =
History(
new[] { symbol }, start, end, resolution);
686 [Obsolete(
"Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")]
689 var history =
History(
new[] { symbol }, start, end, resolution);
703 public IEnumerable<IEnumerable<T2>>
UniverseHistory<T1, T2>(DateTime start, DateTime? end =
null, Func<IEnumerable<T2>, IEnumerable<Symbol>> func =
null,
IDateRule dateRule =
null)
707 var universeSymbol = ((
BaseDataCollection)typeof(T1).GetBaseDataInstance()).UniverseSymbol();
709 var symbols =
new[] { universeSymbol };
710 var endDate = end ?? DateTime.UtcNow.Date;
712 var history = GetDataTypedHistory<BaseDataCollection>(requests).Select(x => x.Values.Single());
714 HashSet<Symbol> filteredSymbols =
null;
715 Func<BaseDataCollection, IEnumerable<T2>> castDataPoint = dataPoint =>
717 var castedType = dataPoint.Data.OfType<T2>();
720 var selection = func(castedType);
723 filteredSymbols = selection.ToHashSet();
725 return castedType.Where(x => filteredSymbols ==
null || filteredSymbols.Contains(x.Symbol));
733 Func<BaseDataCollection, DateTime> getTime = datapoint => datapoint.EndTime.Date;
736 return PerformSelection<IEnumerable<T2>,
BaseDataCollection>(history, castDataPoint, getTime, start, endDate, dateRule);
749 return RunUniverseSelection(universe, start, end, dateRule);
765 public PyObject
UniverseHistory(PyObject universe, DateTime start, DateTime? end =
null, PyObject func =
null,
IDateRule dateRule =
null,
766 bool flatten =
false)
768 if (universe.TryConvert<
Universe>(out var convertedUniverse))
772 throw new ArgumentException($
"When providing a universe, the selection func argument isn't supported. Please provider a universe or a type and a func");
774 var filteredUniverseSelectionData = RunUniverseSelection(convertedUniverse, start, end, dateRule);
776 return GetDataFrame(filteredUniverseSelectionData, flatten);
779 if (universe.TryConvert<Type>(out var convertedType) && convertedType.IsAssignableTo(typeof(
BaseDataCollection)))
781 var endDate = end ?? DateTime.UtcNow.Date;
782 var universeSymbol = ((
BaseDataCollection)convertedType.GetBaseDataInstance()).UniverseSymbol();
785 return History(universe, universeSymbol, start, endDate, flatten: flatten);
789 var history =
History(requests);
791 return GetDataFrame(GetFilteredSlice(history, func, start, endDate, dateRule), flatten, convertedType);
794 throw new ArgumentException($
"Failed to convert given universe {universe}. Please provider a valid {nameof(Universe)}");
804 var dictBenchmark =
new SortedDictionary<DateTime, double>();
805 var dictEquity =
new SortedDictionary<DateTime, double>();
806 var dictPL =
new SortedDictionary<DateTime, double>();
810 var result =
new PyDict();
815 var df = ((dynamic)dataFrame).dropna();
816 dictBenchmark = GetDictionaryFromSeries((PyObject)df[
"benchmark"]);
817 dictEquity = GetDictionaryFromSeries((PyObject)df[
"equity"]);
818 dictPL = GetDictionaryFromSeries((PyObject)df[
"equity"].pct_change());
820 catch (PythonException e)
822 result.SetItem(
"Runtime Error", e.Message.ToPython());
827 var equity =
new SortedDictionary<DateTime, decimal>(dictEquity.ToDictionary(kvp => kvp.Key, kvp => (decimal)kvp.Value));
828 var profitLoss =
new SortedDictionary<DateTime, decimal>(dictPL.ToDictionary(kvp => kvp.Key, kvp =>
double.IsNaN(kvp.Value) ? 0 : (decimal)kvp.Value));
831 var listBenchmark = CalculateDailyRateOfChange(dictBenchmark);
832 var listPerformance = CalculateDailyRateOfChange(dictEquity);
835 var startingCapital = Convert.ToDecimal(dictEquity.FirstOrDefault().Value);
844 result.SetItem(
"Average Win (%)", Convert.ToDouble(stats.AverageWinRate * 100).ToPython());
845 result.SetItem(
"Average Loss (%)", Convert.ToDouble(stats.AverageLossRate * 100).ToPython());
846 result.SetItem(
"Compounding Annual Return (%)", Convert.ToDouble(stats.CompoundingAnnualReturn * 100m).ToPython());
847 result.SetItem(
"Drawdown (%)", Convert.ToDouble(stats.Drawdown * 100).ToPython());
848 result.SetItem(
"Expectancy", Convert.ToDouble(stats.Expectancy).ToPython());
849 result.SetItem(
"Net Profit (%)", Convert.ToDouble(stats.TotalNetProfit * 100).ToPython());
850 result.SetItem(
"Sharpe Ratio", Convert.ToDouble(stats.SharpeRatio).ToPython());
851 result.SetItem(
"Win Rate (%)", Convert.ToDouble(stats.WinRate * 100).ToPython());
852 result.SetItem(
"Loss Rate (%)", Convert.ToDouble(stats.LossRate * 100).ToPython());
853 result.SetItem(
"Profit-Loss Ratio", Convert.ToDouble(stats.ProfitLossRatio).ToPython());
854 result.SetItem(
"Alpha", Convert.ToDouble(stats.Alpha).ToPython());
855 result.SetItem(
"Beta", Convert.ToDouble(stats.Beta).ToPython());
856 result.SetItem(
"Annual Standard Deviation", Convert.ToDouble(stats.AnnualStandardDeviation).ToPython());
857 result.SetItem(
"Annual Variance", Convert.ToDouble(stats.AnnualVariance).ToPython());
858 result.SetItem(
"Information Ratio", Convert.ToDouble(stats.InformationRatio).ToPython());
859 result.SetItem(
"Tracking Error", Convert.ToDouble(stats.TrackingError).ToPython());
860 result.SetItem(
"Treynor Ratio", Convert.ToDouble(stats.TreynorRatio).ToPython());
869 private IEnumerable<Slice> GetFilteredSlice(IEnumerable<Slice> history, dynamic func, DateTime start, DateTime end,
IDateRule dateRule =
null)
871 HashSet<Symbol> filteredSymbols =
null;
872 Func<Slice, Slice> processSlice = slice =>
877 using PyObject selection = func(filteredData.SelectMany(baseData => baseData.Data));
878 if (!selection.TryConvert<
object>(out var result) || !ReferenceEquals(result,
Universe.
Unchanged))
880 filteredSymbols = ((
Symbol[])selection.AsManagedObject(typeof(
Symbol[]))).ToHashSet();
883 return new Slice(slice.Time, filteredData.Where(x => {
884 if (filteredSymbols == null)
888 x.Data =
new List<BaseData>(x.Data.Where(dataPoint => filteredSymbols.Contains(dataPoint.Symbol)));
893 Func<Slice, DateTime> getTime = slice => slice.Time.Date;
894 return PerformSelection<Slice, Slice>(history, processSlice, getTime, start, end, dateRule);
900 private IEnumerable<BaseDataCollection> RunUniverseSelection(
Universe universe, DateTime start, DateTime? end =
null,
IDateRule dateRule =
null)
902 var endDate = end ?? DateTime.UtcNow.Date;
903 var history =
History(universe, start, endDate);
905 HashSet<Symbol> filteredSymbols =
null;
906 Func<BaseDataCollection, BaseDataCollection> processDataPoint = dataPoint =>
912 filteredSymbols = selection.ToHashSet();
914 dataPoint.Data = dataPoint.Data.Where(x => filteredSymbols ==
null || filteredSymbols.Contains(x.Symbol)).ToList();
918 Func<BaseDataCollection, DateTime> getTime = dataPoint => dataPoint.EndTime.Date;
920 return PerformSelection<BaseDataCollection, BaseDataCollection>(history, processDataPoint, getTime, start, endDate, dateRule);
928 private SortedDictionary<DateTime, double> GetDictionaryFromSeries(PyObject series)
930 var dictionary =
new SortedDictionary<DateTime, double>();
932 var pyDict =
new PyDict(((dynamic)series).to_dict());
933 foreach (PyObject item
in pyDict.Items())
935 var key = (DateTime)item[0].AsManagedObject(typeof(DateTime));
936 var value = (double)item[1].AsManagedObject(typeof(
double));
937 dictionary.Add(key, value);
948 private List<double> CalculateDailyRateOfChange(IDictionary<DateTime, double> dictionary)
950 var daily = dictionary.GroupBy(kvp => kvp.Key.Date)
951 .ToDictionary(x => x.Key, v => v.LastOrDefault().Value)
954 var rocp =
new double[daily.Length];
955 for (var i = 1; i < daily.Length; i++)
957 rocp[i] = (daily[i] - daily[i - 1]) / daily[i - 1];
961 return rocp.ToList();
970 private object GetPropertyValue(
object baseData,
string fullName)
972 foreach (var name
in fullName.Split(
'.'))
974 if (baseData ==
null)
return null;
977 var info = baseData.GetType().GetProperty(name);
979 baseData = info?.GetValue(baseData,
null);
992 private Dictionary<DateTime, DataDictionary<dynamic>> GetAllFundamental(IEnumerable<Symbol> symbols,
string selector, DateTime? start =
null, DateTime? end =
null)
996 var endTime = end.HasValue ? (DateTime) end : DateTime.UtcNow.Date;
999 var data =
new Dictionary<DateTime, DataDictionary<dynamic>>();
1002 foreach (var symbol
in symbols)
1008 var time = currentData.EndTime;
1009 object dataPoint = currentData;
1010 if (!
string.IsNullOrWhiteSpace(selector))
1012 dataPoint = GetPropertyValue(currentData, selector);
1019 if (!data.TryGetValue(time, out var dataAtTime))
1023 dataAtTime.Add(currentData.Symbol, dataPoint);
1029 private Symbol GetOptionSymbolForHistoryRequest(Symbol symbol,
string targetOption,
Resolution? resolution,
bool fillForward)
1032 if (!symbol.SecurityType.IsOption())
1034 var option = AddOption(symbol, targetOption, resolution, symbol.ID.Market, fillForward);
1038 if (symbol.SecurityType ==
SecurityType.Future && symbol.IsCanonical())
1040 throw new ArgumentException(
"The Future Symbol provided is a canonical Symbol (i.e. a Symbol representing all Futures), which is not supported at this time. " +
1041 "If you are using the Symbol accessible from `AddFuture(...)`, use the Symbol from `AddFutureContract(...)` instead. " +
1042 "You can use `qb.FutureOptionChainProvider(canonicalFuture, datetime)` to get a list of futures contracts for a given date, and add them to your algorithm with `AddFutureContract(symbol, Resolution)`.");
1044 if (symbol.SecurityType ==
SecurityType.Future && !symbol.IsCanonical())
1046 option.SetFilter(universe => universe.Strikes(-10, +10));
1049 symbol = option.Symbol;
1055 private static void RecycleMemory()
1057 Task.Delay(TimeSpan.FromSeconds(20)).ContinueWith(_ =>
1059 if (Logging.Log.DebuggingEnabled)
1061 Logging.Log.Debug($
"QuantBook.RecycleMemory(): running...");
1067 }, TaskScheduler.Current);
1070 protected static IEnumerable<T1> PerformSelection<T1, T2>(
1071 IEnumerable<T2> history,
1072 Func<T2, T1> processDataPointFunction,
1073 Func<T2, DateTime> getTime,
1078 if (dateRule ==
null)
1080 foreach(var dataPoint
in history)
1082 yield
return processDataPointFunction(dataPoint);
1088 var targetDatesQueue =
new Queue<DateTime>(dateRule.GetDates(start, endDate));
1089 T2 previousDataPoint =
default;
1090 foreach (var dataPoint
in history)
1092 var dataPointWasProcessed =
false;
1097 while (targetDatesQueue.TryPeek(out var targetDate) && getTime(dataPoint) >= targetDate)
1099 if (getTime(dataPoint) == targetDate)
1101 yield
return processDataPointFunction(dataPoint);
1105 dataPointWasProcessed =
true;
1109 if (!
Equals(previousDataPoint,
default(T2)))
1111 yield
return processDataPointFunction(previousDataPoint);
1115 previousDataPoint =
default;
1117 targetDatesQueue.Dequeue();
1120 if (!dataPointWasProcessed)
1122 previousDataPoint = dataPoint;