Trading Calendar & Session Correctness Playbook (Multi-Venue Execution)
Date: 2026-03-07
Category: knowledge (trading operations / execution reliability)
Why this matters
Many live-trading incidents are not model errors. They are time-domain errors:
- strategy runs when venue is closed,
- auction logic fires in continuous session,
- half-day cutoffs are treated as normal day,
- DST transition shifts market-open assumptions,
- one venue is open while another is on holiday, breaking hedges.
These failures often look like "random rejects" or "weird slippage day" but are actually calendar bugs.
If your execution stack is multi-venue (KRX + US, cash + futures, lit + dark), calendar correctness is a first-class risk control.
Scope of "calendar correctness"
Calendar correctness is not just a holiday CSV. It is the full set of rules that determine when each action is legal and safe:
- Trading day eligibility (business day / holiday / ad-hoc closure)
- Session segmentation (pre-open, auction, continuous, close auction, post)
- Special schedules (half-days, early close, delayed open)
- Clock domain handling (exchange local time, server time, DST)
- Cross-venue consistency (hedge venue status, data venue status, routing venue status)
Failure modes to design against
1) Hard rejects
- order submitted outside venue session,
- order type invalid for current phase,
- venue maintenance window not modeled.
Impact: reject loops, message churn, degraded queue priority when re-entering.
2) Silent opportunity loss
- strategy disabled on open trading day due to stale holiday table,
- close-auction participation skipped on early-close days.
Impact: underfill and benchmark drift without obvious alerts.
3) Cross-venue hedge break
- primary leg tradable, hedge venue closed or in auction-only mode,
- synthetic spread logic assumes both legs continuous.
Impact: unintended directional exposure and tail slippage.
4) DST drift incidents
- US venue open/close shifts relative to KST during DST transitions,
- cron-based jobs remain fixed in server-local time.
Impact: jobs execute one hour early/late for weeks.
Data model: separate day rules from session rules
Use explicit, versioned entities.
A) TradingDay
Per venue + date:
venuedate_localisTradingDaydayType(NORMAL,HALF_DAY,HOLIDAY,SPECIAL)reasonCode(holiday name, emergency halt reason, maintenance)sourceVersion
B) SessionTemplate
Reusable session structure:
templateId- ordered phases (
PREOPEN,OPEN_AUCTION,CONTINUOUS,CLOSE_AUCTION,POST) - phase time ranges in venue local timezone
- allowed order types by phase
C) DaySessionOverride
For exceptions:
venue,date_local,phase,overrideStart,overrideEnd,overrideRules
Never patch runtime logic ad hoc. Encode exceptions in data.
Time handling rules (non-negotiable)
- Store canonical timestamps in UTC.
- Store venue-local date/time + timezone id (e.g.,
America/New_York,Asia/Seoul) for schedule logic. - Do not use fixed UTC offsets for DST venues.
- Evaluate session state using venue clock semantics, not server local time.
- Snapshot timezone database version in deployment metadata for reproducibility.
A lot of “mystery” date bugs are timezone-db drift or fixed-offset shortcuts.
Session-state machine for execution safety
Create one resolver API per venue:
resolveSessionState(venue, nowUtc) -> { state, phase, nextTransition, dayType, tradable }
Recommended high-level states:
CLOSEDPREOPENAUCTION_OPENCONTINUOUSAUCTION_CLOSEPOSTHALTED_SPECIAL
Execution components consume this resolver instead of re-implementing schedule checks.
Control policy by state
CLOSED
- block new orders,
- allow cancel if venue supports queued cancellation semantics,
- run end-of-day reconciliation tasks.
PREOPEN / AUCTION_*
- enforce auction-compatible order-type policy,
- disable tactics that assume continuous matching,
- tighten max child-order churn.
CONTINUOUS
- normal routing and execution policy.
HALTED_SPECIAL
- freeze aggressive routing,
- allow risk-reducing exits only if legal,
- notify operators with reason code + expected recovery condition.
Cross-venue consistency checks (critical)
Before enabling a multi-leg strategy bucket:
- primary venue tradable now
- hedge venue tradable now (or explicit hedge deferral policy active)
- both venues in compatible session types (continuous vs auction mismatch explicitly handled)
- market-data feed session aligns with order-routing session
If any fail, auto-shift strategy to a degraded mode (NO_HEDGE_ENTRY, EXIT_ONLY, or PAUSE).
Validation and test strategy
1) Golden calendar test set
Build a permanent regression suite containing:
- known holidays,
- known half-days,
- DST start/end weeks,
- historical special closure dates,
- venue-specific session anomalies.
Every calendar update must pass this suite.
2) Transition-edge tests
For each phase boundary T test:
T-1s,T,T+1s- expected state and allowed order actions.
Most production bugs sit on boundaries, not mid-session.
3) Shadow resolver
During rollout, run new and old resolver in parallel and log diffs:
calendar_diff_rate = mismatched_state_events / total_state_events
Promote only when diff rate is near zero and explained.
Observability KPIs
Track by venue + strategy + dayType:
OutOfSessionRejectRateSessionMismatchAlertCount(order vs resolver state)CalendarDiffRate(new vs baseline resolver)HalfDayMissRate(planned close-auction actions not executed)DSTTransitionIncidentCount
Good target: out-of-session rejects should be operationally near-zero except rare emergency closures.
Rollout sequence (practical)
- Build canonical calendar service + versioned data model.
- Migrate all execution clients to shared session resolver API.
- Enable read-only shadow checks and collect mismatch metrics.
- Canary on one venue/strategy cluster.
- Expand after two full cycles including at least one boundary-heavy window (e.g., month-end, known half-day, or DST week).
- Add kill-switch: fallback to last known-good calendar version.
Common anti-patterns
Embedding market hours in strategy code
- leads to duplicated, drifting logic.
Single global "is market open" flag
- invalid for multi-venue systems.
Ignoring half-days as rare events
- rare is exactly why they break silently.
Assuming data-feed status implies order-routing status
- they can diverge.
Manual emergency patch without versioning
- impossible postmortem and replay.
Minimal runbook for incident response
When calendar/session incident suspected:
- Freeze affected strategy buckets to safe mode.
- Record
resolver output + raw venue schedule + local clock + tzdb version. - Compare expected phase vs actual order/ack timestamps.
- If resolver bug confirmed, rollback to previous calendar version.
- Reconcile missed/invalid orders and document affected date/venue scope.
One-line takeaway
In live execution, time assumptions are risk assumptions: treat trading-calendar/session logic as a versioned control system, not static metadata.
References
- Exchange trading-hours and holiday notices (venue official pages).
- IANA Time Zone Database (tzdb) release notes.
- Practical operator references:
exchange_calendars/pandas_market_calendarsecosystem docs for modeling sessions and special dates.