Skip to content

Commit 68ec0b7

Browse files
authored
Disable extended market hours for universe history requests (#8670)
* Disable extended market hours for universe history requests * Exclude extended market hours for all chain universe history requests * Disable extended market hours for chain universes * Minor change * Minor fix * Minor change
1 parent a8d3649 commit 68ec0b7

File tree

7 files changed

+133
-63
lines changed

7 files changed

+133
-63
lines changed

Algorithm/QCAlgorithm.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2017,7 +2017,11 @@ public Security AddSecurity(Symbol symbol, Resolution? resolution = null, bool f
20172017
continuousContractSymbol.ID.Symbol,
20182018
continuousContractSymbol.ID.SecurityType,
20192019
security.Exchange.Hours);
2020-
AddUniverse(new ContinuousContractUniverse(security, continuousUniverseSettings, LiveMode, new SubscriptionDataConfig(canonicalConfig, symbol: continuousContractSymbol)));
2020+
AddUniverse(new ContinuousContractUniverse(security, continuousUniverseSettings, LiveMode,
2021+
new SubscriptionDataConfig(canonicalConfig, symbol: continuousContractSymbol,
2022+
// We can use any data type here, since we are not going to use the data.
2023+
// We just don't want to use the FutureUniverse type because it will force disable extended market hours
2024+
objectType: typeof(Tick), extendedHours: extendedMarketHours)));
20212025

20222026
universe = new FuturesChainUniverse((Future)security, settings);
20232027
}
@@ -2372,7 +2376,10 @@ public Option AddOptionContract(Symbol symbol, Resolution? resolution = null, bo
23722376
Resolution = underlyingConfigs.GetHighestResolution(),
23732377
ExtendedMarketHours = extendedMarketHours
23742378
};
2375-
universe = AddUniverse(new OptionContractUniverse(new SubscriptionDataConfig(configs.First(), symbol: universeSymbol), settings));
2379+
universe = AddUniverse(new OptionContractUniverse(new SubscriptionDataConfig(configs.First(),
2380+
// We can use any data type here, since we are not going to use the data.
2381+
// We just don't want to use the OptionUniverse type because it will force disable extended market hours
2382+
symbol: universeSymbol, objectType: typeof(Tick), extendedHours: extendedMarketHours), settings));
23762383
}
23772384

23782385
// update the universe
@@ -3560,8 +3567,8 @@ private RestResponse SendBroadcast(string typeName, Dictionary<string, object> p
35603567
{
35613568
payload["$type"] = typeName;
35623569
}
3563-
return _api.BroadcastLiveCommand(Globals.OrganizationID,
3564-
AlgorithmMode == AlgorithmMode.Live ? ProjectId : null,
3570+
return _api.BroadcastLiveCommand(Globals.OrganizationID,
3571+
AlgorithmMode == AlgorithmMode.Live ? ProjectId : null,
35653572
payload);
35663573
}
35673574

Common/Data/HistoryRequest.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using NodaTime;
1919
using QuantConnect.Securities;
2020
using System.Collections.Generic;
21+
using QuantConnect.Util;
2122

2223
namespace QuantConnect.Data
2324
{
@@ -27,6 +28,7 @@ namespace QuantConnect.Data
2728
public class HistoryRequest : BaseDataRequest
2829
{
2930
private Resolution? _fillForwardResolution;
31+
private bool _includeExtendedMarketHours;
3032

3133
/// <summary>
3234
/// Gets the symbol to request data for
@@ -57,7 +59,17 @@ public Resolution? FillForwardResolution
5759
/// <summary>
5860
/// Gets whether or not to include extended market hours data, set to false for only normal market hours
5961
/// </summary>
60-
public bool IncludeExtendedMarketHours { get; set; }
62+
public bool IncludeExtendedMarketHours
63+
{
64+
get
65+
{
66+
return _includeExtendedMarketHours;
67+
}
68+
set
69+
{
70+
_includeExtendedMarketHours = value && LeanData.SupportsExtendedMarketHours(DataType);
71+
}
72+
}
6173

6274
/// <summary>
6375
/// Gets the time zone of the time stamps on the raw input data

Common/Data/HistoryRequestFactory.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System;
1717
using NodaTime;
18+
using QuantConnect.Data.UniverseSelection;
1819
using QuantConnect.Interfaces;
1920
using QuantConnect.Securities;
2021
using QuantConnect.Util;
@@ -166,8 +167,8 @@ public DateTime GetStartTimeAlgoTz(
166167
bool? extendedMarketHours = null)
167168
{
168169
var isExtendedMarketHours = false;
169-
// hour resolution does no have extended market hours data
170-
if (resolution != Resolution.Hour)
170+
// hour resolution does no have extended market hours data. Same for chain universes
171+
if (resolution != Resolution.Hour && LeanData.SupportsExtendedMarketHours(dataType))
171172
{
172173
if (extendedMarketHours.HasValue)
173174
{

Common/Data/SubscriptionDataConfig.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ public SubscriptionDataConfig(Type objectType,
222222
Resolution = resolution;
223223
_sid = symbol.ID;
224224
Symbol = symbol;
225-
ExtendedMarketHours = extendedHours;
225+
ExtendedMarketHours = extendedHours && LeanData.SupportsExtendedMarketHours(Type);
226226
PriceScaleFactor = 1;
227227
IsInternalFeed = isInternalFeed;
228228
IsCustomData = isCustom;

Common/Util/LeanData.cs

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,8 @@ public static string GenerateZipFileName(string symbol, SecurityType securityTyp
872872

873873
if (tickType == null)
874874
{
875-
if (securityType == SecurityType.Forex || securityType == SecurityType.Cfd) {
875+
if (securityType == SecurityType.Forex || securityType == SecurityType.Cfd)
876+
{
876877
tickType = TickType.Quote;
877878
}
878879
else
@@ -1589,24 +1590,34 @@ public static bool SetStrictEndTimes(IBaseData baseData, SecurityExchangeHours e
15891590
/// <param name="fileName">File name extracted</param>
15901591
/// <param name="entryName">Entry name extracted</param>
15911592
public static void ParseKey(string key, out string fileName, out string entryName)
1592-
{
1593-
// Default scenario, no entryName included in key
1594-
entryName = null; // default to all entries
1595-
fileName = key;
1596-
1597-
if (key == null)
1598-
{
1599-
return;
1600-
}
1601-
1602-
// Try extracting an entry name; Anything after a # sign
1603-
var hashIndex = key.LastIndexOf("#", StringComparison.Ordinal);
1604-
if (hashIndex != -1)
1605-
{
1606-
entryName = key.Substring(hashIndex + 1);
1607-
fileName = key.Substring(0, hashIndex);
1608-
}
1609-
}
1593+
{
1594+
// Default scenario, no entryName included in key
1595+
entryName = null; // default to all entries
1596+
fileName = key;
1597+
1598+
if (key == null)
1599+
{
1600+
return;
1601+
}
1602+
1603+
// Try extracting an entry name; Anything after a # sign
1604+
var hashIndex = key.LastIndexOf("#", StringComparison.Ordinal);
1605+
if (hashIndex != -1)
1606+
{
1607+
entryName = key.Substring(hashIndex + 1);
1608+
fileName = key.Substring(0, hashIndex);
1609+
}
1610+
}
1611+
1612+
/// <summary>
1613+
/// Helper method to determine if the specified data type supports extended market hours
1614+
/// </summary>
1615+
/// <param name="dataType">The data type</param>
1616+
/// <returns>Whether the specified data type supports extended market hours</returns>
1617+
public static bool SupportsExtendedMarketHours(Type dataType)
1618+
{
1619+
return !dataType.IsAssignableTo(typeof(BaseChainUniverseData));
1620+
}
16101621

16111622
/// <summary>
16121623
/// Helper method to aggregate ticks or bars into lower frequency resolutions

Tests/Algorithm/AlgorithmAddSecurityTests.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
using NUnit.Framework;
1818
using QuantConnect.Algorithm;
19+
using QuantConnect.Data.UniverseSelection;
1920
using QuantConnect.Lean.Engine.DataFeeds;
2021
using QuantConnect.Securities;
2122
using QuantConnect.Securities.Cfd;
@@ -26,7 +27,6 @@
2627
using QuantConnect.Securities.Future;
2728
using QuantConnect.Securities.IndexOption;
2829
using QuantConnect.Securities.Option;
29-
using QuantConnect.Securities.Positions;
3030
using QuantConnect.Tests.Engine.DataFeeds;
3131
using System;
3232
using System.Collections.Generic;
@@ -124,7 +124,16 @@ public void ProperlyAddsFutureWithExtendedMarketHours(
124124
[ValueSource(nameof(FuturesTestCases))] Func<QCAlgorithm, Security> getFuture)
125125
{
126126
var future = _algo.AddFuture(Futures.Indices.VIX, Resolution.Minute, extendedMarketHours: extendedMarketHours);
127-
Assert.That(_algo.SubscriptionManager.Subscriptions.Where(x => x.Symbol == future.Symbol).Select(x => x.ExtendedMarketHours),
127+
var subscriptions = _algo.SubscriptionManager.Subscriptions.Where(x => x.Symbol == future.Symbol).ToList();
128+
129+
var universeSubscriptions = subscriptions.Where(x => x.Type == typeof(FutureUniverse)).ToList();
130+
Assert.AreEqual(1, universeSubscriptions.Count);
131+
// Universe does not support extended market hours
132+
Assert.IsFalse(universeSubscriptions[0].ExtendedMarketHours);
133+
134+
var nonUniverseSubscriptions = subscriptions.Where(x => x.Type != typeof(FutureUniverse)).ToList();
135+
Assert.Greater(nonUniverseSubscriptions.Count, 0);
136+
Assert.That(nonUniverseSubscriptions.Select(x => x.ExtendedMarketHours),
128137
Has.All.EqualTo(extendedMarketHours));
129138
}
130139

Tests/Algorithm/AlgorithmChainsTests.cs

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
using NUnit.Framework;
2020
using Python.Runtime;
2121
using QuantConnect.Algorithm;
22-
using QuantConnect.Data;
2322
using QuantConnect.Data.Market;
24-
using QuantConnect.Interfaces;
2523
using QuantConnect.Lean.Engine.DataFeeds;
2624
using QuantConnect.Securities;
2725
using QuantConnect.Tests.Engine.DataFeeds;
@@ -211,37 +209,65 @@ private static PyObject GetCanonicalSubDataFrame(PyObject dataFrame, Symbol symb
211209
private static IEnumerable<TestCaseData> GetOptionChainApisTestData()
212210
{
213211
var indexSymbol = Symbols.SPX;
214-
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 23, 23, 0, 0));
215-
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 0, 0, 0));
216-
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 1, 0, 0));
217-
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 2, 0, 0));
218-
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 6, 0, 0));
219-
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 12, 0, 0));
220-
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 16, 0, 0));
221-
222212
var equitySymbol = Symbols.GOOG;
223-
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 0, 0, 0));
224-
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 1, 0, 0));
225-
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 2, 0, 0));
226-
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 6, 0, 0));
227-
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 12, 0, 0));
228-
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 16, 0, 0));
229-
230213
var futureSymbol = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2020, 6, 19));
231-
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 04, 23, 0, 0));
232-
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 0, 0, 0));
233-
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 1, 0, 0));
234-
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 2, 0, 0));
235-
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 6, 0, 0));
236-
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 12, 0, 0));
237-
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 16, 0, 0));
214+
215+
foreach (var withSecurityAdded in new[] { true, false })
216+
{
217+
var extendedMarketHoursCases = withSecurityAdded ? [true, false] : new[] { false };
218+
foreach (var withExtendedMarketHours in extendedMarketHoursCases)
219+
{
220+
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 23, 23, 0, 0), withSecurityAdded, withExtendedMarketHours);
221+
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 0, 0, 0), withSecurityAdded, withExtendedMarketHours);
222+
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 1, 0, 0), withSecurityAdded, withExtendedMarketHours);
223+
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 2, 0, 0), withSecurityAdded, withExtendedMarketHours);
224+
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 6, 0, 0), withSecurityAdded, withExtendedMarketHours);
225+
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 12, 0, 0), withSecurityAdded, withExtendedMarketHours);
226+
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 16, 0, 0), withSecurityAdded, withExtendedMarketHours);
227+
228+
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 0, 0, 0), withSecurityAdded, withExtendedMarketHours);
229+
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 1, 0, 0), withSecurityAdded, withExtendedMarketHours);
230+
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 2, 0, 0), withSecurityAdded, withExtendedMarketHours);
231+
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 6, 0, 0), withSecurityAdded, withExtendedMarketHours);
232+
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 12, 0, 0), withSecurityAdded, withExtendedMarketHours);
233+
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 16, 0, 0), withSecurityAdded, withExtendedMarketHours);
234+
235+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 04, 23, 0, 0), withSecurityAdded, withExtendedMarketHours);
236+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 0, 0, 0), withSecurityAdded, withExtendedMarketHours);
237+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 1, 0, 0), withSecurityAdded, withExtendedMarketHours);
238+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 2, 0, 0), withSecurityAdded, withExtendedMarketHours);
239+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 6, 0, 0), withSecurityAdded, withExtendedMarketHours);
240+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 12, 0, 0), withSecurityAdded, withExtendedMarketHours);
241+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 16, 0, 0), withSecurityAdded, withExtendedMarketHours);
242+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 0, 0, 0), withSecurityAdded, withExtendedMarketHours);
243+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 1, 0, 0), withSecurityAdded, withExtendedMarketHours);
244+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 2, 0, 0), withSecurityAdded, withExtendedMarketHours);
245+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 6, 0, 0), withSecurityAdded, withExtendedMarketHours);
246+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 12, 0, 0), withSecurityAdded, withExtendedMarketHours);
247+
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 16, 0, 0), withSecurityAdded, withExtendedMarketHours);
248+
}
249+
}
238250
}
239251

240252
[TestCaseSource(nameof(GetOptionChainApisTestData))]
241-
public void OptionChainApisAreConsistent(Symbol symbol, DateTime dateTime)
253+
public void OptionChainApisAreConsistent(Symbol symbol, DateTime dateTime, bool withSecurityAdded, bool withExtendedMarketHours)
242254
{
243255
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
244256

257+
if (withSecurityAdded)
258+
{
259+
if (symbol.SecurityType == SecurityType.Future)
260+
{
261+
var future = _algorithm.AddFuture(symbol.ID.Symbol, extendedMarketHours: withExtendedMarketHours);
262+
_algorithm.AddFutureOption(future.Symbol);
263+
_algorithm.AddFutureContract(symbol, extendedMarketHours: withExtendedMarketHours);
264+
}
265+
else
266+
{
267+
_algorithm.AddSecurity(symbol, extendedMarketHours: withExtendedMarketHours);
268+
}
269+
}
270+
245271
var exchange = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
246272
var chainFromAlgorithmApi = _algorithm.OptionChain(symbol).Select(x => x.Symbol).ToList();
247273
var chainFromChainProviderApi = _optionChainProvider.GetOptionContractList(symbol,
@@ -262,26 +288,30 @@ private static IEnumerable<TestCaseData> GetFutureChainApisTestData()
262288
{
263289
foreach (var withFutureAdded in new[] { true, false })
264290
{
265-
yield return new TestCaseData(symbol, new DateTime(2013, 10, 06, 23, 0, 0), withFutureAdded);
266-
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 0, 0, 0), withFutureAdded);
267-
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 1, 0, 0), withFutureAdded);
268-
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 2, 0, 0), withFutureAdded);
269-
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 6, 0, 0), withFutureAdded);
270-
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 12, 0, 0), withFutureAdded);
271-
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 16, 0, 0), withFutureAdded);
291+
var extendedMarketHoursCases = withFutureAdded ? [true, false] : new[] { false };
292+
foreach (var withExtendedMarketHours in extendedMarketHoursCases)
293+
{
294+
yield return new TestCaseData(symbol, new DateTime(2013, 10, 06, 23, 0, 0), withFutureAdded, withExtendedMarketHours);
295+
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 0, 0, 0), withFutureAdded, withExtendedMarketHours);
296+
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 1, 0, 0), withFutureAdded, withExtendedMarketHours);
297+
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 2, 0, 0), withFutureAdded, withExtendedMarketHours);
298+
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 6, 0, 0), withFutureAdded, withExtendedMarketHours);
299+
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 12, 0, 0), withFutureAdded, withExtendedMarketHours);
300+
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 16, 0, 0), withFutureAdded, withExtendedMarketHours);
301+
}
272302
}
273303
}
274304
}
275305

276306
[TestCaseSource(nameof(GetFutureChainApisTestData))]
277-
public void FuturesChainApisAreConsistent(Symbol symbol, DateTime dateTime, bool withFutureAdded)
307+
public void FuturesChainApisAreConsistent(Symbol symbol, DateTime dateTime, bool withFutureAdded, bool withExtendedMarketHours)
278308
{
279309
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
280310

281311
if (withFutureAdded)
282312
{
283313
// It should work regardless of whether the future is added to the algorithm
284-
_algorithm.AddFuture("ES");
314+
_algorithm.AddFuture(symbol.ID.Symbol, extendedMarketHours: withExtendedMarketHours);
285315
}
286316

287317
var exchange = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);

0 commit comments

Comments
 (0)