""" core/market_hours.py — Börsen-Session-Status (Frankfurt / US) ============================================================== Liefert den aktuellen Session-Status für die Empfehlungs-Logik, das UI und den KI-Agenten. Zeiten in **Berlin-Lokalzeit** (DST-sicher via zoneinfo): DE (Frankfurt): 09:00 – 17:30 US (Wall St.): 15:00 – 22:00 (Overlap 15:00–17:30 = höchste Liquidität) Nutzen: • "just_opened": die ersten _CAUTION_MIN nach einem Open (Whipsaw-Vorsicht) • "active": welche Sessions gerade offen sind (Liquiditäts-Bonus) • "next_open": nächster Open + Minuten bis dahin (UI-Countdown) """ from __future__ import annotations from datetime import datetime, time, timezone try: from zoneinfo import ZoneInfo _BERLIN = ZoneInfo("Europe/Berlin") except Exception: _BERLIN = None # Sessions in Berlin-Lokalzeit (Start, Ende) _SESSIONS = { "DE": (time(9, 0), time(17, 30)), "US": (time(15, 0), time(22, 0)), } _CAUTION_MIN = 20 # erste N Min nach einem Open = volatil → Vorsicht def _mins(t: time) -> int: return t.hour * 60 + t.minute def session_state(now: datetime | None = None) -> dict: now = now or datetime.now(timezone.utc) loc = now.astimezone(_BERLIN) if _BERLIN else now is_weekend = loc.weekday() >= 5 # 5=Sa, 6=So nowm = loc.hour * 60 + loc.minute active, since, just_opened, next_open = [], {}, None, None for name, (o, c) in _SESSIONS.items(): om, cm = _mins(o), _mins(c) open_now = (not is_weekend) and (om <= nowm <= cm) if open_now: active.append(name) if (not is_weekend) and nowm >= om: since[name] = nowm - om if open_now and (nowm - om) < _CAUTION_MIN: just_opened = name else: since[name] = None if (not is_weekend) and nowm < om: d = om - nowm if next_open is None or d < next_open["in_min"]: next_open = {"name": name, "in_min": d} if is_weekend: phase = "Wochenende" elif just_opened: phase = f"{just_opened}-Open frisch (volatil)" elif active: phase = " + ".join(active) + "-Session offen" elif next_open: phase = f"vor {next_open['name']}-Open" else: phase = "außerhalb DE/US" return { "active": active, "since_open_min": since, "just_opened": just_opened, "next_open": next_open, "phase": phase, "weekend": is_weekend, }