From 640f7b6b91d0d612ee01b6769f7a79fb1945edf6 Mon Sep 17 00:00:00 2001 From: Martin Molinero Date: Tue, 7 Jan 2025 18:36:08 -0300 Subject: [PATCH] Fix for multiple market closes in date - Fixes related to support assets with multiple daily market closes. Adding unit and regression tests --- ...FuturesWithExtendedMarketDailyAlgorithm.cs | 2 +- ...eTimeZoneAheadOfDataRegressionAlgorithm.cs | 2 +- ...geTimeZoneSameAsDataRegressionAlgorithm.cs | 2 +- ...seWithExtendedMarketRegressionAlgorithm.cs | 28 ++- ...rketOpenConsolidatorRegressionAlgorithm.cs | 1 + ...orWithExtendedMarketRegressionAlgorithm.cs | 25 ++- .../HSIFutureDailyRegressionAlgorithm.cs | 80 +++++++ .../HSIFutureHourRegressionAlgorithm.cs | 210 ++++++++++++++++++ Algorithm/QCAlgorithm.Indicators.cs | 3 +- Algorithm/QCAlgorithm.cs | 2 +- Common/AlgorithmSettings.cs | 5 + Common/Extensions.cs | 21 ++ Common/Interfaces/IAlgorithmSettings.cs | 5 + Common/Orders/TimeInForces/DayTimeInForce.cs | 2 +- .../TimeInForces/GoodTilDateTimeInForce.cs | 2 +- Common/Scheduling/TimeRules.cs | 6 +- Common/Securities/LocalMarketHours.cs | 89 +++++++- Common/Securities/Option/OptionSymbol.cs | 2 +- Common/Securities/SecurityExchangeHours.cs | 63 +++++- Common/Util/LeanData.cs | 18 +- Data/future/hkfe/daily/hsi_quote.zip | Bin 0 -> 3449 bytes Data/future/hkfe/daily/hsi_trade.zip | Bin 0 -> 1462 bytes Data/future/hkfe/factor_files/hsi.csv | 27 +++ Data/future/hkfe/hour/hsi_quote.zip | Bin 0 -> 5026 bytes Data/future/hkfe/hour/hsi_trade.zip | Bin 0 -> 3473 bytes Data/future/hkfe/map_files/hsi.csv | 25 +++ Data/index/hkfe/daily/hsi.zip | Bin 0 -> 988 bytes Data/index/hkfe/hour/hsi.zip | Bin 0 -> 2535 bytes Engine/DataFeeds/AggregationManager.cs | 6 +- .../Enumerators/FillForwardEnumerator.cs | 25 ++- Engine/RealTime/ScheduledEventFactory.cs | 2 +- Tests/Common/Scheduling/TimeRulesTests.cs | 66 +++++- .../Securities/SecurityExchangeHoursTests.cs | 34 +++ Tests/Common/Util/LeanDataTests.cs | 76 +++++-- 34 files changed, 734 insertions(+), 95 deletions(-) create mode 100644 Algorithm.CSharp/HSIFutureDailyRegressionAlgorithm.cs create mode 100644 Algorithm.CSharp/HSIFutureHourRegressionAlgorithm.cs create mode 100644 Data/future/hkfe/daily/hsi_quote.zip create mode 100644 Data/future/hkfe/daily/hsi_trade.zip create mode 100644 Data/future/hkfe/factor_files/hsi.csv create mode 100644 Data/future/hkfe/hour/hsi_quote.zip create mode 100644 Data/future/hkfe/hour/hsi_trade.zip create mode 100644 Data/future/hkfe/map_files/hsi.csv create mode 100644 Data/index/hkfe/daily/hsi.zip create mode 100644 Data/index/hkfe/hour/hsi.zip diff --git a/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketDailyAlgorithm.cs b/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketDailyAlgorithm.cs index 177dc4a3e0c4..13f969d21eb2 100644 --- a/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketDailyAlgorithm.cs +++ b/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketDailyAlgorithm.cs @@ -36,7 +36,7 @@ public class BasicTemplateFuturesWithExtendedMarketDailyAlgorithm : BasicTemplat /// /// Data Points count of all timeslices of algorithm /// - public override long DataPoints => 14713; + public override long DataPoints => 14181; /// /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm diff --git a/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs index cda1e7ea5055..58321d23ec02 100644 --- a/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs +++ b/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs @@ -33,6 +33,6 @@ public class ContinuousFutureRolloverDailyExchangeTimeZoneAheadOfDataRegressionA /// /// Data Points count of all timeslices of algorithm /// - public override long DataPoints => 1002; + public override long DataPoints => 1014; } } diff --git a/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneSameAsDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneSameAsDataRegressionAlgorithm.cs index f28da4baf145..df67ff88901d 100644 --- a/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneSameAsDataRegressionAlgorithm.cs +++ b/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneSameAsDataRegressionAlgorithm.cs @@ -33,6 +33,6 @@ public class ContinuousFutureRolloverDailyExchangeTimeZoneSameAsDataRegressionAl /// /// Data Points count of all timeslices of algorithm /// - public override long DataPoints => 995; + public override long DataPoints => 1007; } } diff --git a/Algorithm.CSharp/FutureMarketOpenAndCloseWithExtendedMarketRegressionAlgorithm.cs b/Algorithm.CSharp/FutureMarketOpenAndCloseWithExtendedMarketRegressionAlgorithm.cs index 8109fcf408bc..956b1bbb7ca4 100644 --- a/Algorithm.CSharp/FutureMarketOpenAndCloseWithExtendedMarketRegressionAlgorithm.cs +++ b/Algorithm.CSharp/FutureMarketOpenAndCloseWithExtendedMarketRegressionAlgorithm.cs @@ -25,21 +25,25 @@ public class FutureMarketOpenAndCloseWithExtendedMarketRegressionAlgorithm : Fut { protected override bool ExtendedMarketHours => true; protected override List AfterMarketOpen => new List() { - new DateTime(2020, 02, 04, 18, 0, 0), // Tuesday - new DateTime(2020, 02, 05, 18, 0, 0), - new DateTime(2020, 02, 06, 18, 0, 0), - new DateTime(2020, 02, 09, 18, 0, 0), - new DateTime(2020, 02, 10, 18, 0, 0), - new DateTime(2020, 02, 11, 18, 0, 0) + new DateTime(2020, 02, 04, 0, 0, 0), // Tuesday + new DateTime(2020, 02, 05, 0, 0, 0), + new DateTime(2020, 02, 06, 0, 0, 0), + new DateTime(2020, 02, 07, 0, 0, 0), + new DateTime(2020, 02, 09, 18, 0, 0), // sunday + new DateTime(2020, 02, 10, 0, 0, 0), + new DateTime(2020, 02, 11, 0, 0, 0), + new DateTime(2020, 02, 12, 0, 0, 0) }; protected override List BeforeMarketClose => new List() { - new DateTime(2020, 02, 04, 17, 0, 0), - new DateTime(2020, 02, 05, 17, 0, 0), - new DateTime(2020, 02, 06, 17, 0, 0), - new DateTime(2020, 02, 07, 17, 0, 0), - new DateTime(2020, 02, 10, 17, 0, 0), - new DateTime(2020, 02, 11, 17, 0, 0) + new DateTime(2020, 02, 04, 0, 0, 0), + new DateTime(2020, 02, 05, 0, 0, 0), + new DateTime(2020, 02, 06, 0, 0, 0), + new DateTime(2020, 02, 07, 0, 0, 0), + new DateTime(2020, 02, 07, 17, 0, 0), // friday + new DateTime(2020, 02, 10, 0, 0, 0), + new DateTime(2020, 02, 11, 0, 0, 0), + new DateTime(2020, 02, 12, 0, 0, 0) }; /// diff --git a/Algorithm.CSharp/FutureMarketOpenConsolidatorRegressionAlgorithm.cs b/Algorithm.CSharp/FutureMarketOpenConsolidatorRegressionAlgorithm.cs index 098084841790..9cee896df9bb 100644 --- a/Algorithm.CSharp/FutureMarketOpenConsolidatorRegressionAlgorithm.cs +++ b/Algorithm.CSharp/FutureMarketOpenConsolidatorRegressionAlgorithm.cs @@ -57,6 +57,7 @@ public override void Initialize() SetStartDate(2013, 10, 06); SetEndDate(2013, 10, 14); + Settings.DailyConsolidationUseExtendedMarketHours = true; var es = AddSecurity(SecurityType.Future, "ES", extendedMarketHours: ExtendedMarketHours); _expectedOpensQueue = new Queue(ExpectedOpens); diff --git a/Algorithm.CSharp/FutureMarketOpenConsolidatorWithExtendedMarketRegressionAlgorithm.cs b/Algorithm.CSharp/FutureMarketOpenConsolidatorWithExtendedMarketRegressionAlgorithm.cs index 51febcc14451..fc1584788367 100644 --- a/Algorithm.CSharp/FutureMarketOpenConsolidatorWithExtendedMarketRegressionAlgorithm.cs +++ b/Algorithm.CSharp/FutureMarketOpenConsolidatorWithExtendedMarketRegressionAlgorithm.cs @@ -29,19 +29,24 @@ public class FutureMarketOpenConsolidatorWithExtendedMarketRegressionAlgorithm : protected override bool ExtendedMarketHours => true; protected override List ExpectedOpens => new List(){ new DateTime(2013, 10, 06, 18, 0, 0), // Sunday - new DateTime(2013, 10, 07, 18, 0, 0), - new DateTime(2013, 10, 08, 18, 0, 0), - new DateTime(2013, 10, 09, 18, 0, 0), - new DateTime(2013, 10, 10, 18, 0, 0), + // market is open for the whole day, so goes from midnight to midnight + new DateTime(2013, 10, 07, 0, 0, 0), + new DateTime(2013, 10, 08, 0, 0, 0), + new DateTime(2013, 10, 09, 0, 0, 0), + new DateTime(2013, 10, 10, 0, 0, 0), + new DateTime(2013, 10, 11, 0, 0, 0), new DateTime(2013, 10, 13, 18, 0, 0), + new DateTime(2013, 10, 14, 0, 0, 0), }; protected override List ExpectedCloses => new List(){ - new DateTime(2013, 10, 07, 17, 0, 0), - new DateTime(2013, 10, 08, 17, 0, 0), - new DateTime(2013, 10, 09, 17, 0, 0), - new DateTime(2013, 10, 10, 17, 0, 0), - new DateTime(2013, 10, 11, 17, 0, 0), - new DateTime(2013, 10, 14, 17, 0, 0), + new DateTime(2013, 10, 07, 0, 0, 0), + new DateTime(2013, 10, 08, 0, 0, 0), + new DateTime(2013, 10, 09, 0, 0, 0), + new DateTime(2013, 10, 10, 0, 0, 0), + new DateTime(2013, 10, 11, 0, 0, 0), + new DateTime(2013, 10, 11, 17, 0, 0), // friday + new DateTime(2013, 10, 14, 0, 0, 0), + new DateTime(2013, 10, 15, 0, 0, 0), }; /// diff --git a/Algorithm.CSharp/HSIFutureDailyRegressionAlgorithm.cs b/Algorithm.CSharp/HSIFutureDailyRegressionAlgorithm.cs new file mode 100644 index 000000000000..d497a26a5bac --- /dev/null +++ b/Algorithm.CSharp/HSIFutureDailyRegressionAlgorithm.cs @@ -0,0 +1,80 @@ +/* + * 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.Generic; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// Regression algorithm using and testing HSI futures and index + /// + public class HSIFutureDailyRegressionAlgorithm : HSIFutureHourRegressionAlgorithm + { + /// + /// The data resolution + /// + protected override Resolution Resolution => Resolution.Daily; + + /// + /// Data Points count of all timeslices of algorithm + /// + public override long DataPoints => 177; + + /// + /// Data Points count of the algorithm history + /// + public override int AlgorithmHistoryDataPoints => 12; + + /// + /// Final status of the algorithm + /// + public override AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; + + /// + /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm + /// + public override Dictionary ExpectedStatistics => new Dictionary + { + {"Total Orders", "15"}, + {"Average Win", "0%"}, + {"Average Loss", "-0.33%"}, + {"Compounding Annual Return", "-55.187%"}, + {"Drawdown", "2.400%"}, + {"Expectancy", "-1"}, + {"Start Equity", "100000"}, + {"End Equity", "97610"}, + {"Net Profit", "-2.390%"}, + {"Sharpe Ratio", "-15.799"}, + {"Sortino Ratio", "-19.207"}, + {"Probabilistic Sharpe Ratio", "0%"}, + {"Loss Rate", "100%"}, + {"Win Rate", "0%"}, + {"Profit-Loss Ratio", "0"}, + {"Alpha", "0"}, + {"Beta", "0"}, + {"Annual Standard Deviation", "0.029"}, + {"Annual Variance", "0.001"}, + {"Information Ratio", "-15.544"}, + {"Tracking Error", "0.029"}, + {"Treynor Ratio", "0"}, + {"Total Fees", "$600.00"}, + {"Estimated Strategy Capacity", "$0"}, + {"Lowest Capacity Asset", "HSI VL6DN7UV65S9"}, + {"Portfolio Turnover", "1590.77%"}, + {"OrderListHash", "42cd8e3b58361b181c911a603f69d2f7"} + }; + } +} diff --git a/Algorithm.CSharp/HSIFutureHourRegressionAlgorithm.cs b/Algorithm.CSharp/HSIFutureHourRegressionAlgorithm.cs new file mode 100644 index 000000000000..b7daa659a3e1 --- /dev/null +++ b/Algorithm.CSharp/HSIFutureHourRegressionAlgorithm.cs @@ -0,0 +1,210 @@ +/* + * 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.Linq; +using QuantConnect.Data; +using QuantConnect.Interfaces; +using QuantConnect.Securities; +using System.Collections.Generic; +using QuantConnect.Securities.Future; +using QuantConnect.Data.UniverseSelection; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// Regression algorithm using and testing HSI futures and index + /// + public class HSIFutureHourRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition + { + private int _symbolChangeEvent; + private Symbol _contractSymbol; + private Symbol _index; + private Symbol _futureSymbol; + + /// + /// The data resolution + /// + protected virtual Resolution Resolution => Resolution.Hour; + + /// + /// Initialize your algorithm and add desired assets. + /// + public override void Initialize() + { + SetStartDate(2013, 10, 20); + SetEndDate(2013, 10, 30); + + SetAccountCurrency("HKD"); + SetTimeZone(TimeZones.HongKong); + + UniverseSettings.Resolution = Resolution; + _index = AddIndex("HSI", Resolution, market: Market.HKFE).Symbol; + var future = AddFuture(Futures.Indices.HangSeng, Resolution); + future.SetFilter(TimeSpan.Zero, TimeSpan.FromDays(182)); + _futureSymbol = future.Symbol; + + var seeder = new FuncSecuritySeeder(GetLastKnownPrices); + SetSecurityInitializer(security => seeder.SeedSecurity(security)); + } + + /// + /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. + /// + /// Slice object keyed by symbol containing the stock data + public override void OnData(Slice slice) + { + foreach (var changedEvent in slice.SymbolChangedEvents.Values) + { + Debug($"{Time} - SymbolChanged event: {changedEvent}"); + if (Time.TimeOfDay != TimeSpan.Zero) + { + throw new RegressionTestException($"{Time} unexpected symbol changed event {changedEvent}!"); + } + _symbolChangeEvent++; + } + + if (!Portfolio.Invested) + { + foreach (var chain in slice.FutureChains) + { + // find the front contract expiring no earlier than in 90 days + var contract = ( + from futuresContract in chain.Value.OrderBy(x => x.Expiry) + select futuresContract + ).FirstOrDefault(); + + // if found, trade it + if (contract != null) + { + _contractSymbol = contract.Symbol; + MarketOrder(_contractSymbol, 1); + } + } + } + else + { + Liquidate(); + } + } + + public override void OnEndOfAlgorithm() + { + if (_symbolChangeEvent != 1) + { + throw new RegressionTestException($"Got no expected symbol changed event count {_symbolChangeEvent}!"); + } + + // Get the margin requirements + var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel; + var futureMarginModel = buyingPowerModel as FutureMarginModel; + if (buyingPowerModel == null) + { + throw new RegressionTestException($"Invalid buying power model. Found: {buyingPowerModel.GetType().Name}. Expected: {nameof(FutureMarginModel)}"); + } + var initialOvernight = futureMarginModel.InitialOvernightMarginRequirement; + var maintenanceOvernight = futureMarginModel.MaintenanceOvernightMarginRequirement; + var initialIntraday = futureMarginModel.InitialIntradayMarginRequirement; + var maintenanceIntraday = futureMarginModel.MaintenanceIntradayMarginRequirement; + + var lastDataFuture = Securities[_futureSymbol].GetLastData(); + if (lastDataFuture == null || (lastDataFuture.EndTime - lastDataFuture.Time) != TimeSpan.FromHours(Resolution == Resolution.Hour ? 1 : 7.25) + || lastDataFuture.EndTime.Date != lastDataFuture.Time.Date) + { + throw new RegressionTestException($"Unexpected data for symbol {_futureSymbol}!"); + } + + var lastDataIndex = Securities[_index].GetLastData(); + if (lastDataIndex == null || (lastDataIndex.EndTime - lastDataIndex.Time) != TimeSpan.FromHours(Resolution == Resolution.Hour ? 1 : 6.5) + || lastDataFuture.EndTime.Date != lastDataFuture.Time.Date) + { + throw new RegressionTestException($"Unexpected data for symbol {_index}!"); + } + } + + public override void OnSecuritiesChanged(SecurityChanges changes) + { + foreach (var addedSecurity in changes.AddedSecurities) + { + if (addedSecurity.Symbol.SecurityType == SecurityType.Future + && !addedSecurity.Symbol.IsCanonical() + && !addedSecurity.HasData) + { + throw new RegressionTestException($"Future contracts did not work up as expected: {addedSecurity.Symbol}"); + } + } + } + + /// + /// 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 }; + + /// + /// Data Points count of all timeslices of algorithm + /// + public virtual long DataPoints => 823; + + /// + /// Data Points count of the algorithm history + /// + public virtual int AlgorithmHistoryDataPoints => 25; + + /// + /// Final status of the algorithm + /// + public virtual AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; + + /// + /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm + /// + public virtual Dictionary ExpectedStatistics => new Dictionary + { + {"Total Orders", "57"}, + {"Average Win", "1.83%"}, + {"Average Loss", "-1.31%"}, + {"Compounding Annual Return", "-99.930%"}, + {"Drawdown", "23.000%"}, + {"Expectancy", "-0.572"}, + {"Start Equity", "100000"}, + {"End Equity", "80330"}, + {"Net Profit", "-19.670%"}, + {"Sharpe Ratio", "-1.298"}, + {"Sortino Ratio", "-1.254"}, + {"Probabilistic Sharpe Ratio", "1.073%"}, + {"Loss Rate", "82%"}, + {"Win Rate", "18%"}, + {"Profit-Loss Ratio", "1.40"}, + {"Alpha", "0"}, + {"Beta", "0"}, + {"Annual Standard Deviation", "0.772"}, + {"Annual Variance", "0.596"}, + {"Information Ratio", "-1.288"}, + {"Tracking Error", "0.772"}, + {"Treynor Ratio", "0"}, + {"Total Fees", "$2280.00"}, + {"Estimated Strategy Capacity", "$120000000.00"}, + {"Lowest Capacity Asset", "HSI VL6DN7UV65S9"}, + {"Portfolio Turnover", "7099.25%"}, + {"OrderListHash", "174bdb031f17212dc9d92372f4fb75c2"} + }; + } +} diff --git a/Algorithm/QCAlgorithm.Indicators.cs b/Algorithm/QCAlgorithm.Indicators.cs index 00d605aa9ded..23f3ea01221f 100644 --- a/Algorithm/QCAlgorithm.Indicators.cs +++ b/Algorithm/QCAlgorithm.Indicators.cs @@ -3881,7 +3881,8 @@ private IDataConsolidator CreateConsolidator(Symbol symbol, Func public bool DailyPreciseEndTime { get; set; } + /// + /// True if extended market hours should be used for daily consolidation, when extended market hours is enabled + /// + public bool DailyConsolidationUseExtendedMarketHours { get; set; } + /// /// Gets the time span used to refresh the market hours and symbol properties databases /// diff --git a/Common/Extensions.cs b/Common/Extensions.cs index 15c7c7961421..4044b0a003f2 100644 --- a/Common/Extensions.cs +++ b/Common/Extensions.cs @@ -3739,6 +3739,27 @@ public static IEnumerable> SafeEnumeration + /// Helper method to determine the right data mapping mode to use by default + /// + public static DataMappingMode GetUniverseNormalizationModeOrDefault(this UniverseSettings universeSettings, SecurityType securityType, string market) + { + switch (securityType) + { + case SecurityType.Future: + if ((universeSettings.DataMappingMode == DataMappingMode.OpenInterest + || universeSettings.DataMappingMode == DataMappingMode.OpenInterestAnnual) + && (market == Market.HKFE || market == Market.EUREX || market == Market.ICE)) + { + // circle around default OI for currently no OI available data + return DataMappingMode.LastTradingDay; + } + return universeSettings.DataMappingMode; + default: + return universeSettings.DataMappingMode; + } + } + /// /// Helper method to determine the right data normalization mode to use by default /// diff --git a/Common/Interfaces/IAlgorithmSettings.cs b/Common/Interfaces/IAlgorithmSettings.cs index 2e3918d81596..e3130b78af55 100644 --- a/Common/Interfaces/IAlgorithmSettings.cs +++ b/Common/Interfaces/IAlgorithmSettings.cs @@ -83,6 +83,11 @@ public interface IAlgorithmSettings /// bool DailyPreciseEndTime { get; set; } + /// + /// True if extended market hours should be used for daily consolidation, when extended market hours is enabled + /// + bool DailyConsolidationUseExtendedMarketHours { get; set; } + /// /// Gets/sets the maximum number of concurrent market data subscriptions available /// diff --git a/Common/Orders/TimeInForces/DayTimeInForce.cs b/Common/Orders/TimeInForces/DayTimeInForce.cs index c8ac760bf134..7966f7092d41 100644 --- a/Common/Orders/TimeInForces/DayTimeInForce.cs +++ b/Common/Orders/TimeInForces/DayTimeInForce.cs @@ -72,7 +72,7 @@ public override bool IsOrderExpired(Security security, Order order) case SecurityType.IndexOption: default: // expires at market close - expired = time >= exchangeHours.GetNextMarketClose(orderTime, false); + expired = time >= exchangeHours.GetLastDailyMarketClose(orderTime, false); break; } diff --git a/Common/Orders/TimeInForces/GoodTilDateTimeInForce.cs b/Common/Orders/TimeInForces/GoodTilDateTimeInForce.cs index 43ad18384687..691c4ce779ef 100644 --- a/Common/Orders/TimeInForces/GoodTilDateTimeInForce.cs +++ b/Common/Orders/TimeInForces/GoodTilDateTimeInForce.cs @@ -81,7 +81,7 @@ public override bool IsOrderExpired(Security security, Order order) case SecurityType.IndexOption: default: // expires at market close of expiry date - expired = time >= exchangeHours.GetNextMarketClose(Expiry.Date, false); + expired = time >= exchangeHours.GetLastDailyMarketClose(Expiry.Date, false); break; } diff --git a/Common/Scheduling/TimeRules.cs b/Common/Scheduling/TimeRules.cs index 0e02dcb04eb2..ab8511ba6f8f 100644 --- a/Common/Scheduling/TimeRules.cs +++ b/Common/Scheduling/TimeRules.cs @@ -182,7 +182,7 @@ public ITimeRule AfterMarketOpen(Symbol symbol, double minutesAfterOpen = 0, boo var timeAfterOpen = TimeSpan.FromMinutes(minutesAfterOpen); Func, IEnumerable> applicator = dates => from date in dates - let marketOpen = exchangeHours.GetNextMarketOpen(date, extendedMarketOpen) + let marketOpen = exchangeHours.GetFirstDailyMarketOpen((date + Time.OneDay).AddTicks(-1), extendedMarketOpen) // make sure the market open is of this date where exchangeHours.IsDateOpen(date, extendedMarketOpen) && marketOpen.Date == date.Date let localEventTime = marketOpen + timeAfterOpen @@ -221,9 +221,9 @@ public ITimeRule BeforeMarketClose(Symbol symbol, double minutesBeforeClose = 0, var timeBeforeClose = TimeSpan.FromMinutes(minutesBeforeClose); Func, IEnumerable> applicator = dates => from date in dates - let marketClose = exchangeHours.GetNextMarketClose(date, extendedMarketClose) + let marketClose = exchangeHours.GetLastDailyMarketClose(date, extendedMarketClose) // make sure the market open is of this date - where exchangeHours.IsDateOpen(date, extendedMarketClose) && marketClose.Date == date.Date + where exchangeHours.IsDateOpen(date, extendedMarketClose) let localEventTime = marketClose - timeBeforeClose let utcEventTime = localEventTime.ConvertToUtc(exchangeHours.TimeZone) select utcEventTime; diff --git a/Common/Securities/LocalMarketHours.cs b/Common/Securities/LocalMarketHours.cs index e68663b79352..bb7c77b7b403 100644 --- a/Common/Securities/LocalMarketHours.cs +++ b/Common/Securities/LocalMarketHours.cs @@ -25,6 +25,22 @@ namespace QuantConnect.Securities /// public class LocalMarketHours { + private static readonly LocalMarketHours _closedMonday = new(DayOfWeek.Monday); + private static readonly LocalMarketHours _closedTuesday = new(DayOfWeek.Tuesday); + private static readonly LocalMarketHours _closedWednesday = new(DayOfWeek.Wednesday); + private static readonly LocalMarketHours _closedThursday = new(DayOfWeek.Thursday); + private static readonly LocalMarketHours _closedFriday = new(DayOfWeek.Friday); + private static readonly LocalMarketHours _closedSaturday = new(DayOfWeek.Saturday); + private static readonly LocalMarketHours _closedSunday = new(DayOfWeek.Sunday); + + private static readonly LocalMarketHours _openMonday = new(DayOfWeek.Monday, new MarketHoursSegment(MarketHoursState.Market, TimeSpan.Zero, Time.OneDay)); + private static readonly LocalMarketHours _openTuesday = new(DayOfWeek.Tuesday, new MarketHoursSegment(MarketHoursState.Market, TimeSpan.Zero, Time.OneDay)); + private static readonly LocalMarketHours _openWednesday = new(DayOfWeek.Wednesday, new MarketHoursSegment(MarketHoursState.Market, TimeSpan.Zero, Time.OneDay)); + private static readonly LocalMarketHours _openThursday = new(DayOfWeek.Thursday, new MarketHoursSegment(MarketHoursState.Market, TimeSpan.Zero, Time.OneDay)); + private static readonly LocalMarketHours _openFriday = new(DayOfWeek.Friday, new MarketHoursSegment(MarketHoursState.Market, TimeSpan.Zero, Time.OneDay)); + private static readonly LocalMarketHours _openSaturday = new(DayOfWeek.Saturday, new MarketHoursSegment(MarketHoursState.Market, TimeSpan.Zero, Time.OneDay)); + private static readonly LocalMarketHours _openSunday = new(DayOfWeek.Sunday, new MarketHoursSegment(MarketHoursState.Market, TimeSpan.Zero, Time.OneDay)); + /// /// Gets whether or not this exchange is closed all day /// @@ -171,6 +187,22 @@ public LocalMarketHours(DayOfWeek day, TimeSpan marketOpen, TimeSpan marketClose /// The market's closing time of day public TimeSpan? GetMarketClose(TimeSpan time, bool extendedMarketHours, TimeSpan? nextDaySegmentStart = null) { + return GetMarketClose(time, extendedMarketHours, lastClose: false, nextDaySegmentStart); + } + + /// + /// Gets the market closing time of day + /// + /// The reference time, the close returned will be the first close after the specified time if there are multiple market open segments + /// True to include extended market hours, false for regular market hours + /// True if the last available close of the date should be returned, else the first will be used + /// Next day first segment start. This is used when the potential next market close is + /// the last segment of the day so we need to check that segment is not continued on next day first segment. + /// If null, it means there are no segments on the next day + /// The market's closing time of day + public TimeSpan? GetMarketClose(TimeSpan time, bool extendedMarketHours, bool lastClose, TimeSpan? nextDaySegmentStart = null) + { + TimeSpan? potentialResult = null; TimeSpan? nextSegment; bool nextSegmentIsFromNextDay = false; for (var i = 0; i < Segments.Count; i++) @@ -201,15 +233,20 @@ public LocalMarketHours(DayOfWeek day, TimeSpan marketOpen, TimeSpan marketClose nextSegmentIsFromNextDay = true; } - if ((segment.State == MarketHoursState.Market || extendedMarketHours) && - !IsContinuousMarketOpen(segment.End, nextSegment, nextSegmentIsFromNextDay)) + if ((segment.State == MarketHoursState.Market || extendedMarketHours)) { - return segment.End; + if (lastClose) + { + // we continue, there might be another close next + potentialResult = segment.End; + } + else if (!IsContinuousMarketOpen(segment.End, nextSegment, nextSegmentIsFromNextDay)) + { + return segment.End; + } } } - - // we couldn't locate an open segment after the specified time - return null; + return potentialResult; } /// @@ -280,7 +317,25 @@ public bool IsOpen(TimeSpan start, TimeSpan end, bool extendedMarketHours) /// A instance that is always closed public static LocalMarketHours ClosedAllDay(DayOfWeek dayOfWeek) { - return new LocalMarketHours(dayOfWeek); + switch (dayOfWeek) + { + case DayOfWeek.Sunday: + return _closedSunday; + case DayOfWeek.Monday: + return _closedMonday; + case DayOfWeek.Tuesday: + return _closedTuesday; + case DayOfWeek.Wednesday: + return _closedWednesday; + case DayOfWeek.Thursday: + return _closedThursday; + case DayOfWeek.Friday: + return _closedFriday; + case DayOfWeek.Saturday: + return _closedSaturday; + default: + throw new ArgumentOutOfRangeException(nameof(dayOfWeek)); + } } /// @@ -290,7 +345,25 @@ public static LocalMarketHours ClosedAllDay(DayOfWeek dayOfWeek) /// A instance that is always open public static LocalMarketHours OpenAllDay(DayOfWeek dayOfWeek) { - return new LocalMarketHours(dayOfWeek, new MarketHoursSegment(MarketHoursState.Market, TimeSpan.Zero, Time.OneDay)); + switch (dayOfWeek) + { + case DayOfWeek.Sunday: + return _openSunday; + case DayOfWeek.Monday: + return _openMonday; + case DayOfWeek.Tuesday: + return _openTuesday; + case DayOfWeek.Wednesday: + return _openWednesday; + case DayOfWeek.Thursday: + return _openThursday; + case DayOfWeek.Friday: + return _openFriday; + case DayOfWeek.Saturday: + return _openSaturday; + default: + throw new ArgumentOutOfRangeException(nameof(dayOfWeek)); + } } /// diff --git a/Common/Securities/Option/OptionSymbol.cs b/Common/Securities/Option/OptionSymbol.cs index 9d2ac51404de..b82146594c38 100644 --- a/Common/Securities/Option/OptionSymbol.cs +++ b/Common/Securities/Option/OptionSymbol.cs @@ -177,7 +177,7 @@ private static bool TryGetExpirationDateTime(Symbol symbol, out DateTime expiryT ? symbol.ID.Date : exchangeHours.GetPreviousTradingDay(symbol.ID.Date); - expiryTime = exchangeHours.GetNextMarketClose(lastTradingDay, false); + expiryTime = exchangeHours.GetLastDailyMarketClose(lastTradingDay, false); // Once bug 6189 was solved in ´GetNextMarketClose()´ there was found possible bugs on some futures symbol.ID.Date or delisting/liquidation handle event. // Specifically see 'DelistingFutureOptionRegressionAlgorithm' where Symbol.ID.Date: 4/1/2012 00:00 ExpiryTime: 4/2/2012 16:00 for Milk 3 futures options. diff --git a/Common/Securities/SecurityExchangeHours.cs b/Common/Securities/SecurityExchangeHours.cs index d8012ed75b9d..0c6154a87a1f 100644 --- a/Common/Securities/SecurityExchangeHours.cs +++ b/Common/Securities/SecurityExchangeHours.cs @@ -226,6 +226,17 @@ public bool IsDateOpen(DateTime localDateTime, bool extendedMarketHours = false) return true; } + /// + /// Gets the local date time corresponding to the first market open to the specified previous date + /// + /// The time to begin searching for the last market open (non-inclusive) + /// True to include extended market hours in the search + /// The previous market opening date time to the specified local date time + public DateTime GetFirstDailyMarketOpen(DateTime localDateTime, bool extendedMarketHours) + { + return GetPreviousMarketOpen(localDateTime, extendedMarketHours, firstOpen: true); + } + /// /// Gets the local date time corresponding to the previous market open to the specified time /// @@ -233,6 +244,17 @@ public bool IsDateOpen(DateTime localDateTime, bool extendedMarketHours = false) /// True to include extended market hours in the search /// The previous market opening date time to the specified local date time public DateTime GetPreviousMarketOpen(DateTime localDateTime, bool extendedMarketHours) + { + return GetPreviousMarketOpen(localDateTime, extendedMarketHours, firstOpen: false); + } + + /// + /// Gets the local date time corresponding to the previous market open to the specified time + /// + /// The time to begin searching for the last market open (non-inclusive) + /// True to include extended market hours in the search + /// The previous market opening date time to the specified local date time + public DateTime GetPreviousMarketOpen(DateTime localDateTime, bool extendedMarketHours, bool firstOpen) { var time = localDateTime; var marketHours = GetMarketHours(time); @@ -246,20 +268,30 @@ public DateTime GetPreviousMarketOpen(DateTime localDateTime, bool extendedMarke // let's loop for a week for (int i = 0; i < 7; i++) { + DateTime? potentialResult = null; foreach(var segment in marketHours.Segments.Reverse()) { if ((time.Date + segment.Start <= localDateTime) && (segment.State == MarketHoursState.Market || extendedMarketHours)) { - // Check the current segment is not part of another segment before var timeOfDay = time.Date + segment.Start; - if (GetNextMarketOpen(timeOfDay.AddTicks(-1), extendedMarketHours) == timeOfDay) + if (firstOpen) + { + potentialResult = timeOfDay; + } + // Check the current segment is not part of another segment before + else if (GetNextMarketOpen(timeOfDay.AddTicks(-1), extendedMarketHours) == timeOfDay) { return timeOfDay; } } } + if (potentialResult.HasValue) + { + return potentialResult.Value; + } + time = time.AddDays(-1); marketHours = GetMarketHours(time); } @@ -320,6 +352,17 @@ public DateTime GetNextMarketOpen(DateTime localDateTime, bool extendedMarketHou throw new ArgumentException(Messages.SecurityExchangeHours.UnableToLocateNextMarketOpenInTwoWeeks); } + /// + /// Gets the local date time corresponding to the last market close following the specified date + /// + /// The time to begin searching for market close (non-inclusive) + /// True to include extended market hours in the search + /// The next market closing date time following the specified local date time + public DateTime GetLastDailyMarketClose(DateTime localDateTime, bool extendedMarketHours) + { + return GetNextMarketClose(localDateTime, extendedMarketHours, lastClose: true); + } + /// /// Gets the local date time corresponding to the next market close following the specified time /// @@ -327,6 +370,18 @@ public DateTime GetNextMarketOpen(DateTime localDateTime, bool extendedMarketHou /// True to include extended market hours in the search /// The next market closing date time following the specified local date time public DateTime GetNextMarketClose(DateTime localDateTime, bool extendedMarketHours) + { + return GetNextMarketClose(localDateTime, extendedMarketHours, lastClose: false); + } + + /// + /// Gets the local date time corresponding to the next market close following the specified time + /// + /// The time to begin searching for market close (non-inclusive) + /// True to include extended market hours in the search + /// True if the last available close of the date should be returned, else the first will be used + /// The next market closing date time following the specified local date time + public DateTime GetNextMarketClose(DateTime localDateTime, bool extendedMarketHours, bool lastClose) { var time = localDateTime; var oneWeekLater = localDateTime.Date.AddDays(15); @@ -340,7 +395,7 @@ public DateTime GetNextMarketClose(DateTime localDateTime, bool extendedMarketHo // the next day first segment for the case in which the next market close is the last segment // of the current day var nextSegment = GetNextOrPreviousSegment(time, isNextDay: true); - var marketCloseTimeOfDay = marketHours.GetMarketClose(time.TimeOfDay, extendedMarketHours, nextSegment?.Start); + var marketCloseTimeOfDay = marketHours.GetMarketClose(time.TimeOfDay, extendedMarketHours, lastClose, nextSegment?.Start); if (marketCloseTimeOfDay.HasValue) { var marketClose = time.Date + marketCloseTimeOfDay.Value; @@ -448,7 +503,7 @@ public LocalMarketHours GetMarketHours(DateTime localDateTime) { if (_holidays.Contains(localDateTime.Date.Ticks)) { - return new LocalMarketHours(localDateTime.DayOfWeek); + return LocalMarketHours.ClosedAllDay(localDateTime.DayOfWeek); } LocalMarketHours marketHours; diff --git a/Common/Util/LeanData.cs b/Common/Util/LeanData.cs index 53ce93315c9d..21afbf95c1b9 100644 --- a/Common/Util/LeanData.cs +++ b/Common/Util/LeanData.cs @@ -1386,20 +1386,8 @@ public static CalendarInfo GetDailyCalendar(DateTime exchangeTimeZoneDate, Secur /// The calendar information that holds a start time and a period public static CalendarInfo GetDailyCalendar(DateTime exchangeTimeZoneDate, SecurityExchangeHours exchangeHours, bool extendedMarketHours) { - var startTime = exchangeHours.GetPreviousMarketOpen(exchangeTimeZoneDate, extendedMarketHours); - var endTime = exchangeHours.GetNextMarketClose(startTime, extendedMarketHours); - - // Let's not consider regular market gaps like when market closes at 16:15 and opens again at 16:30 - while (true) - { - var potentialEnd = exchangeHours.GetNextMarketClose(endTime, extendedMarketHours); - if (potentialEnd.Date != endTime.Date) - { - break; - } - endTime = potentialEnd; - } - + var startTime = exchangeHours.GetFirstDailyMarketOpen(exchangeTimeZoneDate, extendedMarketHours); + var endTime = exchangeHours.GetLastDailyMarketClose(startTime, extendedMarketHours); var period = endTime - startTime; return new CalendarInfo(startTime, period); } @@ -1415,7 +1403,7 @@ public static DateTime GetNextDailyEndTime(Symbol symbol, DateTime exchangeTimeZ return nextMidnight; } - var nextMarketClose = exchangeHours.GetNextMarketClose(exchangeTimeZoneDate, extendedMarketHours: false); + var nextMarketClose = exchangeHours.GetLastDailyMarketClose(exchangeTimeZoneDate, extendedMarketHours: false); if (nextMarketClose > nextMidnight) { // if exchangeTimeZoneDate is after the previous close, the next close might be tomorrow diff --git a/Data/future/hkfe/daily/hsi_quote.zip b/Data/future/hkfe/daily/hsi_quote.zip new file mode 100644 index 0000000000000000000000000000000000000000..8fdf5a5846f103207b0869bccab09ae8483655be GIT binary patch literal 3449 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 b1LA*B`gaJv9o-86JUb@86FmIx@2kH7h2Ot% literal 0 HcmV?d00001 diff --git a/Data/future/hkfe/daily/hsi_trade.zip b/Data/future/hkfe/daily/hsi_trade.zip new file mode 100644 index 0000000000000000000000000000000000000000..3b8f60fc1ebaf4912084254ca89b38ea629887a8 GIT binary patch literal 1462 zcmWIWW@Zs#-~htg0UA*ZNI;fBfgz(fGrpuKF(ozL$iUFp&Ekkup zH#ye$E)|z5f3>do_c`7R7ms{tKV`FGS9)*nrlzQ)lad`ed6u}Wt=keIsqp?vW%^}# z-|Tzcdse2&HP}6Qv2#~S)WX8R(|;!{erY%Ro0I7Gu#XW}TaupK6}k9pLhrhW`jv9k z$;)rGecthyJHVTr!^S+JbuZ9+OiT<60q}5IoT^SJoD3l0WPm%IK;e@J4X4;io_R+N zc!1$_)V@J{R{OpG2~(%s`1it1peem=iEjhX!4Hf-SR-1xY;((I##kxh*j0 z`Yf@FyT6DR$Cm`yteSEsBkLN^Ed3STk>*{}sTXEmWOrhpdh7FwPoZC}nJYqaXC83t z4_`L3CrH>h?fKFK>E7;F#kAInaKB!4DnFW0`e(z@P>;MHEsHF|9CtIJozS)1vnS;-kx?ar*NQ|Gce_pE1beYc$8He2NdOnf|#mKU|Wpe6_ z3=9&?B*us#Bt{I4EODh=P>dwOQtqUa6IBd2SpH`pl~}<3-SD6EHV5-dH5^V}3l}vo zNKSgl{y_YJKhs}ENA|JUDW=jN1Z&bdV_}umHqNZIFgOak28=iMvA9n1?x>J@~Yd>3U zKcMgRh2@32!*2(9Ll5_ti$2|&)X6)MyVU39vujLMc|SKSoE+T!^_5}AK4*=c{<%F- zR|9XWhXe~NE@++4{ot}l!Sw$;%VaK2>f^Q-y;iVtPE7dr4NL1EnsXI>zu7vc{c;57 zb(P1h^TpZBOsl1rY+tQ%Td=sd_3J^atH+Ek=ZSgkH$2$-{?mrw<1P0%F3+2Lz1Vk8 z-k+KCif2E&c~R%tk~PjL(LYW-kZDh^dvjs;vmk?+u|Iz1Y&J8rlh|(DSh?gIqgdT< zvAz3Czn^?Aeo$EIBRKM$G}bH(0!H3kaO5#Ei7+Dy9^|Yh%OC+ShPE|=n4}d$tPG5x zA}GKcVFf79BBvcxD>Q(PLbHNE4hC8QN-yYEAo~#2ie11W1kDNpNfK@aD;p>Xn1N6e K=+1I*SqlIsQw}o# literal 0 HcmV?d00001 diff --git a/Data/future/hkfe/factor_files/hsi.csv b/Data/future/hkfe/factor_files/hsi.csv new file mode 100644 index 000000000000..c9a6c63dd0c3 --- /dev/null +++ b/Data/future/hkfe/factor_files/hsi.csv @@ -0,0 +1,27 @@ +{"Date":"2012-12-31T00:00:00","BackwardsRatioScale":[0.9973412799992576020223162631,1.016861217641254691395179536,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[-385.0,187.0,-443.0],"ForwardPanamaCanalScale":[701.0,444.0,0.0],"DataMappingMode":1} +{"Date":"2013-01-29T00:00:00","BackwardsRatioScale":[0.6774221353848421444584873569,1.1473277918298587589614212406,1.0405749041409657864830738468],"BackwardsPanamaCanalScale":[-9324.0,2492.0,510.0],"ForwardPanamaCanalScale":[838.0,448.0,-8.0],"DataMappingMode":0} +{"Date":"2013-01-31T00:00:00","BackwardsRatioScale":[0.9964184262041492341156201344,1.019196455167966931113968978,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[-406.0,239.0,-443.0],"ForwardPanamaCanalScale":[749.0,444.0,0.0],"DataMappingMode":1} +{"Date":"2013-02-26T00:00:00","BackwardsRatioScale":[0.6764218495670777485990582949,1.1495101297549726166059078395,1.0405749041409657864830738468],"BackwardsPanamaCanalScale":[-9359.0,2537.0,510.0],"ForwardPanamaCanalScale":[908.0,448.0,-8.0],"DataMappingMode":0} +{"Date":"2013-02-28T00:00:00","BackwardsRatioScale":[0.9984341074439418488845660956,1.019196455167966931113968978,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[-358.0,239.0,-443.0],"ForwardPanamaCanalScale":[744.0,444.0,0.0],"DataMappingMode":1} +{"Date":"2013-03-29T00:00:00","BackwardsRatioScale":[0.6785274896911358907434943727,1.1495101297549726166059078395,1.0405749041409657864830738468],"BackwardsPanamaCanalScale":[-9289.0,2537.0,510.0],"ForwardPanamaCanalScale":[1039.0,594.0,247.0],"DataMappingMode":0} +{"Date":"2013-03-31T00:00:00","BackwardsRatioScale":[0.9982168676468478667590472605,1.019196455167966931113968978,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[-363.0,239.0,-443.0],"ForwardPanamaCanalScale":[890.0,699.0,0.0],"DataMappingMode":1} +{"Date":"2013-04-28T00:00:00","BackwardsRatioScale":[0.6825106005151876384441305872,1.157080200974829450256086574,1.0526828941275478661341431523],"BackwardsPanamaCanalScale":[-9158.0,2683.0,765.0],"ForwardPanamaCanalScale":[1140.0,1013.0,247.0],"DataMappingMode":0} +{"Date":"2013-04-30T00:00:00","BackwardsRatioScale":[1.0047906007400566979970635393,1.0310556883903183601549939422,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[-217.0,494.0,-443.0],"ForwardPanamaCanalScale":[1149.0,699.0,0.0],"DataMappingMode":1} +{"Date":"2013-05-29T00:00:00","BackwardsRatioScale":[0.6855808646632383221909173852,1.1790843131796337683088846621,1.0526828941275478661341431523],"BackwardsPanamaCanalScale":[-9057.0,3102.0,765.0],"ForwardPanamaCanalScale":[1401.0,1013.0,247.0],"DataMappingMode":0} +{"Date":"2013-05-31T00:00:00","BackwardsRatioScale":[1.0164370883476501052372174644,1.0310556883903183601549939422,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[42.0,494.0,-443.0],"ForwardPanamaCanalScale":[1149.0,699.0,0.0],"DataMappingMode":1} +{"Date":"2013-06-28T00:00:00","BackwardsRatioScale":[0.6934966623043400785320732993,1.1790843131796337683088846621,1.0526828941275478661341431523],"BackwardsPanamaCanalScale":[-8796.0,3102.0,765.0],"ForwardPanamaCanalScale":[1221.0,1013.0,247.0],"DataMappingMode":0} +{"Date":"2013-06-30T00:00:00","BackwardsRatioScale":[1.0164370883476501052372174644,1.0310556883903183601549939422,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[42.0,494.0,-443.0],"ForwardPanamaCanalScale":[1149.0,699.0,0.0],"DataMappingMode":1} +{"Date":"2013-07-29T00:00:00","BackwardsRatioScale":[0.6874746931203571939676575921,1.1790843131796337683088846621,1.0526828941275478661341431523],"BackwardsPanamaCanalScale":[-8976.0,3102.0,765.0],"ForwardPanamaCanalScale":[1221.0,1013.0,247.0],"DataMappingMode":0} +{"Date":"2013-07-31T00:00:00","BackwardsRatioScale":[1.0164370883476501052372174644,1.0310556883903183601549939422,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[42.0,494.0,-443.0],"ForwardPanamaCanalScale":[1286.0,699.0,0.0],"DataMappingMode":1} +{"Date":"2013-08-29T00:00:00","BackwardsRatioScale":[0.6874746931203571939676575921,1.1790843131796337683088846621,1.0526828941275478661341431523],"BackwardsPanamaCanalScale":[-8976.0,3102.0,765.0],"ForwardPanamaCanalScale":[1450.0,1013.0,247.0],"DataMappingMode":0} +{"Date":"2013-08-31T00:00:00","BackwardsRatioScale":[1.0228462511912837437707241056,1.0310556883903183601549939422,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[179.0,494.0,-443.0],"ForwardPanamaCanalScale":[1134.0,699.0,0.0],"DataMappingMode":1} +{"Date":"2013-09-28T00:00:00","BackwardsRatioScale":[0.6948350982222582551401571754,1.1790843131796337683088846621,1.0526828941275478661341431523],"BackwardsPanamaCanalScale":[-8747.0,3102.0,765.0],"ForwardPanamaCanalScale":[1465.0,1013.0,247.0],"DataMappingMode":0} +{"Date":"2013-09-30T00:00:00","BackwardsRatioScale":[1.0156287297121938626578161596,1.0310556883903183601549939422,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[27.0,494.0,-443.0],"ForwardPanamaCanalScale":[1134.0,677.0,0.0],"DataMappingMode":1} +{"Date":"2013-10-29T00:00:00","BackwardsRatioScale":[0.6952849849781322946716935141,1.1790843131796337683088846621,1.0526828941275478661341431523],"BackwardsPanamaCanalScale":[-8732.0,3102.0,765.0],"ForwardPanamaCanalScale":[1544.0,914.0,247.0],"DataMappingMode":0} +{"Date":"2013-10-31T00:00:00","BackwardsRatioScale":[1.0156287297121938626578161596,1.0300654572319568547669723826,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[27.0,472.0,-443.0],"ForwardPanamaCanalScale":[613.0,677.0,0.0],"DataMappingMode":1} +{"Date":"2013-11-28T00:00:00","BackwardsRatioScale":[0.6976953550770120757529232749,1.1739840664454709796925269227,1.0526828941275478661341431523],"BackwardsPanamaCanalScale":[-8653.0,3003.0,765.0],"ForwardPanamaCanalScale":[1619.0,914.0,247.0],"DataMappingMode":0} +{"Date":"2013-11-30T00:00:00","BackwardsRatioScale":[0.9929275169540295054376556114,1.0300654572319568547669723826,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[-494.0,472.0,-443.0],"ForwardPanamaCanalScale":[613.0,677.0,0.0],"DataMappingMode":1} +{"Date":"2013-12-29T00:00:00","BackwardsRatioScale":[0.6998878947414206865666947174,1.1739840664454709796925269227,1.0526828941275478661341431523],"BackwardsPanamaCanalScale":[-8578.0,3003.0,765.0],"ForwardPanamaCanalScale":[1619.0,914.0,247.0],"DataMappingMode":0} +{"Date":"2013-12-31T00:00:00","BackwardsRatioScale":[0.9929275169540295054376556114,1.0300654572319568547669723826,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[-494.0,472.0,-443.0],"ForwardPanamaCanalScale":[614.0,731.0,0.0],"DataMappingMode":1} +{"Date":"2014-01-29T00:00:00","BackwardsRatioScale":[0.6998878947414206865666947174,1.1739840664454709796925269227,1.0526828941275478661341431523],"BackwardsPanamaCanalScale":[-8578.0,3003.0,765.0],"ForwardPanamaCanalScale":[1704.0,973.0,247.0],"DataMappingMode":0} +{"Date":"2014-01-31T00:00:00","BackwardsRatioScale":[0.9929700734222685775062925759,1.0324549896097610333973279333,0.9756059166740907303396051538],"BackwardsPanamaCanalScale":[-493.0,526.0,-443.0],"ForwardPanamaCanalScale":[696.0,731.0,0.0],"DataMappingMode":1} \ No newline at end of file diff --git a/Data/future/hkfe/hour/hsi_quote.zip b/Data/future/hkfe/hour/hsi_quote.zip new file mode 100644 index 0000000000000000000000000000000000000000..240f6bcccfe93e6cda55fe051c5921c17e3fea6d GIT binary patch literal 5026 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 literal 0 HcmV?d00001 diff --git a/Data/future/hkfe/map_files/hsi.csv b/Data/future/hkfe/map_files/hsi.csv new file mode 100644 index 000000000000..d20857bc4faa --- /dev/null +++ b/Data/future/hkfe/map_files/hsi.csv @@ -0,0 +1,25 @@ +18991230,hsi +20130129,hsi vdpklozsa6yx,,0 +20130131,hsi veh54b8ifbgp,,1 +20130226,hsi veh54b8ifbgp,,0 +20130228,hsi vfbnz7kqspft,,1 +20130329,hsi vfbnz7kqspft,,0 +20130331,hsi vg57e0jt3c95,,1 +20130428,hsi vg57e0jt3c95,,0 +20130430,hsi vgzq8ww1gq89,,1 +20130529,hsi vgzq8ww1gq89,,0 +20130531,hsi vht9npv3rd1l,,1 +20130628,hsi vht9npv3rd1l,,0 +20130630,hsi vinsim7c4r0p,,1 +20130729,hsi vinsim7c4r0p,,0 +20130731,hsi vjibdijki4zt,,1 +20130829,hsi vjibdijki4zt,,0 +20130831,hsi vkbusbimsrt5,,1 +20130928,hsi vkbusbimsrt5,,0 +20130930,hsi vl6dn7uv65s9,,1 +20131029,hsi vl6dn7uv65s9,,0 +20131031,hsi vlzx20txgsll,,1 +20131128,hsi vlzx20txgsll,,0 +20131130,hsi vmufwx65u6kp,,1 +20131229,hsi vmufwx65u6kp,,0 +20131231,hsi vnoyrtie7kjt,,1 \ No newline at end of file diff --git a/Data/index/hkfe/daily/hsi.zip b/Data/index/hkfe/daily/hsi.zip new file mode 100644 index 0000000000000000000000000000000000000000..8524c3e23428fef967d0126e587e15bad0d5ec94 GIT binary patch literal 988 zcmWIWW@Zs#-~d9#V2vmSB*4y~z>ra#sh3<_R$9T$z{moU0~4VkybSC`HFfDAyfC{q z-8VE=Xt5$k_5I*Cj8;7Mi#C0?zrmJsbb8<4V;&rgQ;u3h{5Sfa{BOmP8(Y3CUe(-r z{I~11C6;!7-1$$`DVUo6;hVojSkh;=-G>D?cqPOYyft2Rnz2Dh1cGo#meO$dj?1;}g$=?8%}Wm9~wKlCC8M9Z%dXnC<#RI788O zBe&4L1qMr1#H%k)V^Ll_cOo<6QE6p+o_`j(4L(yc&oJeF;t@%nXs__rp|9Nb;*QlP z^(y4wCUJQt@3L$B9=?(J|5hWxD;c8J9?Ny-#=P`tJvmAC(s>q#V#&7?Svg8 zJdKVEUl1FT*Zu9(VX>+uxBV3siFx7*5XN1+5%=>KQnQO*^8>H6TFHl_zp#}H7aP&t#G`)#^YK5*Pi|* z>2fS4{beg!?cPKca4vl`r7$g2qG~#ikjmQa>)3_e)0}sEO`QCfS7lPW{0X`Bi)yl` zM5cFg+_&ugv+22qx7-8+`+eLJON|`m-x*DooH^yaTouFC2S$uq<{Uq_&kI}{cKW*B zlBa3YOX{?iJ!RWqBK1^(<1znEceXpcwS2pnMZaa)drlMIV!gOliX+ABY?eYr=&PBb zu{Dlwtv#G#?HB=hbSqK9aqB$yU8(w&~|gy?-mj8yZ|ToU+I9H6v&5D~|TKr&s3i zOif5==w-8w-M6ncYeU3T$yh?`8XR}zo>5&W<=IQmSbm-fM>*QjUXnjjL6Es Y2+D*3-mGjOWsE@R3#2Q7mM|~?0RGQ}`v3p{ literal 0 HcmV?d00001 diff --git a/Data/index/hkfe/hour/hsi.zip b/Data/index/hkfe/hour/hsi.zip new file mode 100644 index 0000000000000000000000000000000000000000..bf4f2b743656742990d0625eca49afeabba9630d GIT binary patch literal 2535 zcmZ{mcQhN07RM7w2x@P&YW&RFv__2Dv(W?%@iTr!5j7HfRVa>7!yDOa1-mN z{1-MnERq!f1pn6llN^z30H%%Dc;D@_coY!l>y1L?$XtM)JVcBDbXtGb9$Q%B+F9IKACxg@fMg%cd zRVu|@i^&l{ikK4cF2Yj=h@8q>inRg=(W$+@Nd7441$utbYOLz08nL1~ziO-YiKCsn zeHg4Dp8WXAVc#5T41?E@ye(SBnHb`Rc*j!rROyts@%EMICZ^c zU{v5m$2TT~hzWsu?ZGX`RUa#afXoc?Gl)#z9e*E@zL=LNJPyU#oK92GJTg_Gg@Z4a zO3x+f71O(L{qXMIrsLXKEv;~w4PR$5h80k>l}_}$;bC-1Mag%Q^m-rJ@y zPR@isT>lI#z0Slu%IYG}`nJo`Nh9|1Fi4kH4o+vGU6V0UH}!CgG3xi0pX{|io+~S5 z&VItkUVDCvleppk;)b=CRvAK)rhNAswqtr-Z#Y~GcUH6;) z^tg}9_`bpF2Zac=8I&L9zFJdzeS+W7JWVUFy#%&v)CetWkmgv0kAo=_ccE)9Rc?!g z_#Q`N3=D1q{4QzgzADN-*hnh~ctuCA4|iH>zkvxnOg9d5Z*?HwOSAN2d7tuQ9u=cC zUC!*JuI%B=|DvAQpwvzM0uOViIgm-4UGAXGZ?co8``)55?2@M~JkWz;m5Pr6`kQHe zu27!v$F1CcW$RG@4v6^o1;0cb&6pb|advc>zsk-JQbRV@WjwUs;LQ z5 zKDoKB@0e(rPgrpgD=4Ei&TwlVG5KouIFe9?bAgrr!+0C$3Jxat*a{riS8gg0UyhHN zGo2|I9Bw-sFaEtwv9x_#(_EHD83|}}9B@i5;}NWK2cA6cXw~Z}PqrTr=D<8zT64G1 zWiKAjR%b<1+4CZ@>XyW|_Z97plKSdhU&;C$T`P53d2)Es9!)0EkHG?|u*4z3&Y+NG z?xHfM8@11#HhknV7XMz=?=?E|HHzU%&5zsf_Kb4Z$!ZtUL*e2QfAo_=(|I0D1Y|yo zeru<14lxVWFiToNDSgVz)cMm5V_CEh;5P1$RCd6vX`d&jL()?8pfl2!5U)**mD&Q5 zj1Pr)?_Q7?9_6bV^zwVNmr1cJwG=ts9t*TcR8Z11G07K2-~BVB=k*qv{Pds%`Jvwy z;<5wILO+LCjn*kjUV}0E7Sy3RRn-RHtucK~BgL!62YAFa#5Q+>j~U8VZ4mT|7Kp)XIWA5P)ltFr{_Ok2J9!nik3yHxsAJP4-%G9%kebD{$rKtptI$ z{cLZ@kz`%DXpwjG6r~NDr2s6Et(!GmpEJy5u9jw+jacy#RSjoF0?QA!HaA+yhVa%* zLH#?Ubd*x^vX6{3K6eDF8(3p=c>ysZg5j^Z$p(4W;L3-zcx0FpuIP21<_EFoTMOQQAM;CbZVTAFiJUh3y8;M|oiur0O=bg)A&{<$s5Z;edKMY&7k7+7)|g%?C>mlm2YUB$U;~QY<1YZaBh1sa~vG zzlrOYMHGVxq;VXvqWwCR~eZ*afM|KJ%S< zb14fk?DOGE*1S^vN$Q!+Use`BtB-*(Gn!hJ_v(YdMY}TDLt7!P zZ)_!UlQjD_HEL7nq8S+COjo?@aw+EFwt%r$M}dj1^*WDs!Nc{Xcj2kyr$Esuhwh)# zdUPMQ{IJTEY2G7e_LC4)l7bFHmseki@rkF`?g6ye;!wW>%Z{=Z@u5x}2|RzKVO%w|Jwn)|XC1#W{k{t7EwH<~ zxyH8fwkSe!{n*9r?6y~&vYQZWo`e~PQb6z2%f-C zI?la#yMc3iJ*rF89%+}+Sjfb#>^hrA^Dnp!6O`~Hj-tX009c6t0B{>1125=*6!uSz m2_X1iCTmLk-Tpr&g8+cPnG9|NVf?)YJafCVntXNy0Qfgjhnb@Q literal 0 HcmV?d00001 diff --git a/Engine/DataFeeds/AggregationManager.cs b/Engine/DataFeeds/AggregationManager.cs index 8fdce610b4dc..b587ff906918 100644 --- a/Engine/DataFeeds/AggregationManager.cs +++ b/Engine/DataFeeds/AggregationManager.cs @@ -149,9 +149,9 @@ protected virtual IDataConsolidator GetConsolidator(SubscriptionDataConfig confi var period = config.Resolution.ToTimeSpan(); if (config.Resolution == Resolution.Daily && (config.Type == typeof(QuoteBar) || config.Type == typeof(TradeBar))) { - // let's build daily bars that respect market hours data as requested by 'ExtendedMarketHours', - // also this allows us to enable the daily strict end times if required - return new MarketHourAwareConsolidator(_dailyStrictEndTimeEnabled, config.Resolution, typeof(Tick), config.TickType, config.ExtendedMarketHours); + // in backtesting, daily resolution data does not have extended market hours even if requested, so let's respect the same behavior for live + // also this allows us to enable the daily strict end times if required. See 'SetStrictEndTimes' + return new MarketHourAwareConsolidator(_dailyStrictEndTimeEnabled, config.Resolution, typeof(Tick), config.TickType, extendedMarketHours: false); } if (config.Type == typeof(QuoteBar)) { diff --git a/Engine/DataFeeds/Enumerators/FillForwardEnumerator.cs b/Engine/DataFeeds/Enumerators/FillForwardEnumerator.cs index fd6f795a2189..7702ae7ca416 100644 --- a/Engine/DataFeeds/Enumerators/FillForwardEnumerator.cs +++ b/Engine/DataFeeds/Enumerators/FillForwardEnumerator.cs @@ -99,10 +99,10 @@ public FillForwardEnumerator(IEnumerator enumerator, // Use the non strict end time calendar for the last day of data so that all data for that date is emitted. if (_useStrictEndTime && !_strictEndTimeIntraDayFillForward) { - var lastDayCalendar = LeanData.GetDailyCalendar(_subscriptionEndTime, Exchange.Hours, false); + var lastDayCalendar = GetDailyCalendar(_subscriptionEndTime); while (lastDayCalendar.End > _subscriptionEndTime) { - lastDayCalendar = LeanData.GetDailyCalendar(lastDayCalendar.Start.AddDays(-1), Exchange.Hours, false); + lastDayCalendar = GetDailyCalendar(lastDayCalendar.Start.AddDays(-1)); } _subscriptionEndDataCalendar = lastDayCalendar; } @@ -454,8 +454,6 @@ private IEnumerable GetReferenceDateIntervals(DateTime previousEnd yield return new (previousEndTime, resolution); } - // now we can try the bar after next market open - var marketOpen = Exchange.Hours.GetNextMarketOpen(previousEndTime, _isExtendedMarketHours); if (_useStrictEndTime) { // If we're using strict end times for open interest data, for instance, the actual data comes at any time @@ -466,7 +464,7 @@ private IEnumerable GetReferenceDateIntervals(DateTime previousEnd if (_strictEndTimeIntraDayFillForward) { var firtMarketOpen = Exchange.Hours.GetNextMarketOpen(previousEndTime.Date, _isExtendedMarketHours); - var firstCalendar = LeanData.GetDailyCalendar(firtMarketOpen, Exchange.Hours, _isExtendedMarketHours); + var firstCalendar = LeanData.GetDailyCalendar(firtMarketOpen, Exchange.Hours, false); if (firstCalendar.End > previousEndTime) { @@ -474,10 +472,14 @@ private IEnumerable GetReferenceDateIntervals(DateTime previousEnd } } - yield return LeanData.GetDailyCalendar(marketOpen, Exchange.Hours, _isExtendedMarketHours); + // now we can try the bar after next market open + var marketOpen = Exchange.Hours.GetNextMarketOpen(previousEndTime, false); + yield return GetDailyCalendar(marketOpen); } else { + // now we can try the bar after next market open + var marketOpen = Exchange.Hours.GetNextMarketOpen(previousEndTime, _isExtendedMarketHours); yield return new(marketOpen, resolution); } } @@ -512,7 +514,7 @@ private IEnumerable GetReferenceDateIntervals(DateTime previousEnd { // case B: say smaller resolution (FF res) is 1 hour, larget resolution (daily data resolution) is 1 day // For example for SPX we need to emit the daily FF bar from 8:30->15:15, even before the 'A' case above which would be 15->16 bar - var dailyCalendar = LeanData.GetDailyCalendar(previousEndTime, Exchange.Hours, _isExtendedMarketHours); + var dailyCalendar = GetDailyCalendar(previousEndTime); if (previousEndTime < (dailyCalendar.Start + dailyCalendar.Period)) { result.Add(new(dailyCalendar.Start, dailyCalendar.Period)); @@ -534,7 +536,7 @@ private IEnumerable GetReferenceDateIntervals(DateTime previousEnd result.Add(new (marketOpen, smallerResolution)); if (_useStrictEndTime) { - result.Add(LeanData.GetDailyCalendar(marketOpen, Exchange.Hours, _isExtendedMarketHours)); + result.Add(GetDailyCalendar(Exchange.Hours.GetNextMarketOpen(previousEndTime, false))); } // we need to order them because they might not be in an incremental order and consumer expects them to be @@ -554,5 +556,12 @@ private DateTime RoundDown(DateTime value, TimeSpan interval) { return value.RoundDownInTimeZone(interval, Exchange.TimeZone, _dataTimeZone); } + + private CalendarInfo GetDailyCalendar(DateTime localReferenceTime) + { + // daily data does not have extended market hours, even if requested + // and it's times are always market hours if using strict end times see 'SetStrictEndTimes' + return LeanData.GetDailyCalendar(localReferenceTime, Exchange.Hours, extendedMarketHours: false); + } } } diff --git a/Engine/RealTime/ScheduledEventFactory.cs b/Engine/RealTime/ScheduledEventFactory.cs index d2e30c236a7b..1b935186563f 100644 --- a/Engine/RealTime/ScheduledEventFactory.cs +++ b/Engine/RealTime/ScheduledEventFactory.cs @@ -130,7 +130,7 @@ from date in Time.EachTradeableDay(security, start, end) // get the next market close for the specified date if the market closes at some point. // Otherwise, use the given date at midnight let marketClose = isMarketAlwaysOpen ? - date.Date.AddDays(1) : security.Exchange.Hours.GetNextMarketClose(date, security.IsExtendedMarketHours) + date.Date.AddDays(1) : security.Exchange.Hours.GetLastDailyMarketClose(date, security.IsExtendedMarketHours) // define the time of day we want the event to fire before marketclose let eventTime = isMarketAlwaysOpen ? marketClose : marketClose.Subtract(endOfDayDelta) // convert the event time into UTC diff --git a/Tests/Common/Scheduling/TimeRulesTests.cs b/Tests/Common/Scheduling/TimeRulesTests.cs index f8c068fee84d..5d9c4b30876b 100644 --- a/Tests/Common/Scheduling/TimeRulesTests.cs +++ b/Tests/Common/Scheduling/TimeRulesTests.cs @@ -161,12 +161,14 @@ public void ExtendedMarketOpenNoDeltaForContinuousSchedules() }); var expectedMarketOpenDates = new[] { - new DateTime(2022, 01, 02, 23, 0, 0), - new DateTime(2022, 01, 03, 23, 0, 0), - new DateTime(2022, 01, 04, 23, 0, 0), - new DateTime(2022, 01, 05, 23, 0, 0), - new DateTime(2022, 01, 06, 23, 0, 0), - new DateTime(2022, 01, 09, 23, 0, 0) + new DateTime(2022, 01, 02, 23, 0, 0), // sunday 6pm + // market is open for the whole day, so goes from midnight to midnight + new DateTime(2022, 01, 03, 5, 0, 0), + new DateTime(2022, 01, 04, 5, 0, 0), + new DateTime(2022, 01, 05, 5, 0, 0), + new DateTime(2022, 01, 06, 5, 0, 0), + new DateTime(2022, 01, 07, 5, 0, 0), + new DateTime(2022, 01, 09, 23, 0, 0) // sunday 6pm }; int count = 0; foreach (var time in times) @@ -174,7 +176,7 @@ public void ExtendedMarketOpenNoDeltaForContinuousSchedules() Assert.AreEqual(expectedMarketOpenDates[count], time); count++; } - Assert.AreEqual(6, count); + Assert.AreEqual(7, count); } [TestCase(true, 9 - 0.5)] @@ -245,6 +247,56 @@ public void ExtendedMarketOpenWithDelta() Assert.AreEqual(1, count); } + [TestCase(true)] + [TestCase(false)] + public void MultipleMarketOpen(bool extendedMarketHours) + { + var symbol = Symbols.CreateFutureSymbol("HSI", new DateTime(2025, 01, 27)); + var rules = GetTimeRules(TimeZones.Utc); + var rule = rules.AfterMarketOpen(symbol, extendedMarketOpen: extendedMarketHours); + var times = rule.CreateUtcEventTimes(new[] { new DateTime(2000, 01, 04) }); + + int count = 0; + foreach (var time in times) + { + count++; + if (extendedMarketHours) + { + Assert.AreEqual(TimeSpan.FromHours(24 - 8), time.TimeOfDay); + } + else + { + Assert.AreEqual(TimeSpan.FromHours(9.25 - 8), time.TimeOfDay); + } + } + Assert.AreEqual(1, count); + } + + [TestCase(true)] + [TestCase(false)] + public void MultipleMarketClosure(bool extendedMarketHours) + { + var symbol = Symbols.CreateFutureSymbol("HSI", new DateTime(2025, 01, 27)); + var rules = GetTimeRules(TimeZones.Utc); + var rule = rules.BeforeMarketClose(symbol, extendedMarketClose: extendedMarketHours); + var times = rule.CreateUtcEventTimes(new[] { new DateTime(2000, 01, 03) }); + + int count = 0; + foreach (var time in times) + { + count++; + if (extendedMarketHours) + { + Assert.AreEqual(TimeSpan.FromHours(24 - 8), time.TimeOfDay); + } + else + { + Assert.AreEqual(TimeSpan.FromHours(16.5 - 8), time.TimeOfDay); + } + } + Assert.AreEqual(1, count); + } + [Test] public void RegularMarketCloseNoDelta() { diff --git a/Tests/Common/Securities/SecurityExchangeHoursTests.cs b/Tests/Common/Securities/SecurityExchangeHoursTests.cs index 181897453949..45ecc627076b 100644 --- a/Tests/Common/Securities/SecurityExchangeHoursTests.cs +++ b/Tests/Common/Securities/SecurityExchangeHoursTests.cs @@ -40,6 +40,40 @@ public void IsAlwaysOpen() Assert.IsFalse(futureExchangeHours.IsMarketAlwaysOpen); } + [TestCase(false)] + [TestCase(true)] + public void LastMarketCloseOfDay(bool extendedMarketHours) + { + var marketHourDbEntry = MarketHoursDatabase.FromDataFolder() + .GetEntry(Market.HKFE, QuantConnect.Securities.Futures.Indices.HangSeng, SecurityType.Future); + var exchangeHours = marketHourDbEntry.ExchangeHours; + + var date = new DateTime(2025, 1, 7); + + var expectedFirstMarketClose = new DateTime(2025, 1, 7, 12, 0, 0); + var expectedLastMarketClose = new DateTime(2025, 1, 7, 16, 30, 0); + if (extendedMarketHours) + { + expectedFirstMarketClose = new DateTime(2025, 1, 7, 3, 0, 0); + expectedLastMarketClose = new DateTime(2025, 1, 8); + } + + var nextMarketClose = exchangeHours.GetNextMarketClose(date, extendedMarketHours); + Assert.AreEqual(expectedFirstMarketClose, nextMarketClose); + + if (extendedMarketHours) + { + Assert.AreEqual(new DateTime(2025, 1, 7, 12, 0, 0), exchangeHours.GetNextMarketClose(nextMarketClose, extendedMarketHours)); + } + else + { + Assert.AreEqual(new DateTime(2025, 1, 7, 16, 30, 0), exchangeHours.GetNextMarketClose(nextMarketClose, extendedMarketHours)); + } + + var lastMarketClose = exchangeHours.GetLastDailyMarketClose(date, extendedMarketHours); + Assert.AreEqual(expectedLastMarketClose, lastMarketClose); + } + [Test] public void StartIsOpen() { diff --git a/Tests/Common/Util/LeanDataTests.cs b/Tests/Common/Util/LeanDataTests.cs index f21c1d7455d4..3d5918f4f208 100644 --- a/Tests/Common/Util/LeanDataTests.cs +++ b/Tests/Common/Util/LeanDataTests.cs @@ -46,14 +46,28 @@ public void TearDown() SymbolCache.Clear(); } - [TestCase(16, false, "20240506 09:30", "06:30")] - [TestCase(10, false, "20240506 09:30", "06:30")] - [TestCase(10, true, "20240506 04:00", "16:00")] - [TestCase(5, true, "20240506 04:00", "16:00")] - [TestCase(19, true, "20240506 04:00", "16:00")] - public void DailyCalendarInfo(int hours, bool extendedMarketHours, string startTime, string timeSpan) + [TestCase(16, false, "20240506 09:30", "06:30", false)] + [TestCase(10, false, "20240506 09:30", "06:30", false)] + [TestCase(10, true, "20240506 04:00", "16:00", false)] + [TestCase(5, true, "20240506 04:00", "16:00", false)] + [TestCase(19, true, "20240506 04:00", "16:00", false)] + + [TestCase(16, false, "20240506 09:15", "07:15", true)] + [TestCase(10, false, "20240506 09:15", "07:15", true)] + [TestCase(10, true, "20240506 08:45", "15:15", true)] + [TestCase(9, true, "20240506 08:45", "15:15", true)] + [TestCase(19, true, "20240506 08:45", "15:15", true)] + public void DailyCalendarInfo(int hours, bool extendedMarketHours, string startTime, string timeSpan, bool multipleMarketClosureSymbol) { - var symbol = Symbols.SPY; + Symbol symbol; + if (multipleMarketClosureSymbol) + { + symbol = Symbols.CreateFutureSymbol("HSI", new DateTime(2025, 01, 27)); + } + else + { + symbol = Symbols.SPY; + } var targetTime = new DateTime(2024, 5, 6).AddHours(hours); var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.ID.SecurityType); var result = LeanData.GetDailyCalendar(targetTime, exchangeHours, extendedMarketHours); @@ -64,16 +78,46 @@ public void DailyCalendarInfo(int hours, bool extendedMarketHours, string startT Assert.AreEqual(expected, result); } - [TestCase(1, "20240506 16:00")] // market closed - [TestCase(5, "20240506 16:00")] // pre market - [TestCase(10, "20240506 16:00")] // market hours - [TestCase(16, "20240507 16:00")] // at the close - [TestCase(18, "20240507 16:00")] // post market hours - [TestCase(20, "20240507 16:00")] // market closed - [TestCase(24 * 5, "20240513 16:00")] // saturday - public void GetNextDailyEndTime(int hours, string expectedTime) + [TestCase(true, "20131219 00:00", "1.00:00")] + [TestCase(false, "20131219 09:30", "07:30")] + public void DailyCalendarInfoFuture(bool extendedMarketHours, string startTime, string timeSpan) + { + var symbol = Symbols.Future_ESZ18_Dec2018; + var targetTime = new DateTime(2013, 12, 19, 18, 0, 0); + var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.ID.SecurityType); + var result = LeanData.GetDailyCalendar(targetTime, exchangeHours, extendedMarketHours); + + var expected = new CalendarInfo(DateTime.ParseExact(startTime, DateFormat.TwelveCharacter, CultureInfo.InvariantCulture), + TimeSpan.Parse(timeSpan, CultureInfo.InvariantCulture)); + + Assert.AreEqual(expected, result); + } + + [TestCase(1, "20240506 16:00", false)] // market closed + [TestCase(5, "20240506 16:00", false)] // pre market + [TestCase(10, "20240506 16:00", false)] // market hours + [TestCase(16, "20240507 16:00", false)] // at the close + [TestCase(18, "20240507 16:00", false)] // post market hours + [TestCase(20, "20240507 16:00", false)] // market closed + + [TestCase(1, "20240506 16:30", true)] // market closed + [TestCase(9, "20240506 16:30", true)] // pre market + [TestCase(10, "20240506 16:30", true)] // market hours + [TestCase(12, "20240506 16:30", true)] // pre market + [TestCase(14, "20240506 16:30", true)] // market hours + [TestCase(18, "20240507 16:30", true)] // post market hours + [TestCase(20, "20240507 16:30", true)] // post market hours + public void GetNextDailyEndTime(int hours, string expectedTime, bool multipleMarketClosureSymbol) { - var symbol = Symbols.SPY; + Symbol symbol; + if (multipleMarketClosureSymbol) + { + symbol = Symbols.CreateFutureSymbol("HSI", new DateTime(2025, 01, 27)); + } + else + { + symbol = Symbols.SPY; + } var targetTime = new DateTime(2024, 5, 6).AddHours(hours); var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.ID.SecurityType); var result = LeanData.GetNextDailyEndTime(symbol, targetTime, exchangeHours);