Files
Axel HocksandClaude Opus 4.8 75d28827e8 Initial commit: Oil Trading Bot (MT5, WTI)
Headless FastAPI-Backend (server.py + core/engine.py) mit Mobile-PWA (web/),
Strategie-/Backtest-Suite und Doku. Secrets, DB, Logs und Laufzeit-State sind
via .gitignore ausgeschlossen; Config-Vorlage: oil_widget_config.ini.example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 08:29:23 +02:00

78 lines
2.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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:0017: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,
}