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.cs — GetPreviousMarketOpen (lines ~269–311)
Common/Util/LeanData.cs:1473 — GetDailyCalendar calls GetFirstDailyMarketOpen
Engine/DataFeeds/Enumerators/FillForwardEnumerator.cs:124 — GetDailyCalendar call during fill-forward
Data/market-hours/market-hours-database.json — 2001 holiday list
Intercom conversation: 215474991123433
GetPreviousMarketOpen: 7-day backward-search limit exhausted for the 2001 NYSE closure, causing deterministic crash on 2001-09-17
Summary
SecurityExchangeHours.GetPreviousMarketOpenthrowsInvalidOperationExceptionwhenever 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
Reproduction
Resolution.Hour,time_rules.every(60 minutes)scheduled event.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:Holiday configuration
Data/market-hours/market-hours-database.jsoncorrectly marks the post-9/11 closure as holidays: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):
Loop exhausted. 2001-09-10 (the last open trading day before the closure) is at index 7 and is never reached. The
throwat 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):
Alternatively, change to a time-based stopping condition so the limit is always sufficient:
Either change eliminates the crash for the 2001 closure and provides headroom for any future extended closures.
References
Common/Securities/SecurityExchangeHours.cs—GetPreviousMarketOpen(lines ~269–311)Common/Util/LeanData.cs:1473—GetDailyCalendarcallsGetFirstDailyMarketOpenEngine/DataFeeds/Enumerators/FillForwardEnumerator.cs:124—GetDailyCalendarcall during fill-forwardData/market-hours/market-hours-database.json— 2001 holiday listIntercom conversation: 215474991123433