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);