Skip to content

core.time

Session boundaries and the trading-day definition. A Topstep day is not a calendar day, and this module is where that lives.

time

Time spine: int-nanosecond UTC hot path, ET (America/New_York) boundaries.

The engine's hot path stores time as int nanoseconds since the UTC epoch. The project's canonical timezone is ET — every session boundary below is defined in ET and converted tz-aware (Topstep publishes rules in CT, but ET = CT + 1h always; the two share the identical US DST schedule).

Topstep session boundaries (ET): - Globex open: 18:00 previous calendar day (Sun 18:00 for Monday) - auto-flatten start: 16:08 - flatten deadline: 16:10 (positions & working orders force-closed) - Globex close: 17:00 (EOD balance snapshot for the MLL ratchet) - day reset: 18:00 (DLL reset; next trading day begins)

A bar timestamped at/after 18:00 ET belongs to the next trading day.

ET module-attribute

ET = ZoneInfo('America/New_York')

UTC module-attribute

UTC = ZoneInfo('UTC')

NS_PER_SEC module-attribute

NS_PER_SEC = 1000000000

NS_PER_MIN module-attribute

NS_PER_MIN = 60 * NS_PER_SEC

TOPSTEP_SESSION module-attribute

TOPSTEP_SESSION = SessionTimes()

SessionTimes

Bases: Struct

The ET wall-clock boundaries of a Topstep trading day (all datetime.time).

open_prev_day class-attribute instance-attribute

open_prev_day: time = time(18, 0)

auto_flatten class-attribute instance-attribute

auto_flatten: time = time(16, 8)

flatten class-attribute instance-attribute

flatten: time = time(16, 10)

close class-attribute instance-attribute

close: time = time(17, 0)

reset class-attribute instance-attribute

reset: time = time(18, 0)

open_ns

open_ns(day: date) -> int

Session open (18:00 ET on the previous calendar day).

Source code in src/topstep_backtest/core/time.py
def open_ns(self, day: date) -> int:
    """Session open (18:00 ET on the previous calendar day)."""
    return et_time_of(day - timedelta(days=1), self.open_prev_day)

auto_flatten_ns

auto_flatten_ns(day: date) -> int
Source code in src/topstep_backtest/core/time.py
def auto_flatten_ns(self, day: date) -> int:
    return et_time_of(day, self.auto_flatten)

flatten_ns

flatten_ns(day: date) -> int
Source code in src/topstep_backtest/core/time.py
def flatten_ns(self, day: date) -> int:
    return et_time_of(day, self.flatten)

close_ns

close_ns(day: date) -> int
Source code in src/topstep_backtest/core/time.py
def close_ns(self, day: date) -> int:
    return et_time_of(day, self.close)

reset_ns

reset_ns(day: date) -> int
Source code in src/topstep_backtest/core/time.py
def reset_ns(self, day: date) -> int:
    return et_time_of(day, self.reset)

in_no_trade_window

in_no_trade_window(ns: int) -> bool

True inside the daily no-trade window [flatten, reset) = [16:10, 18:00) ET.

Source code in src/topstep_backtest/core/time.py
def in_no_trade_window(self, ns: int) -> bool:
    """True inside the daily no-trade window [flatten, reset) = [16:10, 18:00) ET."""
    t = ns_to_et(ns).time()
    return self.flatten <= t < self.reset

dt_to_ns

dt_to_ns(dt: datetime) -> int

tz-aware datetime -> int UTC nanoseconds. Naive datetimes are rejected.

Computed with exact integer arithmetic (datetime carries microseconds); a float round-trip would lose sub-microsecond exactness at 2026 epochs.

Source code in src/topstep_backtest/core/time.py
def dt_to_ns(dt: datetime) -> int:
    """tz-aware datetime -> int UTC nanoseconds. Naive datetimes are rejected.

    Computed with exact integer arithmetic (datetime carries microseconds);
    a float round-trip would lose sub-microsecond exactness at 2026 epochs.
    """
    if dt.tzinfo is None:
        raise ValueError(f"naive datetime {dt!r}: all engine datetimes must be tz-aware")
    delta = dt.astimezone(UTC) - _EPOCH
    return ((delta.days * 86_400 + delta.seconds) * NS_PER_SEC) + delta.microseconds * 1_000

ns_to_dt

ns_to_dt(ns: int) -> datetime

int UTC nanoseconds -> tz-aware UTC datetime.

Exact integer arithmetic, truncating sub-microsecond remainders toward the past (datetime resolution is 1 us). Truncation — never float rounding — so an instant strictly before a boundary (e.g. 17:59:59.999999999 ET) can never classify as at/after it (the 18:00 trading-day boundary hinges on this).

Source code in src/topstep_backtest/core/time.py
def ns_to_dt(ns: int) -> datetime:
    """int UTC nanoseconds -> tz-aware UTC datetime.

    Exact integer arithmetic, truncating sub-microsecond remainders toward the
    past (datetime resolution is 1 us). Truncation — never float rounding — so
    an instant strictly before a boundary (e.g. 17:59:59.999999999 ET) can
    never classify as at/after it (the 18:00 trading-day boundary hinges on this).
    """
    seconds, rem = divmod(ns, NS_PER_SEC)
    return datetime.fromtimestamp(seconds, tz=UTC) + timedelta(microseconds=rem // 1_000)

ns_to_et

ns_to_et(ns: int) -> datetime

int UTC nanoseconds -> tz-aware ET datetime.

Source code in src/topstep_backtest/core/time.py
def ns_to_et(ns: int) -> datetime:
    """int UTC nanoseconds -> tz-aware ET datetime."""
    return ns_to_dt(ns).astimezone(ET)

et_time_of

et_time_of(d: date, t: time) -> int

An ET wall-clock instant on calendar date d -> int UTC nanoseconds.

Source code in src/topstep_backtest/core/time.py
def et_time_of(d: date, t: time) -> int:
    """An ET wall-clock instant on calendar date ``d`` -> int UTC nanoseconds."""
    return dt_to_ns(datetime.combine(d, t, tzinfo=ET))

trading_day_of

trading_day_of(ns: int) -> date

The Topstep trading day containing UTC-ns instant ns.

The day boundary is 18:00 ET: at/after 18:00 the instant belongs to the next calendar day's session (Sunday 18:00+ belongs to Monday).

Source code in src/topstep_backtest/core/time.py
def trading_day_of(ns: int) -> date:
    """The Topstep trading day containing UTC-ns instant ``ns``.

    The day boundary is 18:00 ET: at/after 18:00 the instant belongs to the
    next calendar day's session (Sunday 18:00+ belongs to Monday).
    """
    local = ns_to_et(ns)
    if local.time() >= time(18, 0):
        return local.date() + timedelta(days=1)
    return local.date()