SPB Git

spb/wp41_uqo

Public
3.2 KB · 75 lines python
1"""XNYS session calendar on a dense regular-hours minute grid.23Conventions (verified against FinData on 2026-11-02, docs/PHASE_0_REPORT.md):4- bars are stamped at bar OPEN in UTC; the bar opening at 09:30 ET is complete at 09:31 ET;5- regular session = 09:30 .. 16:00 ET inclusive -> slots 0..390 (391 bars); the slot 390 bar is the 16:00 closing6  print, treated separately (never a forecast target, never a feature bar for same-day context);7- early close at 13:00 ET -> slots 0..210 (211 bars); slot 210 is the closing print;8- DST handled through zoneinfo via exchange_calendars (opens/closes are tz-aware UTC timestamps).9"""10from __future__ import annotations1112from dataclasses import dataclass13from functools import lru_cache1415import numpy as np16import pandas as pd1718REGULAR_SLOTS = 391          # 09:30 .. 16:00 inclusive19LAST_TRADING_SLOT = 389      # 15:59 bar (last bar that can be a target)20EARLY_CLOSE_SLOTS = 211212223@dataclass(frozen=True)24class Session:25    date: pd.Timestamp          # session date (naive, ET calendar date)26    open_utc: pd.Timestamp      # open instant (09:30 ET) as UTC27    close_utc: pd.Timestamp     # close instant (16:00 or 13:00 ET) as UTC, == open of the closing print bar28    n_slots: int                # 391 or 21129    early_close: bool3031    def slot_of(self, ts_utc: pd.Timestamp) -> int:32        """Slot index of a bar-open UTC timestamp, or -1 if outside the regular session grid."""33        k = int((ts_utc - self.open_utc).total_seconds() // 60)34        if k < 0 or k >= self.n_slots or (ts_utc - self.open_utc).total_seconds() % 60 != 0:35            return -136        return k373839@lru_cache(maxsize=4)40def _xnys():41    import exchange_calendars as xc42    return xc.get_calendar("XNYS")434445def sessions(start: str, end: str) -> list[Session]:46    cal = _xnys()47    sched = cal.schedule.loc[pd.Timestamp(start):pd.Timestamp(end)]48    out = []49    for date, row in sched.iterrows():50        o = pd.Timestamp(row["open"]).tz_convert("UTC")51        c = pd.Timestamp(row["close"]).tz_convert("UTC")52        n = int((c - o).total_seconds() // 60) + 153        out.append(Session(pd.Timestamp(date).tz_localize(None), o, c, n, n != REGULAR_SLOTS))54    return out555657def session_frame(start: str, end: str) -> pd.DataFrame:58    rows = [{"session_date": s.date.date(), "open_utc": s.open_utc, "close_utc": s.close_utc, "n_slots": s.n_slots,59             "early_close": s.early_close} for s in sessions(start, end)]60    return pd.DataFrame(rows)616263def slot_index(ts_utc: np.ndarray, open_utc: np.ndarray, n_slots: np.ndarray) -> np.ndarray:64    """Vectorised slot index (-1 outside the regular grid). All arrays are datetime64[ns]/int aligned per row."""65    delta = (ts_utc.astype("datetime64[s]") - open_utc.astype("datetime64[s]")).astype("int64")66    k = delta // 6067    ok = (delta >= 0) & (delta % 60 == 0) & (k < n_slots)68    return np.where(ok, k, -1).astype(np.int64)697071def minute_of_session_features(slot: np.ndarray, n_slots: int) -> dict[str, np.ndarray]:72    """Calendar features that are known at origin: normalised minute of session and time to close."""73    s = slot.astype(np.float64)74    return {"minute_frac": s / (REGULAR_SLOTS - 1), "time_to_close": (n_slots - 1 - s) / (REGULAR_SLOTS - 1)}75
Files · main
Menu

Theme