diff --git a/Algorithm.CSharp/AddFutureContractWithContinuousRegressionAlgorithm.cs b/Algorithm.CSharp/AddFutureContractWithContinuousRegressionAlgorithm.cs
index ed794a054c72..7d0296464e23 100644
--- a/Algorithm.CSharp/AddFutureContractWithContinuousRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/AddFutureContractWithContinuousRegressionAlgorithm.cs
@@ -115,7 +115,7 @@ public override void OnSecuritiesChanged(SecurityChanges changes)
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 73;
+ public long DataPoints => 76;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/AddFutureOptionContractDataStreamingRegressionAlgorithm.cs b/Algorithm.CSharp/AddFutureOptionContractDataStreamingRegressionAlgorithm.cs
index 7ca259332550..f2f506569166 100644
--- a/Algorithm.CSharp/AddFutureOptionContractDataStreamingRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/AddFutureOptionContractDataStreamingRegressionAlgorithm.cs
@@ -164,7 +164,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 311879;
+ public long DataPoints => 311881;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/AddFutureOptionContractFromFutureChainRegressionAlgorithm.cs b/Algorithm.CSharp/AddFutureOptionContractFromFutureChainRegressionAlgorithm.cs
index cc31259c1d97..fa91f4b20606 100644
--- a/Algorithm.CSharp/AddFutureOptionContractFromFutureChainRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/AddFutureOptionContractFromFutureChainRegressionAlgorithm.cs
@@ -93,7 +93,7 @@ public override void OnData(Slice slice)
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 12169;
+ public long DataPoints => 12172;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm.cs b/Algorithm.CSharp/AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm.cs
index 6f4c24e89106..481956791110 100644
--- a/Algorithm.CSharp/AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm.cs
@@ -220,7 +220,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 608377;
+ public long DataPoints => 608380;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/BasicTemplateContinuousFutureAlgorithm.cs b/Algorithm.CSharp/BasicTemplateContinuousFutureAlgorithm.cs
index 79c8a952e88a..270acee85cad 100644
--- a/Algorithm.CSharp/BasicTemplateContinuousFutureAlgorithm.cs
+++ b/Algorithm.CSharp/BasicTemplateContinuousFutureAlgorithm.cs
@@ -118,7 +118,7 @@ public override void OnSecuritiesChanged(SecurityChanges changes)
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 713369;
+ public long DataPoints => 713371;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/BasicTemplateContinuousFutureWithExtendedMarketAlgorithm.cs b/Algorithm.CSharp/BasicTemplateContinuousFutureWithExtendedMarketAlgorithm.cs
index 7370b942bb02..faff9f82d4c3 100644
--- a/Algorithm.CSharp/BasicTemplateContinuousFutureWithExtendedMarketAlgorithm.cs
+++ b/Algorithm.CSharp/BasicTemplateContinuousFutureWithExtendedMarketAlgorithm.cs
@@ -123,7 +123,7 @@ public override void OnSecuritiesChanged(SecurityChanges changes)
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 2217299;
+ public long DataPoints => 2217326;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/BasicTemplateFutureRolloverAlgorithm.cs b/Algorithm.CSharp/BasicTemplateFutureRolloverAlgorithm.cs
index d303b9aa8c91..68eaa5737f63 100644
--- a/Algorithm.CSharp/BasicTemplateFutureRolloverAlgorithm.cs
+++ b/Algorithm.CSharp/BasicTemplateFutureRolloverAlgorithm.cs
@@ -176,7 +176,7 @@ public void Dispose()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 1190;
+ public long DataPoints => 1199;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/BasicTemplateFuturesDailyAlgorithm.cs b/Algorithm.CSharp/BasicTemplateFuturesDailyAlgorithm.cs
index 7845df1c8d86..de73bcceb0dd 100644
--- a/Algorithm.CSharp/BasicTemplateFuturesDailyAlgorithm.cs
+++ b/Algorithm.CSharp/BasicTemplateFuturesDailyAlgorithm.cs
@@ -117,7 +117,7 @@ select futuresContract
///
/// Data Points count of all timeslices of algorithm
///
- public virtual long DataPoints => 12452;
+ public virtual long DataPoints => 12453;
///
/// Data Points count of the algorithm history
@@ -134,33 +134,33 @@ select futuresContract
///
public virtual Dictionary ExpectedStatistics => new Dictionary
{
- {"Total Orders", "32"},
+ {"Total Orders", "34"},
{"Average Win", "0.33%"},
{"Average Loss", "-0.04%"},
- {"Compounding Annual Return", "0.110%"},
+ {"Compounding Annual Return", "0.106%"},
{"Drawdown", "0.300%"},
- {"Expectancy", "0.184"},
+ {"Expectancy", "0.178"},
{"Start Equity", "1000000"},
- {"End Equity", "1001108"},
- {"Net Profit", "0.111%"},
- {"Sharpe Ratio", "-1.688"},
- {"Sortino Ratio", "-0.772"},
- {"Probabilistic Sharpe Ratio", "14.944%"},
+ {"End Equity", "1001066.2"},
+ {"Net Profit", "0.107%"},
+ {"Sharpe Ratio", "-1.695"},
+ {"Sortino Ratio", "-0.804"},
+ {"Probabilistic Sharpe Ratio", "14.797%"},
{"Loss Rate", "88%"},
{"Win Rate", "12%"},
- {"Profit-Loss Ratio", "8.47"},
+ {"Profit-Loss Ratio", "9.01"},
{"Alpha", "-0.007"},
{"Beta", "0.002"},
{"Annual Standard Deviation", "0.004"},
{"Annual Variance", "0"},
{"Information Ratio", "-1.353"},
{"Tracking Error", "0.089"},
- {"Treynor Ratio", "-4.099"},
- {"Total Fees", "$72.00"},
+ {"Treynor Ratio", "-4.112"},
+ {"Total Fees", "$76.30"},
{"Estimated Strategy Capacity", "$0"},
{"Lowest Capacity Asset", "ES VRJST036ZY0X"},
- {"Portfolio Turnover", "0.87%"},
- {"OrderListHash", "168731c8f3a19f230cc1410818b3b573"}
+ {"Portfolio Turnover", "0.92%"},
+ {"OrderListHash", "7afa589d648c3f24253cd59156a2014e"}
};
}
}
diff --git a/Algorithm.CSharp/BasicTemplateFuturesFrameworkWithExtendedMarketAlgorithm.cs b/Algorithm.CSharp/BasicTemplateFuturesFrameworkWithExtendedMarketAlgorithm.cs
index 80db9524d603..560934813d2f 100644
--- a/Algorithm.CSharp/BasicTemplateFuturesFrameworkWithExtendedMarketAlgorithm.cs
+++ b/Algorithm.CSharp/BasicTemplateFuturesFrameworkWithExtendedMarketAlgorithm.cs
@@ -41,7 +41,7 @@ public class BasicTemplateFuturesFrameworkWithExtendedMarketAlgorithm : BasicTem
///
/// Data Points count of all timeslices of algorithm
///
- public override long DataPoints => 163415;
+ public override long DataPoints => 163416;
///
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
diff --git a/Algorithm.CSharp/BasicTemplateFuturesHourlyAlgorithm.cs b/Algorithm.CSharp/BasicTemplateFuturesHourlyAlgorithm.cs
index dcc0d00e2065..349d51956488 100644
--- a/Algorithm.CSharp/BasicTemplateFuturesHourlyAlgorithm.cs
+++ b/Algorithm.CSharp/BasicTemplateFuturesHourlyAlgorithm.cs
@@ -41,7 +41,7 @@ public class BasicTemplateFuturesHourlyAlgorithm : BasicTemplateFuturesDailyAlgo
///
/// Data Points count of all timeslices of algorithm
///
- public override long DataPoints => 87289;
+ public override long DataPoints => 87290;
///
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
diff --git a/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketDailyAlgorithm.cs b/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketDailyAlgorithm.cs
index b42c25a2d9b0..345582e45693 100644
--- a/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketDailyAlgorithm.cs
+++ b/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketDailyAlgorithm.cs
@@ -43,7 +43,7 @@ public class BasicTemplateFuturesWithExtendedMarketDailyAlgorithm : BasicTemplat
///
/// Data Points count of all timeslices of algorithm
///
- public override long DataPoints => 14790;
+ public override long DataPoints => 14895;
///
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
diff --git a/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketHourlyAlgorithm.cs b/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketHourlyAlgorithm.cs
index caee6edc792c..41a5a5957605 100644
--- a/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketHourlyAlgorithm.cs
+++ b/Algorithm.CSharp/BasicTemplateFuturesWithExtendedMarketHourlyAlgorithm.cs
@@ -41,7 +41,7 @@ public class BasicTemplateFuturesWithExtendedMarketHourlyAlgorithm : BasicTempla
///
/// Data Points count of all timeslices of algorithm
///
- public override long DataPoints => 228834;
+ public override long DataPoints => 228934;
///
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
diff --git a/Algorithm.CSharp/ConsolidateRegressionAlgorithm.cs b/Algorithm.CSharp/ConsolidateRegressionAlgorithm.cs
index d75896d64d16..7346898866a5 100644
--- a/Algorithm.CSharp/ConsolidateRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/ConsolidateRegressionAlgorithm.cs
@@ -189,7 +189,7 @@ public override void OnData(Slice slice)
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 14227;
+ public long DataPoints => 14228;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/ContinuousBackMonthRawFutureRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousBackMonthRawFutureRegressionAlgorithm.cs
index 43be4c45e944..92a547a7563f 100644
--- a/Algorithm.CSharp/ContinuousBackMonthRawFutureRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/ContinuousBackMonthRawFutureRegressionAlgorithm.cs
@@ -139,7 +139,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 713369;
+ public long DataPoints => 713371;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/ContinuousFutureBackMonthRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureBackMonthRegressionAlgorithm.cs
index be0f4585da57..77f6d858b5d1 100644
--- a/Algorithm.CSharp/ContinuousFutureBackMonthRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/ContinuousFutureBackMonthRegressionAlgorithm.cs
@@ -155,7 +155,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 723496;
+ public long DataPoints => 723498;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/ContinuousFutureImmediateUniverseSelectionRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureImmediateUniverseSelectionRegressionAlgorithm.cs
index c8ae875e3d8c..ebf044b4fff6 100644
--- a/Algorithm.CSharp/ContinuousFutureImmediateUniverseSelectionRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/ContinuousFutureImmediateUniverseSelectionRegressionAlgorithm.cs
@@ -184,7 +184,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 596356;
+ public long DataPoints => 596358;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/ContinuousFutureLimitIfTouchedOrderRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureLimitIfTouchedOrderRegressionAlgorithm.cs
index 913cc49a5738..f883aa001273 100644
--- a/Algorithm.CSharp/ContinuousFutureLimitIfTouchedOrderRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/ContinuousFutureLimitIfTouchedOrderRegressionAlgorithm.cs
@@ -78,7 +78,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 19886;
+ public long DataPoints => 19888;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/ContinuousFutureRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRegressionAlgorithm.cs
index fa6392fb97db..22afb222af9a 100644
--- a/Algorithm.CSharp/ContinuousFutureRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/ContinuousFutureRegressionAlgorithm.cs
@@ -169,7 +169,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 713369;
+ public long DataPoints => 713371;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/ContinuousFutureRolloverBaseRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverBaseRegressionAlgorithm.cs
new file mode 100644
index 000000000000..8975109da32c
--- /dev/null
+++ b/Algorithm.CSharp/ContinuousFutureRolloverBaseRegressionAlgorithm.cs
@@ -0,0 +1,262 @@
+/*
+ * 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.Securities;
+using QuantConnect.Securities.Future;
+using System;
+using QuantConnect.Util;
+using System.Linq;
+using NodaTime;
+using QuantConnect.Interfaces;
+using System.Collections.Generic;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Base class for regression algorithms testing that when a continuous future rollover happens,
+ /// the continuous contract is updated correctly with the new contract data, regardless of the
+ /// offset between the exchange time zone and the data time zone.
+ ///
+ public abstract class ContinuousFutureRolloverBaseRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
+ {
+ const string Ticker = Futures.Indices.SP500EMini;
+
+ private Future _continuousContract;
+
+ private DateTime _rolloverTime;
+
+ private MarketHoursDatabase.Entry _originalMhdbEntry;
+
+ protected abstract Resolution Resolution { get; }
+
+ protected abstract Offset ExchangeToDataTimeZoneOffset { get; }
+
+ private DateTimeZone DataTimeZone => TimeZones.Utc;
+
+ private DateTimeZone ExchangeTimeZone => DateTimeZone.ForOffset(ExchangeToDataTimeZoneOffset);
+
+ private bool RolloverHappened => _rolloverTime != DateTime.MinValue;
+
+ public override void Initialize()
+ {
+ SetStartDate(2013, 10, 8);
+ SetEndDate(2013, 12, 20);
+
+ _originalMhdbEntry = MarketHoursDatabase.GetEntry(Market.CME, Ticker, SecurityType.Future);
+ var exchangeHours = new SecurityExchangeHours(ExchangeTimeZone,
+ _originalMhdbEntry.ExchangeHours.Holidays,
+ _originalMhdbEntry.ExchangeHours.MarketHours.ToDictionary(),
+ _originalMhdbEntry.ExchangeHours.EarlyCloses,
+ _originalMhdbEntry.ExchangeHours.LateOpens);
+ MarketHoursDatabase.SetEntry(Market.CME, Ticker, SecurityType.Future, exchangeHours, DataTimeZone);
+
+ SetTimeZone(ExchangeTimeZone);
+
+ _continuousContract = AddFuture(Ticker,
+ Resolution,
+ extendedMarketHours: true,
+ dataNormalizationMode: DataNormalizationMode.Raw,
+ dataMappingMode: DataMappingMode.OpenInterest,
+ contractDepthOffset: 0
+ );
+
+ SetBenchmark(x => 0);
+ }
+
+ public override void OnData(Slice slice)
+ {
+ try
+ {
+ var receivedRollover = false;
+ foreach (var (symbol, symbolChangedEvent) in slice.SymbolChangedEvents)
+ {
+ if (RolloverHappened)
+ {
+ throw new RegressionTestException($"[{Time}] -- Unexpected symbol changed event for {symbol}. Expected only one mapping.");
+ }
+
+ receivedRollover = true;
+ _rolloverTime = symbolChangedEvent.EndTime;
+
+ var oldSymbol = symbolChangedEvent.OldSymbol;
+ var newSymbol = symbolChangedEvent.NewSymbol;
+ Debug($"[{Time}] -- Rollover: {oldSymbol} -> {newSymbol}");
+
+ if (symbol != _continuousContract.Symbol)
+ {
+ throw new RegressionTestException($"[{Time}] -- Unexpected symbol changed event for {symbol}");
+ }
+
+ var expectedMappingDate = new DateTime(2013, 12, 18);
+ if (_rolloverTime != expectedMappingDate)
+ {
+ throw new RegressionTestException($"[{Time}] -- Unexpected date {_rolloverTime}. Expected {expectedMappingDate}");
+ }
+
+ var expectedMappingOldSymbol = "ES VMKLFZIH2MTD";
+ var expectedMappingNewSymbol = "ES VP274HSU1AF5";
+ if (symbolChangedEvent.OldSymbol != expectedMappingOldSymbol || symbolChangedEvent.NewSymbol != expectedMappingNewSymbol)
+ {
+ throw new RegressionTestException($"[{Time}] -- Unexpected mapping. " +
+ $"Expected {expectedMappingOldSymbol} -> {expectedMappingNewSymbol} " +
+ $"but was {symbolChangedEvent.OldSymbol} -> {symbolChangedEvent.NewSymbol}");
+ }
+ }
+
+ var mappedFuture = Securities[_continuousContract.Mapped];
+ var mappedFuturePrice = mappedFuture.Price;
+
+ var otherFuture = Securities.Values.SingleOrDefault(x => !x.Symbol.IsCanonical() && x.Symbol != _continuousContract.Mapped);
+ var otherFuturePrice = otherFuture?.Price;
+
+ var continuousContractPrice = _continuousContract.Price;
+
+ Debug($"[{Time}] Contracts prices:\n" +
+ $" -- Mapped future: {mappedFuture.Symbol} :: {mappedFuture.Price} :: {mappedFuture.GetLastData()}\n" +
+ $" -- Other future: {otherFuture?.Symbol} :: {otherFuture?.Price} :: {otherFuture?.GetLastData()}\n" +
+ $" -- Mapped future from continuous contract: {_continuousContract.Symbol} :: {_continuousContract.Mapped} :: " +
+ $"{_continuousContract.Price} :: {_continuousContract.GetLastData()}\n");
+
+ if (receivedRollover)
+ {
+ if (continuousContractPrice != otherFuturePrice)
+ {
+ var continuousContractLastData = _continuousContract.GetLastData();
+ throw new RegressionTestException($"[{Time}] -- Prices do not match. " +
+ $"At the time of the rollover, expected continuous future price to be the same as " +
+ $"the previously mapped contract since no data for the new mapped contract has been received:\n" +
+ $" Continuous contract ({_continuousContract.Symbol}) price: " +
+ $"{continuousContractPrice} :: {continuousContractLastData.Symbol.Underlying} :: " +
+ $"{continuousContractLastData.Time} - {continuousContractLastData.EndTime} :: {continuousContractLastData}. \n" +
+ $" Mapped contract ({mappedFuture.Symbol}) price: {mappedFuturePrice} :: {mappedFuture.GetLastData()}. \n" +
+ $" Other contract ({otherFuture?.Symbol}) price: {otherFuturePrice} :: {otherFuture?.GetLastData()}\n");
+ }
+ }
+ else if (mappedFuturePrice != 0 || !RolloverHappened)
+ {
+ if (continuousContractPrice != mappedFuturePrice)
+ {
+ var continuousContractLastData = _continuousContract.GetLastData();
+ throw new RegressionTestException($"[{Time}] -- Prices do not match. " +
+ $"Expected continuous future price to be the same as the mapped contract:\n" +
+ $" Continuous contract ({_continuousContract.Symbol}) price: {continuousContractPrice} :: " +
+ $"{continuousContractLastData.Symbol.Underlying} :: {continuousContractLastData}. \n" +
+ $" Mapped contract ({mappedFuture.Symbol}) price: {mappedFuturePrice} :: {mappedFuture.GetLastData()}. \n" +
+ $" Other contract ({otherFuture?.Symbol}) price: {otherFuturePrice} :: {otherFuture?.GetLastData()}\n");
+ }
+ }
+ // No data for the mapped future yet after rollover
+ else
+ {
+ if (otherFuture == null)
+ {
+ throw new RegressionTestException($"[{Time}] --" +
+ $" Mapped future price is 0 (no data has arrived) so the previous mapped contract is expected to be there");
+ }
+
+ var continuousContractLastData = _continuousContract.GetLastData();
+
+ if (continuousContractLastData.EndTime > _rolloverTime)
+ {
+ throw new RegressionTestException($"[{Time}] -- Expected continuous future contract last data to be from the previously " +
+ $"mapped contract until the new mapped contract gets data:\n" +
+ $" Rollover time: {_rolloverTime}\n" +
+ $" Continuous contract ({_continuousContract.Symbol}) last data: " +
+ $"{continuousContractLastData.Symbol.Underlying} :: " +
+ $"{continuousContractLastData.Time} - {continuousContractLastData.EndTime} :: {continuousContractLastData}.");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ ResetMarketHoursDatabase();
+ throw;
+ }
+ }
+
+ public override void OnEndOfAlgorithm()
+ {
+ ResetMarketHoursDatabase();
+
+ if (!RolloverHappened)
+ {
+ throw new RegressionTestException($"[{Time}] -- Rollover did not happen.");
+ }
+ }
+
+ private void ResetMarketHoursDatabase()
+ {
+ MarketHoursDatabase.SetEntry(Market.CME, Ticker, SecurityType.Future, _originalMhdbEntry.ExchangeHours, _originalMhdbEntry.DataTimeZone);
+ }
+
+ ///
+ /// 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 => 0;
+
+ ///
+ /// Data Points count of the algorithm history
+ ///
+ public int AlgorithmHistoryDataPoints => 0;
+
+ ///
+ /// 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", "0"},
+ {"Tracking Error", "0"},
+ {"Treynor Ratio", "0"},
+ {"Total Fees", "$0.00"},
+ {"Estimated Strategy Capacity", "$0"},
+ {"Lowest Capacity Asset", ""},
+ {"Portfolio Turnover", "0%"},
+ {"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"}
+ };
+
+ public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;
+ }
+}
diff --git a/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs
new file mode 100644
index 000000000000..a7441c6581a7
--- /dev/null
+++ b/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs
@@ -0,0 +1,38 @@
+/*
+ * 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 NodaTime;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Base class for regression algorithms testing that when a continuous future rollover happens,
+ /// the continuous contract is updated correctly with the new contract data.
+ /// The algorithms asserts the behavior for the case when the exchange time zone is ahead of the data time zone.
+ ///
+ public class ContinuousFutureRolloverDailyExchangeTimeZoneAheadOfDataRegressionAlgorithm
+ : ContinuousFutureRolloverBaseRegressionAlgorithm
+ {
+ protected override Resolution Resolution => Resolution.Daily;
+
+ protected override Offset ExchangeToDataTimeZoneOffset => Offset.FromHours(2);
+
+ ///
+ /// Data Points count of all timeslices of algorithm
+ ///
+ public override long DataPoints => 1022;
+ }
+}
diff --git a/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneBehindOfDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneBehindOfDataRegressionAlgorithm.cs
new file mode 100644
index 000000000000..40825b180c7f
--- /dev/null
+++ b/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneBehindOfDataRegressionAlgorithm.cs
@@ -0,0 +1,38 @@
+/*
+ * 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 NodaTime;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Base class for regression algorithms testing that when a continuous future rollover happens,
+ /// the continuous contract is updated correctly with the new contract data.
+ /// The algorithms asserts the behavior for the case when the exchange time zone is behind of the data time zone.
+ ///
+ public class ContinuousFutureRolloverDailyExchangeTimeZoneBehindOfDataRegressionAlgorithm
+ : ContinuousFutureRolloverBaseRegressionAlgorithm
+ {
+ protected override Resolution Resolution => Resolution.Daily;
+
+ protected override Offset ExchangeToDataTimeZoneOffset => Offset.FromHours(-2);
+
+ ///
+ /// Data Points count of all timeslices of algorithm
+ ///
+ public override long DataPoints => 1017;
+ }
+}
diff --git a/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneSameAsDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneSameAsDataRegressionAlgorithm.cs
new file mode 100644
index 000000000000..2f3e039f781a
--- /dev/null
+++ b/Algorithm.CSharp/ContinuousFutureRolloverDailyExchangeTimeZoneSameAsDataRegressionAlgorithm.cs
@@ -0,0 +1,38 @@
+/*
+ * 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 NodaTime;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Base class for regression algorithms testing that when a continuous future rollover happens,
+ /// the continuous contract is updated correctly with the new contract data.
+ /// The algorithms asserts the behavior for the case when the data time zone is the same as the exchange time zone.
+ ///
+ public class ContinuousFutureRolloverDailyExchangeTimeZoneSameAsDataRegressionAlgorithm
+ : ContinuousFutureRolloverBaseRegressionAlgorithm
+ {
+ protected override Resolution Resolution => Resolution.Daily;
+
+ protected override Offset ExchangeToDataTimeZoneOffset => Offset.Zero;
+
+ ///
+ /// Data Points count of all timeslices of algorithm
+ ///
+ public override long DataPoints => 1015;
+ }
+}
diff --git a/Algorithm.CSharp/ContinuousFutureRolloverHourExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverHourExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs
new file mode 100644
index 000000000000..ae991fda65d9
--- /dev/null
+++ b/Algorithm.CSharp/ContinuousFutureRolloverHourExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs
@@ -0,0 +1,38 @@
+/*
+ * 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 NodaTime;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Base class for regression algorithms testing that when a continuous future rollover happens,
+ /// the continuous contract is updated correctly with the new contract data.
+ /// The algorithms asserts the behavior for the case when the exchange time zone is ahead of the data time zone.
+ ///
+ public class ContinuousFutureRolloverHourExchangeTimeZoneAheadOfDataRegressionAlgorithm
+ : ContinuousFutureRolloverBaseRegressionAlgorithm
+ {
+ protected override Resolution Resolution => Resolution.Hour;
+
+ protected override Offset ExchangeToDataTimeZoneOffset => Offset.FromHours(2);
+
+ ///
+ /// Data Points count of all timeslices of algorithm
+ ///
+ public override long DataPoints => 18850;
+ }
+}
diff --git a/Algorithm.CSharp/ContinuousFutureRolloverHourExchangeTimeZoneBehindOfDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverHourExchangeTimeZoneBehindOfDataRegressionAlgorithm.cs
new file mode 100644
index 000000000000..4ca71fcd85d0
--- /dev/null
+++ b/Algorithm.CSharp/ContinuousFutureRolloverHourExchangeTimeZoneBehindOfDataRegressionAlgorithm.cs
@@ -0,0 +1,38 @@
+/*
+ * 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 NodaTime;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Base class for regression algorithms testing that when a continuous future rollover happens,
+ /// the continuous contract is updated correctly with the new contract data.
+ /// The algorithms asserts the behavior for the case when the exchange time zone is behind of the data time zone.
+ ///
+ public class ContinuousFutureRolloverHourExchangeTimeZoneBehindOfDataRegressionAlgorithm
+ : ContinuousFutureRolloverBaseRegressionAlgorithm
+ {
+ protected override Resolution Resolution => Resolution.Hour;
+
+ protected override Offset ExchangeToDataTimeZoneOffset => Offset.FromHours(-2);
+
+ ///
+ /// Data Points count of all timeslices of algorithm
+ ///
+ public override long DataPoints => 18844;
+ }
+}
diff --git a/Algorithm.CSharp/ContinuousFutureRolloverHourExchangeTimeZoneSameAsDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverHourExchangeTimeZoneSameAsDataRegressionAlgorithm.cs
new file mode 100644
index 000000000000..57d85882b0f4
--- /dev/null
+++ b/Algorithm.CSharp/ContinuousFutureRolloverHourExchangeTimeZoneSameAsDataRegressionAlgorithm.cs
@@ -0,0 +1,38 @@
+/*
+ * 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 NodaTime;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Base class for regression algorithms testing that when a continuous future rollover happens,
+ /// the continuous contract is updated correctly with the new contract data.
+ /// The algorithms asserts the behavior for the case when the data time zone is the same as the exchange time zone.
+ ///
+ public class ContinuousFutureRolloverHourExchangeTimeZoneSameAsDataRegressionAlgorithm
+ : ContinuousFutureRolloverBaseRegressionAlgorithm
+ {
+ protected override Resolution Resolution => Resolution.Hour;
+
+ protected override Offset ExchangeToDataTimeZoneOffset => Offset.Zero;
+
+ ///
+ /// Data Points count of all timeslices of algorithm
+ ///
+ public override long DataPoints => 18846;
+ }
+}
diff --git a/Algorithm.CSharp/ContinuousFutureRolloverMinuteExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverMinuteExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs
new file mode 100644
index 000000000000..3e77be5e229d
--- /dev/null
+++ b/Algorithm.CSharp/ContinuousFutureRolloverMinuteExchangeTimeZoneAheadOfDataRegressionAlgorithm.cs
@@ -0,0 +1,38 @@
+/*
+ * 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 NodaTime;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Base class for regression algorithms testing that when a continuous future rollover happens,
+ /// the continuous contract is updated correctly with the new contract data.
+ /// The algorithms asserts the behavior for the case when the exchange time zone is ahead of the data time zone.
+ ///
+ public class ContinuousFutureRolloverMinuteExchangeTimeZoneAheadOfDataRegressionAlgorithm
+ : ContinuousFutureRolloverBaseRegressionAlgorithm
+ {
+ protected override Resolution Resolution => Resolution.Minute;
+
+ protected override Offset ExchangeToDataTimeZoneOffset => Offset.FromHours(2);
+
+ ///
+ /// Data Points count of all timeslices of algorithm
+ ///
+ public override long DataPoints => 1127376;
+ }
+}
diff --git a/Algorithm.CSharp/ContinuousFutureRolloverMinuteExchangeTimeZoneBehindOfDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverMinuteExchangeTimeZoneBehindOfDataRegressionAlgorithm.cs
new file mode 100644
index 000000000000..93b3130d8734
--- /dev/null
+++ b/Algorithm.CSharp/ContinuousFutureRolloverMinuteExchangeTimeZoneBehindOfDataRegressionAlgorithm.cs
@@ -0,0 +1,38 @@
+/*
+ * 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 NodaTime;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Base class for regression algorithms testing that when a continuous future rollover happens,
+ /// the continuous contract is updated correctly with the new contract data.
+ /// The algorithms asserts the behavior for the case when the exchange time zone is behind of the data time zone.
+ ///
+ public class ContinuousFutureRolloverMinuteExchangeTimeZoneBehindOfDataRegressionAlgorithm
+ : ContinuousFutureRolloverBaseRegressionAlgorithm
+ {
+ protected override Resolution Resolution => Resolution.Minute;
+
+ protected override Offset ExchangeToDataTimeZoneOffset => Offset.FromHours(-2);
+
+ ///
+ /// Data Points count of all timeslices of algorithm
+ ///
+ public override long DataPoints => 1127374;
+ }
+}
diff --git a/Algorithm.CSharp/ContinuousFutureRolloverMinuteExchangeTimeZoneSameAsDataRegressionAlgorithm.cs b/Algorithm.CSharp/ContinuousFutureRolloverMinuteExchangeTimeZoneSameAsDataRegressionAlgorithm.cs
new file mode 100644
index 000000000000..be11903cfd31
--- /dev/null
+++ b/Algorithm.CSharp/ContinuousFutureRolloverMinuteExchangeTimeZoneSameAsDataRegressionAlgorithm.cs
@@ -0,0 +1,38 @@
+/*
+ * 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 NodaTime;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Base class for regression algorithms testing that when a continuous future rollover happens,
+ /// the continuous contract is updated correctly with the new contract data.
+ /// The algorithms asserts the behavior for the case when the data time zone is the same as the exchange time zone.
+ ///
+ public class ContinuousFutureRolloverMinuteExchangeTimeZoneSameAsDataRegressionAlgorithm
+ : ContinuousFutureRolloverBaseRegressionAlgorithm
+ {
+ protected override Resolution Resolution => Resolution.Minute;
+
+ protected override Offset ExchangeToDataTimeZoneOffset => Offset.Zero;
+
+ ///
+ /// Data Points count of all timeslices of algorithm
+ ///
+ public override long DataPoints => 1127488;
+ }
+}
diff --git a/Algorithm.CSharp/FutureMarketOpenAndCloseWithExtendedMarketRegressionAlgorithm.cs b/Algorithm.CSharp/FutureMarketOpenAndCloseWithExtendedMarketRegressionAlgorithm.cs
index 907cd6948ee4..8109fcf408bc 100644
--- a/Algorithm.CSharp/FutureMarketOpenAndCloseWithExtendedMarketRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureMarketOpenAndCloseWithExtendedMarketRegressionAlgorithm.cs
@@ -50,7 +50,7 @@ public class FutureMarketOpenAndCloseWithExtendedMarketRegressionAlgorithm : Fut
///
/// Data Points count of all timeslices of algorithm
///
- public override long DataPoints => 41466;
+ public override long DataPoints => 41467;
///
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
diff --git a/Algorithm.CSharp/FutureMarketOpenConsolidatorRegressionAlgorithm.cs b/Algorithm.CSharp/FutureMarketOpenConsolidatorRegressionAlgorithm.cs
index 41db052bd7ee..098084841790 100644
--- a/Algorithm.CSharp/FutureMarketOpenConsolidatorRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureMarketOpenConsolidatorRegressionAlgorithm.cs
@@ -93,7 +93,7 @@ public void Assert(BaseData bar)
///
/// Data Points count of all timeslices of algorithm
///
- public virtual long DataPoints => 32071;
+ public virtual long DataPoints => 32073;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/FutureMarketOpenConsolidatorWithExtendedMarketRegressionAlgorithm.cs b/Algorithm.CSharp/FutureMarketOpenConsolidatorWithExtendedMarketRegressionAlgorithm.cs
index ad9434455ce8..51febcc14451 100644
--- a/Algorithm.CSharp/FutureMarketOpenConsolidatorWithExtendedMarketRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureMarketOpenConsolidatorWithExtendedMarketRegressionAlgorithm.cs
@@ -52,7 +52,7 @@ public class FutureMarketOpenConsolidatorWithExtendedMarketRegressionAlgorithm :
///
/// Data Points count of all timeslices of algorithm
///
- public override long DataPoints => 103816;
+ public override long DataPoints => 103818;
///
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
diff --git a/Algorithm.CSharp/FutureOptionBuySellCallIntradayRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionBuySellCallIntradayRegressionAlgorithm.cs
index 0b2df56cc07b..9c3724d89748 100644
--- a/Algorithm.CSharp/FutureOptionBuySellCallIntradayRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionBuySellCallIntradayRegressionAlgorithm.cs
@@ -112,7 +112,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 309280;
+ public long DataPoints => 309282;
///
/// Data Points count of the algorithm history
@@ -150,7 +150,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.005"},
{"Information Ratio", "-0.134"},
{"Tracking Error", "0.385"},
- {"Treynor Ratio", "3.784"},
+ {"Treynor Ratio", "3.785"},
{"Total Fees", "$2.84"},
{"Estimated Strategy Capacity", "$120000000.00"},
{"Lowest Capacity Asset", "ES XFH59UPBIJ7O|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FutureOptionCallITMExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionCallITMExpiryRegressionAlgorithm.cs
index 25f88b83b090..e173b2eb12a1 100644
--- a/Algorithm.CSharp/FutureOptionCallITMExpiryRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionCallITMExpiryRegressionAlgorithm.cs
@@ -200,7 +200,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212195;
+ public long DataPoints => 212196;
///
/// Data Points count of the algorithm history
@@ -238,7 +238,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.004"},
{"Information Ratio", "-0.226"},
{"Tracking Error", "0.378"},
- {"Treynor Ratio", "-21.838"},
+ {"Treynor Ratio", "-21.841"},
{"Total Fees", "$1.42"},
{"Estimated Strategy Capacity", "$120000000.00"},
{"Lowest Capacity Asset", "ES XFH59UPBIJ7O|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FutureOptionCallITMGreeksExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionCallITMGreeksExpiryRegressionAlgorithm.cs
index 68bce654dfe6..d2d4b08323dc 100644
--- a/Algorithm.CSharp/FutureOptionCallITMGreeksExpiryRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionCallITMGreeksExpiryRegressionAlgorithm.cs
@@ -165,7 +165,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212195;
+ public long DataPoints => 212196;
///
/// Data Points count of the algorithm history
@@ -203,7 +203,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.098"},
{"Information Ratio", "-0.649"},
{"Tracking Error", "0.483"},
- {"Treynor Ratio", "-18.589"},
+ {"Treynor Ratio", "-18.59"},
{"Total Fees", "$7.10"},
{"Estimated Strategy Capacity", "$24000000.00"},
{"Lowest Capacity Asset", "ES XFH59UPBIJ7O|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FutureOptionCallOTMExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionCallOTMExpiryRegressionAlgorithm.cs
index 19c4bfb64db7..945e63fefb2d 100644
--- a/Algorithm.CSharp/FutureOptionCallOTMExpiryRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionCallOTMExpiryRegressionAlgorithm.cs
@@ -176,7 +176,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212195;
+ public long DataPoints => 212196;
///
/// Data Points count of the algorithm history
@@ -214,7 +214,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.003"},
{"Information Ratio", "-0.198"},
{"Tracking Error", "0.377"},
- {"Treynor Ratio", "-23.06"},
+ {"Treynor Ratio", "-23.065"},
{"Total Fees", "$1.42"},
{"Estimated Strategy Capacity", "$180000000.00"},
{"Lowest Capacity Asset", "ES XFH59UPHGV9G|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FutureOptionMultipleContractsInDifferentContractMonthsWithSameUnderlyingFutureRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionMultipleContractsInDifferentContractMonthsWithSameUnderlyingFutureRegressionAlgorithm.cs
index d1efdfe28313..fec8ca9eb321 100644
--- a/Algorithm.CSharp/FutureOptionMultipleContractsInDifferentContractMonthsWithSameUnderlyingFutureRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionMultipleContractsInDifferentContractMonthsWithSameUnderlyingFutureRegressionAlgorithm.cs
@@ -109,7 +109,7 @@ private static Symbol CreateOption(DateTime expiry, OptionRight optionRight, dec
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 24378;
+ public long DataPoints => 24379;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/FutureOptionPutITMExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionPutITMExpiryRegressionAlgorithm.cs
index c0a01fa99020..177f9372d5bc 100644
--- a/Algorithm.CSharp/FutureOptionPutITMExpiryRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionPutITMExpiryRegressionAlgorithm.cs
@@ -201,7 +201,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212195;
+ public long DataPoints => 212196;
///
/// Data Points count of the algorithm history
@@ -239,7 +239,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.003"},
{"Information Ratio", "-0.248"},
{"Tracking Error", "0.377"},
- {"Treynor Ratio", "-36.295"},
+ {"Treynor Ratio", "-36.3"},
{"Total Fees", "$1.42"},
{"Estimated Strategy Capacity", "$79000000.00"},
{"Lowest Capacity Asset", "ES 31EL5FAOOQON8|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FutureOptionPutOTMExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionPutOTMExpiryRegressionAlgorithm.cs
index 50d24d7ca9df..c60149c64e81 100644
--- a/Algorithm.CSharp/FutureOptionPutOTMExpiryRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionPutOTMExpiryRegressionAlgorithm.cs
@@ -174,7 +174,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212195;
+ public long DataPoints => 212196;
///
/// Data Points count of the algorithm history
@@ -212,7 +212,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.003"},
{"Information Ratio", "-0.249"},
{"Tracking Error", "0.377"},
- {"Treynor Ratio", "-32.551"},
+ {"Treynor Ratio", "-32.556"},
{"Total Fees", "$1.42"},
{"Estimated Strategy Capacity", "$290000000.00"},
{"Lowest Capacity Asset", "ES 31EL5FBZBMXES|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FutureOptionShortCallITMExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionShortCallITMExpiryRegressionAlgorithm.cs
index d97531471a15..ed67a742d933 100644
--- a/Algorithm.CSharp/FutureOptionShortCallITMExpiryRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionShortCallITMExpiryRegressionAlgorithm.cs
@@ -185,7 +185,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212195;
+ public long DataPoints => 212196;
///
/// Data Points count of the algorithm history
@@ -223,7 +223,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.002"},
{"Information Ratio", "0.064"},
{"Tracking Error", "0.378"},
- {"Treynor Ratio", "-13.132"},
+ {"Treynor Ratio", "-13.127"},
{"Total Fees", "$1.42"},
{"Estimated Strategy Capacity", "$13000000.00"},
{"Lowest Capacity Asset", "ES XFH59UP5K75W|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FutureOptionShortCallOTMExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionShortCallOTMExpiryRegressionAlgorithm.cs
index 3387daef3906..45b8f5f2ba24 100644
--- a/Algorithm.CSharp/FutureOptionShortCallOTMExpiryRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionShortCallOTMExpiryRegressionAlgorithm.cs
@@ -168,7 +168,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212195;
+ public long DataPoints => 212196;
///
/// Data Points count of the algorithm history
@@ -194,7 +194,7 @@ public override void OnEndOfAlgorithm()
{"Start Equity", "100000"},
{"End Equity", "101736.08"},
{"Net Profit", "1.736%"},
- {"Sharpe Ratio", "0.597"},
+ {"Sharpe Ratio", "0.596"},
{"Sortino Ratio", "0"},
{"Probabilistic Sharpe Ratio", "49.736%"},
{"Loss Rate", "0%"},
@@ -206,7 +206,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.001"},
{"Information Ratio", "0.009"},
{"Tracking Error", "0.375"},
- {"Treynor Ratio", "-11.057"},
+ {"Treynor Ratio", "-11.048"},
{"Total Fees", "$1.42"},
{"Estimated Strategy Capacity", "$100000000.00"},
{"Lowest Capacity Asset", "ES XFH59UPNF7B8|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FutureOptionShortPutITMExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionShortPutITMExpiryRegressionAlgorithm.cs
index deedc5a91894..10b05989581b 100644
--- a/Algorithm.CSharp/FutureOptionShortPutITMExpiryRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionShortPutITMExpiryRegressionAlgorithm.cs
@@ -182,7 +182,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212195;
+ public long DataPoints => 212196;
///
/// Data Points count of the algorithm history
@@ -220,7 +220,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.001"},
{"Information Ratio", "0.058"},
{"Tracking Error", "0.375"},
- {"Treynor Ratio", "-41.846"},
+ {"Treynor Ratio", "-41.831"},
{"Total Fees", "$1.42"},
{"Estimated Strategy Capacity", "$12000000.00"},
{"Lowest Capacity Asset", "ES 31EL5FAOUP0P0|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FutureOptionShortPutOTMExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionShortPutOTMExpiryRegressionAlgorithm.cs
index cb9e6c11a9b9..4b9132b16c8d 100644
--- a/Algorithm.CSharp/FutureOptionShortPutOTMExpiryRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureOptionShortPutOTMExpiryRegressionAlgorithm.cs
@@ -167,7 +167,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212195;
+ public long DataPoints => 212196;
///
/// Data Points count of the algorithm history
@@ -193,7 +193,7 @@ public override void OnEndOfAlgorithm()
{"Start Equity", "100000"},
{"End Equity", "103423.58"},
{"Net Profit", "3.424%"},
- {"Sharpe Ratio", "1.089"},
+ {"Sharpe Ratio", "1.088"},
{"Sortino Ratio", "0"},
{"Probabilistic Sharpe Ratio", "73.287%"},
{"Loss Rate", "0%"},
@@ -205,7 +205,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.001"},
{"Information Ratio", "0.075"},
{"Tracking Error", "0.377"},
- {"Treynor Ratio", "-24.083"},
+ {"Treynor Ratio", "-24.076"},
{"Total Fees", "$1.42"},
{"Estimated Strategy Capacity", "$110000000.00"},
{"Lowest Capacity Asset", "ES 31EL5FAJQ6SBO|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FutureStopMarketOrderOnExtendedHoursRegressionAlgorithm.cs b/Algorithm.CSharp/FutureStopMarketOrderOnExtendedHoursRegressionAlgorithm.cs
index 02c3d8cfb4ac..24affa9ea1d8 100644
--- a/Algorithm.CSharp/FutureStopMarketOrderOnExtendedHoursRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FutureStopMarketOrderOnExtendedHoursRegressionAlgorithm.cs
@@ -113,7 +113,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all time slices of algorithm
///
- public long DataPoints => 75960;
+ public long DataPoints => 75961;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/FuturesAndFuturesOptionsExpiryTimeAndLiquidationRegressionAlgorithm.cs b/Algorithm.CSharp/FuturesAndFuturesOptionsExpiryTimeAndLiquidationRegressionAlgorithm.cs
index f0e012b69826..24730ef97112 100644
--- a/Algorithm.CSharp/FuturesAndFuturesOptionsExpiryTimeAndLiquidationRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FuturesAndFuturesOptionsExpiryTimeAndLiquidationRegressionAlgorithm.cs
@@ -178,7 +178,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212941;
+ public long DataPoints => 212942;
///
/// Data Points count of the algorithm history
@@ -204,7 +204,7 @@ public override void OnEndOfAlgorithm()
{"Start Equity", "100000"},
{"End Equity", "98233.93"},
{"Net Profit", "-1.766%"},
- {"Sharpe Ratio", "-1.14"},
+ {"Sharpe Ratio", "-1.141"},
{"Sortino Ratio", "0"},
{"Probabilistic Sharpe Ratio", "0.020%"},
{"Loss Rate", "50%"},
@@ -216,7 +216,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0"},
{"Information Ratio", "-0.602"},
{"Tracking Error", "0.291"},
- {"Treynor Ratio", "-16.64"},
+ {"Treynor Ratio", "-16.65"},
{"Total Fees", "$3.57"},
{"Estimated Strategy Capacity", "$16000000.00"},
{"Lowest Capacity Asset", "ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/FuturesExpiredContractRegression.cs b/Algorithm.CSharp/FuturesExpiredContractRegression.cs
index 7812a48aaacb..0c8ec37ef63e 100644
--- a/Algorithm.CSharp/FuturesExpiredContractRegression.cs
+++ b/Algorithm.CSharp/FuturesExpiredContractRegression.cs
@@ -89,7 +89,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 268273;
+ public long DataPoints => 268275;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/FuturesExtendedMarketHoursRegressionAlgorithm.cs b/Algorithm.CSharp/FuturesExtendedMarketHoursRegressionAlgorithm.cs
index 5dd438fe857d..69a687b6a8aa 100644
--- a/Algorithm.CSharp/FuturesExtendedMarketHoursRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/FuturesExtendedMarketHoursRegressionAlgorithm.cs
@@ -113,7 +113,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 3630;
+ public long DataPoints => 3631;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/LimitOrdersAreFilledAfterHoursForFuturesRegressionAlgorithm.cs b/Algorithm.CSharp/LimitOrdersAreFilledAfterHoursForFuturesRegressionAlgorithm.cs
index 436b5ec8fd81..7af2780d0a10 100644
--- a/Algorithm.CSharp/LimitOrdersAreFilledAfterHoursForFuturesRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/LimitOrdersAreFilledAfterHoursForFuturesRegressionAlgorithm.cs
@@ -113,7 +113,7 @@ public override void OnOrderEvent(OrderEvent orderEvent)
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 82371;
+ public long DataPoints => 82372;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/MarketOrdersAreSupportedOnExtendedHoursForFuturesRegressionAlgorithm.cs b/Algorithm.CSharp/MarketOrdersAreSupportedOnExtendedHoursForFuturesRegressionAlgorithm.cs
index 30aa43e92d44..9bdeeada06c0 100644
--- a/Algorithm.CSharp/MarketOrdersAreSupportedOnExtendedHoursForFuturesRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/MarketOrdersAreSupportedOnExtendedHoursForFuturesRegressionAlgorithm.cs
@@ -90,7 +90,7 @@ public override void OnOrderEvent(OrderEvent orderEvent)
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 82371;
+ public long DataPoints => 82372;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/OptionOTMExpiryOrderHasZeroPriceRegressionAlgorithm.cs b/Algorithm.CSharp/OptionOTMExpiryOrderHasZeroPriceRegressionAlgorithm.cs
index 4e496f3e5ddf..e695e5583065 100644
--- a/Algorithm.CSharp/OptionOTMExpiryOrderHasZeroPriceRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/OptionOTMExpiryOrderHasZeroPriceRegressionAlgorithm.cs
@@ -153,7 +153,7 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 212195;
+ public long DataPoints => 212196;
///
/// Data Points count of the algorithm history
@@ -191,7 +191,7 @@ public override void OnEndOfAlgorithm()
{"Annual Variance", "0.003"},
{"Information Ratio", "-0.198"},
{"Tracking Error", "0.377"},
- {"Treynor Ratio", "-23.06"},
+ {"Treynor Ratio", "-23.065"},
{"Total Fees", "$1.42"},
{"Estimated Strategy Capacity", "$180000000.00"},
{"Lowest Capacity Asset", "ES XFH59UPHGV9G|ES XFH59UK0MYO1"},
diff --git a/Algorithm.CSharp/OptionPriceModelForSupportedEuropeanOptionRegressionAlgorithm.cs b/Algorithm.CSharp/OptionPriceModelForSupportedEuropeanOptionRegressionAlgorithm.cs
index 84f792815220..b6077696d50b 100644
--- a/Algorithm.CSharp/OptionPriceModelForSupportedEuropeanOptionRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/OptionPriceModelForSupportedEuropeanOptionRegressionAlgorithm.cs
@@ -41,7 +41,7 @@ public override void Initialize()
///
/// Data Points count of all timeslices of algorithm
///
- public override long DataPoints => 175;
+ public override long DataPoints => 177;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.cs b/Algorithm.CSharp/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.cs
index f68eb24d6660..65f892960aed 100644
--- a/Algorithm.CSharp/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.cs
@@ -42,7 +42,7 @@ public override void Initialize()
///
/// Data Points count of all timeslices of algorithm
///
- public override long DataPoints => 175;
+ public override long DataPoints => 177;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/RegisterIndicatorRegressionAlgorithm.cs b/Algorithm.CSharp/RegisterIndicatorRegressionAlgorithm.cs
index fe1e1a022cf8..c39c08b7a929 100644
--- a/Algorithm.CSharp/RegisterIndicatorRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/RegisterIndicatorRegressionAlgorithm.cs
@@ -160,7 +160,7 @@ protected override decimal ComputeNextValue(QuoteBar input)
///
/// Data Points count of all timeslices of algorithm
///
- public long DataPoints => 6802;
+ public long DataPoints => 6803;
///
/// Data Points count of the algorithm history
diff --git a/Algorithm.CSharp/WarmupFutureTimeSpanWarmupRegressionAlgorithm.cs b/Algorithm.CSharp/WarmupFutureTimeSpanWarmupRegressionAlgorithm.cs
index 02cd88ce6a61..8bc3c4d26cd3 100644
--- a/Algorithm.CSharp/WarmupFutureTimeSpanWarmupRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/WarmupFutureTimeSpanWarmupRegressionAlgorithm.cs
@@ -36,6 +36,6 @@ public override void OnEndOfAlgorithm()
///
/// Data Points count of all timeslices of algorithm
///
- public override long DataPoints => 28889;
+ public override long DataPoints => 28892;
}
}
diff --git a/Common/Data/UniverseSelection/ContinuousContractUniverse.cs b/Common/Data/UniverseSelection/ContinuousContractUniverse.cs
index bd7a8ee44670..78829b8809a1 100644
--- a/Common/Data/UniverseSelection/ContinuousContractUniverse.cs
+++ b/Common/Data/UniverseSelection/ContinuousContractUniverse.cs
@@ -120,7 +120,7 @@ public IEnumerable GetTriggerTimes(DateTime startTimeUtc, DateTime end
var startTimeLocal = startTimeUtc.ConvertFromUtc(_security.Exchange.TimeZone);
var endTimeLocal = endTimeUtc.ConvertFromUtc(_security.Exchange.TimeZone);
- return Time.EachTradeableDay(_security, startTimeLocal, endTimeLocal, extendedMarketHours: false)
+ return Time.EachTradeableDay(_security, startTimeLocal, endTimeLocal, Configuration.ExtendedMarketHours)
// in live trading selection happens on start see 'DataQueueFuturesChainUniverseDataCollectionEnumerator'
.Where(tradeableDay => _liveMode || tradeableDay >= startTimeLocal)
// in live trading we delay selection so that we make sure auxiliary data is ready
diff --git a/Common/Data/UniverseSelection/SubscriptionRequest.cs b/Common/Data/UniverseSelection/SubscriptionRequest.cs
index 07cfb3f3b07c..1a4bf4cc0f44 100644
--- a/Common/Data/UniverseSelection/SubscriptionRequest.cs
+++ b/Common/Data/UniverseSelection/SubscriptionRequest.cs
@@ -47,7 +47,7 @@ public class SubscriptionRequest : BaseDataRequest
///
/// Gets the tradable days specified by this request, in the security's data time zone
///
- public override IEnumerable TradableDaysInDataTimeZone => Time.EachTradeableDayInTimeZone(Security.Exchange.Hours,
+ public override IEnumerable TradableDaysInDataTimeZone => Time.EachTradeableDayInTimeZone(ExchangeHours,
StartTimeLocal,
EndTimeLocal,
Configuration.DataTimeZone,
diff --git a/Common/TimeKeeper.cs b/Common/TimeKeeper.cs
index 775a8a386f72..720f29db237c 100644
--- a/Common/TimeKeeper.cs
+++ b/Common/TimeKeeper.cs
@@ -67,7 +67,7 @@ public TimeKeeper(DateTime utcDateTime, IEnumerable timeZones)
/// Sets the current UTC time for this time keeper and the attached child instances.
///
/// The current time in UTC
- public void SetUtcDateTime(DateTime utcDateTime)
+ public virtual void SetUtcDateTime(DateTime utcDateTime)
{
_utcDateTime = utcDateTime;
foreach (var timeZone in _localTimeKeepers)
diff --git a/Data/future/comex/map_files/gc.csv b/Data/future/comex/map_files/gc.csv
index d22c6052fd82..c395b0b03b2c 100644
--- a/Data/future/comex/map_files/gc.csv
+++ b/Data/future/comex/map_files/gc.csv
@@ -39,4 +39,31 @@
20131226,gc vmrhkn2nlwv1,COMEX,0
20131226,gc vmrhkn2nlwv1,COMEX,2
20131227,gc vnnzbq584t5p,COMEX,2
-20131231,gc vnnzbq584t5p,COMEX,1
\ No newline at end of file
+20131231,gc vnnzbq584t5p,COMEX,1
+20190523,gc x5nnsa8f63z1,COMEX,3
+20190528,gc x4w39nzp0zh9,COMEX,0
+20190531,gc x5nnsa8f63z1,COMEX,1
+20190605,gc x5nnsa8f63z1,COMEX,2
+20190606,gc x6k5jdazp09p,COMEX,2
+20190625,gc x5nnsa8f63z1,COMEX,0
+20190630,gc x6k5jdazp09p,COMEX,1
+20190724,gc x7doy6a1zn31,COMEX,3
+20190728,gc x6k5jdazp09p,COMEX,0
+20190731,gc x7doy6a1zn31,COMEX,1
+20190802,gc x7doy6a1zn31,COMEX,2
+20190804,gc x868wvvy7iql,COMEX,2
+20190827,gc x7doy6a1zn31,COMEX,0
+20190831,gc x868wvvy7iql,COMEX,1
+20190925,gc x868wvvy7iql,COMEX,0
+20190930,gc x92qnyyiqf19,COMEX,1
+20191006,gc x92qnyyiqf19,COMEX,2
+20191007,gc x9ub6l78vjj1,COMEX,2
+20191028,gc x92qnyyiqf19,COMEX,0
+20191031,gc x9ub6l78vjj1,COMEX,1
+20191121,gc xaou1hjh8xi5,COMEX,3
+20191125,gc x9ub6l78vjj1,COMEX,0
+20191130,gc xaou1hjh8xi5,COMEX,1
+20191203,gc xaou1hjh8xi5,COMEX,2
+20191204,gc xblbskm1rtst,COMEX,2
+20191226,gc xaou1hjh8xi5,COMEX,0
+20191231,gc xblbskm1rtst,COMEX,1
\ No newline at end of file
diff --git a/Engine/DataFeeds/DateChangeTimeKeeper.cs b/Engine/DataFeeds/DateChangeTimeKeeper.cs
new file mode 100644
index 000000000000..7092f71db9f7
--- /dev/null
+++ b/Engine/DataFeeds/DateChangeTimeKeeper.cs
@@ -0,0 +1,332 @@
+/*
+ * 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 QuantConnect.Util;
+using QuantConnect.Data;
+using System.Collections.Generic;
+using QuantConnect.Securities;
+using System.Runtime.CompilerServices;
+
+namespace QuantConnect.Lean.Engine.DataFeeds
+{
+ ///
+ /// Time keeper specialization to keep time for a subscription both in data and exchange time zones.
+ /// It also emits events when the exchange date changes, which is useful to emit date change events
+ /// required for some daily actions like mapping symbols, delistings, splits, etc.
+ ///
+ internal class DateChangeTimeKeeper : TimeKeeper, IDisposable
+ {
+ private IEnumerator _tradableDatesInDataTimeZone;
+ private SubscriptionDataConfig _config;
+ private SecurityExchangeHours _exchangeHours;
+ private DateTime _delistingDate;
+
+ private DateTime _previousNewExchangeDate;
+
+ private bool _needsMoveNext;
+ private bool _initialized;
+
+ private DateTime _exchangeTime;
+ private DateTime _dataTime;
+ private bool _exchangeTimeNeedsUpdate;
+ private bool _dataTimeNeedsUpdate;
+
+ ///
+ /// The current time in the data time zone
+ ///
+ public DateTime DataTime
+ {
+ get
+ {
+ if (_dataTimeNeedsUpdate)
+ {
+ _dataTime = GetTimeIn(_config.DataTimeZone);
+ _dataTimeNeedsUpdate = false;
+ }
+ return _dataTime;
+ }
+ }
+
+ ///
+ /// The current time in the exchange time zone
+ ///
+ public DateTime ExchangeTime
+ {
+ get
+ {
+ if (_exchangeTimeNeedsUpdate)
+ {
+ _exchangeTime = GetTimeIn(_config.ExchangeTimeZone);
+ _exchangeTimeNeedsUpdate = false;
+ }
+ return _exchangeTime;
+ }
+ }
+
+ ///
+ /// Event that fires every time the exchange date changes
+ ///
+ public event EventHandler NewExchangeDate;
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// The tradable dates in data time zone
+ /// The subscription data configuration this instance will keep track of time for
+ /// The exchange hours
+ /// The symbol's delisting date
+ public DateChangeTimeKeeper(IEnumerable tradableDatesInDataTimeZone, SubscriptionDataConfig config,
+ SecurityExchangeHours exchangeHours, DateTime delistingDate)
+ : base(Time.BeginningOfTime, new[] { config.DataTimeZone, config.ExchangeTimeZone })
+ {
+ _tradableDatesInDataTimeZone = tradableDatesInDataTimeZone.GetEnumerator();
+ _config = config;
+ _exchangeHours = exchangeHours;
+ _delistingDate = delistingDate;
+ _exchangeTimeNeedsUpdate = true;
+ _dataTimeNeedsUpdate = true;
+ _needsMoveNext = true;
+ }
+
+ ///
+ /// Disposes the resources
+ ///
+ public void Dispose()
+ {
+ _tradableDatesInDataTimeZone.DisposeSafely();
+ }
+
+ ///
+ /// Sets the current UTC time for this time keeper
+ ///
+ /// The current time in UTC
+ public override void SetUtcDateTime(DateTime utcDateTime)
+ {
+ base.SetUtcDateTime(utcDateTime);
+ _exchangeTimeNeedsUpdate = true;
+ _dataTimeNeedsUpdate = true;
+ }
+
+ ///
+ /// Advances the time keeper towards the target exchange time.
+ /// If an exchange date is found before the target time, it is emitted and the time keeper is set to that date.
+ /// The caller must check whether the target time was reached or if the time keeper was set to a new exchange date before the target time.
+ ///
+ public void AdvanceTowardsExchangeTime(DateTime targetExchangeTime)
+ {
+ if (!_initialized)
+ {
+ throw new InvalidOperationException($"The time keeper has not been initialized. " +
+ $"{nameof(TryAdvanceUntilNextDataDate)} needs to be called at least once to flush the first date before advancing.");
+ }
+
+ var currentExchangeTime = ExchangeTime;
+ // Advancing within the same exchange date, just update the time, no new exchange date will be emitted
+ if (targetExchangeTime.Date == currentExchangeTime.Date)
+ {
+ SetExchangeTime(targetExchangeTime);
+ return;
+ }
+
+ while (currentExchangeTime < targetExchangeTime)
+ {
+ var newExchangeTime = currentExchangeTime + Time.OneDay;
+ if (newExchangeTime > targetExchangeTime)
+ {
+ newExchangeTime = targetExchangeTime;
+ }
+
+ var newExchangeDate = newExchangeTime.Date;
+
+ // We found a new exchange date before the target time, emit it first
+ if (newExchangeDate != currentExchangeTime.Date &&
+ _exchangeHours.IsDateOpen(newExchangeDate, _config.ExtendedMarketHours))
+ {
+ // Stop here, set the new exchange date
+ SetExchangeTime(newExchangeDate);
+ EmitNewExchangeDate(newExchangeDate);
+ return;
+ }
+
+ currentExchangeTime = newExchangeTime;
+ }
+
+ // We reached the target time, set it
+ SetExchangeTime(targetExchangeTime);
+ }
+
+ ///
+ /// Advances the time keeper until the next data date, emitting the new exchange date if this happens before the new data date
+ ///
+ public bool TryAdvanceUntilNextDataDate()
+ {
+ if (!_initialized)
+ {
+ return EmitFirstExchangeDate();
+ }
+
+ // Before moving forward, check whether we need to emit a new exchange date
+ if (TryEmitPassedExchangeDate())
+ {
+ return true;
+ }
+
+ if (!_needsMoveNext || _tradableDatesInDataTimeZone.MoveNext())
+ {
+ var nextDataDate = _tradableDatesInDataTimeZone.Current;
+ var nextExchangeTime = nextDataDate.ConvertTo(_config.DataTimeZone, _config.ExchangeTimeZone);
+ var nextExchangeDate = nextExchangeTime.Date;
+
+ if (nextExchangeDate > _delistingDate)
+ {
+ // We are done, but an exchange date might still need to be emitted
+ TryEmitPassedExchangeDate();
+ _needsMoveNext = false;
+ return false;
+ }
+
+ // If the exchange is not behind the data, the data might have not been enough to emit the exchange date,
+ // which already passed if we are moving on to the next data date. So we need to check if we need to emit it here.
+ // e.g. moving data date from tuesday to wednesday, but the exchange date is already past the end of tuesday
+ // (by N hours, depending on the time zones offset). If data didn't trigger the exchange date change, we need to do it here.
+ if (!IsExchangeBehindData(nextExchangeTime, nextDataDate) && nextExchangeDate > _previousNewExchangeDate)
+ {
+ EmitNewExchangeDate(nextExchangeDate);
+ SetExchangeTime(nextExchangeDate);
+ // nextExchangeDate == DataTime means time zones are synchronized, need to move next only when exchange is actually ahead
+ _needsMoveNext = nextExchangeDate == DataTime;
+ return true;
+ }
+
+ _needsMoveNext = true;
+ SetDataTime(nextDataDate);
+ return true;
+ }
+
+ _needsMoveNext = false;
+ return false;
+ }
+
+ ///
+ /// Emits the first exchange date for the algorithm so that the first daily events are triggered (mappings, delistings, etc.)
+ ///
+ /// True if the new exchange date is emitted. False if already done or the tradable dates enumerable is empty
+ private bool EmitFirstExchangeDate()
+ {
+ if (_initialized)
+ {
+ return false;
+ }
+
+ if (!_tradableDatesInDataTimeZone.MoveNext())
+ {
+ _initialized = true;
+ return false;
+ }
+
+ var firstDataDate = _tradableDatesInDataTimeZone.Current;
+ var firstExchangeTime = firstDataDate.ConvertTo(_config.DataTimeZone, _config.ExchangeTimeZone);
+ var firstExchangeDate = firstExchangeTime.Date;
+
+ DateTime exchangeDateToEmit;
+ // The exchange is ahead of the data, so we need to emit the current exchange date, which already passed
+ if (firstExchangeTime < firstDataDate && _exchangeHours.IsDateOpen(firstExchangeDate, _config.ExtendedMarketHours))
+ {
+ exchangeDateToEmit = firstExchangeDate;
+ SetExchangeTime(exchangeDateToEmit);
+ // Don't move, the current data date still needs to be consumed
+ _needsMoveNext = false;
+ }
+ // The exchange is behind of (or in sync with) data: exchange has not passed to this new date, but with emit it here
+ // so that first daily things are done (mappings, delistings, etc.)
+ else
+ {
+ exchangeDateToEmit = firstDataDate;
+ SetDataTime(firstDataDate);
+ _needsMoveNext = true;
+ }
+
+ EmitNewExchangeDate(exchangeDateToEmit);
+ _initialized = true;
+ return true;
+ }
+
+ ///
+ /// Determines whether the exchange time zone is behind the data time zone
+ ///
+ ///
+ public bool IsExchangeBehindData()
+ {
+ return IsExchangeBehindData(ExchangeTime, DataTime);
+ }
+
+ ///
+ /// Determines whether the exchange time zone is behind the data time zone
+ ///
+ private static bool IsExchangeBehindData(DateTime exchangeTime, DateTime dataTime)
+ {
+ return dataTime > exchangeTime;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void SetExchangeTime(DateTime exchangeTime)
+ {
+ SetUtcDateTime(exchangeTime.ConvertToUtc(_config.ExchangeTimeZone));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void SetDataTime(DateTime dataTime)
+ {
+ SetUtcDateTime(dataTime.ConvertToUtc(_config.DataTimeZone));
+ }
+
+ ///
+ /// Checks that before moving to the next data date, if the exchange date has already passed and has been emitted, else it emits it.
+ /// This can happen when the exchange is behind of the data. e.g We advance data date from Monday to Tuesday, then the data itself
+ /// will drive the exchange data change (N hours later, depending on the time zones offset).
+ /// But if there is no enough data or the file is not found, the new exchange date will not be emitted, so we need to do it here.
+ ///
+ private bool TryEmitPassedExchangeDate()
+ {
+ if (_needsMoveNext && _tradableDatesInDataTimeZone.Current != default)
+ {
+ // This data date passed, and it should have emitted as an exchange tradable date when detected
+ // as a date change in the data itself, if not, emit it now before moving to the next data date
+ var currentDataDate = _tradableDatesInDataTimeZone.Current;
+ if (_previousNewExchangeDate < currentDataDate &&
+ _exchangeHours.IsDateOpen(currentDataDate, _config.ExtendedMarketHours))
+ {
+ var nextExchangeDate = currentDataDate;
+ SetExchangeTime(nextExchangeDate);
+ EmitNewExchangeDate(nextExchangeDate);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Emits a new exchange date event
+ ///
+ private void EmitNewExchangeDate(DateTime newExchangeDate)
+ {
+ NewExchangeDate?.Invoke(this, newExchangeDate);
+ _previousNewExchangeDate = newExchangeDate;
+ }
+ }
+}
diff --git a/Engine/DataFeeds/SubscriptionDataReader.cs b/Engine/DataFeeds/SubscriptionDataReader.cs
index 96cb6ce7e93f..e05ad98553dc 100644
--- a/Engine/DataFeeds/SubscriptionDataReader.cs
+++ b/Engine/DataFeeds/SubscriptionDataReader.cs
@@ -27,6 +27,7 @@
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Custom.Tiingo;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
+using QuantConnect.Securities;
namespace QuantConnect.Lean.Engine.DataFeeds
{
@@ -71,12 +72,16 @@ public class SubscriptionDataReader : IEnumerator, ITradableDatesNotif
private BaseData _previous;
private decimal? _lastRawPrice;
- private readonly IEnumerator _tradeableDates;
+ private DateChangeTimeKeeper _timeKeeper;
+ private readonly IEnumerable _tradableDatesInDataTimeZone;
+ private readonly SecurityExchangeHours _exchangeHours;
// used when emitting aux data from within while loop
private readonly IDataCacheProvider _dataCacheProvider;
private DateTime _delistingDate;
+ private bool _updatingDataEnumerator;
+
///
/// Event fired when an invalid configuration has been detected
///
@@ -151,10 +156,11 @@ public SubscriptionDataReader(SubscriptionDataConfig config,
_factorFileProvider = factorFileProvider;
_dataCacheProvider = dataCacheProvider;
- //Save access to securities
- _tradeableDates = dataRequest.TradableDaysInDataTimeZone.GetEnumerator();
_dataProvider = dataProvider;
_objectStore = objectStore;
+
+ _tradableDatesInDataTimeZone = dataRequest.TradableDaysInDataTimeZone;
+ _exchangeHours = dataRequest.ExchangeHours;
}
///
@@ -248,6 +254,10 @@ public void Initialize()
// adding a day so we stop at EOD
_delistingDate = _delistingDate.AddDays(1);
+
+ _timeKeeper = new DateChangeTimeKeeper(_tradableDatesInDataTimeZone, _config, _exchangeHours, _delistingDate);
+ _timeKeeper.NewExchangeDate += HandleNewTradableDate;
+
UpdateDataEnumerator(true);
_initialized = true;
@@ -292,6 +302,7 @@ public bool MoveNext()
{
break;
}
+
// keep enumerating until we find something that is within our time frame
while (_subscriptionFactoryEnumerator.MoveNext())
{
@@ -302,6 +313,50 @@ public bool MoveNext()
continue;
}
+ // We rely on symbol change to detect a mapping or symbol change, instead of using SubscriptionDataConfig.NewSymbol
+ // because only one of the configs with the same symbol will trigger a symbol change event.
+ var previousMappedSymbol = _config.MappedSymbol;
+
+ // Advance the time keeper either until the current instance time (to synchronize) or until the source changes.
+ // Note: use time instead of end time to avoid skipping instances that all have the same timestamps in the same file (e.g. universe data)
+ var currentSource = _source;
+ var nextExchangeDate = _config.Resolution == Resolution.Daily && _timeKeeper.IsExchangeBehindData()
+ // If daily and exchange is behind data, data for date X will have a start time within date X-1,
+ // so we use the actual date from end time. e.g. a daily bar for Jan15 can have a start time of Jan14 8PM
+ // (exchange tz 4 hours behind data tz) and end time would be Jan15 8PM.
+ ? instance.EndTime
+ : instance.Time;
+ while (_timeKeeper.ExchangeTime < nextExchangeDate && currentSource == _source)
+ {
+ _timeKeeper.AdvanceTowardsExchangeTime(nextExchangeDate);
+ }
+
+ // Source change, check if we should emit the current instance
+ if (currentSource != _source
+ && (
+ // After a mapping for every resolution except daily:
+ // For other resolutions, the instance that triggered the exchange date change should be skipped,
+ // it's end time will be either midnight or for a future date. The new source might have a data point with this times.
+ (_config.MappedSymbol != previousMappedSymbol && _config.Resolution != Resolution.Daily)
+ // Skip if the exchange time zone is behind of the data time zone:
+ // The new source might have data for these same times, we want data for the new symbol
+ || (_config.Resolution == Resolution.Daily && _timeKeeper.IsExchangeBehindData())
+ // skip if the instance if it's beyond what the previous source should have.
+ // e.g. A file mistakenly has data for the next day
+ // (see SubscriptionDataReaderTests.DoesNotEmitDataBeyondTradableDate unit test)
+ // or the instance that triggered the exchange date change is for a future date (no data found in between)
+ || instance.EndTime.ConvertTo(_config.ExchangeTimeZone, _config.DataTimeZone).Date >= _timeKeeper.DataTime.Date
+ ))
+ {
+ continue;
+ }
+
+ // This can happen after a mapping, we already have data but we need to skip some points that belong to a previous date.
+ if (Current != null && instance.EndTime < _timeKeeper.ExchangeTime)
+ {
+ continue;
+ }
+
// prevent emitting past data, this can happen when switching symbols on daily data
if (_previous != null && _config.Resolution != Resolution.Tick)
{
@@ -325,43 +380,6 @@ public bool MoveNext()
continue;
}
- // if we move past our current 'date' then we need to do daily things, such
- // as updating factors and symbol mapping
- var shouldSkip = false;
-
- while (instance.Time.ConvertTo(_config.ExchangeTimeZone, _config.DataTimeZone).Date > _tradeableDates.Current)
- {
- var currentTradeableDate = _tradeableDates.Current;
- if (UpdateDataEnumerator(false))
- {
- shouldSkip = true;
- if (_subscriptionFactoryEnumerator == null)
- {
- // if null enumerator we have not been mapped into something new, we just ended,
- // let's double check this data point should be skipped or not based on current tradeable date
- shouldSkip = instance.Time.ConvertTo(_config.ExchangeTimeZone, _config.DataTimeZone).Date > _tradeableDates.Current;
- if (shouldSkip)
- {
- // the end, no new enumerator and current instance is beyond current date
- _endOfStream = true;
- return false;
- }
- }
- break;
- }
-
- if (currentTradeableDate == _tradeableDates.Current)
- {
- // if tradeable dates did not advanced let's not check again
- break;
- }
- }
- if(shouldSkip)
- {
- // Skip current 'instance' if its start time is beyond the current date, fixes GH issue 3912
- continue;
- }
-
// We have to perform this check after refreshing the enumerator, if appropriate
// 'instance' could be a data point far in the future due to remapping (GH issue 5232) in which case it will be dropped
if (instance.Time > _periodFinish)
@@ -389,6 +407,15 @@ public bool MoveNext()
return false;
}
+ ///
+ /// Emits a new tradable date event and tries to update the data enumerator if necessary
+ ///
+ private void HandleNewTradableDate(object sender, DateTime date)
+ {
+ OnNewTradableDate(new NewTradableDateEventArgs(date, _previous, _config.Symbol, _lastRawPrice));
+ UpdateDataEnumerator(false);
+ }
+
///
/// Resolves the next enumerator to be used in and updates
///
@@ -396,58 +423,74 @@ public bool MoveNext()
/// True, if the enumerator has been updated (even if updated to null)
private bool UpdateDataEnumerator(bool endOfEnumerator)
{
- do
+ // Guard for infinite recursion: during an enumerator update, we might ask for a new date,
+ // which might end up with a new exchange date being detected and another update being requested.
+ // Just skip that update and let's do it ourselves after the date is resolved
+ if (_updatingDataEnumerator)
{
- // always advance the date enumerator, this function is intended to be
- // called on date changes, never return null for live mode, we'll always
- // just keep trying to refresh the subscription
- DateTime date;
- if (!TryGetNextDate(out date))
- {
- _subscriptionFactoryEnumerator = null;
- // if we run out of dates then we're finished with this subscription
- return true;
- }
+ return false;
+ }
- // fetch the new source, using the data time zone for the date
- var newSource = _dataFactory.GetSource(_config, date, false);
- if (newSource == null)
+ _updatingDataEnumerator = true;
+ try
+ {
+ do
{
- // move to the next day
- continue;
- }
+ var date = _timeKeeper.DataTime.Date;
- // check if we should create a new subscription factory
- var sourceChanged = _source != newSource && !string.IsNullOrEmpty(newSource.Source);
- if (sourceChanged)
- {
- // dispose of the current enumerator before creating a new one
- Dispose();
+ // Update current date only if the enumerator has ended, else we might just need to change files
+ // (e.g. same date, but symbol was mapped)
+ if (endOfEnumerator && !TryGetNextDate(out date))
+ {
+ _subscriptionFactoryEnumerator = null;
+ // if we run out of dates then we're finished with this subscription
+ return true;
+ }
- // save off for comparison next time
- _source = newSource;
- var subscriptionFactory = CreateSubscriptionFactory(newSource, _dataFactory, _dataProvider);
- _subscriptionFactoryEnumerator = SortEnumerator.TryWrapSortEnumerator(newSource.Sort, subscriptionFactory.Read(newSource));
- return true;
- }
+ // fetch the new source, using the data time zone for the date
+ var newSource = _dataFactory.GetSource(_config, date, false);
+ if (newSource == null)
+ {
+ // move to the next day
+ continue;
+ }
- // if there's still more in the enumerator and we received the same source from the GetSource call
- // above, then just keep using the same enumerator as we were before
- if (!endOfEnumerator) // && !sourceChanged is always true here
- {
- return false;
- }
+ // check if we should create a new subscription factory
+ var sourceChanged = _source != newSource && !string.IsNullOrEmpty(newSource.Source);
+ if (sourceChanged)
+ {
+ // dispose of the current enumerator before creating a new one
+ _subscriptionFactoryEnumerator.DisposeSafely();
+
+ // save off for comparison next time
+ _source = newSource;
+ var subscriptionFactory = CreateSubscriptionFactory(newSource, _dataFactory, _dataProvider);
+ _subscriptionFactoryEnumerator = SortEnumerator.TryWrapSortEnumerator(newSource.Sort, subscriptionFactory.Read(newSource));
+ return true;
+ }
+
+ // if there's still more in the enumerator and we received the same source from the GetSource call
+ // above, then just keep using the same enumerator as we were before
+ if (!endOfEnumerator) // && !sourceChanged is always true here
+ {
+ return false;
+ }
- // keep churning until we find a new source or run out of tradeable dates
- // in live mode tradeable dates won't advance beyond today's date, but
- // TryGetNextDate will return false if it's already at today
+ // keep churning until we find a new source or run out of tradeable dates
+ // in live mode tradeable dates won't advance beyond today's date, but
+ // TryGetNextDate will return false if it's already at today
+ }
+ while (true);
+ }
+ finally
+ {
+ _updatingDataEnumerator = false;
}
- while (true);
}
private ISubscriptionDataSourceReader CreateSubscriptionFactory(SubscriptionDataSource source, BaseData baseDataInstance, IDataProvider dataProvider)
{
- var factory = SubscriptionDataSourceReader.ForSource(source, _dataCacheProvider, _config, _tradeableDates.Current, false, baseDataInstance, dataProvider, _objectStore);
+ var factory = SubscriptionDataSourceReader.ForSource(source, _dataCacheProvider, _config, _timeKeeper.DataTime.Date, false, baseDataInstance, dataProvider, _objectStore);
AttachEventHandlers(factory, source);
return factory;
}
@@ -514,11 +557,9 @@ private void AttachEventHandlers(ISubscriptionDataSourceReader dataSourceReader,
/// True if we got a new date from the enumerator, false if it's exhausted, or in live mode if we're already at today
private bool TryGetNextDate(out DateTime date)
{
- while (_tradeableDates.MoveNext())
+ while (_timeKeeper.TryAdvanceUntilNextDataDate())
{
- date = _tradeableDates.Current;
-
- OnNewTradableDate(new NewTradableDateEventArgs(date, _previous, _config.Symbol, _lastRawPrice));
+ date = _timeKeeper.DataTime.Date;
if (_pastDelistedDate || date > _delistingDate)
{
@@ -533,7 +574,7 @@ private bool TryGetNextDate(out DateTime date)
}
// don't do other checks if we haven't gotten data for this date yet
- if (_previous != null && _previous.EndTime.ConvertTo(_config.ExchangeTimeZone, _config.DataTimeZone) > _tradeableDates.Current)
+ if (_previous != null && _previous.EndTime.ConvertTo(_config.ExchangeTimeZone, _config.DataTimeZone) > date)
{
continue;
}
@@ -562,7 +603,12 @@ public void Reset()
public void Dispose()
{
_subscriptionFactoryEnumerator.DisposeSafely();
- _tradeableDates.DisposeSafely();
+
+ if (_initialized)
+ {
+ _timeKeeper.NewExchangeDate -= HandleNewTradableDate;
+ _timeKeeper.DisposeSafely();
+ }
}
///
diff --git a/Engine/Properties/AssemblyInfo.cs b/Engine/Properties/AssemblyInfo.cs
index b316a6a0c5f5..cfc555b0400b 100644
--- a/Engine/Properties/AssemblyInfo.cs
+++ b/Engine/Properties/AssemblyInfo.cs
@@ -1,17 +1,20 @@
-using System.Reflection;
+using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("QuantConnect.Lean.Engine")]
[assembly: AssemblyProduct("QuantConnect.Lean.Engine")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("5c62be9c-5ecb-413e-bb6e-c6544e5b809c")]
\ No newline at end of file
+[assembly: Guid("5c62be9c-5ecb-413e-bb6e-c6544e5b809c")]
+
+[assembly: InternalsVisibleTo("QuantConnect.Tests")]
diff --git a/Tests/Algorithm/AlgorithmChainsTests.cs b/Tests/Algorithm/AlgorithmChainsTests.cs
index 91386cc01d88..3749d22177f1 100644
--- a/Tests/Algorithm/AlgorithmChainsTests.cs
+++ b/Tests/Algorithm/AlgorithmChainsTests.cs
@@ -41,7 +41,14 @@ public void OneTimeSetUp()
var parameters = new HistoryProviderInitializeParameters(null, null, TestGlobals.DataProvider, TestGlobals.DataCacheProvider,
TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, (_) => { }, true, new DataPermissionManager(), null,
new AlgorithmSettings());
- historyProvider.Initialize(parameters);
+ try
+ {
+ historyProvider.Initialize(parameters);
+ }
+ catch (InvalidOperationException)
+ {
+ // Already initialized
+ }
_algorithm = new QCAlgorithm();
_algorithm.SetHistoryProvider(historyProvider);
diff --git a/Tests/Common/Securities/Options/OptionChainProviderTests.cs b/Tests/Common/Securities/Options/OptionChainProviderTests.cs
index 720f24fce065..9dc85b92a7a6 100644
--- a/Tests/Common/Securities/Options/OptionChainProviderTests.cs
+++ b/Tests/Common/Securities/Options/OptionChainProviderTests.cs
@@ -35,7 +35,14 @@ public void OneTimeSetUp()
{
var historyProvider = Composer.Instance.GetExportedValueByTypeName("SubscriptionDataReaderHistoryProvider", true);
var parameters = new HistoryProviderInitializeParameters(null, null, TestGlobals.DataProvider, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, (_) => { }, true, new DataPermissionManager(), null, new AlgorithmSettings());
- historyProvider.Initialize(parameters);
+ try
+ {
+ historyProvider.Initialize(parameters);
+ }
+ catch (InvalidOperationException)
+ {
+ // Already initialized
+ }
}
[Test]
diff --git a/Tests/Engine/DataFeeds/DateChangeTimeKeeperTests.cs b/Tests/Engine/DataFeeds/DateChangeTimeKeeperTests.cs
new file mode 100644
index 000000000000..052a1576fdea
--- /dev/null
+++ b/Tests/Engine/DataFeeds/DateChangeTimeKeeperTests.cs
@@ -0,0 +1,360 @@
+/*
+ * 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 System.Linq;
+using NodaTime;
+using NUnit.Framework;
+using QuantConnect.Data;
+using QuantConnect.Data.Market;
+using QuantConnect.Lean.Engine.DataFeeds;
+using QuantConnect.Securities;
+using QuantConnect.Util;
+
+namespace QuantConnect.Tests.Engine.DataFeeds
+{
+ [TestFixture]
+ public class DateChangeTimeKeeperTests
+ {
+ private static DateTime _start = new DateTime(2024, 10, 01);
+ private static DateTime _end = new DateTime(2024, 10, 11);
+
+ private static TestCaseData[] TimeZonesTestCases => new TestCaseData[]
+ {
+ new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(-1))),
+ new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(-2))),
+ new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(-3))),
+ new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(-4))),
+ new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(1))),
+ new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(2))),
+ new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(3))),
+ new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(4))),
+ new(TimeZones.Utc, TimeZones.Utc),
+ new(TimeZones.NewYork, TimeZones.NewYork),
+ new(TimeZones.HongKong, TimeZones.HongKong),
+ };
+
+ [TestCaseSource(nameof(TimeZonesTestCases))]
+ public void EmitsFirstExchangeDateEvent(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
+ {
+ using var timeKeeper = GetTimeKeeper(dataTimeZone, exchangeTimeZone, true, null, out var tradableDates, out var config, out var exchangeHours);
+
+ var emittedExchangeDates = new List();
+ void HandleNewTradableDate(object sender, DateTime date)
+ {
+ emittedExchangeDates.Add(date);
+ }
+
+ timeKeeper.NewExchangeDate += HandleNewTradableDate;
+
+ var firstDataDate = tradableDates[0];
+ var firstDataDateInExchangeTimeZone = firstDataDate.ConvertTo(dataTimeZone, exchangeTimeZone);
+ var firstExchangeDateIsBeforeFirstDataDate = firstDataDateInExchangeTimeZone < firstDataDate;
+
+ try
+ {
+ Assert.IsTrue(timeKeeper.TryAdvanceUntilNextDataDate());
+ Assert.AreEqual(1, emittedExchangeDates.Count);
+
+ if (firstExchangeDateIsBeforeFirstDataDate)
+ {
+ // The first exchange date is the day before the first data date when the exchange is behind the data
+ var expectedFirstExchangeDate = firstDataDate.AddDays(-1);
+ Assert.AreEqual(expectedFirstExchangeDate, emittedExchangeDates[0]);
+ Assert.AreEqual(expectedFirstExchangeDate.ConvertTo(exchangeTimeZone, dataTimeZone), timeKeeper.DataTime);
+ }
+ else
+ {
+ // If exchange is ahead of the data, even though technically at the first data date the exchange date hasn't changed,
+ // we emit the first one so that first daily actions are performed (mappings, delistings, etc).
+ Assert.AreEqual(firstDataDate, emittedExchangeDates[0]);
+ Assert.AreEqual(firstDataDate, timeKeeper.DataTime);
+ }
+ }
+ finally
+ {
+ timeKeeper.NewExchangeDate -= HandleNewTradableDate;
+ }
+ }
+
+ [TestCaseSource(nameof(TimeZonesTestCases))]
+ public void ExchangeDatesAreEmittedByAdvancingToNextDataDate(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
+ {
+ using var timeKeeper = GetTimeKeeper(dataTimeZone, exchangeTimeZone, false, null, out var tradableDates, out var config, out var exchangeHours);
+
+ var exchangeDateEmitted = false;
+ var emittedExchangeDates = new List();
+ void HandleNewTradableDate(object sender, DateTime date)
+ {
+ emittedExchangeDates.Add(date);
+ exchangeDateEmitted = true;
+ }
+
+ timeKeeper.NewExchangeDate += HandleNewTradableDate;
+
+ var expectedExchangeDates = new List()
+ {
+ new(2024, 10, 1),
+ new(2024, 10, 2),
+ new(2024, 10, 3),
+ new(2024, 10, 4),
+ new(2024, 10, 7),
+ new(2024, 10, 8),
+ new(2024, 10, 9),
+ new(2024, 10, 10),
+ new(2024, 10, 11),
+ };
+
+ var firstDataDate = tradableDates[0];
+ var firstDataDateInExchangeTimeZone = firstDataDate.ConvertTo(dataTimeZone, exchangeTimeZone);
+ var exchangeIsBehindData = firstDataDateInExchangeTimeZone < firstDataDate;
+ if (exchangeIsBehindData)
+ {
+ expectedExchangeDates.Insert(0, new(2024, 9, 30));
+ expectedExchangeDates.RemoveAt(expectedExchangeDates.Count - 1);
+ }
+
+ try
+ {
+ // Flush first date:
+ Assert.IsTrue(timeKeeper.TryAdvanceUntilNextDataDate());
+ Assert.IsTrue(exchangeDateEmitted);
+
+ for (var i = 1; i < tradableDates.Count; i++)
+ {
+ exchangeDateEmitted = false;
+ Assert.IsTrue(timeKeeper.TryAdvanceUntilNextDataDate());
+
+ if (timeKeeper.DataTime == timeKeeper.ExchangeTime)
+ {
+ Assert.AreEqual(tradableDates[i], timeKeeper.DataTime);
+ Assert.AreEqual(tradableDates[i], timeKeeper.ExchangeTime);
+ Assert.IsTrue(exchangeDateEmitted);
+ Assert.AreEqual(tradableDates[i], emittedExchangeDates[^1]);
+ }
+ else
+ {
+ if (exchangeIsBehindData)
+ {
+ Assert.IsFalse(exchangeDateEmitted);
+ Assert.AreEqual(tradableDates[i - 1], timeKeeper.DataTime);
+ }
+ else
+ {
+ Assert.IsTrue(exchangeDateEmitted);
+ Assert.AreEqual(emittedExchangeDates[^1], timeKeeper.ExchangeTime);
+ }
+
+ // Move again to the next data date or next exchange date
+ exchangeDateEmitted = false;
+ Assert.IsTrue(timeKeeper.TryAdvanceUntilNextDataDate());
+
+ if (exchangeIsBehindData)
+ {
+ Assert.IsTrue(exchangeDateEmitted);
+ Assert.AreEqual(emittedExchangeDates[^1], timeKeeper.ExchangeTime);
+ }
+ else
+ {
+ Assert.IsFalse(exchangeDateEmitted);
+ Assert.AreEqual(tradableDates[i], timeKeeper.DataTime);
+ }
+ }
+ }
+
+ CollectionAssert.AreEqual(expectedExchangeDates, emittedExchangeDates);
+ }
+ finally
+ {
+ timeKeeper.NewExchangeDate -= HandleNewTradableDate;
+ }
+ }
+
+ [TestCaseSource(nameof(TimeZonesTestCases))]
+ public void ExchangeDatesAreEmittedByAdvancingToNextGivenExchangeTime(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
+ {
+ using var timeKeeper = GetTimeKeeper(dataTimeZone, exchangeTimeZone, false, null, out var tradableDates, out var config, out var exchangeHours);
+
+ var exchangeDateEmitted = false;
+ var emittedExchangeDates = new List();
+ void HandleNewTradableDate(object sender, DateTime date)
+ {
+ emittedExchangeDates.Add(date);
+ exchangeDateEmitted = true;
+ }
+
+ timeKeeper.NewExchangeDate += HandleNewTradableDate;
+
+ var expectedExchangeDates = new List()
+ {
+ new(2024, 10, 1),
+ new(2024, 10, 2),
+ new(2024, 10, 3),
+ new(2024, 10, 4),
+ new(2024, 10, 7),
+ new(2024, 10, 8),
+ new(2024, 10, 9),
+ new(2024, 10, 10),
+ new(2024, 10, 11),
+ };
+
+ var firstDataDate = tradableDates[0];
+ var firstDataDateInExchangeTimeZone = firstDataDate.ConvertTo(dataTimeZone, exchangeTimeZone);
+ var exchangeIsBehindData = firstDataDateInExchangeTimeZone < firstDataDate;
+ if (exchangeIsBehindData)
+ {
+ expectedExchangeDates.Insert(0, new(2024, 9, 30));
+ expectedExchangeDates.RemoveAt(expectedExchangeDates.Count - 1);
+ }
+
+ try
+ {
+ // Flush first date:
+ Assert.IsTrue(timeKeeper.TryAdvanceUntilNextDataDate());
+ Assert.IsTrue(exchangeDateEmitted);
+
+ // Using multiple step sizes to simulate data coming in with gaps
+ var steps = new Queue();
+ steps.Enqueue(TimeSpan.FromMinutes(1));
+ steps.Enqueue(TimeSpan.FromMinutes(5));
+ steps.Enqueue(TimeSpan.FromMinutes(30));
+ steps.Enqueue(TimeSpan.FromHours(1));
+
+ while (emittedExchangeDates.Count != expectedExchangeDates.Count)
+ {
+ exchangeDateEmitted = false;
+ var currentExchangeTime = timeKeeper.ExchangeTime;
+ var step = steps.Dequeue();
+ steps.Enqueue(step);
+ var nextExchangeTime = currentExchangeTime + step;
+ timeKeeper.AdvanceTowardsExchangeTime(nextExchangeTime);
+
+ if (nextExchangeTime.Date != currentExchangeTime.Date &&
+ exchangeHours.IsDateOpen(nextExchangeTime.Date, config.ExtendedMarketHours))
+ {
+ Assert.IsTrue(exchangeDateEmitted);
+ Assert.AreEqual(emittedExchangeDates[^1], nextExchangeTime.Date);
+ Assert.AreEqual(timeKeeper.ExchangeTime, nextExchangeTime.Date);
+ }
+ else
+ {
+ Assert.IsFalse(exchangeDateEmitted);
+ Assert.AreEqual(timeKeeper.ExchangeTime, nextExchangeTime);
+ }
+ }
+
+ CollectionAssert.AreEqual(expectedExchangeDates, emittedExchangeDates);
+ }
+ finally
+ {
+ timeKeeper.NewExchangeDate -= HandleNewTradableDate;
+ }
+ }
+
+ [TestCaseSource(nameof(TimeZonesTestCases))]
+ public void DoesNotAdvancePastDelistingDateWhenAdvancingToNextDataDate(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
+ {
+ var delistingDate = _start.AddDays(7);
+ using var timeKeeper = GetTimeKeeper(dataTimeZone, exchangeTimeZone, false, delistingDate, out var _, out var _, out var _);
+
+ while (timeKeeper.TryAdvanceUntilNextDataDate())
+ {
+ Assert.LessOrEqual(timeKeeper.ExchangeTime.Date, delistingDate.AddDays(1));
+ }
+ }
+
+ [TestCaseSource(nameof(TimeZonesTestCases))]
+ public void ThrowsIfNotInitialized(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
+ {
+ using var timeKeeper = GetTimeKeeper(dataTimeZone, exchangeTimeZone, false, null, out var _, out var _, out var _);
+
+ Assert.Throws(() => timeKeeper.AdvanceTowardsExchangeTime(timeKeeper.ExchangeTime.AddHours(1)));
+ }
+
+ [TestCaseSource(nameof(TimeZonesTestCases))]
+ public void FinishesRightAwayIfThereAreNoTradableDates(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
+ {
+ using var timeKeeper = GetNoDatesTimeKeeper(dataTimeZone, exchangeTimeZone);
+
+ Assert.IsFalse(timeKeeper.TryAdvanceUntilNextDataDate());
+ }
+
+ private static DateChangeTimeKeeper GetTimeKeeper(DateTimeZone dataTimeZone,
+ DateTimeZone exchangeTimeZone,
+ bool exchangeAlwaysOpen,
+ DateTime? delistingDate,
+ out List tradableDates,
+ out SubscriptionDataConfig config,
+ out SecurityExchangeHours exchangeHours)
+ {
+ var symbol = Symbols.SPY;
+ exchangeHours = GetMarketHours(symbol, exchangeTimeZone, exchangeAlwaysOpen);
+ config = new SubscriptionDataConfig(typeof(TradeBar),
+ symbol,
+ Resolution.Minute,
+ dataTimeZone,
+ exchangeTimeZone,
+ false,
+ true,
+ false);
+
+ tradableDates = Time.EachTradeableDayInTimeZone(exchangeHours,
+ _start,
+ _end,
+ config.DataTimeZone,
+ config.ExtendedMarketHours).ToList();
+ return new DateChangeTimeKeeper(tradableDates, config, exchangeHours, delistingDate ?? symbol.GetDelistingDate());
+ }
+
+ private static DateChangeTimeKeeper GetNoDatesTimeKeeper(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
+ {
+ var symbol = Symbols.SPY;
+ var exchangeHours = GetMarketHours(symbol, exchangeTimeZone, true);
+ var config = new SubscriptionDataConfig(typeof(TradeBar),
+ symbol,
+ Resolution.Minute,
+ dataTimeZone,
+ exchangeTimeZone,
+ false,
+ true,
+ false);
+
+ var tradableDates = Enumerable.Empty().ToList();
+ return new DateChangeTimeKeeper(tradableDates, config, exchangeHours, symbol.GetDelistingDate());
+ }
+
+ private static SecurityExchangeHours GetMarketHours(Symbol symbol, DateTimeZone exchangeTimeZone, bool alwaysOpen)
+ {
+ SecurityExchangeHours exchangeHours;
+ if (alwaysOpen)
+ {
+ exchangeHours = SecurityExchangeHours.AlwaysOpen(exchangeTimeZone);
+ }
+ else
+ {
+ var databaseExchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
+ exchangeHours = new SecurityExchangeHours(exchangeTimeZone,
+ databaseExchangeHours.Holidays,
+ databaseExchangeHours.MarketHours.ToDictionary(),
+ databaseExchangeHours.EarlyCloses,
+ databaseExchangeHours.LateOpens);
+ }
+
+ return exchangeHours;
+ }
+ }
+}
diff --git a/Tests/Engine/DataFeeds/FileSystemDataFeedTests.cs b/Tests/Engine/DataFeeds/FileSystemDataFeedTests.cs
index 821b0b058b15..7516bf8dce9a 100644
--- a/Tests/Engine/DataFeeds/FileSystemDataFeedTests.cs
+++ b/Tests/Engine/DataFeeds/FileSystemDataFeedTests.cs
@@ -15,6 +15,7 @@
*/
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
@@ -289,5 +290,56 @@ public void FutureChainEnumerator(bool fillForward)
// 2 days worth of minute data
Assert.AreEqual(24 * 2 * 60 + 1, count);
}
+
+ [Test]
+ public void ContinuousFutureUniverseSelectionIsPerformedOnExtendedMarketHoursDates([Values] bool extendedMarketHours)
+ {
+ var job = new BacktestNodePacket();
+ var resultHandler = new BacktestingResultHandler();
+ var feed = new FileSystemDataFeed();
+ var algorithm = new AlgorithmStub(feed);
+ algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
+ algorithm.SetStartDate(new DateTime(2019, 08, 01));
+ algorithm.SetEndDate(new DateTime(2019, 08, 08));
+
+ var dataPermissionManager = new DataPermissionManager();
+ using var synchronizer = new Synchronizer();
+ synchronizer.Initialize(algorithm, algorithm.DataManager);
+
+ feed.Initialize(algorithm, job, resultHandler, TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, TestGlobals.DataProvider,
+ algorithm.DataManager, synchronizer, dataPermissionManager.DataChannelProvider);
+ var future = algorithm.AddFuture("GC", Resolution.Daily, extendedMarketHours: extendedMarketHours);
+ algorithm.PostInitialize();
+
+ var addedSecurities = new HashSet();
+ var mappingCounts = 0;
+
+ using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
+ foreach (var timeSlice in synchronizer.StreamData(cancellationTokenSource.Token))
+ {
+ if (timeSlice.IsTimePulse) continue;
+
+ var addedSymbols = timeSlice.SecurityChanges.AddedSecurities.Select(x => x.Symbol).ToHashSet();
+
+ if (timeSlice.Slice.SymbolChangedEvents.TryGetValue(future.Symbol, out var symbolChangedEvent))
+ {
+ mappingCounts++;
+ var oldSymbol = algorithm.Symbol(symbolChangedEvent.OldSymbol);
+ var newSymbol = algorithm.Symbol(symbolChangedEvent.NewSymbol);
+
+ Assert.IsTrue(addedSecurities.Contains(oldSymbol));
+
+ Assert.IsTrue(addedSymbols.Contains(newSymbol));
+ }
+
+ addedSecurities.UnionWith(addedSymbols);
+ }
+
+ feed.Exit();
+ algorithm.DataManager.RemoveAllSubscriptions();
+
+ var expectedMappingCounts = extendedMarketHours ? 2 : 1;
+ Assert.AreEqual(expectedMappingCounts, mappingCounts);
+ }
}
}