diff --git a/Algorithm.CSharp/IndicatorHistoryRegressionAlgorithm.cs b/Algorithm.CSharp/IndicatorHistoryRegressionAlgorithm.cs new file mode 100644 index 000000000000..7026316beba3 --- /dev/null +++ b/Algorithm.CSharp/IndicatorHistoryRegressionAlgorithm.cs @@ -0,0 +1,149 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System.Collections.Generic; +using System.Linq; +using QuantConnect.Data; +using QuantConnect.Indicators; +using QuantConnect.Interfaces; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// Regression algorithm asserting the behavior of the indicator history api + /// + public class IndicatorHistoryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition + { + private Symbol _symbol; + + /// + /// Initialize the data and resolution you require for your strategy + /// + public override void Initialize() + { + SetStartDate(2013, 1, 1); + SetEndDate(2014, 12, 31); + + _symbol = AddEquity("SPY", Resolution.Daily).Symbol; + } + + public void OnData(Slice slice) + { + var bollingerBands = new BollingerBands("BB", 20, 2.0m, MovingAverageType.Simple); + + if (bollingerBands.Window.IsReady) + { + throw new RegressionTestException("Unexpected ready bollinger bands state"); + } + + var indicatorHistory = IndicatorHistory(bollingerBands, _symbol, 50); + + if (!bollingerBands.Window.IsReady) + { + throw new RegressionTestException("Unexpected not ready bollinger bands state"); + } + + // we ask for 50 data points + if (indicatorHistory.Count != 50) + { + throw new RegressionTestException($"Unexpected indicators values {indicatorHistory.Count}"); + } + + foreach (var indicatorDataPoints in indicatorHistory) + { + var upperBand = ((dynamic)indicatorDataPoints).UpperBand; + Debug($"BB @{indicatorDataPoints.Current}: middleband: {indicatorDataPoints["middleband"]} upperBand {upperBand}"); + + if (indicatorDataPoints == 0) + { + throw new RegressionTestException($"Unexpected indicators point {indicatorDataPoints}"); + } + } + + var currentValues = indicatorHistory.Current; + if (currentValues.Count != 50 || currentValues.Any(x => x.Value == 0)) + { + throw new RegressionTestException($"Unexpected indicators current values {currentValues.Count}"); + } + var upperBandPoints = indicatorHistory["UpperBand"]; + if (upperBandPoints.Count != 50 || upperBandPoints.Any(x => x.Value == 0)) + { + throw new RegressionTestException($"Unexpected indicators upperBandPoints values {upperBandPoints.Count}"); + } + + // We are done now! + Quit(); + } + + /// + /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. + /// + public bool CanRunLocally { get; } = true; + + /// + /// This is used by the regression test system to indicate which languages this algorithm is written in. + /// + public List Languages { get; } = new() { Language.CSharp, Language.Python }; + + /// + /// Data Points count of all timeslices of algorithm + /// + public long DataPoints => 9; + + /// + /// Data Points count of the algorithm history + /// + public int AlgorithmHistoryDataPoints => 70; + + /// + /// Final status of the algorithm + /// + public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; + + /// + /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm + /// + public Dictionary ExpectedStatistics => new Dictionary + { + {"Total Orders", "0"}, + {"Average Win", "0%"}, + {"Average Loss", "0%"}, + {"Compounding Annual Return", "0%"}, + {"Drawdown", "0%"}, + {"Expectancy", "0"}, + {"Start Equity", "100000"}, + {"End Equity", "100000"}, + {"Net Profit", "0%"}, + {"Sharpe Ratio", "0"}, + {"Sortino Ratio", "0"}, + {"Probabilistic Sharpe Ratio", "0%"}, + {"Loss Rate", "0%"}, + {"Win Rate", "0%"}, + {"Profit-Loss Ratio", "0"}, + {"Alpha", "0"}, + {"Beta", "0"}, + {"Annual Standard Deviation", "0"}, + {"Annual Variance", "0"}, + {"Information Ratio", "0"}, + {"Tracking Error", "0"}, + {"Treynor Ratio", "0"}, + {"Total Fees", "$0.00"}, + {"Estimated Strategy Capacity", "$0"}, + {"Lowest Capacity Asset", ""}, + {"Portfolio Turnover", "0%"}, + {"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"} + }; + } +} diff --git a/Algorithm.Python/IndicatorHistoryRegressionAlgorithm.py b/Algorithm.Python/IndicatorHistoryRegressionAlgorithm.py new file mode 100644 index 000000000000..36a8a9175bbc --- /dev/null +++ b/Algorithm.Python/IndicatorHistoryRegressionAlgorithm.py @@ -0,0 +1,62 @@ +# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. +# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from AlgorithmImports import * + +### +### Regression algorithm asserting the behavior of the indicator history api +### +class IndicatorHistoryRegressionAlgorithm(QCAlgorithm): + '''Regression algorithm asserting the behavior of the indicator history api''' + + def initialize(self): + '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' + self.set_start_date(2013, 1, 1) + self.set_end_date(2014, 12, 31) + + self._symbol = self.add_equity("SPY", Resolution.DAILY).symbol + + def on_data(self, slice: Slice): + self.bollinger_bands = BollingerBands("BB", 20, 2.0, MovingAverageType.SIMPLE) + + if self.bollinger_bands.window.is_ready: + raise ValueError("Unexpected ready bollinger bands state") + + indicatorHistory = self.indicator_history(self.bollinger_bands, self._symbol, 50) + + self.debug(f"indicatorHistory: {indicatorHistory}") + + self.debug(f"data_frame: {indicatorHistory.data_frame}") + + if not self.bollinger_bands.window.is_ready: + raise ValueError("Unexpected not ready bollinger bands state") + + # we ask for 50 data points + if indicatorHistory.count != 50: + raise ValueError(f"Unexpected indicators values {indicatorHistory.count}") + + for indicatorDataPoints in indicatorHistory: + middle_band = indicatorDataPoints["middle_band"] + self.debug(f"BB @{indicatorDataPoints.current}: middle_band: {middle_band} upper_band: {indicatorDataPoints.upper_band}") + if indicatorDataPoints == 0: + raise ValueError(f"Unexpected indicators point {indicatorDataPoints}") + + currentValues = indicatorHistory.current + if len(currentValues) != 50 or len([x for x in currentValues if x.value == 0]) > 0: + raise ValueError(f"Unexpected indicators current values {len(currentValues)}") + upperBandPoints = indicatorHistory["upper_band"] + if len(upperBandPoints) != 50 or len([x for x in upperBandPoints if x.value == 0]) > 0: + raise ValueError(f"Unexpected indicators upperBandPoints values {len(upperBandPoints)}") + + # We are done now! + self.quit() diff --git a/Algorithm/QCAlgorithm.Indicators.cs b/Algorithm/QCAlgorithm.Indicators.cs index d8721edf5449..d7356bee197b 100644 --- a/Algorithm/QCAlgorithm.Indicators.cs +++ b/Algorithm/QCAlgorithm.Indicators.cs @@ -3294,9 +3294,9 @@ public IDataConsolidator Consolidate(Symbol symbol, FuncThe resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of an indicator - public DataHistory Indicator(IndicatorBase indicator, Symbol symbol, int period, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, Symbol symbol, int period, Resolution? resolution = null, Func selector = null) { - return Indicator(indicator, new[] { symbol }, period, resolution, selector); + return IndicatorHistory(indicator, new[] { symbol }, period, resolution, selector); } /// @@ -3309,10 +3309,11 @@ public DataHistory Indicator(IndicatorBase /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of an indicator - public DataHistory Indicator(IndicatorBase indicator, IEnumerable symbols, int period, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, IEnumerable symbols, int period, Resolution? resolution = null, Func selector = null) { - var history = History(symbols, period, resolution); - return Indicator(indicator, history, selector); + var warmupPeriod = (indicator as IIndicatorWarmUpPeriodProvider)?.WarmUpPeriod ?? 0; + var history = History(symbols, period + warmupPeriod, resolution); + return IndicatorHistory(indicator, history, selector); } /// @@ -3325,10 +3326,10 @@ public DataHistory Indicator(IndicatorBase /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of a bar indicator - public DataHistory Indicator(IndicatorBase indicator, Symbol symbol, int period, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, Symbol symbol, int period, Resolution? resolution = null, Func selector = null) where T : IBaseData { - return Indicator(indicator, new[] { symbol }, period, resolution, selector); + return IndicatorHistory(indicator, new[] { symbol }, period, resolution, selector); } /// @@ -3341,11 +3342,12 @@ public DataHistory Indicator(IndicatorBase indicator, Sym /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of a bar indicator - public DataHistory Indicator(IndicatorBase indicator, IEnumerable symbols, int period, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, IEnumerable symbols, int period, Resolution? resolution = null, Func selector = null) where T : IBaseData { - var history = History(symbols, period, resolution); - return Indicator(indicator, history, selector); + var warmupPeriod = (indicator as IIndicatorWarmUpPeriodProvider)?.WarmUpPeriod ?? 0; + var history = History(symbols, period + warmupPeriod, resolution); + return IndicatorHistory(indicator, history, selector); } /// @@ -3358,9 +3360,9 @@ public DataHistory Indicator(IndicatorBase indicator, IEn /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of an indicator - public DataHistory Indicator(IndicatorBase indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func selector = null) { - return Indicator(indicator, new[] { symbol }, span, resolution, selector); + return IndicatorHistory(indicator, new[] { symbol }, span, resolution, selector); } /// @@ -3373,10 +3375,9 @@ public DataHistory Indicator(IndicatorBase /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of an indicator - public DataHistory Indicator(IndicatorBase indicator, IEnumerable symbols, TimeSpan span, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, IEnumerable symbols, TimeSpan span, Resolution? resolution = null, Func selector = null) { - var history = History(symbols, span, resolution); - return Indicator(indicator, history, selector); + return IndicatorHistory(indicator, symbols, Time - span, Time, resolution, selector); } /// @@ -3389,11 +3390,10 @@ public DataHistory Indicator(IndicatorBase /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of a bar indicator - public DataHistory Indicator(IndicatorBase indicator, IEnumerable symbols, TimeSpan span, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, IEnumerable symbols, TimeSpan span, Resolution? resolution = null, Func selector = null) where T : IBaseData { - var history = History(symbols, span, resolution); - return Indicator(indicator, history, selector); + return IndicatorHistory(indicator, symbols, Time - span, Time, resolution, selector); } /// @@ -3406,10 +3406,10 @@ public DataHistory Indicator(IndicatorBase indicator, IEn /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of a bar indicator - public DataHistory Indicator(IndicatorBase indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func selector = null) where T : IBaseData { - return Indicator(indicator, new[] { symbol }, span, resolution, selector); + return IndicatorHistory(indicator, new[] { symbol }, span, resolution, selector); } /// @@ -3423,10 +3423,10 @@ public DataHistory Indicator(IndicatorBase indicator, Sym /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of an indicator - public DataHistory Indicator(IndicatorBase indicator, IEnumerable symbols, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, IEnumerable symbols, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) { - var history = History(symbols, start, end, resolution); - return Indicator(indicator, history, selector); + var history = History(symbols, GetIndicatorAdjustedHistoryStart(indicator, symbols, start, end, resolution), end, resolution); + return IndicatorHistory(indicator, history, selector); } /// @@ -3440,9 +3440,9 @@ public DataHistory Indicator(IndicatorBase /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of an indicator - public DataHistory Indicator(IndicatorBase indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) { - return Indicator(indicator, new[] { symbol }, start, end, resolution, selector); + return IndicatorHistory(indicator, new[] { symbol }, start, end, resolution, selector); } /// @@ -3456,10 +3456,10 @@ public DataHistory Indicator(IndicatorBase /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of a bar indicator - public DataHistory Indicator(IndicatorBase indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) where T : IBaseData { - return Indicator(indicator, new[] { symbol }, start, end, resolution, selector); + return IndicatorHistory(indicator, new[] { symbol }, start, end, resolution, selector); } /// @@ -3473,11 +3473,11 @@ public DataHistory Indicator(IndicatorBase indicator, Sym /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of a bar indicator - public DataHistory Indicator(IndicatorBase indicator, IEnumerable symbols, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, IEnumerable symbols, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) where T : IBaseData { - var history = History(symbols, start, end, resolution); - return Indicator(indicator, history, selector); + var history = History(symbols, GetIndicatorAdjustedHistoryStart(indicator, symbols, start, end, resolution), end, resolution); + return IndicatorHistory(indicator, history, selector); } /// @@ -3487,10 +3487,10 @@ public DataHistory Indicator(IndicatorBase indicator, IEn /// Historical data used to calculate the indicator /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame containing the historical data of - public DataHistory Indicator(IndicatorBase indicator, IEnumerable history, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, IEnumerable history, Func selector = null) { selector ??= (x => x.Value); - return Indicator(indicator, history, (bar) => indicator.Update(bar.EndTime, selector(bar))); + return IndicatorHistory(indicator, history, (bar) => indicator.Update(bar.EndTime, selector(bar))); } /// @@ -3500,11 +3500,11 @@ public DataHistory Indicator(IndicatorBase /// Historical data used to calculate the indicator /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame containing the historical data of - public DataHistory Indicator(IndicatorBase indicator, IEnumerable history, Func selector = null) + public IndicatorHistory IndicatorHistory(IndicatorBase indicator, IEnumerable history, Func selector = null) where T : IBaseData { selector ??= (x => (T)x); - return Indicator(indicator, history, (bar) => indicator.Update(selector(bar))); + return IndicatorHistory(indicator, history, (bar) => indicator.Update(selector(bar))); } /// @@ -3635,20 +3635,38 @@ private void RegisterConsolidator(IndicatorBase indicatorBase, Symbol symbol, ID SubscriptionManager.AddConsolidator(symbol, consolidator); } - private DataHistory Indicator(IndicatorBase indicator, IEnumerable history, Action updateIndicator) + private DateTime GetIndicatorAdjustedHistoryStart(IndicatorBase indicator, IEnumerable symbols, DateTime start, DateTime end, Resolution? resolution = null) + { + var warmupPeriod = (indicator as IIndicatorWarmUpPeriodProvider)?.WarmUpPeriod ?? 0; + if (warmupPeriod != 0) + { + foreach (var request in CreateDateRangeHistoryRequests(symbols, start, end, resolution)) + { + var adjustedStart = _historyRequestFactory.GetStartTimeAlgoTz(request.StartTimeUtc, request.Symbol, warmupPeriod, request.Resolution, + request.ExchangeHours, request.DataTimeZone, request.IncludeExtendedMarketHours); + if (adjustedStart < start) + { + start = adjustedStart; + } + } + } + return start; + } + + private IndicatorHistory IndicatorHistory(IndicatorBase indicator, IEnumerable history, Action updateIndicator) { // Reset the indicator indicator.Reset(); var indicatorType = indicator.GetType(); // Create a dictionary of the indicator properties & the indicator value itself - var name = indicatorType.Name; - var indicatorValues = indicatorType.GetProperties() + var indicatorsDataPointPerProperty = indicatorType.GetProperties() .Where(x => x.PropertyType.IsGenericType && x.Name != "Consolidators" && x.Name != "Window") - .Select(x => IndicatorValues.Create(indicator, x)) - .Concat(new[] { IndicatorValues.Create(indicator, name) }) + .Select(x => InternalIndicatorValues.Create(indicator, x)) + .Concat(new[] { InternalIndicatorValues.Create(indicator, "Current") }) .ToList(); + var indicatorsDataPointsByTime = new List(); IndicatorDataPoint lastPoint = null; void consumeLastPoint() { @@ -3657,9 +3675,12 @@ void consumeLastPoint() return; } - for (var i = 0; i < indicatorValues.Count; i++) + var IndicatorDataPoints = new IndicatorDataPoints { Time = lastPoint.Time, EndTime = lastPoint.EndTime }; + indicatorsDataPointsByTime.Add(IndicatorDataPoints); + for (var i = 0; i < indicatorsDataPointPerProperty.Count; i++) { - indicatorValues[i].UpdateValue(lastPoint); + var newPoint = indicatorsDataPointPerProperty[i].UpdateValue(); + IndicatorDataPoints.SetProperty(indicatorsDataPointPerProperty[i].Name, newPoint); } lastPoint = null; } @@ -3687,8 +3708,8 @@ void consumeLastPoint() history.PushThrough(bar => updateIndicator(bar)); indicator.Updated -= callback; - return new DataHistory(indicatorValues, - new Lazy(() => PandasConverter.GetIndicatorDataFrame(indicatorValues.Select(x => new KeyValuePair>(x.Name, x.Values))))); + return new IndicatorHistory(indicatorsDataPointsByTime, indicatorsDataPointPerProperty, + new Lazy(() => PandasConverter.GetIndicatorDataFrame(indicatorsDataPointPerProperty.Select(x => new KeyValuePair>(x.Name, x.Values))))); } } } diff --git a/Algorithm/QCAlgorithm.Python.cs b/Algorithm/QCAlgorithm.Python.cs index 1d89b67f8528..f47cd6ad80bb 100644 --- a/Algorithm/QCAlgorithm.Python.cs +++ b/Algorithm/QCAlgorithm.Python.cs @@ -1489,22 +1489,22 @@ public IDataConsolidator Consolidate(Symbol symbol, Func /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of an indicator - public DataHistory Indicator(PyObject indicator, PyObject symbol, int period, Resolution? resolution = null, PyObject selector = null) + public IndicatorHistory IndicatorHistory(PyObject indicator, PyObject symbol, int period, Resolution? resolution = null, PyObject selector = null) { var symbols = symbol.ConvertToSymbolEnumerable(); if (indicator.TryConvert(out IndicatorBase indicatorDataPoint)) { - return Indicator(indicatorDataPoint, symbols, period, resolution, selector?.ConvertToDelegate>()); + return IndicatorHistory(indicatorDataPoint, symbols, period, resolution, selector?.ConvertToDelegate>()); } else if (indicator.TryConvert(out IndicatorBase indicatorBar)) { - return Indicator(indicatorBar, symbols, period, resolution, selector?.ConvertToDelegate>()); + return IndicatorHistory(indicatorBar, symbols, period, resolution, selector?.ConvertToDelegate>()); } else if (indicator.TryConvert(out IndicatorBase indicatorTradeBar)) { - return Indicator(indicatorTradeBar, symbols, period, resolution, selector?.ConvertToDelegate>()); + return IndicatorHistory(indicatorTradeBar, symbols, period, resolution, selector?.ConvertToDelegate>()); } - return Indicator(WrapPythonIndicator(indicator), symbols, period, resolution, selector?.ConvertToDelegate>()); + return IndicatorHistory(WrapPythonIndicator(indicator), symbols, period, resolution, selector?.ConvertToDelegate>()); } /// @@ -1517,22 +1517,9 @@ public DataHistory Indicator(PyObject indicator, PyObject symbo /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of an indicator - public DataHistory Indicator(PyObject indicator, PyObject symbol, TimeSpan span, Resolution? resolution = null, PyObject selector = null) + public IndicatorHistory IndicatorHistory(PyObject indicator, PyObject symbol, TimeSpan span, Resolution? resolution = null, PyObject selector = null) { - var symbols = symbol.ConvertToSymbolEnumerable(); - if (indicator.TryConvert(out IndicatorBase indicatorDataPoint)) - { - return Indicator(indicatorDataPoint, symbols, span, resolution, selector?.ConvertToDelegate>()); - } - else if (indicator.TryConvert(out IndicatorBase indicatorBar)) - { - return Indicator(indicatorBar, symbols, span, resolution, selector?.ConvertToDelegate>()); - } - else if (indicator.TryConvert(out IndicatorBase indicatorTradeBar)) - { - return Indicator(indicatorTradeBar, symbols, span, resolution, selector?.ConvertToDelegate>()); - } - return Indicator(WrapPythonIndicator(indicator), symbols, span, resolution, selector?.ConvertToDelegate>()); + return IndicatorHistory(indicator, symbol, Time - span, Time, resolution, selector); } /// @@ -1546,22 +1533,22 @@ public DataHistory Indicator(PyObject indicator, PyObject symbo /// The resolution to request /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame of historical data of an indicator - public DataHistory Indicator(PyObject indicator, PyObject symbol, DateTime start, DateTime end, Resolution? resolution = null, PyObject selector = null) + public IndicatorHistory IndicatorHistory(PyObject indicator, PyObject symbol, DateTime start, DateTime end, Resolution? resolution = null, PyObject selector = null) { var symbols = symbol.ConvertToSymbolEnumerable(); if (indicator.TryConvert(out IndicatorBase indicatorDataPoint)) { - return Indicator(indicatorDataPoint, symbols, start, end, resolution, selector?.ConvertToDelegate>()); + return IndicatorHistory(indicatorDataPoint, symbols, start, end, resolution, selector?.ConvertToDelegate>()); } else if (indicator.TryConvert(out IndicatorBase indicatorBar)) { - return Indicator(indicatorBar, symbols, start, end, resolution, selector?.ConvertToDelegate>()); + return IndicatorHistory(indicatorBar, symbols, start, end, resolution, selector?.ConvertToDelegate>()); } else if (indicator.TryConvert(out IndicatorBase indicatorTradeBar)) { - return Indicator(indicatorTradeBar, symbols, start, end, resolution, selector?.ConvertToDelegate>()); + return IndicatorHistory(indicatorTradeBar, symbols, start, end, resolution, selector?.ConvertToDelegate>()); } - return Indicator(WrapPythonIndicator(indicator), symbols, start, end, resolution, selector?.ConvertToDelegate>()); + return IndicatorHistory(WrapPythonIndicator(indicator), symbols, start, end, resolution, selector?.ConvertToDelegate>()); } /// @@ -1571,21 +1558,21 @@ public DataHistory Indicator(PyObject indicator, PyObject symbo /// Historical data used to calculate the indicator /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) /// pandas.DataFrame containing the historical data of - public DataHistory Indicator(PyObject indicator, IEnumerable history, PyObject selector = null) + public IndicatorHistory IndicatorHistory(PyObject indicator, IEnumerable history, PyObject selector = null) { if (indicator.TryConvert(out IndicatorBase indicatorDataPoint)) { - return Indicator(indicatorDataPoint, history, selector?.ConvertToDelegate>()); + return IndicatorHistory(indicatorDataPoint, history, selector?.ConvertToDelegate>()); } else if (indicator.TryConvert(out IndicatorBase indicatorBar)) { - return Indicator(indicatorBar, history, selector?.ConvertToDelegate>()); + return IndicatorHistory(indicatorBar, history, selector?.ConvertToDelegate>()); } else if (indicator.TryConvert(out IndicatorBase indicatorTradeBar)) { - return Indicator(indicatorTradeBar, history, selector?.ConvertToDelegate>()); + return IndicatorHistory(indicatorTradeBar, history, selector?.ConvertToDelegate>()); } - return Indicator(WrapPythonIndicator(indicator), history, selector?.ConvertToDelegate>()); + return IndicatorHistory(WrapPythonIndicator(indicator), history, selector?.ConvertToDelegate>()); } /// diff --git a/Common/Data/DynamicData.cs b/Common/Data/DynamicData.cs index 407cb962f73e..bc2ed9c68ff1 100644 --- a/Common/Data/DynamicData.cs +++ b/Common/Data/DynamicData.cs @@ -32,6 +32,7 @@ public abstract class DynamicData : BaseData, IDynamicMetaObjectProvider private static readonly MethodInfo SetPropertyMethodInfo = typeof(DynamicData).GetMethod("SetProperty"); private static readonly MethodInfo GetPropertyMethodInfo = typeof(DynamicData).GetMethod("GetProperty"); + private readonly IDictionary _snakeNameStorage = new Dictionary(); private readonly IDictionary _storage = new Dictionary(); /// @@ -50,6 +51,8 @@ public DynamicMetaObject GetMetaObject(Expression parameter) /// Returns the input value back to the caller public object SetProperty(string name, object value) { + // let's be polite and support snake name access for the given object value too + var snakeName = name.ToSnakeCase(); name = name.LazyToLower(); if (name == "time") @@ -105,6 +108,10 @@ public object SetProperty(string name, object value) } _storage[name] = value; + if (snakeName != name) + { + _snakeNameStorage[snakeName] = value; + } return value; } @@ -140,7 +147,7 @@ public object GetProperty(string name) } object value; - if (!_storage.TryGetValue(name, out value)) + if (!_storage.TryGetValue(name, out value) && !_snakeNameStorage.TryGetValue(name, out value)) { // let the user know the property name that we couldn't find throw new KeyNotFoundException( diff --git a/Common/Data/HistoryRequestFactory.cs b/Common/Data/HistoryRequestFactory.cs index 1c2dbafc62f2..e43ae007d8e7 100644 --- a/Common/Data/HistoryRequestFactory.cs +++ b/Common/Data/HistoryRequestFactory.cs @@ -114,10 +114,34 @@ public HistoryRequest CreateHistoryRequest(SubscriptionDataConfig subscription, return request; } + /// + /// Gets the start time required for the specified bar count in terms of the algorithm's time zone + /// + /// The symbol to select proper config + /// The number of bars requested + /// The length of each bar + /// The exchange hours used for market open hours + /// The time zone in which data are stored + /// + /// True to include extended market hours data, false otherwise. + /// If not passed, the config will be used to determined whether to include extended market hours. + /// + /// The start time that would provide the specified number of bars ending at the algorithm's current time + public DateTime GetStartTimeAlgoTz( + Symbol symbol, + int periods, + Resolution resolution, + SecurityExchangeHours exchange, + DateTimeZone dataTimeZone, + bool? extendedMarketHours = null) + { + return GetStartTimeAlgoTz(_algorithm.UtcTime, symbol, periods, resolution, exchange, dataTimeZone, extendedMarketHours); + } /// /// Gets the start time required for the specified bar count in terms of the algorithm's time zone /// + /// The end time in utc /// The symbol to select proper config /// The number of bars requested /// The length of each bar @@ -129,6 +153,7 @@ public HistoryRequest CreateHistoryRequest(SubscriptionDataConfig subscription, /// /// The start time that would provide the specified number of bars ending at the algorithm's current time public DateTime GetStartTimeAlgoTz( + DateTime referenceUtcTime, Symbol symbol, int periods, Resolution resolution, @@ -159,7 +184,7 @@ public DateTime GetStartTimeAlgoTz( var localStartTime = Time.GetStartTimeForTradeBars( exchange, - _algorithm.UtcTime.ConvertFromUtc(exchange.TimeZone), + referenceUtcTime.ConvertFromUtc(exchange.TimeZone), timeSpan, periods, isExtendedMarketHours, diff --git a/Common/Data/IndicatorHistory.cs b/Common/Data/IndicatorHistory.cs new file mode 100644 index 000000000000..9d7002cfa5b5 --- /dev/null +++ b/Common/Data/IndicatorHistory.cs @@ -0,0 +1,64 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Linq; +using Python.Runtime; +using QuantConnect.Indicators; +using System.Collections.Generic; + +namespace QuantConnect.Data +{ + /// + /// Provides historical values of an indicator + /// + public class IndicatorHistory : DataHistory + { + private readonly Dictionary> _pointsPerName; + + /// + /// The indicators historical values + /// + public List Current => this["current"]; + + /// + /// Creates a new instance + /// + /// Indicators data points by time + /// Indicators data points by property name + /// The lazy data frame constructor + public IndicatorHistory(List indicatorsDataPointsByTime, List indicatorsDataPointPerProperty, Lazy dataframe) + : base(indicatorsDataPointsByTime, dataframe) + { + // for the index accessor we enforce uniqueness by name + _pointsPerName = indicatorsDataPointPerProperty.DistinctBy(x => x.Name.ToLowerInvariant()).ToDictionary(x => x.Name.ToSnakeCase(), x => x.Values); + } + + /// + /// Access the historical indicator values per indicator property name + /// + public List this[string name] + { + get + { + if (_pointsPerName.TryGetValue(name.ToSnakeCase().ToLowerInvariant(), out var result)) + { + return result; + } + return null; + } + } + } +} diff --git a/Common/Indicators/InternalIndicatorValues.cs b/Common/Indicators/InternalIndicatorValues.cs new file mode 100644 index 000000000000..2a219ce92a09 --- /dev/null +++ b/Common/Indicators/InternalIndicatorValues.cs @@ -0,0 +1,173 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System.Linq; +using System.Reflection; +using QuantConnect.Data; +using System.Collections; +using System.Collections.Generic; + +namespace QuantConnect.Indicators +{ + /// + /// Collection of indicator data points for a given time + /// + public class IndicatorDataPoints : DynamicData + { + /// + /// The indicator value at a given point + /// + public IndicatorDataPoint Current => (IndicatorDataPoint)GetProperty("Current"); + + /// + /// The indicator value at a given point + /// + public override decimal Value => Current.Value; + + /// + /// Access the historical indicator values per indicator property name + /// + public IndicatorDataPoint this[string name] + { + get + { + return GetProperty(name) as IndicatorDataPoint; + } + } + + /// + /// String representation + /// + public override string ToString() + { + return $"{EndTime} {string.Join(", ", GetStorageDictionary().OrderBy(x => x.Key).Select(x => $"{x.Key}: {HandleObjectStorage(x.Value)}"))}"; + } + + /// + /// Returns the current data value held within the instance + /// + /// The DataPoint instance + /// The current data value held within the instance + public static implicit operator decimal(IndicatorDataPoints instance) + { + return instance.Value; + } + + private static string HandleObjectStorage(object storedObject) + { + if (storedObject is IndicatorDataPoint point) + { + return point.Value.SmartRounding().ToStringInvariant(); + } + return storedObject?.ToString() ?? string.Empty; + } + } + + /// + /// Internal carrier of an indicator values by property name + /// + public class InternalIndicatorValues : IEnumerable + { + /// + /// The name of the values associated to this dto + /// + public string Name { get; } + + /// + /// The indicator values + /// + public List Values { get; } + + /// + /// The target indicator + /// + protected IIndicator Indicator { get; } + + /// + /// Creates a new instance + /// + public InternalIndicatorValues(IIndicator indicator, string name) + { + Name = name; + Values = new(); + Indicator = indicator; + } + + /// + /// Update with a new indicator point + /// + public virtual IndicatorDataPoint UpdateValue() + { + Values.Add(Indicator.Current); + return Indicator.Current; + } + + /// + /// Creates a new instance + /// + public static InternalIndicatorValues Create(IIndicator indicator, string name) + { + return new InternalIndicatorValues(indicator, name); + } + + /// + /// Creates a new instance + /// + public static InternalIndicatorValues Create(IIndicator indicator, PropertyInfo propertyInfo) + { + return new IndicatorPropertyValues(indicator, propertyInfo); + } + + /// + /// String representation + /// + public override string ToString() + { + return $"{Name} {Values.Count} indicator values"; + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)Values).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)Values).GetEnumerator(); + } + + private class IndicatorPropertyValues : InternalIndicatorValues + { + private readonly PropertyInfo _currentInfo; + private readonly PropertyInfo _propertyInfo; + public IndicatorPropertyValues(IIndicator indicator, PropertyInfo propertyInfo) : base(indicator, propertyInfo.Name) + { + _propertyInfo = propertyInfo; + _currentInfo = _propertyInfo.PropertyType.GetProperty("Current"); + } + public override IndicatorDataPoint UpdateValue() + { + var value = _propertyInfo.GetValue(Indicator); + if (_currentInfo != null) + { + value = _currentInfo.GetValue(value); + } + var point = value as IndicatorDataPoint; + Values.Add(point); + return point; + } + } + } +} diff --git a/Indicators/IndicatorValues.cs b/Indicators/IndicatorValues.cs deleted file mode 100644 index 1106019f04be..000000000000 --- a/Indicators/IndicatorValues.cs +++ /dev/null @@ -1,114 +0,0 @@ -/* - * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. - * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ - -using System.Reflection; -using System.Collections; -using System.Collections.Generic; - -namespace QuantConnect.Indicators -{ - /// - /// Carrier of an indicator values - /// - public class IndicatorValues : IEnumerable - { - /// - /// The name of the values associated to this dto - /// - public string Name { get; } - - /// - /// The indicator values - /// - public List Values { get; } - - /// - /// The target indicator - /// - protected IndicatorBase Indicator { get; } - - /// - /// Creates a new instance - /// - public IndicatorValues(IndicatorBase indicator, string name) - { - Name = name; - Values = new(); - Indicator = indicator; - } - - /// - /// Update with a new indicator point - /// - public virtual void UpdateValue(IndicatorDataPoint point) - { - Values.Add(point); - } - - /// - /// Creates a new instance - /// - public static IndicatorValues Create(IndicatorBase indicator, string name) - { - return new IndicatorValues(indicator, name); - } - - /// - /// Creates a new instance - /// - public static IndicatorValues Create(IndicatorBase indicator, PropertyInfo propertyInfo) - { - return new IndicatorPropertyValues(indicator, propertyInfo); - } - - /// - /// String representation - /// - public override string ToString() - { - return $"{Name} {Values.Count} indicator values"; - } - - public IEnumerator GetEnumerator() - { - return ((IEnumerable)Values).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)Values).GetEnumerator(); - } - - private class IndicatorPropertyValues : IndicatorValues - { - private readonly PropertyInfo _currentInfo; - private readonly PropertyInfo _propertyInfo; - public IndicatorPropertyValues(IndicatorBase indicator, PropertyInfo propertyInfo) : base(indicator, propertyInfo.Name) - { - _propertyInfo = propertyInfo; - _currentInfo = _propertyInfo.PropertyType.GetProperty("Current"); - } - public override void UpdateValue(IndicatorDataPoint _) - { - var value = _propertyInfo.GetValue(Indicator); - if (_currentInfo != null) - { - value = _currentInfo.GetValue(value); - } - Values.Add(value as IndicatorDataPoint); - } - } - } -} diff --git a/Research/QuantBook.cs b/Research/QuantBook.cs index bf7b9f708983..20e451fa5412 100644 --- a/Research/QuantBook.cs +++ b/Research/QuantBook.cs @@ -36,6 +36,7 @@ using System.Threading.Tasks; using QuantConnect.Data.UniverseSelection; using QuantConnect.Lean.Engine.Setup; +using QuantConnect.Indicators; namespace QuantConnect.Research { @@ -523,6 +524,159 @@ public FutureHistory GetFutureHistory(Symbol symbol, DateTime start, DateTime? e return FutureHistory(symbol, start, end, resolution, fillForward, extendedMarketHours); } + /// + /// Gets the historical data of an indicator for the specified symbol. The exact number of bars will be returned. + /// The symbol must exist in the Securities collection. + /// + /// The symbol to retrieve historical data for + /// The number of bars to request + /// The resolution to request + /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) + /// pandas.DataFrame of historical data of an indicator + [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")] + public PyObject Indicator(IndicatorBase indicator, Symbol symbol, int period, Resolution? resolution = null, Func selector = null) + { + var history = History(new[] { symbol }, period, resolution); + return IndicatorHistory(indicator, history, selector).DataFrame; + } + + /// + /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned. + /// The symbol must exist in the Securities collection. + /// + /// The symbol to retrieve historical data for + /// The number of bars to request + /// The resolution to request + /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) + /// pandas.DataFrame of historical data of a bar indicator + [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")] + public PyObject Indicator(IndicatorBase indicator, Symbol symbol, int period, Resolution? resolution = null, Func selector = null) + { + var history = History(new[] { symbol }, period, resolution); + return IndicatorHistory(indicator, history, selector).DataFrame; + } + + /// + /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned. + /// The symbol must exist in the Securities collection. + /// + /// The symbol to retrieve historical data for + /// The number of bars to request + /// The resolution to request + /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) + /// pandas.DataFrame of historical data of a bar indicator + [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")] + public PyObject Indicator(IndicatorBase indicator, Symbol symbol, int period, Resolution? resolution = null, Func selector = null) + { + var history = History(new[] { symbol }, period, resolution); + return IndicatorHistory(indicator, history, selector).DataFrame; + } + + /// + /// Gets the historical data of an indicator for the specified symbol. The exact number of bars will be returned. + /// The symbol must exist in the Securities collection. + /// + /// Indicator + /// The symbol to retrieve historical data for + /// The span over which to retrieve recent historical data + /// The resolution to request + /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) + /// pandas.DataFrame of historical data of an indicator + [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")] + public PyObject Indicator(IndicatorBase indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func selector = null) + { + var history = History(new[] { symbol }, span, resolution); + return IndicatorHistory(indicator, history, selector).DataFrame; + } + + /// + /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned. + /// The symbol must exist in the Securities collection. + /// + /// Indicator + /// The symbol to retrieve historical data for + /// The span over which to retrieve recent historical data + /// The resolution to request + /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) + /// pandas.DataFrame of historical data of a bar indicator + [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")] + public PyObject Indicator(IndicatorBase indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func selector = null) + { + var history = History(new[] { symbol }, span, resolution); + return IndicatorHistory(indicator, history, selector).DataFrame; + } + + /// + /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned. + /// The symbol must exist in the Securities collection. + /// + /// Indicator + /// The symbol to retrieve historical data for + /// The span over which to retrieve recent historical data + /// The resolution to request + /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) + /// pandas.DataFrame of historical data of a bar indicator + [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")] + public PyObject Indicator(IndicatorBase indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func selector = null) + { + var history = History(new[] { symbol }, span, resolution); + return IndicatorHistory(indicator, history, selector).DataFrame; + } + + /// + /// Gets the historical data of an indicator for the specified symbol. The exact number of bars will be returned. + /// The symbol must exist in the Securities collection. + /// + /// Indicator + /// The symbol to retrieve historical data for + /// The start time in the algorithm's time zone + /// The end time in the algorithm's time zone + /// The resolution to request + /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) + /// pandas.DataFrame of historical data of an indicator + [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")] + public PyObject Indicator(IndicatorBase indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) + { + var history = History(new[] { symbol }, start, end, resolution); + return IndicatorHistory(indicator, history, selector).DataFrame; + } + + /// + /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned. + /// The symbol must exist in the Securities collection. + /// + /// Indicator + /// The symbol to retrieve historical data for + /// The start time in the algorithm's time zone + /// The end time in the algorithm's time zone + /// The resolution to request + /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) + /// pandas.DataFrame of historical data of a bar indicator + [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")] + public PyObject Indicator(IndicatorBase indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) + { + var history = History(new[] { symbol }, start, end, resolution); + return IndicatorHistory(indicator, history, selector).DataFrame; + } + + /// + /// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned. + /// The symbol must exist in the Securities collection. + /// + /// Indicator + /// The symbol to retrieve historical data for + /// The start time in the algorithm's time zone + /// The end time in the algorithm's time zone + /// The resolution to request + /// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value) + /// pandas.DataFrame of historical data of a bar indicator + [Obsolete("Please use the 'IndicatorHistory()', pandas dataframe available through '.DataFrame'")] + public PyObject Indicator(IndicatorBase indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func selector = null) + { + var history = History(new[] { symbol }, start, end, resolution); + return IndicatorHistory(indicator, history, selector).DataFrame; + } + /// /// Will return the universe selection data and will optionally perform selection /// diff --git a/Tests/Algorithm/AlgorithmIndicatorsTests.cs b/Tests/Algorithm/AlgorithmIndicatorsTests.cs index 3ad4b893c215..f932e626471a 100644 --- a/Tests/Algorithm/AlgorithmIndicatorsTests.cs +++ b/Tests/Algorithm/AlgorithmIndicatorsTests.cs @@ -100,34 +100,30 @@ public void SharpeRatioIndicatorUsesAlgorithmsRiskFreeRateModelSetAfterIndicator [TestCase("StartAndEndDate", Language.Python)] public void IndicatorsDataPoint(string testCase, Language language) { - var indicator = new BollingerBands(10, 2); + var period = 10; + var indicator = new BollingerBands(period, 2); _algorithm.SetDateTime(new DateTime(2013, 10, 11)); int dataCount; - DataHistory indicatorValues; + IndicatorHistory indicatorValues; if (language == Language.CSharp) { if (testCase == "StartAndEndDate") { - indicatorValues = _algorithm.Indicator(indicator, _equity, new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute); } else if (testCase == "Span") { - indicatorValues = _algorithm.Indicator(indicator, _equity, TimeSpan.FromDays(5), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, TimeSpan.FromDays(5), Resolution.Minute); } else { - indicatorValues = _algorithm.Indicator(indicator, _equity, (int)(4 * 60 * 6.5), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, (int)(4 * 60 * 6.5), Resolution.Minute); } // BollingerBands, upper, lower, mid bands, std, band width, percentB, price - Assert.AreEqual(8, indicatorValues.Count); - dataCount = indicatorValues.First().Values.Count; - foreach (var indicatorValue in indicatorValues) - { - Assert.AreEqual(dataCount, indicatorValue.Values.Count); - } - dataCount = indicatorValues.SelectMany(x => x.Values).DistinctBy(y => y.EndTime).Count(); + Assert.AreEqual(8, indicatorValues.First().GetStorageDictionary().Count); + dataCount = indicatorValues.ToList().Count; } else { @@ -135,21 +131,31 @@ public void IndicatorsDataPoint(string testCase, Language language) { if (testCase == "StartAndEndDate") { - indicatorValues = _algorithm.Indicator(indicator.ToPython(), _equity.ToPython(), new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute); } else if (testCase == "Span") { - indicatorValues = _algorithm.Indicator(indicator.ToPython(), _equity.ToPython(), TimeSpan.FromDays(5), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), TimeSpan.FromDays(5), Resolution.Minute); } else { - indicatorValues = _algorithm.Indicator(indicator.ToPython(), _equity.ToPython(), (int)(4 * 60 * 6.5), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), (int)(4 * 60 * 6.5), Resolution.Minute); } dataCount = QuantBookIndicatorsTests.GetDataFrameLength(indicatorValues.DataFrame); } } + + // the historical indicator current values + Assert.AreEqual(1550 + period, indicatorValues.Current.Count); + Assert.AreEqual(1550 + period, indicatorValues["current"].Count); + Assert.AreEqual(indicatorValues.Current, indicatorValues["current"]); + Assert.IsNull(indicatorValues["NonExisting"]); + Assert.IsTrue(indicator.IsReady); - Assert.AreEqual(1551, dataCount); + Assert.AreEqual(1550 + period, dataCount); + + var lastData = indicatorValues.Current.Last(); + Assert.AreEqual(new DateTime(2013, 10, 10, 16, 0, 0), lastData.EndTime); } [TestCase("Span", Language.CSharp)] @@ -160,31 +166,29 @@ public void IndicatorsDataPoint(string testCase, Language language) [TestCase("StartAndEndDate", Language.Python)] public void IndicatorsBar(string testCase, Language language) { - var indicator = new AverageTrueRange(10); + var period = 10; + var indicator = new AverageTrueRange(period); _algorithm.SetDateTime(new DateTime(2013, 10, 11)); - DataHistory indicatorValues; + IndicatorHistory indicatorValues; int dataCount; if (language == Language.CSharp) { if (testCase == "StartAndEndDate") { - indicatorValues = _algorithm.Indicator(indicator, _equity, new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute); } else if (testCase == "Span") { - indicatorValues = _algorithm.Indicator(indicator, _equity, TimeSpan.FromDays(5), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, TimeSpan.FromDays(5), Resolution.Minute); } else { - indicatorValues = _algorithm.Indicator(indicator, _equity, (int)(4 * 60 * 6.5), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, (int)(4 * 60 * 6.5), Resolution.Minute); } // the TrueRange & the AVGTrueRange - Assert.AreEqual(2, indicatorValues.Count); - dataCount = indicatorValues.First().Values.Count; - Assert.AreEqual(dataCount, indicatorValues.Skip(1).First().Values.Count); - - dataCount = indicatorValues.SelectMany(x => x.Values).DistinctBy(y => y.EndTime).Count(); + Assert.AreEqual(2, indicatorValues.First().GetStorageDictionary().Count); + dataCount = indicatorValues.ToList().Count; } else { @@ -192,21 +196,31 @@ public void IndicatorsBar(string testCase, Language language) { if (testCase == "StartAndEndDate") { - indicatorValues = _algorithm.Indicator(indicator.ToPython(), _equity.ToPython(), new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute); } else if (testCase == "Span") { - indicatorValues = _algorithm.Indicator(indicator.ToPython(), _equity.ToPython(), TimeSpan.FromDays(5), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), TimeSpan.FromDays(5), Resolution.Minute); } else { - indicatorValues = _algorithm.Indicator(indicator.ToPython(), _equity.ToPython(), (int)(4 * 60 * 6.5), Resolution.Minute); + indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), (int)(4 * 60 * 6.5), Resolution.Minute); } dataCount = QuantBookIndicatorsTests.GetDataFrameLength(indicatorValues.DataFrame); } } + + // the historical indicator current values + Assert.AreEqual(1550 + period, indicatorValues.Current.Count); + Assert.AreEqual(1550 + period, indicatorValues["current"].Count); + Assert.AreEqual(indicatorValues.Current, indicatorValues["current"]); + Assert.IsNull(indicatorValues["NonExisting"]); + Assert.IsTrue(indicator.IsReady); - Assert.AreEqual(1551, dataCount); + Assert.AreEqual(1550 + period, dataCount); + + var lastData = indicatorValues.Current.Last(); + Assert.AreEqual(new DateTime(2013, 10, 10, 16, 0, 0), lastData.EndTime); } [TestCase(Language.Python)] @@ -218,21 +232,29 @@ public void IndicatorMultiSymbol(Language language) _algorithm.SetDateTime(new DateTime(2013, 10, 11)); int dataCount; + IndicatorHistory indicatorValues; if (language == Language.CSharp) { - var indicatorValues = _algorithm.Indicator(indicator, new[] { _equity, referenceSymbol }, TimeSpan.FromDays(5)); - Assert.AreEqual(1, indicatorValues.Count); - dataCount = indicatorValues.First().Values.Count; + indicatorValues = _algorithm.IndicatorHistory(indicator, new[] { _equity, referenceSymbol }, TimeSpan.FromDays(5)); + Assert.AreEqual(1, indicatorValues.First().GetStorageDictionary().Count); + dataCount = indicatorValues.ToList().Count; } else { using (Py.GIL()) { - var pandasFrame = _algorithm.Indicator(indicator.ToPython(), (new[] { _equity, referenceSymbol }).ToPython(), TimeSpan.FromDays(5)); - dataCount = QuantBookIndicatorsTests.GetDataFrameLength(pandasFrame.DataFrame); + indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), (new[] { _equity, referenceSymbol }).ToPython(), TimeSpan.FromDays(5)); + dataCount = QuantBookIndicatorsTests.GetDataFrameLength(indicatorValues.DataFrame); } } - Assert.AreEqual(1549, dataCount); + + // the historical indicator current values + Assert.AreEqual(1560, indicatorValues.Current.Count); + Assert.AreEqual(1560, indicatorValues["current"].Count); + Assert.AreEqual(indicatorValues.Current, indicatorValues["current"]); + Assert.IsNull(indicatorValues["NonExisting"]); + + Assert.AreEqual(1560, dataCount); Assert.IsTrue(indicator.IsReady); } @@ -243,8 +265,9 @@ public void BetaCalculation() var indicator = new Beta(_equity, referenceSymbol, 10); _algorithm.SetDateTime(new DateTime(2013, 10, 11)); - var indicatorValues = _algorithm.Indicator(indicator, new[] { _equity, referenceSymbol }, TimeSpan.FromDays(50), Resolution.Daily); - Assert.AreEqual(0.676480102032563m, indicatorValues.Last().Values.Last().Price); + var indicatorValues = _algorithm.IndicatorHistory(indicator, new[] { _equity, referenceSymbol }, TimeSpan.FromDays(50), Resolution.Daily); + Assert.AreEqual(0.676480102032563m, indicatorValues.Last().Price); + Assert.AreEqual(0.676480102032563m, indicatorValues.Last().Current.Value); } [TestCase(Language.Python)] @@ -259,15 +282,15 @@ public void IndicatorsPassingHistory(Language language) int dataCount; if (language == Language.CSharp) { - var indicatorValues = _algorithm.Indicator(indicator, history); - Assert.AreEqual(1, indicatorValues.Count); - dataCount = indicatorValues.First().Values.Count; + var indicatorValues = _algorithm.IndicatorHistory(indicator, history); + Assert.AreEqual(1, indicatorValues.First().GetStorageDictionary().Count); + dataCount = indicatorValues.Count; } else { using (Py.GIL()) { - var pandasFrame = _algorithm.Indicator(indicator.ToPython(), history); + var pandasFrame = _algorithm.IndicatorHistory(indicator.ToPython(), history); dataCount = QuantBookIndicatorsTests.GetDataFrameLength(pandasFrame.DataFrame); } } @@ -310,7 +333,7 @@ def Update(self, input): } var goodIndicator = module.GetAttr("GoodCustomIndicator").Invoke(); - var pandasFrame = _algorithm.Indicator(goodIndicator, _equity.ToPython(), TimeSpan.FromDays(5), Resolution.Minute); + var pandasFrame = _algorithm.IndicatorHistory(goodIndicator, _equity.ToPython(), TimeSpan.FromDays(5), Resolution.Minute); var dataCount = QuantBookIndicatorsTests.GetDataFrameLength(pandasFrame.DataFrame); Assert.IsTrue((bool)((dynamic)goodIndicator).IsReady); diff --git a/Tests/Common/Data/DynamicDataTests.cs b/Tests/Common/Data/DynamicDataTests.cs index ccc777e55dbb..26d822f0b337 100644 --- a/Tests/Common/Data/DynamicDataTests.cs +++ b/Tests/Common/Data/DynamicDataTests.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -23,6 +23,16 @@ namespace QuantConnect.Tests.Common.Data [TestFixture] public class DynamicDataTests { + [Test] + public void SupportsSnakeNameRetrival() + { + dynamic data = new DataType(); + data.PropertyA = 1; + + Assert.AreEqual(1, data.property_a); + Assert.AreEqual(data.PropertyA, data.property_a); + } + [Test] public void StoresValues_Using_LowerCaseKeys() { diff --git a/Tests/Research/QuantBookIndicatorsTests.cs b/Tests/Research/QuantBookIndicatorsTests.cs index e0afe7a50e87..62eea3e5dc0f 100644 --- a/Tests/Research/QuantBookIndicatorsTests.cs +++ b/Tests/Research/QuantBookIndicatorsTests.cs @@ -49,7 +49,6 @@ public void OneTimeTearDown() Log.LogHandler = _logHandler; } - [Test] [TestCase(2013, 10, 11, SecurityType.Equity, "SPY")] [TestCase(2014, 5, 9, SecurityType.Forex, "EURUSD")] [TestCase(2016, 10, 9, SecurityType.Crypto, "BTCUSD")] @@ -82,6 +81,38 @@ public void QuantBookIndicatorTests(int year, int month, int day, SecurityType s } } + [TestCase(2013, 10, 11, SecurityType.Equity, "SPY")] + [TestCase(2014, 5, 9, SecurityType.Forex, "EURUSD")] + [TestCase(2016, 10, 9, SecurityType.Crypto, "BTCUSD")] + public void QuantBookIndicatorTests_BackwardsCompatibility(int year, int month, int day, SecurityType securityType, string symbol) + { + using (Py.GIL()) + { + var startDate = new DateTime(year, month, day); + var indicatorTest = _module.IndicatorTest(startDate, securityType, symbol); + + var endDate = startDate; + startDate = endDate.AddYears(-1); + + // Tests a data point indicator + var dfBB = indicatorTest.test_bollinger_bands_backwards_compatibility(symbol, startDate, endDate, Resolution.Daily); + Assert.IsTrue(GetDataFrameLength(dfBB) > 0); + + // Tests a bar indicator + var dfATR = indicatorTest.test_average_true_range_backwards_compatibility(symbol, startDate, endDate, Resolution.Daily); + Assert.IsTrue(GetDataFrameLength(dfATR) > 0); + + if (securityType == SecurityType.Forex) + { + return; + } + + // Tests a trade bar indicator + var dfOBV = indicatorTest.test_on_balance_volume_backwards_compatibility(symbol, startDate, endDate, Resolution.Daily); + Assert.IsTrue(GetDataFrameLength(dfOBV) > 0); + } + } + internal static int GetDataFrameLength(dynamic df) => (int)(df.shape[0] as PyObject).AsManagedObject(typeof(int)); } } diff --git a/Tests/Research/RegressionScripts/Test_QuantBookIndicator.py b/Tests/Research/RegressionScripts/Test_QuantBookIndicator.py index 999c8343cf9e..286cd9fadfe7 100644 --- a/Tests/Research/RegressionScripts/Test_QuantBookIndicator.py +++ b/Tests/Research/RegressionScripts/Test_QuantBookIndicator.py @@ -24,12 +24,24 @@ def __str__(self): def test_bollinger_bands(self, symbol, start, end, resolution): ind = BollingerBands(10, 2) - return self.qb.Indicator(ind, symbol, start, end, resolution) + return self.qb.IndicatorHistory(ind, symbol, start, end, resolution) def test_average_true_range(self, symbol, start, end, resolution): ind = AverageTrueRange(14) - return self.qb.Indicator(ind, symbol, start, end, resolution) + return self.qb.IndicatorHistory(ind, symbol, start, end, resolution) def test_on_balance_volume(self, symbol, start, end, resolution): + ind = OnBalanceVolume(symbol) + return self.qb.IndicatorHistory(ind, symbol, start, end, resolution) + + def test_bollinger_bands_backwards_compatibility(self, symbol, start, end, resolution): + ind = BollingerBands(10, 2) + return self.qb.Indicator(ind, symbol, start, end, resolution) + + def test_average_true_range_backwards_compatibility(self, symbol, start, end, resolution): + ind = AverageTrueRange(14) + return self.qb.Indicator(ind, symbol, start, end, resolution) + + def test_on_balance_volume_backwards_compatibility(self, symbol, start, end, resolution): ind = OnBalanceVolume(symbol) return self.qb.Indicator(ind, symbol, start, end, resolution) diff --git a/Tests/Research/RegressionTemplates/BasicTemplateResearchPython.cs b/Tests/Research/RegressionTemplates/BasicTemplateResearchPython.cs index 2ea422992587..b7e9fd516e1a 100644 --- a/Tests/Research/RegressionTemplates/BasicTemplateResearchPython.cs +++ b/Tests/Research/RegressionTemplates/BasicTemplateResearchPython.cs @@ -76,7 +76,7 @@ public class BasicTemplateResearchPython : IRegressionResearchDefinition "Z\" }, \"papermill\": { \"duration\": 0.044872, \"end_time\": \"2024-06-06T22:15:51.503757\", \"exception\": false, \"start_time" + "\": \"2024-06-06T22:15:51.458885\", \"status\": \"completed\" }, \"tags\": [] }, \"outputs\": [], \"source\": [ \"# Example with BB" + ", it is a datapoint indicator\", \"# Define the indicator\", \"bb = BollingerBands(30, 2)\", \"\", \"# Gets historical data of indicator\"" + - ", \"bbdf = qb.Indicator(bb, \\\"SPY\\\", startDate, endDate, Resolution.Daily).data_frame\", \"\", \"# drop undesired fields\", \"bbdf = b" + + ", \"bbdf = qb.IndicatorHistory(bb, \\\"SPY\\\", startDate, endDate, Resolution.Daily).data_frame\", \"\", \"# drop undesired fields\", \"bbdf = b" + "bdf.drop('standarddeviation', axis=1)\", \"\", \"if bbdf.shape[0] < 1:\", \" raise Exception(\\\"Bollinger Bands resulted in no data\\\")\"" + " ] }, { \"cell_type\": \"code\", \"execution_count\": null, \"id\": \"3095c061\", \"metadata\": { \"papermill\": { \"duration\": 0." + "001003, \"end_time\": \"2024-06-06T22:15:51.506759\", \"exception\": false, \"start_time\": \"2024-06-06T22:15:51.505756\", \"status\"" + diff --git a/Tests/Research/RegressionTemplates/BasicTemplateResearchPython.ipynb b/Tests/Research/RegressionTemplates/BasicTemplateResearchPython.ipynb index c06045832985..0e7b9a7c914e 100644 --- a/Tests/Research/RegressionTemplates/BasicTemplateResearchPython.ipynb +++ b/Tests/Research/RegressionTemplates/BasicTemplateResearchPython.ipynb @@ -112,7 +112,7 @@ "bb = BollingerBands(30, 2)\n", "\n", "# Gets historical data of indicator\n", - "bbdf = qb.Indicator(bb, \"SPY\", startDate, endDate, Resolution.Daily).data_frame\n", + "bbdf = qb.IndicatorHistory(bb, \"SPY\", startDate, endDate, Resolution.Daily).data_frame\n", "\n", "# drop undesired fields\n", "bbdf = bbdf.drop('standarddeviation', axis=1)\n",