core.sessions¶
Asia, London and New York — the regional windows that scope indicator data and tradable hours. A different axis from the Topstep day above.
sessions
¶
Regional trading sessions (Asia / London / New York).
This is a different axis from core/time.py, and confusing the two is
the easiest bug to write here:
core/time.pymodels the Topstep trading day — one 23-hour block that opens 18:00 ET the previous calendar day and resets at 18:00 ET. It answers "which day does this bar's P&L land on" and drives the DLL, the MLL ratchet and the 16:10 flatten.- This module answers "which regional session was trading when this bar printed". A session says nothing about which trading day the bar belongs to, and a trading day spans all three sessions.
The interaction that surprises people: the Asia session sits at roughly 19:00-01:00 ET, i.e. after the 18:00 ET rollover, so Asia bars belong to the next trading day. Both classifications are needed; neither substitutes for the other.
SESSIONS ARE DEFINED IN THEIR OWN LOCAL TIMEZONE, NOT AS FIXED ET OFFSETS.
That is deliberate and it is the whole reason this module resolves timezones
through zoneinfo instead of shipping a table of ET windows:
- Daylight-saving correctness comes from the IANA database, so there is no
hand-maintained table to rot. This repository already dropped its exchange
calendar for exactly that failure mode (
docs/ROADMAP.md), and a fixed-ET session table is the same mistake in a smaller package: London and New York shift on different dates — roughly two weeks in March and one in late October — across which "London = 03:00 ET" is silently an hour wrong. - In its own timezone no real session wraps midnight, which keeps the common path a single comparison.
None of this costs flexibility. Session is a plain value type, so a fixed
ET block is one line away and behaves identically::
LONDON_ET = Session("LONDON_ET", ET, time(3), time(11))
Midnight-wrapping windows (start > end) are supported for exactly that
case — an Asia session expressed in ET necessarily wraps.
The three shipped constants are reference definitions of the cash sessions
each region is named for. They are a starting point, not an authority: if your
venue or your strategy means something else by "the London session", define
your own Session rather than bending these.
LONDON
module-attribute
¶
LONDON = Session('LONDON', ZoneInfo('Europe/London'), time(8, 0), time(16, 30))
SESSIONS
module-attribute
¶
Session
¶
Bases: Struct
A named intraday window, defined in its own local timezone.
The window is half-open — [start, end) — so a bar opening exactly at
end belongs to the next window, and two adjacent sessions sharing a
boundary never both claim the same bar.
start > end denotes a window that wraps midnight (an Asia session
expressed in ET, say); membership is then "at/after start OR before
end". start == end is rejected: it reads equally as an empty
window and a 24-hour one.
wraps_midnight
property
¶
True when the window runs past local midnight (start > end).
contains_ns
¶
Is UTC-nanosecond instant ns inside this session's window?
The instant is converted to the session's OWN timezone before the comparison, so the answer follows that region's daylight-saving schedule rather than ET's.
Source code in src/topstep_backtest/core/sessions.py
contains
¶
contains(bar: Bar) -> bool
Does bar belong to this session?
Tested on ts_event — the bar's OPEN — not ts_init. A bar's
session is a property of the window it covers, and ts_init is the
close: testing it would pull the 09:29->09:30 bar, whose every print is
pre-market, into the New York session. Both stamps are already in the
past when a decision is made, so this introduces no look-ahead either
way; it is purely about classifying the bar correctly.