From ac5ff8a47a19c63b80e4bb349703cbcd7a8e9c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Andr=C3=A9s=20Marino=20Rojas?= <47573394+Marinovsky@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:40:09 -0500 Subject: [PATCH] Add regression tests --- ...ourBarsIntoDailyBarsRegressionAlgorithm.cs | 138 ++++++++++++++++++ ...ourBarsIntoDailyBarsRegressionAlgorithm.py | 51 +++++++ 2 files changed, 189 insertions(+) create mode 100644 Algorithm.CSharp/ConsolidateHourBarsIntoDailyBarsRegressionAlgorithm.cs create mode 100644 Algorithm.Python/ConsolidateHourBarsIntoDailyBarsRegressionAlgorithm.py diff --git a/Algorithm.CSharp/ConsolidateHourBarsIntoDailyBarsRegressionAlgorithm.cs b/Algorithm.CSharp/ConsolidateHourBarsIntoDailyBarsRegressionAlgorithm.cs new file mode 100644 index 000000000000..c379ed4f6ffa --- /dev/null +++ b/Algorithm.CSharp/ConsolidateHourBarsIntoDailyBarsRegressionAlgorithm.cs @@ -0,0 +1,138 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using QuantConnect.Data; +using QuantConnect.Indicators; +using QuantConnect.Interfaces; +using System; +using System.Collections.Generic; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// Regression algorithm that asserts Stochastic indicator, registered with a different resolution consolidator, + /// is warmed up properly by calling QCAlgorithm.WarmUpIndicator + /// + public class ConsolidateHourBarsIntoDailyBarsRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition + { + private Symbol _spy; + private RelativeStrengthIndex _rsi; + private RelativeStrengthIndex _rsiTimeDelta; + private Dictionary _values = new(); + private int _count; + + public override void Initialize() + { + SetStartDate(2020, 5, 1); + SetEndDate(2020, 6, 5); + + _spy = AddEquity("SPY", Resolution.Hour).Symbol; + _rsi = new RelativeStrengthIndex("FIRST", 15, MovingAverageType.Wilders); + RegisterIndicator(_spy, _rsi, Resolution.Daily); + + _rsiTimeDelta = new RelativeStrengthIndex("SECOND" ,15, MovingAverageType.Wilders); + } + + public override void OnData(Slice slice) + { + if (IsWarmingUp) return; + + if (slice.ContainsKey(_spy) && slice[_spy] != null) + { + if (Time.Month == EndDate.Month) + { + var history = History(_spy, _count, Resolution.Daily); + foreach (var bar in history) + { + _rsiTimeDelta.Update(bar.EndTime, bar.Close); + var time = bar.EndTime.Date; + if (_rsiTimeDelta.Current.Value != _values[time]) + { + throw new Exception($"Both {_rsi.Name} and {_rsiTimeDelta.Name} should have the same values, but they differ. {_rsi.Name}: {_values[time]} | {_rsiTimeDelta.Name}: {_rsiTimeDelta.Current.Value}"); + } + } + Quit(); + } + else + { + _values[Time.Date] = _rsi.Current.Value; + if (Time.Hour == 16) + { + _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 => 290; + + /// + /// Data Points count of the algorithm history + /// + public int AlgorithmHistoryDataPoints => 20; + + /// + /// 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", "-5.215"}, + {"Tracking Error", "0.159"}, + {"Treynor Ratio", "0"}, + {"Total Fees", "$0.00"}, + {"Estimated Strategy Capacity", "$0"}, + {"Lowest Capacity Asset", ""}, + {"Portfolio Turnover", "0%"}, + {"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"} + }; + } +} diff --git a/Algorithm.Python/ConsolidateHourBarsIntoDailyBarsRegressionAlgorithm.py b/Algorithm.Python/ConsolidateHourBarsIntoDailyBarsRegressionAlgorithm.py new file mode 100644 index 000000000000..e694d6205049 --- /dev/null +++ b/Algorithm.Python/ConsolidateHourBarsIntoDailyBarsRegressionAlgorithm.py @@ -0,0 +1,51 @@ +# 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 * + +class ConsolidateHourBarsIntoDailyBarsRegressionAlgorithm(QCAlgorithm): + def initialize(self): + # change the start date between runs to check that warm up shows the correct value + self.set_start_date(2020, 5, 1) + self.set_end_date(2020, 6, 5) + self.set_cash(100000) + + self.spy = self.add_equity("SPY", Resolution.HOUR).symbol + + # Resolution.DAILY indicators + self._rsi = RelativeStrengthIndex("First", 15, MovingAverageType.WILDERS) + self.register_indicator(self.spy, self._rsi, Resolution.DAILY) + + # Resolution.DAILY indicators + self._rsi_timedelta = RelativeStrengthIndex("Second", 15, MovingAverageType.WILDERS) + self._values = {} + self.count = 0; + + def on_data(self, data: Slice): + if self.is_warming_up: + return + + if data.contains_key(self.spy) and data[self.spy] != None: + if self.time.month == self.end_date.month: + history = self.history[TradeBar](self.spy, self.count, Resolution.DAILY) + for bar in history: + time = bar.end_time.strftime('%Y-%m-%d') + self._rsi_timedelta.update(bar.end_time, bar.close) + if self._rsi_timedelta.current.value != self._values[time]: + raise Exception(f"Both {self._rsi.name} and {self._rsi_timedelta.name} should have the same values, but they differ. {self._rsi.name}: {self._values[time]} | {self._rsi_timedelta.name}: {self._rsi_timedelta.current.value}") + self.quit() + else: + time = self.time.strftime('%Y-%m-%d') + self._values[time] = self._rsi.current.value + if self.time.hour == 16: + self.count += 1