diff --git a/Algorithm.CSharp/StochasticIndicatorWarmsUpProperlyRegressionAlgorithm.cs b/Algorithm.CSharp/StochasticIndicatorWarmsUpProperlyRegressionAlgorithm.cs
new file mode 100644
index 000000000000..6159f4956430
--- /dev/null
+++ b/Algorithm.CSharp/StochasticIndicatorWarmsUpProperlyRegressionAlgorithm.cs
@@ -0,0 +1,172 @@
+/*
+ * 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 QuantConnect.Data;
+using QuantConnect.Data.Consolidators;
+using QuantConnect.Indicators;
+using QuantConnect.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Regression algorithm that asserts Stochastic indicator, registered with a different resolution consolidator,
+ /// is warmed up properly by calling QCAlgorithm.WarmUpIndicator
+ ///
+ public class StochasticIndicatorWarmsUpProperlyRegressionAlgorithm: QCAlgorithm, IRegressionAlgorithmDefinition
+ {
+ private bool _dataPointsReceived;
+ private Symbol _spy;
+ private RelativeStrengthIndex _rsi;
+ private RelativeStrengthIndex _rsiHistory;
+ private Stochastic _sto;
+ private Stochastic _stoHistory;
+
+ public override void Initialize()
+ {
+ SetStartDate(2020, 1, 1);
+ SetEndDate(2020, 2, 1);
+
+ _spy = AddEquity("SPY", Resolution.Hour).Symbol;
+
+ var dailyConsolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
+ _rsi = new RelativeStrengthIndex(14, MovingAverageType.Wilders);
+ _sto = new Stochastic("FIRST", 14, 3, 3);
+ RegisterIndicator(_spy, _rsi, dailyConsolidator);
+ RegisterIndicator(_spy, _sto, dailyConsolidator);
+
+ WarmUpIndicator(_spy, _rsi, TimeSpan.FromDays(1));
+ WarmUpIndicator(_spy, _sto, TimeSpan.FromDays(1));
+
+ _rsiHistory = new RelativeStrengthIndex(14, MovingAverageType.Wilders);
+ _stoHistory = new Stochastic("SECOND", 14, 3, 3);
+ RegisterIndicator(_spy, _rsiHistory, dailyConsolidator);
+ RegisterIndicator(_spy, _stoHistory, dailyConsolidator);
+
+ var history = History(_spy, 15, Resolution.Daily);
+ foreach (var bar in history)
+ {
+ _rsiHistory.Update(bar.EndTime, bar.Close);
+ if (_rsiHistory.Samples == 1) continue;
+
+ _stoHistory.Update(bar);
+ }
+
+ var indicators = new List() { _rsi, _sto, _rsiHistory, _stoHistory };
+
+ foreach (var indicator in indicators)
+ {
+ if (!indicator.IsReady)
+ {
+ throw new RegressionTestException($"{indicator.Name} should be ready, but it is not. Number of samples: {indicator.Samples}");
+ }
+ }
+ }
+
+ public override void OnData(Slice slice)
+ {
+ if (IsWarmingUp) return;
+
+ if (slice.ContainsKey(_spy))
+ {
+ _dataPointsReceived = true;
+
+ if (_rsi.Current.Value != _rsiHistory.Current.Value)
+ {
+ throw new RegressionTestException($"Values of indicators differ: {_rsi.Name}: {_rsi.Current.Value} | {_rsiHistory.Name}: {_rsiHistory.Current.Value}");
+ }
+
+ if (_sto.StochK.Current.Value != _stoHistory.StochK.Current.Value)
+ {
+ throw new RegressionTestException($"Stoch K values of indicators differ: {_sto.Name}.StochK: {_sto.StochK.Current.Value} | {_stoHistory.Name}.StochK: {_stoHistory.StochK.Current.Value}");
+ }
+
+ if (_sto.StochD.Current.Value != _stoHistory.StochD.Current.Value)
+ {
+ throw new RegressionTestException($"Stoch D values of indicators differ: {_sto.Name}.StochD: {_sto.StochD.Current.Value} | {_stoHistory.Name}.StochD: {_stoHistory.StochD.Current.Value}");
+ }
+ }
+ }
+
+ public override void OnEndOfAlgorithm()
+ {
+ if (!_dataPointsReceived)
+ {
+ throw new Exception("No data points received");
+ }
+ }
+
+ ///
+ /// 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 => 302;
+
+ ///
+ /// Data Points count of the algorithm history
+ ///
+ public int AlgorithmHistoryDataPoints => 44;
+
+ ///
+ /// 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.016"},
+ {"Tracking Error", "0.101"},
+ {"Treynor Ratio", "0"},
+ {"Total Fees", "$0.00"},
+ {"Estimated Strategy Capacity", "$0"},
+ {"Lowest Capacity Asset", ""},
+ {"Portfolio Turnover", "0%"},
+ {"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"}
+ };
+ }
+}
diff --git a/Algorithm.Python/StochasticIndicatorWarmsUpProperlyRegressionAlgorithm.py b/Algorithm.Python/StochasticIndicatorWarmsUpProperlyRegressionAlgorithm.py
new file mode 100644
index 000000000000..ffcc0b83f3ef
--- /dev/null
+++ b/Algorithm.Python/StochasticIndicatorWarmsUpProperlyRegressionAlgorithm.py
@@ -0,0 +1,77 @@
+# 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 datetime import timedelta
+from AlgorithmImports import *
+
+###
+### Regression algorithm that asserts Stochastic indicator, registered with a different resolution consolidator,
+### is warmed up properly by calling QCAlgorithm.WarmUpIndicator
+###
+class StochasticIndicatorWarmsUpProperlyRegressionAlgorithm(QCAlgorithm):
+ def initialize(self):
+ self.set_start_date(2020, 1, 1) # monday = holiday..
+ self.set_end_date(2020, 2, 1)
+ self.set_cash(100000)
+
+ self.data_points_received = False;
+ self.spy = self.add_equity("SPY", Resolution.HOUR).symbol
+
+ self.daily_consolidator = TradeBarConsolidator(timedelta(days=1))
+
+ self._rsi = RelativeStrengthIndex(14, MovingAverageType.WILDERS)
+ self._sto = Stochastic("FIRST", 14, 3, 3)
+ self.register_indicator(self.spy, self._rsi, self.daily_consolidator)
+ self.register_indicator(self.spy, self._sto, self.daily_consolidator)
+
+ # warm_up indicator
+ self.warm_up_indicator(self.spy, self._rsi, timedelta(days=1))
+ self.warm_up_indicator(self.spy, self._sto, timedelta(days=1))
+
+
+ self._rsi_history = RelativeStrengthIndex(14, MovingAverageType.WILDERS)
+ self._sto_history = Stochastic("SECOND", 14, 3, 3)
+ self.register_indicator(self.spy, self._rsi_history, self.daily_consolidator)
+ self.register_indicator(self.spy, self._sto_history, self.daily_consolidator)
+
+ # history warm up
+ history = self.history[TradeBar](self.spy, 15, Resolution.DAILY)
+ for bar in history:
+ self._rsi_history.update(bar.end_time, bar.close)
+ if self._rsi_history.samples == 1:
+ continue
+ self._sto_history.update(bar)
+
+ indicators = [self._rsi, self._sto, self._rsi_history, self._sto_history]
+ for indicator in indicators:
+ if not indicator.is_ready:
+ raise Exception(f"{indicator.name} should be ready, but it is not. Number of samples: {indicator.samples}")
+
+ def on_data(self, data: Slice):
+ if self.is_warming_up:
+ return
+
+ if data.contains_key(self.spy):
+ self.data_points_received = True
+ if self._rsi.current.value != self._rsi_history.current.value:
+ raise Exception(f"Values of indicators differ: {self._rsi.name}: {self._rsi.current.value} | {self._rsi_history.name}: {self._rsi_history.current.value}")
+
+ if self._sto.stoch_k.current.value != self._sto_history.stoch_k.current.value:
+ raise Exception(f"Stoch K values of indicators differ: {self._sto.name}.StochK: {self._sto.stoch_k.current.value} | {self._sto_history.name}.StochK: {self._sto_history.stoch_k.current.value}")
+
+ if self._sto.stoch_d.current.value != self._sto_history.stoch_d.current.value:
+ raise Exception(f"Stoch D values of indicators differ: {self._sto.name}.StochD: {self._sto.stoch_d.current.value} | {self._sto_history.name}.StochD: {self._sto_history.stoch_d.current.value}")
+
+ def on_end_of_algorithm(self):
+ if not self.data_points_received:
+ raise Exception("No data points received")
diff --git a/Algorithm/QCAlgorithm.Indicators.cs b/Algorithm/QCAlgorithm.Indicators.cs
index 8ba5d84b20e7..eb0b0cabcd10 100644
--- a/Algorithm/QCAlgorithm.Indicators.cs
+++ b/Algorithm/QCAlgorithm.Indicators.cs
@@ -3052,7 +3052,7 @@ public void WarmUpIndicator(IEnumerable symbols, IndicatorBase indicator, TimeSpan period, Func selector = null)
{
- var history = GetIndicatorWarmUpHistory(new[] { symbol }, indicator, period, out var identityConsolidator);
+ var history = GetIndicatorWarmUpHistory(symbol, indicator, period, out var identityConsolidator, out var historyRequest);
if (history == Enumerable.Empty()) return;
// assign default using cast
@@ -3064,7 +3064,7 @@ public void WarmUpIndicator(Symbol symbol, IndicatorBase ind
indicator.Update(input);
};
- WarmUpIndicatorImpl(symbol, period, onDataConsolidated, history, identityConsolidator);
+ WarmUpIndicatorImpl(symbol, period, onDataConsolidated, history, identityConsolidator, historyRequest);
}
///
@@ -3112,7 +3112,7 @@ public void WarmUpIndicator(IEnumerable symbols, IndicatorBase ind
public void WarmUpIndicator(Symbol symbol, IndicatorBase indicator, TimeSpan period, Func selector = null)
where T : class, IBaseData
{
- var history = GetIndicatorWarmUpHistory(new[] { symbol }, indicator, period, out var identityConsolidator);
+ var history = GetIndicatorWarmUpHistory(symbol, indicator, period, out var identityConsolidator, out var historyRequest);
if (history == Enumerable.Empty()) return;
// assign default using cast
@@ -3124,12 +3124,14 @@ public void WarmUpIndicator(Symbol symbol, IndicatorBase indicator, TimeSp
indicator.Update(selector(bar));
};
- WarmUpIndicatorImpl(symbol, period, onDataConsolidated, history, identityConsolidator);
+ WarmUpIndicatorImpl(symbol, period, onDataConsolidated, history, identityConsolidator, historyRequest);
}
- private IEnumerable GetIndicatorWarmUpHistory(IEnumerable symbols, IIndicator indicator, TimeSpan timeSpan, out bool identityConsolidator)
+ private IEnumerable GetIndicatorWarmUpHistory(Symbol symbol, IIndicator indicator, TimeSpan timeSpan, out bool identityConsolidator, out HistoryRequest historyRequest)
{
identityConsolidator = false;
+ historyRequest = null;
+
if (!AssertIndicatorHasWarmupPeriod(indicator))
{
return Enumerable.Empty();
@@ -3149,7 +3151,10 @@ private IEnumerable GetIndicatorWarmUpHistory(IEnumerable symbols
try
{
- return History(symbols, periods, resolution, dataNormalizationMode: GetIndicatorHistoryDataNormalizationMode(indicator));
+ var symbols = new[] { symbol };
+ CheckPeriodBasedHistoryRequestResolution(symbols, resolution, null);
+ historyRequest = CreateBarCountHistoryRequests(symbols, periods, resolution, dataNormalizationMode: GetIndicatorHistoryDataNormalizationMode(indicator)).Single();
+ return GetSlicesFromHistoryRequests(historyRequest);
}
catch (ArgumentException e)
{
@@ -3159,6 +3164,11 @@ private IEnumerable GetIndicatorWarmUpHistory(IEnumerable symbols
return Enumerable.Empty();
}
+ private IEnumerable GetSlicesFromHistoryRequests(HistoryRequest historyRequest)
+ {
+ return History(historyRequest).Memoize();
+ }
+
private bool AssertIndicatorHasWarmupPeriod(IIndicator indicator)
{
if (indicator is not IIndicatorWarmUpPeriodProvider)
@@ -3175,7 +3185,7 @@ private bool AssertIndicatorHasWarmupPeriod(IIndicator indicator)
return true;
}
- private void WarmUpIndicatorImpl(Symbol symbol, TimeSpan period, Action handler, IEnumerable history, bool identityConsolidator)
+ private void WarmUpIndicatorImpl(Symbol symbol, TimeSpan period, Action handler, IEnumerable history, bool identityConsolidator, HistoryRequest historyRequest)
where T : class, IBaseData
{
IDataConsolidator consolidator;
@@ -3227,7 +3237,7 @@ private void WarmUpIndicatorImpl(Symbol symbol, TimeSpan period, Action ha
// Scan for time after we've pumped all the data through for this consolidator
if (lastBar != null)
{
- consolidator.Scan(lastBar.EndTime);
+ consolidator.Scan(historyRequest.EndTimeLocal);
}
SubscriptionManager.RemoveConsolidator(symbol, consolidator);