33 using System.Collections.Generic;
37 using System.Threading.Tasks;
48 private dynamic _pandas;
51 private static bool _isPythonNotebook;
61 var isPython = PyModule.FromString(Guid.NewGuid().ToString(),
64 " def IsPythonNotebook():\n" +
65 " return (IPython.get_ipython() != None)\n" +
67 " print('No IPython installed')\n" +
68 " def IsPythonNotebook():\n" +
69 " return false\n").GetAttr(
"IsPythonNotebook").Invoke();
70 isPython.TryConvert(out _isPythonNotebook);
76 _isPythonNotebook =
false;
77 Logging.Log.Error(
"QuantBook failed to determine Notebook kernel language");
82 Logging.Log.Trace($
"QuantBook started; Is Python: {_isPythonNotebook}");
95 _pandas = Py.Import(
"pandas");
104 if (newYorkTime.Hour >= hourThreshold)
138 systemHandlers.LeanManager.Initialize(systemHandlers,
142 systemHandlers.LeanManager.SetAlgorithm(
this);
145 algorithmHandlers.DataPermissionsManager.Initialize(algorithmPacket);
147 algorithmHandlers.ObjectStore.Initialize(algorithmPacket.UserId,
148 algorithmPacket.ProjectId,
149 algorithmPacket.UserToken,
153 PersistenceIntervalSeconds = -1,
154 StorageLimit = Config.GetValue(
"storage-limit", 10737418240L),
155 StorageFileCount = Config.GetInt(
"storage-file-count", 10000),
156 StoragePermissions = (FileAccess) Config.GetInt(
"storage-permissions", (int)FileAccess.ReadWrite)
161 _dataProvider = algorithmHandlers.DataProvider;
167 symbolPropertiesDataBase,
175 new UniverseSelection(
this, securityService, algorithmHandlers.DataPermissionsManager, algorithmHandlers.DataProvider),
181 algorithmHandlers.DataPermissionsManager));
183 var mapFileProvider = algorithmHandlers.MapFileProvider;
189 algorithmHandlers.DataProvider,
192 algorithmHandlers.FactorFileProvider,
195 algorithmHandlers.DataPermissionsManager,
206 catch (Exception exception)
208 throw new Exception(
"QuantBook.Main(): " + exception);
220 [Obsolete(
"Please use the 'UniverseHistory()' API")]
221 public PyObject
GetFundamental(PyObject input,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
227 var fundamentalData = GetAllFundamental(symbols, selector, start, end);
231 var data =
new PyDict();
232 foreach (var day
in fundamentalData.OrderBy(x => x.Key))
234 var orderedValues = day.Value.OrderBy(x => x.Key.ID.ToString()).ToList();
235 var columns = orderedValues.Select(x => x.Key.ID.ToString());
236 var values = orderedValues.Select(x => x.Value);
237 var row = _pandas.Series(values, columns);
238 data.SetItem(day.Key.ToPython(), row);
241 return _pandas.DataFrame.from_dict(data, orient:
"index");
253 [Obsolete(
"Please use the 'UniverseHistory()' API")]
254 public IEnumerable<DataDictionary<dynamic>>
GetFundamental(IEnumerable<Symbol> symbols,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
256 var data = GetAllFundamental(symbols, selector, start, end);
258 foreach (var kvp
in data.OrderBy(kvp => kvp.Key))
260 yield
return kvp.Value;
272 [Obsolete(
"Please use the 'UniverseHistory()' API")]
273 public IEnumerable<DataDictionary<dynamic>>
GetFundamental(
Symbol symbol,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
275 var list =
new List<Symbol>
291 [Obsolete(
"Please use the 'UniverseHistory()' API")]
292 public IEnumerable<DataDictionary<dynamic>>
GetFundamental(IEnumerable<string> tickers,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
294 var list =
new List<Symbol>();
295 foreach (var ticker
in tickers)
311 [Obsolete(
"Please use the 'UniverseHistory()' API")]
312 public dynamic
GetFundamental(
string ticker,
string selector =
null, DateTime? start =
null, DateTime? end =
null)
316 if (_isPythonNotebook)
322 var list =
new List<Symbol>
342 bool fillForward =
true,
bool extendedMarketHours =
false)
344 symbol = GetOptionSymbolForHistoryRequest(symbol, targetOption, resolution, fillForward);
346 return OptionHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
360 [Obsolete(
"Please use the 'OptionHistory()' API")]
362 bool fillForward =
true,
bool extendedMarketHours =
false)
364 return OptionHistory(symbol, targetOption, start, end, resolution, fillForward, extendedMarketHours);
378 bool fillForward =
true,
bool extendedMarketHours =
false)
380 if (!end.HasValue || end.Value == start)
382 end = start.AddDays(1);
386 symbol = GetOptionSymbolForHistoryRequest(symbol,
null, resolution, fillForward);
388 IEnumerable<Symbol> symbols;
395 .GetHighestResolution();
402 extendedMarketHours: extendedMarketHours);
412 extendedMarketHours: extendedMarketHours);
417 extendedMarketHours: extendedMarketHours);
420 var allSymbols =
new List<Symbol>();
421 for (var date = start; date < end; date = date.AddDays(1))
423 if (option.Exchange.DateIsOpen(date))
430 var distinctSymbols = allSymbols.Distinct();
431 symbols = base.History(symbol.
Underlying, start, end.
Value, resolution)
435 optionFilterUniverse.Refresh(distinctSymbols, x, x.EndTime);
436 return option.ContractFilter.Filter(optionFilterUniverse);
438 .Distinct().Concat(
new[] { symbol.
Underlying });
443 symbols =
new List<Symbol>{ symbol };
446 return new OptionHistory(
History(symbols, start, end.Value, resolution, fillForward, extendedMarketHours));
459 [Obsolete(
"Please use the 'OptionHistory()' API")]
461 bool fillForward =
true,
bool extendedMarketHours =
false)
463 return OptionHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
477 bool fillForward =
true,
bool extendedMarketHours =
false)
479 if (!end.HasValue || end.Value == start)
481 end = start.AddDays(1);
484 var allSymbols =
new HashSet<Symbol>();
490 for (var date = start; date < end; date = date.AddDays(1))
492 if (future.Exchange.DateIsOpen(date))
503 allSymbols.Add(symbol);
506 return new FutureHistory(
History(allSymbols, start, end.Value, resolution, fillForward, extendedMarketHours));
519 [Obsolete(
"Please use the 'FutureHistory()' API")]
521 bool fillForward =
true,
bool extendedMarketHours =
false)
523 return FutureHistory(symbol, start, end, resolution, fillForward, extendedMarketHours);
537 var history =
History(
new[] { symbol }, period, resolution);
538 return Indicator(indicator, history, selector);
552 var history =
History(
new[] { symbol }, period, resolution);
553 return Indicator(indicator, history, selector);
567 var history =
History(
new[] { symbol }, period, resolution);
568 return Indicator(indicator, history, selector);
583 var history =
History(
new[] { symbol }, span, resolution);
584 return Indicator(indicator, history, selector);
599 var history =
History(
new[] { symbol }, span, resolution);
600 return Indicator(indicator, history, selector);
615 var history =
History(
new[] { symbol }, span, resolution);
616 return Indicator(indicator, history, selector);
632 var history =
History(
new[] { symbol }, start, end, resolution);
633 return Indicator(indicator, history, selector);
649 var history =
History(
new[] { symbol }, start, end, resolution);
650 return Indicator(indicator, history, selector);
666 var history =
History(
new[] { symbol }, start, end, resolution);
667 return Indicator(indicator, history, selector);
679 public IEnumerable<IEnumerable<T2>>
UniverseHistory<T1, T2>(DateTime start, DateTime? end =
null, Func<IEnumerable<T2>, IEnumerable<Symbol>> func =
null)
683 var universeSymbol = ((
BaseDataCollection)typeof(T1).GetBaseDataInstance()).UniverseSymbol();
685 var symbols =
new[] { universeSymbol };
687 var history = GetDataTypedHistory<BaseDataCollection>(requests).Select(x => x.Values.Single());
689 HashSet<Symbol> filteredSymbols =
null;
690 foreach (var data
in history)
692 var castedType = data.Data.OfType<T2>();
696 var selection = func(castedType);
699 filteredSymbols = selection.ToHashSet();
701 yield
return castedType.Where(x => filteredSymbols ==
null || filteredSymbols.Contains(x.Symbol));
705 yield
return castedType;
719 return RunUniverseSelection(universe, start, end);
730 public PyObject
UniverseHistory(PyObject universe, DateTime start, DateTime? end =
null, PyObject func =
null)
732 if (universe.TryConvert<
Universe>(out var convertedUniverse))
736 throw new ArgumentException($
"When providing a universe, the selection func argument isn't supported. Please provider a universe or a type and a func");
738 var filteredUniverseSelectionData = RunUniverseSelection(convertedUniverse, start, end);
740 return GetDataFrame(filteredUniverseSelectionData);
743 if (universe.TryConvert<Type>(out var convertedType) && convertedType.IsAssignableTo(typeof(
BaseDataCollection)))
745 end ??= DateTime.UtcNow.Date;
746 var universeSymbol = ((
BaseDataCollection)convertedType.GetBaseDataInstance()).UniverseSymbol();
749 return History(universe, universeSymbol, start, end.Value);
753 var history =
History(requests);
755 return GetDataFrame(GetFilteredSlice(history, func), convertedType);
758 throw new ArgumentException($
"Failed to convert given universe {universe}. Please provider a valid {nameof(Universe)}");
768 var dictBenchmark =
new SortedDictionary<DateTime, double>();
769 var dictEquity =
new SortedDictionary<DateTime, double>();
770 var dictPL =
new SortedDictionary<DateTime, double>();
774 var result =
new PyDict();
779 var df = ((dynamic)dataFrame).dropna();
780 dictBenchmark = GetDictionaryFromSeries((PyObject)df[
"benchmark"]);
781 dictEquity = GetDictionaryFromSeries((PyObject)df[
"equity"]);
782 dictPL = GetDictionaryFromSeries((PyObject)df[
"equity"].pct_change());
784 catch (PythonException e)
786 result.SetItem(
"Runtime Error", e.Message.ToPython());
791 var equity =
new SortedDictionary<DateTime, decimal>(dictEquity.ToDictionary(kvp => kvp.Key, kvp => (decimal)kvp.Value));
792 var profitLoss =
new SortedDictionary<DateTime, decimal>(dictPL.ToDictionary(kvp => kvp.Key, kvp =>
double.IsNaN(kvp.Value) ? 0 : (decimal)kvp.Value));
795 var listBenchmark = CalculateDailyRateOfChange(dictBenchmark);
796 var listPerformance = CalculateDailyRateOfChange(dictEquity);
799 var startingCapital = Convert.ToDecimal(dictEquity.FirstOrDefault().Value);
808 result.SetItem(
"Average Win (%)", Convert.ToDouble(stats.AverageWinRate * 100).ToPython());
809 result.SetItem(
"Average Loss (%)", Convert.ToDouble(stats.AverageLossRate * 100).ToPython());
810 result.SetItem(
"Compounding Annual Return (%)", Convert.ToDouble(stats.CompoundingAnnualReturn * 100m).ToPython());
811 result.SetItem(
"Drawdown (%)", Convert.ToDouble(stats.Drawdown * 100).ToPython());
812 result.SetItem(
"Expectancy", Convert.ToDouble(stats.Expectancy).ToPython());
813 result.SetItem(
"Net Profit (%)", Convert.ToDouble(stats.TotalNetProfit * 100).ToPython());
814 result.SetItem(
"Sharpe Ratio", Convert.ToDouble(stats.SharpeRatio).ToPython());
815 result.SetItem(
"Win Rate (%)", Convert.ToDouble(stats.WinRate * 100).ToPython());
816 result.SetItem(
"Loss Rate (%)", Convert.ToDouble(stats.LossRate * 100).ToPython());
817 result.SetItem(
"Profit-Loss Ratio", Convert.ToDouble(stats.ProfitLossRatio).ToPython());
818 result.SetItem(
"Alpha", Convert.ToDouble(stats.Alpha).ToPython());
819 result.SetItem(
"Beta", Convert.ToDouble(stats.Beta).ToPython());
820 result.SetItem(
"Annual Standard Deviation", Convert.ToDouble(stats.AnnualStandardDeviation).ToPython());
821 result.SetItem(
"Annual Variance", Convert.ToDouble(stats.AnnualVariance).ToPython());
822 result.SetItem(
"Information Ratio", Convert.ToDouble(stats.InformationRatio).ToPython());
823 result.SetItem(
"Tracking Error", Convert.ToDouble(stats.TrackingError).ToPython());
824 result.SetItem(
"Treynor Ratio", Convert.ToDouble(stats.TreynorRatio).ToPython());
833 private IEnumerable<Slice> GetFilteredSlice(IEnumerable<Slice> history, dynamic func)
835 HashSet<Symbol> filteredSymbols =
null;
836 foreach (var slice
in history)
841 using PyObject selection = func(filteredData.SelectMany(baseData => baseData.Data));
842 if (!selection.TryConvert<
object>(out var result) || !ReferenceEquals(result,
Universe.
Unchanged))
844 filteredSymbols = ((
Symbol[])selection.AsManagedObject(typeof(
Symbol[]))).ToHashSet();
847 yield
return new Slice(slice.Time, filteredData.Where(x => {
848 if (filteredSymbols == null)
852 x.Data =
new List<BaseData>(x.Data.Where(dataPoint => filteredSymbols.Contains(dataPoint.Symbol)));
861 private IEnumerable<BaseDataCollection> RunUniverseSelection(
Universe universe, DateTime start, DateTime? end =
null)
863 var history =
History(universe, start, end ?? DateTime.UtcNow.Date);
865 HashSet<Symbol> filteredSymbols =
null;
866 foreach (var dataPoint
in history)
872 filteredSymbols = selection.ToHashSet();
874 dataPoint.Data = dataPoint.Data.Where(x => filteredSymbols ==
null || filteredSymbols.Contains(x.Symbol)).ToList();
875 yield
return dataPoint;
884 private SortedDictionary<DateTime, double> GetDictionaryFromSeries(PyObject series)
886 var dictionary =
new SortedDictionary<DateTime, double>();
888 var pyDict =
new PyDict(((dynamic)series).to_dict());
889 foreach (PyObject item
in pyDict.Items())
891 var key = (DateTime)item[0].AsManagedObject(typeof(DateTime));
892 var value = (double)item[1].AsManagedObject(typeof(
double));
893 dictionary.Add(key, value);
904 private List<double> CalculateDailyRateOfChange(IDictionary<DateTime, double> dictionary)
906 var daily = dictionary.GroupBy(kvp => kvp.Key.Date)
907 .ToDictionary(x => x.Key, v => v.LastOrDefault().Value)
910 var rocp =
new double[daily.Length];
911 for (var i = 1; i < daily.Length; i++)
913 rocp[i] = (daily[i] - daily[i - 1]) / daily[i - 1];
917 return rocp.ToList();
929 var properties = WireIndicatorProperties(indicator);
931 selector = selector ?? (x => x.Value);
933 history.PushThrough(bar =>
935 var value = selector(bar);
936 indicator.Update(bar.EndTime, value);
939 return PandasConverter.GetIndicatorDataFrame(properties);
952 var properties = WireIndicatorProperties(indicator);
954 selector = selector ?? (x => (T)x);
956 history.PushThrough(bar => indicator.Update(selector(bar)));
958 return PandasConverter.GetIndicatorDataFrame(properties);
967 private object GetPropertyValue(
object baseData,
string fullName)
969 foreach (var name
in fullName.Split(
'.'))
971 if (baseData ==
null)
return null;
974 var info = baseData.GetType().GetProperty(name);
976 baseData = info?.GetValue(baseData,
null);
989 private Dictionary<DateTime, DataDictionary<dynamic>> GetAllFundamental(IEnumerable<Symbol> symbols,
string selector, DateTime? start =
null, DateTime? end =
null)
993 var endTime = end.HasValue ? (DateTime) end : DateTime.UtcNow.Date;
996 var data =
new Dictionary<DateTime, DataDictionary<dynamic>>();
999 foreach (var symbol
in symbols)
1005 var time = currentData.EndTime;
1006 object dataPoint = currentData;
1007 if (!
string.IsNullOrWhiteSpace(selector))
1009 dataPoint = GetPropertyValue(currentData, selector);
1016 if (!data.TryGetValue(time, out var dataAtTime))
1020 dataAtTime.Add(currentData.Symbol, dataPoint);
1026 private Symbol GetOptionSymbolForHistoryRequest(Symbol symbol,
string targetOption,
Resolution? resolution,
bool fillForward)
1029 if (!symbol.SecurityType.IsOption())
1031 var option = AddOption(symbol, targetOption, resolution, symbol.ID.Market, fillForward);
1035 if (symbol.SecurityType ==
SecurityType.Future && symbol.IsCanonical())
1037 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. " +
1038 "If you are using the Symbol accessible from `AddFuture(...)`, use the Symbol from `AddFutureContract(...)` instead. " +
1039 "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)`.");
1041 if (symbol.SecurityType ==
SecurityType.Future && !symbol.IsCanonical())
1043 option.SetFilter(universe => universe.Strikes(-10, +10));
1046 symbol = option.Symbol;
1052 private Dictionary<string, List<IndicatorDataPoint>> WireIndicatorProperties(
IndicatorBase indicator)
1058 var name = indicator.GetType().Name;
1060 var properties = indicator.GetType().GetProperties()
1061 .Where(x => x.PropertyType.IsGenericType && x.Name !=
"Consolidators" && x.Name !=
"Window")
1062 .ToDictionary(x => x.Name, y =>
new List<IndicatorDataPoint>());
1063 properties.Add(name,
new List<IndicatorDataPoint>());
1065 indicator.Updated += (s, e) =>
1067 if (!indicator.IsReady)
1072 foreach (var kvp
in properties)
1074 var dataPoint = kvp.Key == name ? e : GetPropertyValue(s, kvp.Key +
".Current");
1082 private static void RecycleMemory()
1084 Task.Delay(TimeSpan.FromSeconds(20)).ContinueWith(_ =>
1086 if (Logging.Log.DebuggingEnabled)
1088 Logging.Log.Debug($
"QuantBook.RecycleMemory(): running...");
1094 }, TaskScheduler.Current);