chart/app/bars/session.py

39 lines
1.5 KiB
Python

from datetime import datetime, time, timedelta
from zoneinfo import ZoneInfo
from app.bars.models import Timeframe
UTC = ZoneInfo("UTC")
EASTERN = ZoneInfo("America/New_York")
SESSION_OPEN = time(18, 0)
SESSION_CLOSE = time(17, 0)
def _session_open_local(current: datetime) -> datetime:
session_date = current.date() if current.timetz().replace(tzinfo=None) >= SESSION_OPEN else current.date() - timedelta(days=1)
return datetime.combine(session_date, SESSION_OPEN, EASTERN)
def bucket_start(t: int, tf: Timeframe) -> int:
# Everything below a day divides the hour evenly, so UTC boundaries are
# correct and session anchoring is unnecessary. Only the daily bar needs to
# know that the CME trading day runs 18:00 to 17:00 ET.
if tf is not Timeframe.D1:
return (t // tf.seconds) * tf.seconds
current = datetime.fromtimestamp(t, UTC).astimezone(EASTERN)
return int(_session_open_local(current).timestamp())
def bucket_duration(t: int, tf: Timeframe) -> int:
"""Wall-clock span of one logical bar bucket.
Intraday buckets are fixed. A daily line advances only through the active
18:00-17:00 ET session, not the settlement halt; constructing the close in
Eastern keeps DST transitions correct without shifting stored UTC times.
"""
if tf is not Timeframe.D1:
return tf.seconds
current = datetime.fromtimestamp(t, UTC).astimezone(EASTERN)
next_close = datetime.combine(current.date() + timedelta(days=1), SESSION_CLOSE, EASTERN)
return int(next_close.timestamp()) - t