Skip to content

Commit a8d3649

Browse files
authored
Prevent fill-forwarding outside OpenInterest trading hours (#8662)
* Prevent fill-forwarding outside OpenInterest trading hours * Solve review comments * Fix unit test * Resolve review comments * Revert changes in FillForwardEnumerator * Revert unrelated changes
1 parent 73684e8 commit a8d3649

File tree

4 files changed

+61
-5
lines changed

4 files changed

+61
-5
lines changed

Algorithm.CSharp/OptionOpenInterestRegressionAlgorithm.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ public override void OnOrderEvent(OrderEvent orderEvent)
122122
/// <summary>
123123
/// Data Points count of the algorithm history
124124
/// </summary>
125-
public int AlgorithmHistoryDataPoints => 146806;
125+
public int AlgorithmHistoryDataPoints => 77028;
126126

127127
/// <summary>
128128
/// Final status of the algorithm

Engine/HistoricalData/SubscriptionDataReaderHistoryProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ private Subscription CreateSubscription(HistoryRequest request)
165165
}
166166

167167
var readOnlyRef = Ref.CreateReadOnly(() => request.FillForwardResolution.Value.ToTimeSpan());
168-
reader = new FillForwardEnumerator(reader, security.Exchange, readOnlyRef, request.IncludeExtendedMarketHours, request.EndTimeLocal, config.Increment, config.DataTimeZone, useDailyStrictEndTimes, request.DataType);
168+
var exchange = GetSecurityExchange(security.Exchange, request.DataType, request.Symbol);
169+
reader = new FillForwardEnumerator(reader, exchange, readOnlyRef, request.IncludeExtendedMarketHours, request.EndTimeLocal, config.Increment, config.DataTimeZone, useDailyStrictEndTimes, request.DataType);
169170
}
170171

171172
// since the SubscriptionDataReader performs an any overlap condition on the trade bar's entire

Engine/HistoricalData/SynchronizingHistoryProvider.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ namespace QuantConnect.Lean.Engine.HistoricalData
3535
/// </summary>
3636
public abstract class SynchronizingHistoryProvider : HistoryProviderBase
3737
{
38+
/// <summary>
39+
/// The market hours database
40+
/// </summary>
41+
protected static readonly MarketHoursDatabase MarketHours = MarketHoursDatabase.FromDataFolder();
3842
private int _dataPointCount;
3943

4044
/// <summary>
@@ -116,6 +120,25 @@ protected IEnumerable<Slice> CreateSliceEnumerableFromSubscriptions(List<Subscri
116120
}
117121
}
118122

123+
/// <summary>
124+
/// Retrieves the appropriate <see cref="SecurityExchange"/> based on the data type and symbol.
125+
/// </summary>
126+
/// <param name="exchange">The default exchange instance.</param>
127+
/// <param name="dataType">The type of data being processed.</param>
128+
/// <param name="symbol">The security symbol.</param>
129+
/// <returns>The security exchange with appropriate market hours.</returns>
130+
protected static SecurityExchange GetSecurityExchange(SecurityExchange exchange, Type dataType, Symbol symbol)
131+
{
132+
if (dataType == typeof(OpenInterest))
133+
{
134+
// Retrieve the original market hours, which include holidays and closed days.
135+
var originalExchangeHours = MarketHours.GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
136+
// Use the original market hours to prevent fill-forwarding on non-trading hours.
137+
return new SecurityExchange(originalExchangeHours);
138+
}
139+
return exchange;
140+
}
141+
119142
/// <summary>
120143
/// Creates a subscription to process the history request
121144
/// </summary>
@@ -154,7 +177,8 @@ protected Subscription CreateSubscription(HistoryRequest request, IEnumerable<Ba
154177
}
155178

156179
var readOnlyRef = Ref.CreateReadOnly(() => request.FillForwardResolution.Value.ToTimeSpan());
157-
reader = new FillForwardEnumerator(reader, security.Exchange, readOnlyRef, request.IncludeExtendedMarketHours, end, config.Increment, config.DataTimeZone, useDailyStrictEndTimes, request.DataType);
180+
var exchange = GetSecurityExchange(security.Exchange, request.DataType, request.Symbol);
181+
reader = new FillForwardEnumerator(reader, exchange, readOnlyRef, request.IncludeExtendedMarketHours, end, config.Increment, config.DataTimeZone, useDailyStrictEndTimes, request.DataType);
158182
}
159183

160184
var subscriptionRequest = new SubscriptionRequest(false, null, security, config, request.StartTimeUtc, request.EndTimeUtc);

Tests/Algorithm/AlgorithmHistoryTests.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,7 +1749,7 @@ public void GetsHistoryWithGivenBarType(Language language)
17491749
Assert.AreEqual(132104, tickHistory.Count());
17501750

17511751
var openInterestHistory = algorithm.History<OpenInterest>(twxSymbol, twxHistoryStart, twxHistoryEnd);
1752-
Assert.AreEqual(1050, openInterestHistory.Count());
1752+
Assert.AreEqual(391, openInterestHistory.Count());
17531753
}
17541754
else
17551755
{
@@ -1789,7 +1789,7 @@ def getOpenInterestHistory(algorithm, symbol, start, end):
17891789
Assert.AreEqual(132104, tickHistory.shape[0].As<int>());
17901790

17911791
dynamic openInterestHistory = getOpenInterestHistory(algorithm, twxSymbol, twxHistoryStart, twxHistoryEnd);
1792-
Assert.AreEqual(1050, openInterestHistory.shape[0].As<int>());
1792+
Assert.AreEqual(391, openInterestHistory.shape[0].As<int>());
17931793
}
17941794
}
17951795
}
@@ -3595,6 +3595,37 @@ public void HistoryHandlesSymbolChangedEventsCorrectly(bool useCreateSymbol)
35953595
}
35963596
}
35973597

3598+
[Test]
3599+
public void OpenInterestHistoryOnlyContainsDataDuringRegularTradingHours()
3600+
{
3601+
var start = new DateTime(2013, 12, 01);
3602+
_algorithm = GetAlgorithm(start);
3603+
_algorithm.SetEndDate(2013, 12, 31);
3604+
3605+
// Add ES (E-mini S&P 500)
3606+
var future = _algorithm.AddFuture("ES", Resolution.Daily, Market.CME);
3607+
3608+
var history = _algorithm.History<OpenInterest>(future.Symbol, new DateTime(2013, 10, 10), new DateTime(2013, 11, 01), Resolution.Daily).ToList();
3609+
3610+
/* Expected 16 trading days breakdown:
3611+
October 2013:
3612+
10(Thu), 11(Fri),
3613+
14(Mon), 15(Tue), 16(Wed), 17(Thu), 18(Fri),
3614+
21(Mon), 22(Tue), 23(Wed), 24(Thu), 25(Fri),
3615+
28(Mon), 29(Tue), 30(Wed), 31(Thu)
3616+
*/
3617+
Assert.AreEqual(16, history.Count);
3618+
3619+
// Regular trading hours: Monday-Friday 9:30am-5:00pm ET
3620+
foreach (var data in history)
3621+
{
3622+
var date = data.EndTime;
3623+
var dayOfWeek = date.DayOfWeek;
3624+
Assert.AreNotEqual(DayOfWeek.Saturday, dayOfWeek);
3625+
Assert.AreNotEqual(DayOfWeek.Sunday, dayOfWeek);
3626+
}
3627+
}
3628+
35983629
public class CustomUniverseData : BaseDataCollection
35993630
{
36003631
public decimal Weight { get; private set; }

0 commit comments

Comments
 (0)