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..a68aeddd03a0 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
where T : IBaseData
{
///
@@ -37,51 +37,31 @@ public abstract class BaseTimelessConsolidator : IDataConsolidator
///
protected Func VolumeSelector { get; set; }
- ///
- /// Event handler type for the IDataConsolidator.DataConsolidated event
- ///
- protected DataConsolidatedHandler DataConsolidatedHandler { get; set; }
-
///
/// Bar being created
///
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
///
- public abstract IBaseData WorkingData { get; }
+ public abstract override IBaseData WorkingData { get; }
///
/// Gets the type consumed by this consolidator
///
- public Type InputType => typeof(IBaseData);
+ public override Type InputType => typeof(IBaseData);
///
/// Gets which is the type emitted in the event.
///
- public virtual Type OutputType => typeof(T);
+ public override Type OutputType => typeof(T);
///
- /// Event handler that fires when a new piece of data is produced
+ /// Typed event handler that fires when a new piece of data is produced
///
public event EventHandler DataConsolidated;
- ///
- /// Event handler that fires when a new piece of data is produced
- ///
- event DataConsolidatedHandler IDataConsolidator.DataConsolidated
- {
- add { DataConsolidatedHandler += value; }
- remove { DataConsolidatedHandler -= value; }
- }
-
///
/// Initializes a new instance of the class.
///
@@ -141,7 +121,7 @@ private static Func TryToConvertSelector(PyObject selector,
/// Updates this consolidator with the specified data
///
/// The new data for the consolidator
- public void Update(IBaseData data)
+ public override void Update(IBaseData data)
{
var currentValue = Selector(data);
var volume = VolumeSelector(data);
@@ -185,34 +165,31 @@ public void Update(IBaseData data)
protected void OnDataConsolidated(T consolidated)
{
DataConsolidated?.Invoke(this, consolidated);
-
- DataConsolidatedHandler?.Invoke(this, consolidated);
-
- Consolidated = consolidated;
+ base.OnDataConsolidated(consolidated);
}
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// 2
- public virtual void Dispose()
+ public override void Dispose()
{
DataConsolidated = null;
- DataConsolidatedHandler = null;
+ base.Dispose();
}
///
/// Resets the consolidator
///
- public virtual void Reset()
+ public override void Reset()
{
- Consolidated = null;
CurrentBar = default(T);
+ base.Reset();
}
///
/// Scans this consolidator to see if it should emit a bar due to time passing
///
/// The current time in the local time zone (same as )
- public void Scan(DateTime currentLocalTime)
+ public override void Scan(DateTime currentLocalTime)
{
}
}
diff --git a/Common/Data/Consolidators/ConsolidatorBase.cs b/Common/Data/Consolidators/ConsolidatorBase.cs
new file mode 100644
index 000000000000..208123c2d0f2
--- /dev/null
+++ b/Common/Data/Consolidators/ConsolidatorBase.cs
@@ -0,0 +1,105 @@
+/*
+ * 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;
+
+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, IDataConsolidator
+ {
+ private DataConsolidatedHandler _dataConsolidated;
+
+ ///
+ /// Gets the most recently consolidated piece of data. This will be null if this consolidator
+ /// has not produced any data yet. Setting this property adds the value to the rolling window.
+ ///
+ public IBaseData Consolidated
+ {
+ get
+ {
+ return Window.Count > 0 ? Window[0] : null;
+ }
+ protected set
+ {
+ Window.Add(value);
+ }
+ }
+
+ ///
+ /// Gets a clone of the data being currently consolidated
+ ///
+ public abstract IBaseData WorkingData { get; }
+
+ ///
+ /// Gets the type consumed by this consolidator
+ ///
+ public abstract Type InputType { get; }
+
+ ///
+ /// Gets the type produced by this consolidator
+ ///
+ public abstract Type OutputType { get; }
+
+ ///
+ /// Updates this consolidator with the specified data
+ ///
+ /// The new data for the consolidator
+ public abstract void Update(IBaseData data);
+
+ ///
+ /// Scans this consolidator to see if it should emit a bar due to time passing
+ ///
+ /// The current time in the local time zone (same as )
+ public abstract void Scan(DateTime currentLocalTime);
+
+ ///
+ /// Event handler that fires when a new piece of data is produced
+ ///
+ event DataConsolidatedHandler IDataConsolidator.DataConsolidated
+ {
+ add { _dataConsolidated += value; }
+ remove { _dataConsolidated -= value; }
+ }
+
+ ///
+ /// Event invocator for the DataConsolidated event. Fires the event and updates the rolling window.
+ ///
+ protected virtual void OnDataConsolidated(IBaseData consolidated)
+ {
+ _dataConsolidated?.Invoke(this, consolidated);
+ Consolidated = consolidated;
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public virtual void Dispose()
+ {
+ _dataConsolidated = null;
+ }
+
+ ///
+ /// Resets this consolidator, clearing consolidated data and the rolling window.
+ ///
+ public virtual void Reset()
+ {
+ ResetWindow();
+ }
+ }
+}
diff --git a/Common/Data/Consolidators/DataConsolidator.cs b/Common/Data/Consolidators/DataConsolidator.cs
index 8f5f57e46169..147e9df2b112 100644
--- a/Common/Data/Consolidators/DataConsolidator.cs
+++ b/Common/Data/Consolidators/DataConsolidator.cs
@@ -1,4 +1,4 @@
-/*
+/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -23,14 +23,14 @@ namespace QuantConnect.Data.Consolidators
/// and/or aggregated data.
///
/// The type consumed by the consolidator
- public abstract class DataConsolidator : IDataConsolidator
+ public abstract class DataConsolidator : ConsolidatorBase
where TInput : IBaseData
{
///
/// Updates this consolidator with the specified data
///
/// The new data for the consolidator
- public void Update(IBaseData data)
+ public override void Update(IBaseData data)
{
if (!(data is TInput))
{
@@ -45,45 +45,27 @@ public void Update(IBaseData data)
/// Scans this consolidator to see if it should emit a bar due to time passing
///
/// The current time in the local time zone (same as )
- public abstract void Scan(DateTime currentLocalTime);
+ public abstract override void Scan(DateTime currentLocalTime);
///
/// Event handler that fires when a new piece of data is produced
///
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
///
- public abstract IBaseData WorkingData
- {
- get;
- }
+ public abstract override IBaseData WorkingData { get; }
///
/// Gets the type consumed by this consolidator
///
- public Type InputType
- {
- get { return typeof (TInput); }
- }
+ public override Type InputType => typeof(TInput);
///
/// Gets the type produced by this consolidator
///
- public abstract Type OutputType
- {
- get;
- }
+ public abstract override Type OutputType { get; }
///
/// Updates this consolidator with the specified data. This method is
@@ -92,35 +74,20 @@ public abstract Type OutputType
/// The new data for the consolidator
public abstract void Update(TInput data);
- ///
- /// Event invocator for the DataConsolidated event. This should be invoked
- /// by derived classes when they have consolidated a new piece of data.
- ///
- /// The newly consolidated data
- protected virtual void OnDataConsolidated(IBaseData consolidated)
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// 2
+ public override void Dispose()
{
- 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;
+ DataConsolidated = null;
}
///
- /// Resets the consolidator
+ /// Event invocator for the DataConsolidated event. Fires the event and updates the rolling window.
///
- public virtual void Reset()
+ protected override void OnDataConsolidated(IBaseData consolidated)
{
- Consolidated = null;
- }
-
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// 2
- public void Dispose()
- {
- DataConsolidated = null;
+ DataConsolidated?.Invoke(this, consolidated);
+ base.OnDataConsolidated(consolidated);
}
}
}
\ No newline at end of file
diff --git a/Common/Data/Consolidators/MarketHourAwareConsolidator.cs b/Common/Data/Consolidators/MarketHourAwareConsolidator.cs
index 27676b2aeb4a..cfe6ae5108ea 100644
--- a/Common/Data/Consolidators/MarketHourAwareConsolidator.cs
+++ b/Common/Data/Consolidators/MarketHourAwareConsolidator.cs
@@ -15,6 +15,7 @@
using System;
using NodaTime;
+using QuantConnect.Indicators;
using QuantConnect.Util;
using QuantConnect.Securities;
using QuantConnect.Data.Market;
@@ -25,7 +26,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
{
private readonly bool _dailyStrictEndTimeEnabled;
private readonly bool _extendedMarketHours;
@@ -39,7 +40,7 @@ public class MarketHourAwareConsolidator : IDataConsolidator
///
/// The consolidator instance
///
- protected IDataConsolidator Consolidator { get; }
+ private ConsolidatorBase Consolidator { get; }
///
/// The associated security exchange hours instance
@@ -51,26 +52,20 @@ 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
///
- public Type InputType => Consolidator.InputType;
+ public override Type InputType => Consolidator.InputType;
///
/// Gets a clone of the data being currently consolidated
///
- public IBaseData WorkingData => Consolidator.WorkingData;
+ public override IBaseData WorkingData => Consolidator.WorkingData;
///
/// Gets the type produced by this consolidator
///
- public Type OutputType => Consolidator.OutputType;
+ public override Type OutputType => Consolidator.OutputType;
///
/// Initializes a new instance of the class.
@@ -116,7 +111,7 @@ public MarketHourAwareConsolidator(bool dailyStrictEndTimeEnabled, Resolution re
{
throw new ArgumentNullException(nameof(dataType), $"{dataType.Name} not supported");
}
- Consolidator.DataConsolidated += ForwardConsolidatedBar;
+ ((IDataConsolidator)Consolidator).DataConsolidated += ForwardConsolidatedBar;
}
///
@@ -128,7 +123,7 @@ public MarketHourAwareConsolidator(bool dailyStrictEndTimeEnabled, Resolution re
/// Updates this consolidator with the specified data
///
/// The new data for the consolidator
- public virtual void Update(IBaseData data)
+ public override void Update(IBaseData data)
{
Initialize(data);
@@ -147,7 +142,7 @@ public virtual void Update(IBaseData data)
/// Scans this consolidator to see if it should emit a bar due to time passing
///
/// The current time in the local time zone (same as )
- public void Scan(DateTime currentLocalTime)
+ public override void Scan(DateTime currentLocalTime)
{
Consolidator.Scan(currentLocalTime);
}
@@ -155,21 +150,23 @@ public void Scan(DateTime currentLocalTime)
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
- public void Dispose()
+ public override void Dispose()
{
- Consolidator.DataConsolidated -= ForwardConsolidatedBar;
+ ((IDataConsolidator)Consolidator).DataConsolidated -= ForwardConsolidatedBar;
Consolidator.Dispose();
+ base.Dispose();
}
///
/// Resets the consolidator
///
- public void Reset()
+ public override void Reset()
{
_useStrictEndTime = false;
ExchangeHours = null;
DataTimeZone = null;
Consolidator.Reset();
+ base.Reset();
}
///
@@ -214,6 +211,7 @@ protected virtual bool UseStrictEndTime(Symbol symbol)
protected virtual void ForwardConsolidatedBar(object sender, IBaseData consolidated)
{
DataConsolidated?.Invoke(this, consolidated);
+ base.OnDataConsolidated(consolidated);
}
}
}
diff --git a/Common/Data/Consolidators/RenkoConsolidator.cs b/Common/Data/Consolidators/RenkoConsolidator.cs
index b2ddf9d8fba9..b59042290e6a 100644
--- a/Common/Data/Consolidators/RenkoConsolidator.cs
+++ b/Common/Data/Consolidators/RenkoConsolidator.cs
@@ -24,13 +24,11 @@ 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
{
private bool _firstTick = true;
private RenkoBar _lastWicko;
- private DataConsolidatedHandler _dataConsolidatedHandler;
private RenkoBar _currentBar;
- private IBaseData _consolidated;
///
/// Time of consolidated close.
@@ -82,42 +80,23 @@ public class RenkoConsolidator : IDataConsolidator
///
/// Gets a clone of the data being currently consolidated
///
- public IBaseData WorkingData => _currentBar?.Clone();
+ public override IBaseData WorkingData => _currentBar?.Clone();
///
/// Gets the type consumed by this consolidator
///
- public Type InputType => typeof(IBaseData);
+ public override Type InputType => typeof(IBaseData);
///
/// Gets which is the type emitted in the event.
///
- public Type OutputType => typeof(RenkoBar);
+ public override 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
+ /// Typed event handler that fires when a new piece of data is produced
///
public event EventHandler DataConsolidated;
- ///
- /// Event handler that fires when a new piece of data is produced
- ///
- event DataConsolidatedHandler IDataConsolidator.DataConsolidated
- {
- add { _dataConsolidatedHandler += value; }
- remove { _dataConsolidatedHandler -= value; }
- }
-
///
/// Initializes a new instance of the class using the specified .
///
@@ -136,7 +115,7 @@ public RenkoConsolidator(decimal barSize)
/// Updates this consolidator with the specified data
///
/// The new data for the consolidator
- public void Update(IBaseData data)
+ public override void Update(IBaseData data)
{
var rate = data.Price;
@@ -229,33 +208,33 @@ public void Update(IBaseData data)
/// Scans this consolidator to see if it should emit a bar due to time passing
///
/// The current time in the local time zone (same as )
- public void Scan(DateTime currentLocalTime)
+ public override void Scan(DateTime currentLocalTime)
{
}
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// 2
- public void Dispose()
+ public override void Dispose()
{
DataConsolidated = null;
- _dataConsolidatedHandler = null;
+ base.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();
}
///
@@ -267,8 +246,7 @@ protected void OnDataConsolidated(RenkoBar consolidated)
{
DataConsolidated?.Invoke(this, consolidated);
_currentBar = consolidated;
- _dataConsolidatedHandler?.Invoke(this, consolidated);
- Consolidated = consolidated;
+ base.OnDataConsolidated(consolidated);
}
private void Rising(IBaseData data)
diff --git a/Common/Data/Consolidators/SequentialConsolidator.cs b/Common/Data/Consolidators/SequentialConsolidator.cs
index 6ce0fccd9e49..3f068f78f951 100644
--- a/Common/Data/Consolidators/SequentialConsolidator.cs
+++ b/Common/Data/Consolidators/SequentialConsolidator.cs
@@ -14,6 +14,7 @@
*/
using System;
+using QuantConnect.Indicators;
namespace QuantConnect.Data.Consolidators
{
@@ -22,7 +23,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
{
///
/// Gets the first consolidator to receive data
@@ -41,21 +42,10 @@ 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
///
- public IBaseData WorkingData
+ public override IBaseData WorkingData
{
get { return Second.WorkingData; }
}
@@ -63,7 +53,7 @@ public IBaseData WorkingData
///
/// Gets the type consumed by this consolidator
///
- public Type InputType
+ public override Type InputType
{
get { return First.InputType; }
}
@@ -71,7 +61,7 @@ public Type InputType
///
/// Gets the type produced by this consolidator
///
- public Type OutputType
+ public override Type OutputType
{
get { return Second.OutputType; }
}
@@ -80,7 +70,7 @@ public Type OutputType
/// Updates this consolidator with the specified data
///
/// The new data for the consolidator
- public void Update(IBaseData data)
+ public override void Update(IBaseData data)
{
First.Update(data);
}
@@ -89,7 +79,7 @@ public void Update(IBaseData data)
/// Scans this consolidator to see if it should emit a bar due to time passing
///
/// The current time in the local time zone (same as )
- public void Scan(DateTime currentLocalTime)
+ public override void Scan(DateTime currentLocalTime)
{
First.Scan(currentLocalTime);
}
@@ -127,15 +117,15 @@ public SequentialConsolidator(IDataConsolidator first, IDataConsolidator second)
/// by derived classes when they have consolidated a new piece of data.
///
/// The newly consolidated data
- protected virtual void OnDataConsolidated(IBaseData consolidated)
+ protected override void OnDataConsolidated(IBaseData consolidated)
{
- var handler = DataConsolidated;
- if (handler != null) handler(this, consolidated);
+ DataConsolidated?.Invoke(this, consolidated);
+ base.OnDataConsolidated(consolidated);
}
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// 2
- public void Dispose()
+ public override void Dispose()
{
First.Dispose();
Second.Dispose();
@@ -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..2ba4891b3e67 100644
--- a/Common/Python/DataConsolidatorPythonWrapper.cs
+++ b/Common/Python/DataConsolidatorPythonWrapper.cs
@@ -23,58 +23,32 @@ 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 : ConsolidatorBase
{
- internal PyObject Model => Instance;
-
- ///
- /// 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 GetProperty(nameof(Consolidated)); }
- }
+ private readonly BasePythonWrapper _pythonWrapper;
///
/// Gets a clone of the data being currently consolidated
///
- public IBaseData WorkingData
+ public override IBaseData WorkingData
{
- get { return GetProperty(nameof(WorkingData)); }
+ get { return _pythonWrapper.GetProperty(nameof(WorkingData)); }
}
///
/// Gets the type consumed by this consolidator
///
- public Type InputType
+ public override Type InputType
{
- get { return GetProperty(nameof(InputType)); }
+ get { return _pythonWrapper.GetProperty(nameof(InputType)); }
}
///
/// Gets the type produced by this consolidator
///
- public Type OutputType
- {
- get { return GetProperty(nameof(OutputType)); }
- }
-
- ///
- /// Event handler that fires when a new piece of data is produced
- ///
- public event DataConsolidatedHandler DataConsolidated
+ public override Type OutputType
{
- add
- {
- var eventHandler = GetEvent(nameof(DataConsolidated));
- eventHandler += value;
- }
- remove
- {
- var eventHandler = GetEvent(nameof(DataConsolidated));
- eventHandler -= value;
- }
+ get { return _pythonWrapper.GetProperty(nameof(OutputType)); }
}
///
@@ -82,40 +56,62 @@ public event DataConsolidatedHandler DataConsolidated
///
/// Represents a custom python consolidator
public DataConsolidatorPythonWrapper(PyObject consolidator)
- : base(consolidator, true)
{
+ _pythonWrapper = new BasePythonWrapper(consolidator, true);
+ var pythonEvent = _pythonWrapper.GetEvent("DataConsolidated");
+ pythonEvent += new DataConsolidatedHandler((_, bar) => OnDataConsolidated(bar));
}
///
/// Scans this consolidator to see if it should emit a bar due to time passing
///
/// The current time in the local time zone (same as )
- public void Scan(DateTime currentLocalTime)
+ public override void Scan(DateTime currentLocalTime)
{
- InvokeMethod(nameof(Scan), currentLocalTime);
+ _pythonWrapper.InvokeMethod(nameof(Scan), currentLocalTime);
}
///
/// Updates this consolidator with the specified data
///
/// The new data for the consolidator
- public void Update(IBaseData data)
+ public override void Update(IBaseData data)
{
- InvokeMethod(nameof(Update), data);
+ _pythonWrapper.InvokeMethod(nameof(Update), data);
}
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// 2
- public void Dispose()
+ ///
+ /// Resets the consolidator
+ ///
+ public override void Reset()
{
+ _pythonWrapper.InvokeMethod(nameof(Reset));
+ base.Reset();
}
///
- /// Resets the consolidator
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public override void Dispose()
+ {
+ _pythonWrapper.Dispose();
+ }
+
+ ///
+ /// Two wrappers are equal if they wrap the same Python object reference.
///
- public void Reset()
+ public override bool Equals(object obj)
{
- InvokeMethod(nameof(Reset));
+ if (obj is DataConsolidatorPythonWrapper other)
+ {
+ return _pythonWrapper.Equals(other._pythonWrapper);
+ }
+ return _pythonWrapper.Equals(obj);
}
+
+ ///
+ /// Hash code based on the underlying Python object reference.
+ ///
+ public override int GetHashCode() => _pythonWrapper.GetHashCode();
}
}
diff --git a/Common/WindowBase.cs b/Common/WindowBase.cs
new file mode 100644
index 000000000000..5aa7281fa347
--- /dev/null
+++ b/Common/WindowBase.cs
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected WindowBase() { }
+
+ ///
+ /// Initializes the rolling window with the given size.
+ ///
+ protected WindowBase(int windowSize)
+ {
+ _window = new RollingWindow(windowSize);
+ }
+
+ ///
+ /// 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 virtual 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.
+ ///
+ protected void ResetWindow()
+ {
+ Window.Reset();
+ }
+ }
+}
diff --git a/Indicators/IndicatorBase.cs b/Indicators/IndicatorBase.cs
index d601d4228873..f16ca6c31021 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,33 +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
- {
- get
- {
- return Window.Count > 1 ? Window[1] : new IndicatorDataPoint(DateTime.MinValue, 0);
- }
- }
+ public override IndicatorDataPoint Previous => base.Previous ?? new IndicatorDataPoint(DateTime.MinValue, 0);
///
/// Gets a name for this indicator
@@ -83,11 +60,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
///
@@ -96,9 +68,8 @@ public IndicatorDataPoint Previous
///
/// Initializes a new instance of the Indicator class.
///
- protected IndicatorBase()
+ protected IndicatorBase() : base(Indicator.DefaultWindowSize)
{
- Window = new RollingWindow(Indicator.DefaultWindowSize);
Current = new IndicatorDataPoint(DateTime.MinValue, 0m);
}
@@ -129,45 +100,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..7f0894d5c29a
--- /dev/null
+++ b/Tests/Common/Data/ConsolidatorBaseTests.cs
@@ -0,0 +1,157 @@
+/*
+ * 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);
+ Assert.AreEqual(windowConsolidator.Window[0], windowConsolidator.Consolidated);
+ Assert.AreEqual(expectedWindow0, windowConsolidator[0].Value);
+ Assert.AreEqual(expectedWindow1, windowConsolidator.Previous.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);
diff --git a/Tests/Python/DataConsolidatorPythonWrapperTests.cs b/Tests/Python/DataConsolidatorPythonWrapperTests.cs
index 91789712d556..11b66710296c 100644
--- a/Tests/Python/DataConsolidatorPythonWrapperTests.cs
+++ b/Tests/Python/DataConsolidatorPythonWrapperTests.cs
@@ -202,6 +202,85 @@ public void RunRegressionAlgorithm()
parameter.ExpectedFinalStatus);
}
+ [Test]
+ public void WindowIsPopulatedOnConsolidation()
+ {
+ using (Py.GIL())
+ {
+ using var wrapper = CreateFedPythonWrapper(1);
+ Assert.AreEqual(1, wrapper.Window.Count);
+ Assert.IsNotNull(wrapper.Consolidated);
+ Assert.AreEqual(wrapper.Consolidated, wrapper[0]);
+ }
+ }
+
+ [Test]
+ public void WindowKeepsPreviousConsolidatedBar()
+ {
+ using (Py.GIL())
+ {
+ using var wrapper = CreateFedPythonWrapper(1);
+ var firstConsolidated = wrapper.Consolidated;
+
+ FeedConsolidation(wrapper, 1);
+
+ Assert.AreEqual(2, wrapper.Window.Count);
+ Assert.AreNotEqual(firstConsolidated, wrapper[0]);
+ Assert.AreEqual(firstConsolidated, wrapper[1]);
+ Assert.AreEqual(firstConsolidated, wrapper.Previous);
+ }
+ }
+
+ [Test]
+ public void CanIterateOverConsolidatedBars()
+ {
+ using (Py.GIL())
+ {
+ using var wrapper = CreateFedPythonWrapper(2);
+ var bars = wrapper.ToList();
+ Assert.AreEqual(2, bars.Count);
+ Assert.AreEqual(wrapper[0], bars[0]);
+ Assert.AreEqual(wrapper[1], bars[1]);
+ }
+ }
+
+ [Test]
+ public void ResetClearsWindow()
+ {
+ using (Py.GIL())
+ {
+ using var wrapper = CreateFedPythonWrapper(1);
+ wrapper.Reset();
+ Assert.AreEqual(0, wrapper.Window.Count);
+ Assert.IsNull(wrapper.Consolidated);
+ }
+ }
+
+ private static DataConsolidatorPythonWrapper CreateFedPythonWrapper(int consolidations)
+ {
+ var module = PyModule.FromString(Guid.NewGuid().ToString(),
+ "from AlgorithmImports import *\n" +
+ "class CustomConsolidator(QuoteBarConsolidator):\n" +
+ " def __init__(self):\n" +
+ " super().__init__(timedelta(minutes=2))\n");
+
+ var wrapper = new DataConsolidatorPythonWrapper(module.GetAttr("CustomConsolidator").Invoke());
+ FeedConsolidation(wrapper, consolidations);
+ return wrapper;
+ }
+
+ private static void FeedConsolidation(DataConsolidatorPythonWrapper wrapper, int consolidations)
+ {
+ var offset = wrapper.Window.Count * 2;
+ var time = DateTime.Today;
+ for (var i = 0; i < consolidations; i++)
+ {
+ var bar = new QuoteBar { Time = time.AddMinutes(offset + i * 2), Symbol = Symbols.SPY, Bid = new Bar(1, 2, 0.75m, 1.25m), LastBidSize = 3, Value = 1, Period = TimeSpan.FromMinutes(1) };
+ wrapper.Update(bar);
+ wrapper.Scan(time.AddMinutes(offset + (i + 1) * 2));
+ }
+ }
+
[Test]
public void AttachAndTriggerEvent()
{