Quantitative Trading5 min read

The Market Is Closed Is Not a Trading Rule

S

Suneet Malhotra

Aug 25, 2026

β€’
1 views
The Market Is Closed Is Not a Trading Rule - Quantitative Trading blog post
πŸ”§PythonπŸ”§Market CalendarsπŸ”§BacktestingπŸ”§Scheduling

The market is closed is not a trading rule. It is an assumption about the calendar, and assumptions about calendars age quietly.

That distinction matters because a trading system usually treats time as metadata. The strategy has a timestamp, the broker has a timestamp, and the scheduler has a timestamp. Price is the data. Time is the label attached to it.

That hierarchy is backwards at the boundary of a session. The calendar decides whether a price can exist, whether an order can be accepted, and whether a position can be managed. A stale calendar can therefore make an otherwise correct signal impossible to execute.

The backtest has an easier calendar

My backtests tend to make session logic look clean. Load daily bars. Group by date. Generate a signal after the close. Send the next order on the next row. If the next row exists, the market was open.

That last sentence smuggles in the entire calendar model.

A historical price file often encodes only the sessions that produced observations. The absence of a row becomes the proxy for a closed market. That works for ordinary weekends and familiar holidays. It says less about early closes, exchange-specific holidays, half-days, daylight-saving transitions, and the difference between an exchange session and a broker's order window.

The backtest also gets to look backward. If the next row is Tuesday, it knows Monday had no bar. A live scheduler has to decide what to do at 09:29:59 on a day whose session metadata may be missing, stale, or expressed in the wrong timezone.

The historical dataset has an answer. The live system has a question.

That question also belongs in incident review. If an order was not sent because the calendar could not resolve, the run should be recoverable from the log without reconstructing state from screenshots or scheduler output. Operational traceability is part of execution correctness.

A timestamp is not a session

The dangerous shortcut is to compare a wall-clock time with a hardcoded interval:

'if weekday < 5 and hour >= 9.5 and hour < 16: trade()'

This is readable and wrong in several independent ways.

It does not know which exchange owns the instrument. It does not know whether the day is a holiday. It does not know whether the close is early. It does not know whether the timestamp arrived in local time, UTC, or daylight time. It does not know whether the instrument is eligible for the strategy during that particular session.

The problem is not that the conditional lacks enough exceptions. The problem is that a clock interval is being used as a substitute for a session object.

A session object can answer more useful questions: is this date a valid trading session, when does it open, when does it close, when is the last time a new position may be entered, and when must an existing position be reconciled? Those are different timestamps with different consequences.

The failure is usually not a bad fill

Calendar defects are easy to miss in performance analysis because they often produce no trade. A bad fill appears in the ledger. A skipped signal may disappear into the same bucket as an ordinary no-op.

That creates a misleading diagnosis. The report says the strategy had fewer trades than expected. The engineer checks slippage, indicators, and data quality. The missing event is filed as randomness.

The better question is: was there a signal whose execution opportunity was removed by calendar state?

That requires logging more than the order table. For every scheduled decision, I want a decision record with the intended timestamp, resolved timezone, instrument calendar, session identifier, session status, and the reason for allowing or rejecting the action. market_closed is not enough. holiday, early_close, outside_entry_window, and calendar_unavailable describe different failures and demand different fixes.

This is the same measurement trap that appears in backtests elsewhere: a metric over the rows that exist cannot explain rows that were never admitted. Trade count can tell me how many orders survived. It cannot tell me how many opportunities the calendar rejected unless the rejection is part of the dataset.

The fix is not more scheduler retries

The tempting repair is to retry until the market opens. That handles a late process and makes a missing calendar look like a transient network error. It is dangerous when the session is genuinely closed. A retry can turn a rejected decision into an order submitted on the wrong day.

The durable control is to make calendar resolution a typed input to the strategy boundary. No resolved session, no order. A resolved session with no entry permission, no order. A session close earlier than the usual close should change the exit and reconciliation deadlines, not merely produce a warning.

I would also test the calendar as data, not as plumbing: ordinary sessions, weekends, named holidays, early closes, daylight-saving transitions, and a deliberately unavailable calendar. The expected result is not always a fill. In several cases, the correct output is an explicit refusal with a reason that remains queryable after the run.

The market does not owe the scheduler a familiar shape. A price series is only the part of market state that made it into the file. Session state determines when the file could have existed in the first place.

Treating the calendar as market data does not make a strategy more profitable. It makes its absence of a trade explainable. That is a smaller promise, and a much more useful one.

Share this post

You Might Also Like

Stay in the Loop

Get weekly insights on AI-driven QA, engineering leadership, and automation strategies.

No spam, ever. Unsubscribe anytime.