Skip to content

GetPreviousMarketOpen: 7-day search limit insufficient for 2001 NYSE closure (regression in 17898) #9592

Description

@AlexCatarino

GetPreviousMarketOpen: 7-day backward-search limit exhausted for the 2001 NYSE closure, causing deterministic crash on 2001-09-17

Summary

SecurityExchangeHours.GetPreviousMarketOpen throws InvalidOperationException whenever a backtest reaches 2001-09-17 before the market opens (e.g., a scheduled event firing at 09:00 AM). The root cause is a 7-iteration backward search loop that runs out of candidates before reaching 2001-09-10 — the last open trading day before the 9/11 NYSE closure.

This appears to be a regression introduced in LEAN 2.5.0.0.17898 (2026-07-06). The same algorithm window (2000-01-01 → 2003-12-31) with identical code completed successfully on LEAN 2.5.0.0.17896.

Error

2001-09-17 10:00:00 Runtime Error: Did not find last market open for 9/17/2001 9:00:00 AM.
IsMarketAlwaysOpen: False in SecurityExchangeHours.cs:line 384

Reproduction

  • Algorithm: SPY at Resolution.Hour, time_rules.every(60 minutes) scheduled event.
  • Backtest window crossing 2001-09-17 deterministically crashes at that date.

Root cause analysis

Loop bound

GetPreviousMarketOpen (Common/Securities/SecurityExchangeHours.cs, ~line 269–311 in current source) searches backwards with a hard limit of 7 iterations:

// let's loop for a week
for (int i = 0; i < 7; i++)

Holiday configuration

Data/market-hours/market-hours-database.json correctly marks the post-9/11 closure as holidays:

"9/11/2001", "9/12/2001", "9/13/2001", "9/14/2001"

9/17/2001 is a regular trading day (opens 09:30 ET) — no special entry.

Why 7 iterations are insufficient

When the query time is 2001-09-17 09:00 AM (before the 09:30 open):

Iteration Date Result
0 2001-09-17 Segment start 09:30 > query 09:00 — no match
1 2001-09-16 Sunday — ClosedAllDay
2 2001-09-15 Saturday — ClosedAllDay
3 2001-09-14 NYSE closure holiday — ClosedAllDay
4 2001-09-13 NYSE closure holiday — ClosedAllDay
5 2001-09-12 NYSE closure holiday — ClosedAllDay
6 2001-09-11 NYSE closure holiday — ClosedAllDay

Loop exhausted. 2001-09-10 (the last open trading day before the closure) is at index 7 and is never reached. The throw at the end of the loop fires.

The 2001 closure (9/11–9/14) + weekend (9/15–9/16) = 6 consecutive calendar days of closed market, creating an 8-calendar-day gap between 9/10 (last open) and the 9:30 open on 9/17. A 7-day limit is 1 day short.

Why this is the only reproducer in US equity history

No other NYSE closure in the market-hours database creates a gap this wide. The 9/11 closure is the only case in US equity history where a 4-day market halt coincides with a weekend to produce a > 7 calendar day gap.

Proposed fix

Increase the loop bound from 7 to at least 14 (or use a date-based bound):

// loop for two weeks to handle extended closures (e.g. 2001-09-11 NYSE closure)
for (int i = 0; i < 14; i++)

Alternatively, change to a time-based stopping condition so the limit is always sufficient:

while (time.Date >= localDateTime.Date.AddDays(-15))

Either change eliminates the crash for the 2001 closure and provides headroom for any future extended closures.

References

  • Common/Securities/SecurityExchangeHours.csGetPreviousMarketOpen (lines ~269–311)
  • Common/Util/LeanData.cs:1473GetDailyCalendar calls GetFirstDailyMarketOpen
  • Engine/DataFeeds/Enumerators/FillForwardEnumerator.cs:124GetDailyCalendar call during fill-forward
  • Data/market-hours/market-hours-database.json — 2001 holiday list

Intercom conversation: 215474991123433

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions