diff --git a/Algorithm.CSharp/MarketHourAwareIntradayConsolidationRegressionAlgorithm.cs b/Algorithm.CSharp/MarketHourAwareIntradayConsolidationRegressionAlgorithm.cs deleted file mode 100644 index b76bdbe5be6e..000000000000 --- a/Algorithm.CSharp/MarketHourAwareIntradayConsolidationRegressionAlgorithm.cs +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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.Common; -using QuantConnect.Data.Market; -using QuantConnect.Interfaces; -using QuantConnect.Securities; -using QuantConnect.Securities.Future; - -namespace QuantConnect.Algorithm.CSharp -{ - /// - /// Regression algorithm asserting that a with an intraday period - /// anchors each bar to the market open and never lets a bar extend past the market close. - /// - public class MarketHourAwareIntradayConsolidationRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition - { - private readonly TimeSpan _period = TimeSpan.FromMinutes(7); - private Future _future; - private SecurityExchangeHours _hours; - private int _consolidatedBarCount; - - public override void Initialize() - { - SetStartDate(2013, 10, 06); - SetEndDate(2013, 10, 11); - - _future = AddFuture(Futures.Indices.SP500EMini, Resolution.Minute, extendedMarketHours: true); - _hours = _future.Exchange.Hours; - - var consolidator = new MarketHourAwareConsolidator(false, _period, typeof(TradeBar), TickType.Trade, extendedMarketHours: true); - consolidator.DataConsolidated += OnSevenMinuteBar; - SubscriptionManager.AddConsolidator(_future.Symbol, consolidator); - } - - private void OnSevenMinuteBar(object sender, IBaseData consolidated) - { - var bar = (TradeBar)consolidated; - var marketOpen = _hours.GetPreviousMarketOpen(bar.Time.AddTicks(1), extendedMarketHours: true); - var marketClose = _hours.GetNextMarketClose(marketOpen, extendedMarketHours: true); - - // the bar must be anchored to the market open - if ((bar.Time - marketOpen).Ticks % _period.Ticks != 0) - { - throw new RegressionTestException($"Bar starting at {bar.Time} is not anchored to the market open {marketOpen}"); - } - - // the bar must not extend past the market close - if (bar.EndTime > marketClose) - { - throw new RegressionTestException($"Bar ending at {bar.EndTime} extends past the market close {marketClose}"); - } - - // bars span the full period unless the last one is clipped at the market close - var barPeriod = bar.EndTime - bar.Time; - if (barPeriod != _period && bar.EndTime != marketClose) - { - throw new RegressionTestException($"Bar from {bar.Time} to {bar.EndTime} has period {barPeriod} instead of {_period}"); - } - - _consolidatedBarCount++; - } - - public override void OnEndOfAlgorithm() - { - if (_consolidatedBarCount == 0) - { - throw new RegressionTestException("The consolidator did not produce any bar"); - } - } - - /// - /// 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 => 41486; - - /// - /// 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", "-2.564"}, - {"Tracking Error", "0.214"}, - {"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/MarketHourAwareIntradayConsolidationRegressionAlgorithm.py b/Algorithm.Python/MarketHourAwareIntradayConsolidationRegressionAlgorithm.py deleted file mode 100644 index fdf8ff4ed855..000000000000 --- a/Algorithm.Python/MarketHourAwareIntradayConsolidationRegressionAlgorithm.py +++ /dev/null @@ -1,58 +0,0 @@ -# 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 a MarketHourAwareConsolidator with an intraday period -### anchors each bar to the market open and never lets a bar extend past the market close. -### -class MarketHourAwareIntradayConsolidationRegressionAlgorithm(QCAlgorithm): - - def initialize(self): - self.set_start_date(2013, 10, 6) - self.set_end_date(2013, 10, 11) - - self._period = timedelta(minutes=7) - self._consolidated_bar_count = 0 - - self._future = self.add_future(Futures.Indices.SP_500_E_MINI, Resolution.MINUTE, extended_market_hours=True) - self._hours = self._future.exchange.hours - - consolidator = MarketHourAwareConsolidator(False, self._period, TradeBar, TickType.TRADE, True) - consolidator.data_consolidated += self._on_seven_minute_bar - self.subscription_manager.add_consolidator(self._future.symbol, consolidator) - - def _on_seven_minute_bar(self, sender, consolidated): - bar = consolidated - market_open = self._hours.get_previous_market_open(bar.time + timedelta(microseconds=1), True) - market_close = self._hours.get_next_market_close(market_open, True) - - # the bar must be anchored to the market open - if (bar.time - market_open) % self._period != timedelta(0): - raise RegressionTestException(f"Bar starting at {bar.time} is not anchored to the market open {market_open}") - - # the bar must not extend past the market close - if bar.end_time > market_close: - raise RegressionTestException(f"Bar ending at {bar.end_time} extends past the market close {market_close}") - - # bars span the full period unless the last one is clipped at the market close - bar_period = bar.end_time - bar.time - if bar_period != self._period and bar.end_time != market_close: - raise RegressionTestException(f"Bar from {bar.time} to {bar.end_time} has period {bar_period} instead of {self._period}") - - self._consolidated_bar_count += 1 - - def on_end_of_algorithm(self): - if self._consolidated_bar_count == 0: - raise RegressionTestException("The consolidator did not produce any bar") diff --git a/Common/AlgorithmImports.py b/Common/AlgorithmImports.py index eb4a39208869..35cda517a1ca 100644 --- a/Common/AlgorithmImports.py +++ b/Common/AlgorithmImports.py @@ -68,7 +68,6 @@ from QuantConnect.Securities.Equity import * from QuantConnect.Securities.Future import * from QuantConnect.Data.Consolidators import * -from QuantConnect.Data.Common import * from QuantConnect.Orders.TimeInForces import * from QuantConnect.Algorithm.Framework import * from QuantConnect.Algorithm.Selection import * diff --git a/Common/Data/Consolidators/MarketHourAwareConsolidator.cs b/Common/Data/Consolidators/MarketHourAwareConsolidator.cs index 6f406f57aa26..b674cfd9d1f8 100644 --- a/Common/Data/Consolidators/MarketHourAwareConsolidator.cs +++ b/Common/Data/Consolidators/MarketHourAwareConsolidator.cs @@ -89,36 +89,6 @@ public MarketHourAwareConsolidator(bool dailyStrictEndTimeEnabled, Resolution re Consolidator.DataConsolidated += ForwardConsolidatedBar; } - /// - /// Initializes a new instance of the class for an arbitrary period. - /// Intraday periods are anchored to the market open without extending past the close. - /// - /// True if daily strict end times should be enabled - /// The consolidation period - /// The target data type - /// The target tick type - /// True if extended market hours should be consolidated - public MarketHourAwareConsolidator(bool dailyStrictEndTimeEnabled, TimeSpan period, Type dataType, TickType tickType, bool extendedMarketHours) - { - _dailyStrictEndTimeEnabled = dailyStrictEndTimeEnabled; - Period = period; - _extendedMarketHours = extendedMarketHours; - - // when the period exactly matches a standard resolution, reuse the resolution based consolidation so its - // well-tested behavior is preserved; only arbitrary periods need the market-open anchored intraday calendar - var resolution = period.ToHigherResolutionEquivalent(false); - if (resolution.ToTimeSpan() == period) - { - Consolidator = CreateConsolidator(resolution, dataType, tickType); - } - else - { - Func calendar = period < Time.OneDay ? IntradayCalendar : DailyStrictEndTime; - Consolidator = CreateConsolidator(calendar, dataType, tickType); - } - Consolidator.DataConsolidated += ForwardConsolidatedBar; - } - /// /// Creates the inner consolidator that produces the requested output. /// @@ -151,28 +121,6 @@ protected virtual IDataConsolidator CreateConsolidator(Resolution resolution, Ty throw new ArgumentNullException(nameof(dataType), $"{dataType.Name} not supported"); } - /// - /// Creates the underlying calendar based consolidator for the given data type, used for arbitrary periods - /// - protected virtual IDataConsolidator CreateConsolidator(Func calendar, Type dataType, TickType tickType) - { - if (dataType == typeof(Tick)) - { - return tickType == TickType.Trade - ? new TickConsolidator(calendar) - : new TickQuoteBarConsolidator(calendar); - } - if (dataType == typeof(TradeBar)) - { - return new TradeBarConsolidator(calendar); - } - if (dataType == typeof(QuoteBar)) - { - return new QuoteBarConsolidator(calendar); - } - throw new ArgumentNullException(nameof(dataType), $"{dataType.Name} not supported"); - } - /// /// Event handler that fires when a new piece of data is produced /// @@ -247,27 +195,13 @@ protected void Initialize(IBaseData data) /// protected virtual CalendarInfo DailyStrictEndTime(DateTime dateTime) { - // strict end times describe a single daily bar, so periods larger than a day fall back to standard period consolidation - if (!_useStrictEndTime || Period > Time.OneDay) + if (!_useStrictEndTime) { return new(Period > Time.OneDay ? dateTime : dateTime.RoundDown(Period), Period); } return LeanData.GetDailyCalendar(dateTime, ExchangeHours, _extendedMarketHours); } - /// - /// Determines a bar start time and period for intraday consolidation, anchored to the market open - /// without extending past the market close so a bar never spans across closed market hours - /// - protected virtual CalendarInfo IntradayCalendar(DateTime dateTime) - { - if (ExchangeHours == null || ExchangeHours.IsMarketAlwaysOpen) - { - return new(dateTime.RoundDown(Period), Period); - } - return LeanData.GetIntradayCalendar(dateTime, Period, ExchangeHours, _extendedMarketHours); - } - /// /// Useful for testing /// diff --git a/Common/Util/LeanData.cs b/Common/Util/LeanData.cs index f473c9f115e0..4bbcf1e62e97 100644 --- a/Common/Util/LeanData.cs +++ b/Common/Util/LeanData.cs @@ -1476,33 +1476,6 @@ public static CalendarInfo GetDailyCalendar(DateTime exchangeTimeZoneDate, Secur return new CalendarInfo(startTime, period); } - /// - /// Helper method to return the intraday bar start time and period, anchored to the market open, without extending past the close - /// - /// The point in time we want to get the bar information about - /// The intraday consolidation period - /// The associated exchange hours - /// True if extended market hours should be taken into consideration - /// The calendar information that holds a start time and a period - public static CalendarInfo GetIntradayCalendar(DateTime exchangeTimeZoneDate, TimeSpan period, SecurityExchangeHours exchangeHours, bool extendedMarketHours) - { - var marketOpen = exchangeHours.IsOpen(exchangeTimeZoneDate, extendedMarketHours) - ? exchangeHours.GetPreviousMarketOpen(exchangeTimeZoneDate.AddTicks(1), extendedMarketHours) - : exchangeHours.GetPreviousMarketOpen(exchangeTimeZoneDate, extendedMarketHours); - - var intervalsPassed = (long)Math.Floor((exchangeTimeZoneDate - marketOpen).Ticks / (double)period.Ticks); - var startTime = marketOpen.AddTicks(intervalsPassed * period.Ticks); - - // keep the last bar from extending past the market close - var endTime = startTime + period; - var marketClose = exchangeHours.GetNextMarketClose(marketOpen, extendedMarketHours); - if (endTime > marketClose) - { - endTime = marketClose; - } - return new CalendarInfo(startTime, endTime - startTime); - } - /// /// Helper method to get the next daily end time, taking into account strict end times if appropriate /// diff --git a/Tests/Common/Data/MarketHourAwareConsolidatorTests.cs b/Tests/Common/Data/MarketHourAwareConsolidatorTests.cs index d6153996dee2..3ad59c56396b 100644 --- a/Tests/Common/Data/MarketHourAwareConsolidatorTests.cs +++ b/Tests/Common/Data/MarketHourAwareConsolidatorTests.cs @@ -21,7 +21,6 @@ using QuantConnect.Data.Consolidators; using System.Collections.Generic; using QuantConnect.Data; -using QuantConnect.Securities; namespace QuantConnect.Tests.Common.Data { @@ -295,91 +294,6 @@ public void WorksWithDailyResolutionAndPreciseEndTimeFalse() Assert.AreEqual(100, consolidatedData.High); } - [Test] - public void IntradayConsolidatorIsAnchoredToMarketOpen() - { - var symbol = Symbols.Future_ESZ18_Dec2018; - var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType); - var marketOpen = exchangeHours.GetNextMarketOpen(new DateTime(2024, 11, 30, 12, 0, 0), extendedMarketHours: true); - - using var consolidator = new MarketHourAwareConsolidator(false, TimeSpan.FromMinutes(7), typeof(TradeBar), TickType.Trade, extendedMarketHours: true); - var bars = new List(); - consolidator.DataConsolidated += (_, b) => bars.Add((TradeBar)b); - - // feed the first 30 minutes after the open, one bar per minute - Feed(consolidator, symbol, marketOpen, 30); - - Assert.GreaterOrEqual(bars.Count, 3); - Assert.AreEqual(marketOpen, bars[0].Time); - Assert.AreEqual(marketOpen.AddMinutes(7), bars[0].EndTime); - Assert.AreEqual(marketOpen.AddMinutes(14), bars[1].EndTime); - Assert.AreEqual(marketOpen.AddMinutes(21), bars[2].EndTime); - } - - [Test] - public void IntradayConsolidatorLastBarEndsAtMarketClose() - { - var symbol = Symbols.SPY; - using var consolidator = new MarketHourAwareConsolidator(false, TimeSpan.FromMinutes(7), typeof(TradeBar), TickType.Trade, extendedMarketHours: false); - var bars = new List(); - consolidator.DataConsolidated += (_, b) => bars.Add((TradeBar)b); - - // feed the last 10 minutes of day 1 (up to the 16:00 close) and the first 10 of day 2 - Feed(consolidator, symbol, new DateTime(2015, 04, 13, 15, 50, 0), 10); - Feed(consolidator, symbol, new DateTime(2015, 04, 14, 9, 30, 0), 10); - - // day 1 produces two 7 minute bars anchored to the market open at 9:30 - var day1Bars = bars.FindAll(b => b.Time.Date == new DateTime(2015, 04, 13)); - Assert.AreEqual(2, day1Bars.Count); - Assert.AreEqual(new DateTime(2015, 04, 13, 15, 48, 0), day1Bars[0].Time); - Assert.AreEqual(new DateTime(2015, 04, 13, 15, 55, 0), day1Bars[0].EndTime); - Assert.AreEqual(new DateTime(2015, 04, 13, 15, 55, 0), day1Bars[1].Time); - Assert.AreEqual(new DateTime(2015, 04, 13, 16, 0, 0), day1Bars[1].EndTime); - - // next day starts over at the market open at 9:30 - var day2Open = new DateTime(2015, 04, 14, 9, 30, 0); - var firstDay2 = bars.Find(b => b.Time == day2Open); - Assert.IsNotNull(firstDay2); - Assert.AreEqual(day2Open.AddMinutes(7), firstDay2.EndTime); - } - - [TestCase(true)] - [TestCase(false)] - public void ConsolidatesPeriodGreaterThanOneDay(bool dailyStrictEndTimeEnabled) - { - var symbol = Symbols.SPX; - using var consolidator = new MarketHourAwareConsolidator(dailyStrictEndTimeEnabled, TimeSpan.FromDays(2), typeof(TradeBar), TickType.Trade, extendedMarketHours: true); - var bars = new List(); - consolidator.DataConsolidated += (_, b) => bars.Add((TradeBar)b); - - // feed 4 daily bars - var start = new DateTime(2015, 04, 13, 10, 0, 0); - for (var i = 0; i < 4; i++) - { - consolidator.Update(new TradeBar { Time = start.AddDays(i), Period = Time.OneDay, Symbol = symbol, Open = 1, High = 1, Low = 1, Close = 1, Volume = 1 }); - } - consolidator.Scan(start.AddDays(4)); - - Assert.AreEqual(2, bars.Count); - // first bar - Assert.AreEqual(TimeSpan.FromDays(2), bars[0].Period); - Assert.AreEqual(start, bars[0].Time); - Assert.AreEqual(start.AddDays(2), bars[0].EndTime); - // second bar - Assert.AreEqual(TimeSpan.FromDays(2), bars[1].Period); - Assert.AreEqual(start.AddDays(2), bars[1].Time); - Assert.AreEqual(start.AddDays(4), bars[1].EndTime); - } - - private static void Feed(IDataConsolidator consolidator, Symbol symbol, DateTime from, int minutes) - { - for (var i = 0; i < minutes; i++) - { - var t = from.AddMinutes(i); - consolidator.Update(new TradeBar { Time = t, Period = Time.OneMinute, Symbol = symbol, Open = 1, High = 1, Low = 1, Close = 1, Volume = 1 }); - } - } - protected override IDataConsolidator CreateConsolidator() { return new MarketHourAwareConsolidator(true, Resolution.Hour, typeof(TradeBar), TickType.Trade, false);