diff --git a/Algorithm.CSharp/ConsolidatorRollingWindowRegressionAlgorithm.cs b/Algorithm.CSharp/ConsolidatorRollingWindowRegressionAlgorithm.cs
new file mode 100644
index 000000000000..3359db0d41e8
--- /dev/null
+++ b/Algorithm.CSharp/ConsolidatorRollingWindowRegressionAlgorithm.cs
@@ -0,0 +1,153 @@
+/*
+ * 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++;
+
+ // 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 (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..087321235b8e
--- /dev/null
+++ b/Algorithm.Python/ConsolidatorRollingWindowRegressionAlgorithm.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 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
+
+ # 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 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..2a219eee4a36
--- /dev/null
+++ b/Common/Data/Consolidators/ConsolidatorBase.cs
@@ -0,0 +1,91 @@
+/*
+ * 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.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 : IEnumerable
+ {
+ ///
+ /// The default number of consolidated bars to keep in the rolling window history
+ ///
+ public static int DefaultWindowSize { get; } = 2;
+
+ private RollingWindow _window;
+
+ ///
+ /// A rolling window keeping a history of the consolidated bars. The most recent bar is at index 0.
+ ///
+ public RollingWindow Window
+ {
+ get
+ {
+ if (_window == null)
+ {
+ _window = new RollingWindow(DefaultWindowSize);
+ }
+ return _window;
+ }
+ }
+
+ ///
+ /// 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; }
+
+ ///
+ /// Indexes the history window, where index 0 is the most recently consolidated bar.
+ ///
+ /// The index
+ /// The ith 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();
+
+ ///
+ /// 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;
+ _window?.Reset();
+ }
+ }
+}
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..9ce6c9912381 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(ConsolidatorBase.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.
@@ -84,6 +107,7 @@ public event DataConsolidatedHandler DataConsolidated
public DataConsolidatorPythonWrapper(PyObject consolidator)
: base(consolidator, true)
{
+ DataConsolidated += (_, bar) => Window.Add(bar);
}
///
@@ -116,6 +140,7 @@ public void Dispose()
public void Reset()
{
InvokeMethod(nameof(Reset));
+ Window.Reset();
}
}
}