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>
This commit is contained in:
Axel Hocks
2026-07-24 08:29:23 +02:00
co-authored by Claude Opus 4.8
commit 75d28827e8
104 changed files with 21059 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
"""
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,
}