"""国内期货交易时段与盘前连接窗口。""" from __future__ import annotations from datetime import datetime, timedelta from typing import Optional from zoneinfo import ZoneInfo TZ = ZoneInfo("Asia/Shanghai") # 各交易段开盘时刻 (时, 分) SESSION_OPENS = ( (9, 0), (13, 30), (21, 0), ) def is_trading_session(now: Optional[datetime] = None) -> bool: d = now or datetime.now(TZ) if d.tzinfo is None: d = d.replace(tzinfo=TZ) else: d = d.astimezone(TZ) wd = d.weekday() if wd == 6: return False if wd == 5 and d.hour < 21: return False t = d.hour * 60 + d.minute def in_range(sh: int, sm: int, eh: int, em: int) -> bool: return t >= sh * 60 + sm and t < eh * 60 + em if in_range(9, 0, 11, 30): return True if in_range(13, 30, 15, 0): return True if in_range(21, 0, 24, 0): return True if in_range(0, 0, 2, 30): return True return False def _session_open_allowed(day: datetime, hour: int, minute: int) -> bool: wd = day.weekday() if (hour, minute) == (9, 0) or (hour, minute) == (13, 30): return wd < 5 if (hour, minute) == (21, 0): if wd < 5: return True return wd == 5 return False def iter_session_starts( start: datetime, *, hours_ahead: int = 36, ) -> list[datetime]: """列出 start 之后若干小时内的各段开盘时刻。""" if start.tzinfo is None: start = start.replace(tzinfo=TZ) else: start = start.astimezone(TZ) end = start + timedelta(hours=hours_ahead) out: list[datetime] = [] day = start.replace(hour=0, minute=0, second=0, microsecond=0) while day <= end: for h, m in SESSION_OPENS: if not _session_open_allowed(day, h, m): continue dt = day.replace(hour=h, minute=m) if dt > start and dt <= end: out.append(dt) day += timedelta(days=1) out.sort() return out def minutes_until_next_session(now: Optional[datetime] = None) -> Optional[float]: d = now or datetime.now(TZ) if d.tzinfo is None: d = d.replace(tzinfo=TZ) else: d = d.astimezone(TZ) starts = iter_session_starts(d, hours_ahead=48) if not starts: return None return (starts[0] - d).total_seconds() / 60.0 def in_premarket_connect_window( now: Optional[datetime] = None, *, minutes_before: int = 30, ) -> bool: """距下一段开盘 <= minutes_before 分钟,且当前尚未进入交易时段。""" if is_trading_session(now): return False mins = minutes_until_next_session(now) if mins is None: return False return 0 < mins <= float(minutes_before)