SPB Git

spb/findata

Public
2.6 KB · 66 lines python
1"""Market session calendar for US equities (XNYS) with extended hours and DST handled via zoneinfo.23Policy (documented in docs/DATA_DICTIONARY.md):4- Regular session 09:30–16:00 ET; vendor extended hours 04:00–20:00 ET (last 1-minute bar opens 19:59).5- Early closes (e.g. day after Thanksgiving, Christmas Eve) come from exchange_calendars; regular-hours6  expectations use the calendar's actual close for that date.7- `expected_regular_bars(date)` = minutes between open and close (inclusive of the 16:00 bar which the vendor8  emits as the closing auction print) — used for coverage %, never to manufacture bars.9"""10from __future__ import annotations1112from datetime import date, datetime, time, timedelta13from functools import lru_cache14from zoneinfo import ZoneInfo1516import exchange_calendars as xcals17import pandas as pd1819ET = ZoneInfo("America/New_York")20UTC = ZoneInfo("UTC")2122EXT_OPEN = time(4, 0)23EXT_CLOSE = time(20, 0)   # exclusive242526class SessionCalendar:27    def __init__(self, name: str = "XNYS", start: str = "1999-01-01", end: str = "2030-12-31"):28        self.name = name29        self.cal = xcals.get_calendar(name, start=start, end=end)30        self._sessions = set(d.date() for d in self.cal.sessions)3132    def is_session(self, d: date) -> bool:33        return d in self._sessions3435    def regular_open_close(self, d: date) -> tuple[datetime, datetime]:36        """Open/close as timezone-aware ET datetimes for session `d` (handles early closes and DST)."""37        ts = pd.Timestamp(d)38        o = self.cal.session_open(ts).tz_convert("America/New_York").to_pydatetime()39        c = self.cal.session_close(ts).tz_convert("America/New_York").to_pydatetime()40        return o, c4142    def expected_regular_bars(self, d: date, interval_seconds: int = 60) -> int:43        if not self.is_session(d):44            return 045        o, c = self.regular_open_close(d)46        return int((c - o).total_seconds() // interval_seconds) + 1  # + closing print bar4748    def extended_bounds_utc(self, d: date) -> tuple[datetime, datetime]:49        start = datetime.combine(d, EXT_OPEN, tzinfo=ET).astimezone(UTC)50        end = datetime.combine(d, EXT_CLOSE, tzinfo=ET).astimezone(UTC)51        return start, end5253    def sessions_between(self, start: date, end: date) -> list[date]:54        return [d for d in sorted(self._sessions) if start <= d <= end]5556    def previous_session(self, d: date) -> date:57        cur = d - timedelta(days=1)58        while cur not in self._sessions:59            cur -= timedelta(days=1)60        return cur616263@lru_cache64def xnys() -> SessionCalendar:65    return SessionCalendar("XNYS")66
Files · 8aa539b79849fb28991011546f6e2c19166d7f01
Menu

Theme