diff --git a/Algorithm.CSharp/ConsolidatorRollingWindowRegressionAlgorithm.cs b/Algorithm.CSharp/ConsolidatorRollingWindowRegressionAlgorithm.cs
new file mode 100644
index 000000000000..103c41a2718e
--- /dev/null
+++ b/Algorithm.CSharp/ConsolidatorRollingWindowRegressionAlgorithm.cs
@@ -0,0 +1,162 @@
+/*
+ * 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.Collections.Generic;
+using QuantConnect.Data;
+using QuantConnect.Data.Consolidators;
+using QuantConnect.Data.Market;
+using QuantConnect.Interfaces;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Regression algorithm asserting that consolidators expose a built-in rolling window
+ ///
+ public class ConsolidatorRollingWindowRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
+ {
+ private TradeBarConsolidator _consolidator;
+ private int _consolidationCount;
+
+ ///
+ /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
+ ///
+ public override void Initialize()
+ {
+ SetStartDate(2013, 10, 07);
+ SetEndDate(2013, 10, 11);
+
+ AddEquity("SPY", Resolution.Minute);
+
+ _consolidator = new TradeBarConsolidator(TimeSpan.FromMinutes(10));
+ _consolidator.DataConsolidated += OnDataConsolidated;
+ SubscriptionManager.AddConsolidator("SPY", _consolidator);
+ }
+
+ private void OnDataConsolidated(object sender, TradeBar bar)
+ {
+ _consolidationCount++;
+
+ if (_consolidator.Current != _consolidator[0])
+ {
+ throw new RegressionTestException("Expected Current to be the same as Window[0]");
+ }
+
+ // Window[0] must always be the bar just consolidated
+ var currentBar = (TradeBar)_consolidator[0];
+ if (currentBar.Time != bar.Time)
+ {
+ throw new RegressionTestException($"Expected consolidator[0].Time == {bar.Time} but was {currentBar.Time}");
+ }
+ if (currentBar.Close != bar.Close)
+ {
+ throw new RegressionTestException($"Expected consolidator[0].Close == {bar.Close} but was {currentBar.Close}");
+ }
+
+ // After the second consolidation the previous bar must be accessible at index 1
+ if (_consolidator.Window.Count >= 2)
+ {
+ var previous = (TradeBar)_consolidator[1];
+ if (_consolidator.Previous != _consolidator[1])
+ {
+ throw new RegressionTestException("Expected Previous to be the same as Window[1]");
+ }
+ if (previous.Time >= bar.Time)
+ {
+ throw new RegressionTestException($"consolidator[1].Time ({previous.Time}) should be earlier than consolidator[0].Time ({bar.Time})");
+ }
+ if (previous.Close <= 0)
+ {
+ throw new RegressionTestException("consolidator[1].Close should be greater than zero");
+ }
+ }
+ }
+
+ public override void OnEndOfAlgorithm()
+ {
+ if (_consolidationCount == 0)
+ {
+ throw new RegressionTestException("Expected at least one consolidation but got zero");
+ }
+
+ // Default window size is 2, it must be full
+ if (_consolidator.Window.Count != 2)
+ {
+ throw new RegressionTestException(
+ $"Expected window count of 2 but was {_consolidator.Window.Count}");
+ }
+ }
+
+ ///
+ /// 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 => 3943;
+
+ ///
+ /// Data Points count of the algorithm history
+ ///
+ public int AlgorithmHistoryDataPoints => 0;
+
+ ///
+ /// 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", "-8.91"},
+ {"Tracking Error", "0.223"},
+ {"Treynor Ratio", "0"},
+ {"Total Fees", "$0.00"},
+ {"Estimated Strategy Capacity", "$0"},
+ {"Lowest Capacity Asset", ""},
+ {"Portfolio Turnover", "0%"},
+ {"Drawdown Recovery", "0"},
+ {"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"}
+ };
+ }
+}
diff --git a/Algorithm.Python/ConsolidatorRollingWindowRegressionAlgorithm.py b/Algorithm.Python/ConsolidatorRollingWindowRegressionAlgorithm.py
new file mode 100644
index 000000000000..1b4b4de69d1e
--- /dev/null
+++ b/Algorithm.Python/ConsolidatorRollingWindowRegressionAlgorithm.py
@@ -0,0 +1,67 @@
+# 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 that consolidators expose a built-in rolling window
+###
+class ConsolidatorRollingWindowRegressionAlgorithm(QCAlgorithm):
+
+ def initialize(self):
+ self.set_start_date(2013, 10, 7)
+ self.set_end_date(2013, 10, 11)
+
+ self.add_equity("SPY", Resolution.MINUTE)
+
+ self._consolidation_count = 0
+ self._consolidator = TradeBarConsolidator(timedelta(minutes=10))
+ self._consolidator.data_consolidated += self._on_data_consolidated
+ self.subscription_manager.add_consolidator("SPY", self._consolidator)
+
+ def _on_data_consolidated(self, sender, bar):
+ self._consolidation_count += 1
+
+ if self._consolidator.current != self._consolidator[0]:
+ raise AssertionError("Expected current to be the same as window[0]")
+
+ # consolidator[0] must always match the bar just fired
+ currentBar = self._consolidator[0]
+ if currentBar.time != bar.time:
+ raise AssertionError(f"Expected consolidator[0].time == {bar.time} but was {currentBar.time}")
+ if currentBar.value != bar.close:
+ raise AssertionError(f"Expected consolidator[0].value == {bar.close} but was {currentBar.value}")
+
+ # After the second consolidation the previous bar must be at index 1
+ if self._consolidator.window.count >= 2:
+ previous = self._consolidator[1]
+ if self._consolidator.previous != self._consolidator[1]:
+ raise AssertionError("Expected previous to be the same as window[1]")
+ if previous.time >= bar.time:
+ raise AssertionError(
+ f"consolidator[1].time ({previous.time}) should be earlier "
+ f"than consolidator[0].time ({bar.time})"
+ )
+ if previous.value <= 0:
+ raise AssertionError("consolidator[1].value should be greater than zero")
+
+ def on_data(self, data):
+ pass
+
+ def on_end_of_algorithm(self):
+ if self._consolidation_count == 0:
+ raise AssertionError("Expected at least one consolidation but got zero")
+
+ # Default window size is 2, it must be full
+ if self._consolidator.window.count != 2:
+ raise AssertionError(f"Expected window count of 2 but was {self._consolidator.window.count}")
diff --git a/Common/Data/Consolidators/BaseTimelessConsolidator.cs b/Common/Data/Consolidators/BaseTimelessConsolidator.cs
index 5fd297bdc09f..7498d0aed3e3 100644
--- a/Common/Data/Consolidators/BaseTimelessConsolidator.cs
+++ b/Common/Data/Consolidators/BaseTimelessConsolidator.cs
@@ -23,7 +23,7 @@ namespace QuantConnect.Data.Consolidators
/// Represents a timeless consolidator which depends on the given values. This consolidator
/// is meant to consolidate data into bars that do not depend on time, e.g., RangeBar's.
///
- public abstract class BaseTimelessConsolidator : IDataConsolidator
+ public abstract class BaseTimelessConsolidator : ConsolidatorBase, IDataConsolidator
where T : IBaseData
{
///
@@ -47,12 +47,6 @@ public abstract class BaseTimelessConsolidator : IDataConsolidator
///
protected virtual T CurrentBar { get; set; }
- ///
- /// Gets the most recently consolidated piece of data. This will be null if this consolidator
- /// has not produced any data yet.
- ///
- public IBaseData Consolidated { get; protected set; }
-
///
/// Gets a clone of the data being currently consolidated
///
@@ -188,7 +182,7 @@ protected void OnDataConsolidated(T consolidated)
DataConsolidatedHandler?.Invoke(this, consolidated);
- Consolidated = consolidated;
+ UpdateConsolidated(consolidated);
}
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
@@ -202,10 +196,10 @@ public virtual void Dispose()
///
/// Resets the consolidator
///
- public virtual void Reset()
+ public override void Reset()
{
- Consolidated = null;
CurrentBar = default(T);
+ base.Reset();
}
///
diff --git a/Common/Data/Consolidators/ConsolidatorBase.cs b/Common/Data/Consolidators/ConsolidatorBase.cs
new file mode 100644
index 000000000000..1a9470524e8b
--- /dev/null
+++ b/Common/Data/Consolidators/ConsolidatorBase.cs
@@ -0,0 +1,48 @@
+/*
+ * 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.
+*/
+
+namespace QuantConnect.Data.Consolidators
+{
+ ///
+ /// Provides a base implementation for consolidators, including a built-in rolling window
+ /// that stores the history of consolidated bars.
+ ///
+ public abstract class ConsolidatorBase : WindowBase
+ {
+ ///
+ /// Gets the most recently consolidated piece of data. This will be null if this consolidator
+ /// has not produced any data yet.
+ ///
+ public IBaseData Consolidated { get; protected set; }
+
+ ///
+ /// Updates and adds the bar to the rolling window.
+ ///
+ protected void UpdateConsolidated(IBaseData consolidated)
+ {
+ Consolidated = consolidated;
+ Window.Add(consolidated);
+ }
+
+ ///
+ /// Resets this consolidator, clearing consolidated data and the rolling window.
+ ///
+ public virtual void Reset()
+ {
+ Consolidated = null;
+ ResetWindow();
+ }
+ }
+}
diff --git a/Common/Data/Consolidators/DataConsolidator.cs b/Common/Data/Consolidators/DataConsolidator.cs
index 8f5f57e46169..1146449fefd5 100644
--- a/Common/Data/Consolidators/DataConsolidator.cs
+++ b/Common/Data/Consolidators/DataConsolidator.cs
@@ -23,7 +23,7 @@ namespace QuantConnect.Data.Consolidators
/// and/or aggregated data.
///
/// The type consumed by the consolidator
- public abstract class DataConsolidator : IDataConsolidator
+ public abstract class DataConsolidator : ConsolidatorBase, IDataConsolidator
where TInput : IBaseData
{
///
@@ -52,15 +52,6 @@ public void Update(IBaseData data)
///
public event DataConsolidatedHandler DataConsolidated;
- ///
- /// Gets the most recently consolidated piece of data. This will be null if this consolidator
- /// has not produced any data yet.
- ///
- public IBaseData Consolidated
- {
- get; protected set;
- }
-
///
/// Gets a clone of the data being currently consolidated
///
@@ -74,7 +65,7 @@ public abstract IBaseData WorkingData
///
public Type InputType
{
- get { return typeof (TInput); }
+ get { return typeof(TInput); }
}
///
@@ -102,18 +93,9 @@ protected virtual void OnDataConsolidated(IBaseData consolidated)
var handler = DataConsolidated;
if (handler != null) handler(this, consolidated);
- // assign the Consolidated property after the event handlers are fired,
- // this allows the event handlers to look at the new consolidated data
- // and the previous consolidated data at the same time without extra bookkeeping
- Consolidated = consolidated;
- }
-
- ///
- /// Resets the consolidator
- ///
- public virtual void Reset()
- {
- Consolidated = null;
+ // assign Consolidated and push to Window after the event handlers fire,
+ // so handlers can compare the new bar against the previous one without extra bookkeeping
+ UpdateConsolidated(consolidated);
}
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
diff --git a/Common/Data/Consolidators/MarketHourAwareConsolidator.cs b/Common/Data/Consolidators/MarketHourAwareConsolidator.cs
index 27676b2aeb4a..3aaad931b889 100644
--- a/Common/Data/Consolidators/MarketHourAwareConsolidator.cs
+++ b/Common/Data/Consolidators/MarketHourAwareConsolidator.cs
@@ -25,7 +25,7 @@ namespace QuantConnect.Data.Common
///
/// Consolidator for open markets bar only, extended hours bar are not consolidated.
///
- public class MarketHourAwareConsolidator : IDataConsolidator
+ public class MarketHourAwareConsolidator : ConsolidatorBase, IDataConsolidator
{
private readonly bool _dailyStrictEndTimeEnabled;
private readonly bool _extendedMarketHours;
@@ -51,12 +51,6 @@ public class MarketHourAwareConsolidator : IDataConsolidator
///
protected DateTimeZone DataTimeZone { get; set; }
- ///
- /// Gets the most recently consolidated piece of data. This will be null if this consolidator
- /// has not produced any data yet.
- ///
- public IBaseData Consolidated => Consolidator.Consolidated;
-
///
/// Gets the type consumed by this consolidator
///
@@ -164,12 +158,13 @@ public void Dispose()
///
/// Resets the consolidator
///
- public void Reset()
+ public override void Reset()
{
_useStrictEndTime = false;
ExchangeHours = null;
DataTimeZone = null;
Consolidator.Reset();
+ base.Reset();
}
///
@@ -214,6 +209,7 @@ protected virtual bool UseStrictEndTime(Symbol symbol)
protected virtual void ForwardConsolidatedBar(object sender, IBaseData consolidated)
{
DataConsolidated?.Invoke(this, consolidated);
+ UpdateConsolidated(consolidated);
}
}
}
diff --git a/Common/Data/Consolidators/RenkoConsolidator.cs b/Common/Data/Consolidators/RenkoConsolidator.cs
index b2ddf9d8fba9..652290804632 100644
--- a/Common/Data/Consolidators/RenkoConsolidator.cs
+++ b/Common/Data/Consolidators/RenkoConsolidator.cs
@@ -24,13 +24,12 @@ namespace QuantConnect.Data.Consolidators
///
/// This implementation replaced the original implementation that was shown to have inaccuracies in its representation
/// of Renko charts. The original implementation has been moved to .
- public class RenkoConsolidator : IDataConsolidator
+ public class RenkoConsolidator : ConsolidatorBase, IDataConsolidator
{
private bool _firstTick = true;
private RenkoBar _lastWicko;
private DataConsolidatedHandler _dataConsolidatedHandler;
private RenkoBar _currentBar;
- private IBaseData _consolidated;
///
/// Time of consolidated close.
@@ -94,16 +93,6 @@ public class RenkoConsolidator : IDataConsolidator
///
public Type OutputType => typeof(RenkoBar);
- ///
- /// Gets the most recently consolidated piece of data. This will be null if this consolidator
- /// has not produced any data yet.
- ///
- public IBaseData Consolidated
- {
- get { return _consolidated; }
- private set { _consolidated = value; }
- }
-
///
/// Event handler that fires when a new piece of data is produced
///
@@ -244,18 +233,18 @@ public void Dispose()
///
/// Resets the consolidator
///
- public void Reset()
+ public override void Reset()
{
_firstTick = true;
_lastWicko = null;
_currentBar = null;
- _consolidated = null;
CloseOn = default;
CloseRate = default;
HighRate = default;
LowRate = default;
OpenOn = default;
OpenRate = default;
+ base.Reset();
}
///
@@ -268,7 +257,7 @@ protected void OnDataConsolidated(RenkoBar consolidated)
DataConsolidated?.Invoke(this, consolidated);
_currentBar = consolidated;
_dataConsolidatedHandler?.Invoke(this, consolidated);
- Consolidated = consolidated;
+ UpdateConsolidated(consolidated);
}
private void Rising(IBaseData data)
diff --git a/Common/Data/Consolidators/SequentialConsolidator.cs b/Common/Data/Consolidators/SequentialConsolidator.cs
index 6ce0fccd9e49..a9643549257e 100644
--- a/Common/Data/Consolidators/SequentialConsolidator.cs
+++ b/Common/Data/Consolidators/SequentialConsolidator.cs
@@ -22,7 +22,7 @@ namespace QuantConnect.Data.Consolidators
/// such that data flows from the First to Second consolidator. It's output comes
/// from the Second.
///
- public class SequentialConsolidator : IDataConsolidator
+ public class SequentialConsolidator : ConsolidatorBase, IDataConsolidator
{
///
/// Gets the first consolidator to receive data
@@ -41,17 +41,6 @@ public IDataConsolidator Second
get; private set;
}
- ///
- /// Gets the most recently consolidated piece of data. This will be null if this consolidator
- /// has not produced any data yet.
- ///
- /// For a SequentialConsolidator, this is the output from the 'Second' consolidator.
- ///
- public IBaseData Consolidated
- {
- get { return Second.Consolidated; }
- }
-
///
/// Gets a clone of the data being currently consolidated
///
@@ -131,6 +120,7 @@ protected virtual void OnDataConsolidated(IBaseData consolidated)
{
var handler = DataConsolidated;
if (handler != null) handler(this, consolidated);
+ UpdateConsolidated(consolidated);
}
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
@@ -145,10 +135,11 @@ public void Dispose()
///
/// Resets the consolidator
///
- public void Reset()
+ public override void Reset()
{
First.Reset();
Second.Reset();
+ base.Reset();
}
}
}
diff --git a/Common/Python/DataConsolidatorPythonWrapper.cs b/Common/Python/DataConsolidatorPythonWrapper.cs
index c2264726e217..94d302cf0126 100644
--- a/Common/Python/DataConsolidatorPythonWrapper.cs
+++ b/Common/Python/DataConsolidatorPythonWrapper.cs
@@ -14,19 +14,42 @@
*/
using System;
+using System.Collections;
+using System.Collections.Generic;
using Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
+using QuantConnect.Indicators;
namespace QuantConnect.Python
{
///
/// Provides an Data Consolidator that wraps a object that represents a custom Python consolidator
///
- public class DataConsolidatorPythonWrapper : BasePythonWrapper, IDataConsolidator
+ public class DataConsolidatorPythonWrapper : BasePythonWrapper, IDataConsolidator, IEnumerable
{
internal PyObject Model => Instance;
+ ///
+ /// A rolling window keeping a history of the consolidated bars. The most recent bar is at index 0.
+ ///
+ public RollingWindow Window { get; } = new RollingWindow(WindowBase.DefaultWindowSize);
+
+ ///
+ /// Indexes the history window, where index 0 is the most recently consolidated bar.
+ ///
+ public IBaseData this[int i] => Window[i];
+
+ ///
+ /// Returns an enumerator that iterates through the history window.
+ ///
+ public IEnumerator GetEnumerator() => Window.GetEnumerator();
+
+ ///
+ /// Returns an enumerator that iterates through the history window.
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
///
/// Gets the most recently consolidated piece of data. This will be null if this consolidator
/// has not produced any data yet.
@@ -36,6 +59,16 @@ public IBaseData Consolidated
get { return GetProperty(nameof(Consolidated)); }
}
+ ///
+ /// Gets the most recently consolidated piece of data. Alias of .
+ ///
+ public IBaseData Current => Consolidated;
+
+ ///
+ /// Gets the previously consolidated piece of data, or null if fewer than two bars have been produced.
+ ///
+ public IBaseData Previous => Window.Count > 1 ? Window[1] : null;
+
///
/// Gets a clone of the data being currently consolidated
///
@@ -84,6 +117,7 @@ public event DataConsolidatedHandler DataConsolidated
public DataConsolidatorPythonWrapper(PyObject consolidator)
: base(consolidator, true)
{
+ DataConsolidated += (_, bar) => Window.Add(bar);
}
///
@@ -116,6 +150,7 @@ public void Dispose()
public void Reset()
{
InvokeMethod(nameof(Reset));
+ Window.Reset();
}
}
}
diff --git a/Common/WindowBase.cs b/Common/WindowBase.cs
new file mode 100644
index 000000000000..ff363ece613b
--- /dev/null
+++ b/Common/WindowBase.cs
@@ -0,0 +1,88 @@
+/*
+ * 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;
+using System.Collections.Generic;
+using QuantConnect.Indicators;
+
+namespace QuantConnect
+{
+ ///
+ /// Provides a base class for types that maintain a rolling window history of values.
+ /// This is the single source of truth for window logic shared between indicators and consolidators.
+ ///
+ /// The type of value stored in the rolling window
+ public abstract class WindowBase : IEnumerable
+ {
+ private RollingWindow _window;
+
+ ///
+ /// The default number of values to keep in the rolling window history
+ ///
+ public static int DefaultWindowSize { get; } = 2;
+
+ ///
+ /// A rolling window keeping a history of values. The most recent value is at index 0.
+ /// Uses lazy initialization to survive Python subclasses that do not call base constructors.
+ ///
+ public RollingWindow Window => _window ??= new RollingWindow(DefaultWindowSize);
+
+ ///
+ /// Gets the most recent value. The protected setter adds the value to the rolling window.
+ ///
+ public virtual T Current
+ {
+ get
+ {
+ return Window[0];
+ }
+ protected set
+ {
+ Window.Add(value);
+ }
+ }
+
+ ///
+ /// Gets the previous value, or default if fewer than two values have been produced.
+ ///
+ public virtual T Previous => Window.Count > 1 ? Window[1] : default;
+
+ ///
+ /// Indexes the history window, where index 0 is the most recent value.
+ ///
+ /// The index
+ /// The ith most recent value
+ public T this[int i] => Window[i];
+
+ ///
+ /// Returns an enumerator that iterates through the history window.
+ ///
+ public IEnumerator GetEnumerator() => Window.GetEnumerator();
+
+ ///
+ /// Returns an enumerator that iterates through the history window.
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ /// Resets the rolling window, clearing all stored values without allocating a new window
+ /// if it has not yet been created.
+ ///
+ protected void ResetWindow()
+ {
+ _window?.Reset();
+ }
+ }
+}
diff --git a/Indicators/IndicatorBase.cs b/Indicators/IndicatorBase.cs
index d601d4228873..634b23feb839 100644
--- a/Indicators/IndicatorBase.cs
+++ b/Indicators/IndicatorBase.cs
@@ -19,14 +19,13 @@
using QuantConnect.Logging;
using System.Collections.Generic;
using QuantConnect.Data.Consolidators;
-using System.Collections;
namespace QuantConnect.Indicators
{
///
/// Abstract Indicator base, meant to contain non-generic fields of indicator base to support non-typed inputs
///
- public abstract partial class IndicatorBase : IIndicator, IEnumerable
+ public abstract partial class IndicatorBase : WindowBase, IIndicator
{
///
/// The data consolidators associated with this indicator if any
@@ -35,27 +34,11 @@ public abstract partial class IndicatorBase : IIndicator, IEnumerable
public ISet Consolidators { get; } = new HashSet();
- ///
- /// Gets the current state of this indicator. If the state has not been updated
- /// then the time on the value will equal DateTime.MinValue.
- ///
- public IndicatorDataPoint Current
- {
- get
- {
- return Window[0];
- }
- protected set
- {
- Window.Add(value);
- }
- }
-
///
/// Gets the previous state of this indicator. If the state has not been updated
/// then the time on the value will equal DateTime.MinValue.
///
- public IndicatorDataPoint Previous
+ public override IndicatorDataPoint Previous
{
get
{
@@ -83,11 +66,6 @@ public IndicatorDataPoint Previous
///
public event IndicatorUpdatedHandler Updated;
- ///
- /// A rolling window keeping a history of the indicator values of a given period
- ///
- public RollingWindow Window { get; }
-
///
/// Resets this indicator to its initial state
///
@@ -98,7 +76,6 @@ public IndicatorDataPoint Previous
///
protected IndicatorBase()
{
- Window = new RollingWindow(Indicator.DefaultWindowSize);
Current = new IndicatorDataPoint(DateTime.MinValue, 0m);
}
@@ -129,45 +106,6 @@ protected virtual void OnUpdated(IndicatorDataPoint consolidated)
/// True if this indicator is ready, false otherwise
public abstract bool Update(IBaseData input);
- ///
- /// Indexes the history windows, where index 0 is the most recent indicator value.
- /// If index is greater or equal than the current count, it returns null.
- /// If the index is greater or equal than the window size, it returns null and resizes the windows to i + 1.
- ///
- /// The index
- /// the ith most recent indicator value
- public IndicatorDataPoint this[int i]
- {
- get
- {
- return Window[i];
- }
- }
-
- ///
- /// Returns an enumerator that iterates through the history window.
- ///
- ///
- /// A that can be used to iterate through the history window.
- ///
- /// 1
- public IEnumerator GetEnumerator()
- {
- return Window.GetEnumerator();
- }
-
- ///
- /// Returns an enumerator that iterates through the history window.
- ///
- ///
- /// An object that can be used to iterate through the history window.
- ///
- /// 2
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
///
/// ToString Overload for Indicator Base
///
diff --git a/Tests/Common/Data/ConsolidatorBaseTests.cs b/Tests/Common/Data/ConsolidatorBaseTests.cs
new file mode 100644
index 000000000000..627596bc3018
--- /dev/null
+++ b/Tests/Common/Data/ConsolidatorBaseTests.cs
@@ -0,0 +1,154 @@
+/*
+ * 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.Collections.Generic;
+using NUnit.Framework;
+using QuantConnect.Data;
+using QuantConnect.Data.Consolidators;
+using QuantConnect.Data.Market;
+using QuantConnect.Indicators;
+
+namespace QuantConnect.Tests.Common.Data
+{
+ [TestFixture]
+ public class ConsolidatorBaseTests
+ {
+ [TestCaseSource(nameof(WindowTestCases))]
+ public void WindowStoresConsolidatedBars(IDataConsolidator consolidator, IBaseData[] bars, decimal expectedWindow0, decimal expectedWindow1)
+ {
+ var windowConsolidator = (ConsolidatorBase)consolidator;
+
+ foreach (var bar in bars)
+ {
+ consolidator.Update(bar);
+ }
+
+ Assert.AreEqual(2, windowConsolidator.Window.Count);
+ Assert.AreEqual(expectedWindow0, windowConsolidator.Window[0].Value);
+ Assert.AreEqual(expectedWindow1, windowConsolidator.Window[1].Value);
+
+ consolidator.Dispose();
+ }
+
+ private static IEnumerable WindowTestCases()
+ {
+ var reference = new DateTime(2015, 4, 13);
+ var spy = Symbols.SPY;
+ var ibm = Symbols.IBM;
+
+ yield return new TestCaseData(
+ new TradeBarConsolidator(1),
+ new IBaseData[]
+ {
+ new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute },
+ new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute }
+ },
+ 20m, 10m
+ ).SetName("TradeBarConsolidator");
+
+ yield return new TestCaseData(
+ new QuoteBarConsolidator(1),
+ new IBaseData[]
+ {
+ new QuoteBar { Symbol = spy, Time = reference, Value = 10m, Period = Time.OneMinute },
+ new QuoteBar { Symbol = spy, Time = reference.AddMinutes(1), Value = 20m, Period = Time.OneMinute }
+ },
+ 20m, 10m
+ ).SetName("QuoteBarConsolidator");
+
+ yield return new TestCaseData(
+ new TickConsolidator(1),
+ new IBaseData[]
+ {
+ new Tick { Symbol = spy, Time = reference, Value = 10m, TickType = TickType.Trade },
+ new Tick { Symbol = spy, Time = reference.AddMinutes(1), Value = 20m, TickType = TickType.Trade }
+ },
+ 20m, 10m
+ ).SetName("TickConsolidator");
+
+ yield return new TestCaseData(
+ new TickQuoteBarConsolidator(1),
+ new IBaseData[]
+ {
+ new Tick { Symbol = spy, Time = reference, Value = 10m, TickType = TickType.Quote, BidPrice = 10m, AskPrice = 10m },
+ new Tick { Symbol = spy, Time = reference.AddMinutes(1), Value = 20m, TickType = TickType.Quote, BidPrice = 20m, AskPrice = 20m }
+ },
+ 20m, 10m
+ ).SetName("TickQuoteBarConsolidator");
+
+ yield return new TestCaseData(
+ new BaseDataConsolidator(1),
+ new IBaseData[]
+ {
+ new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute },
+ new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute }
+ },
+ 20m, 10m
+ ).SetName("BaseDataConsolidator");
+
+ yield return new TestCaseData(
+ new IdentityDataConsolidator(),
+ new IBaseData[]
+ {
+ new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute },
+ new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute }
+ },
+ 20m, 10m
+ ).SetName("IdentityDataConsolidator");
+
+ yield return new TestCaseData(
+ new ClassicRenkoConsolidator(10),
+ new IBaseData[]
+ {
+ new IndicatorDataPoint(spy, reference, 0m),
+ new IndicatorDataPoint(spy, reference.AddMinutes(1), 10m),
+ new IndicatorDataPoint(spy, reference.AddMinutes(2), 20m)
+ },
+ 20m, 10m
+ ).SetName("ClassicRenkoConsolidator");
+
+ yield return new TestCaseData(
+ new RenkoConsolidator(1m),
+ new IBaseData[]
+ {
+ new IndicatorDataPoint(spy, reference, 10m),
+ new IndicatorDataPoint(spy, reference.AddMinutes(1), 12.1m)
+ },
+ 12m, 11m
+ ).SetName("RenkoConsolidator");
+
+ yield return new TestCaseData(
+ new RangeConsolidator(100, x => x.Value, x => 0m),
+ new IBaseData[]
+ {
+ new IndicatorDataPoint(ibm, reference, 90m),
+ new IndicatorDataPoint(ibm, reference.AddMinutes(1), 94.5m)
+ },
+ 94.03m, 93.02m
+ ).SetName("RangeConsolidator");
+
+ yield return new TestCaseData(
+ new SequentialConsolidator(new TradeBarConsolidator(1), new TradeBarConsolidator(1)),
+ new IBaseData[]
+ {
+ new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute },
+ new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute }
+ },
+ 20m, 10m
+ ).SetName("SequentialConsolidator");
+ }
+ }
+}
diff --git a/Tests/Common/Data/MarketHourAwareConsolidatorTests.cs b/Tests/Common/Data/MarketHourAwareConsolidatorTests.cs
index 3ad59c56396b..030c1cdb0434 100644
--- a/Tests/Common/Data/MarketHourAwareConsolidatorTests.cs
+++ b/Tests/Common/Data/MarketHourAwareConsolidatorTests.cs
@@ -294,6 +294,25 @@ public void WorksWithDailyResolutionAndPreciseEndTimeFalse()
Assert.AreEqual(100, consolidatedData.High);
}
+ [Test]
+ public void WindowIsPopulatedOnConsolidation()
+ {
+ var symbol = Symbols.SPY;
+ using var consolidator = new MarketHourAwareConsolidator(false, Resolution.Daily, typeof(TradeBar), TickType.Trade, false);
+
+ consolidator.Update(new TradeBar() { Time = new DateTime(2015, 04, 13, 12, 0, 0), Period = Time.OneMinute, Symbol = symbol, Close = 100 });
+ consolidator.Scan(new DateTime(2015, 04, 14, 0, 0, 0));
+
+ Assert.AreEqual(1, consolidator.Window.Count);
+
+ consolidator.Update(new TradeBar() { Time = new DateTime(2015, 04, 14, 12, 0, 0), Period = Time.OneMinute, Symbol = symbol, Close = 200 });
+ consolidator.Scan(new DateTime(2015, 04, 15, 0, 0, 0));
+
+ Assert.AreEqual(2, consolidator.Window.Count);
+ Assert.AreEqual(200, ((TradeBar)consolidator.Window[0]).Close);
+ Assert.AreEqual(100, ((TradeBar)consolidator.Window[1]).Close);
+ }
+
protected override IDataConsolidator CreateConsolidator()
{
return new MarketHourAwareConsolidator(true, Resolution.Hour, typeof(TradeBar), TickType.Trade, false);