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>
88 lines
3.9 KiB
Python
88 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
||
"""Sucht ungefüllte Kurslücken (Gaps) im WTI-Tageschart 2026.
|
||
|
||
Gap = Vakuum zwischen Vortag und Folgetag:
|
||
Gap UP : Low(heute) > High(gestern) → Lücke [High_gestern … Low_heute]
|
||
Gap DOWN : High(heute) < Low(gestern) → Lücke [High_heute … Low_gestern]
|
||
„Gefüllt" = ein SPÄTERER Bar handelt wieder in/durch das Vakuum (Kurs kehrt zur
|
||
Gap-Kante zurück). Ausgegeben werden die NOCH OFFENEN (ungefüllten) Gaps + das
|
||
Kursniveau, das sie schließen würde, und der Abstand zum aktuellen Kurs.
|
||
"""
|
||
import datetime as dt
|
||
import MetaTrader5 as mt5
|
||
|
||
OFF = 3 * 3600 # Broker UTC+3 → UTC (nur für die Datumsanzeige grob)
|
||
|
||
def main():
|
||
if not mt5.initialize():
|
||
print("MT5 init fehlgeschlagen:", mt5.last_error()); return
|
||
sym = None
|
||
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
|
||
if mt5.symbol_info(c): sym = c; break
|
||
# genug D1-Bars holen (ganzes Jahr + Puffer)
|
||
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_D1, 0, 400)
|
||
tick = mt5.symbol_info_tick(sym)
|
||
price = (tick.bid + tick.ask) / 2 if tick else None
|
||
mt5.shutdown()
|
||
if bars is None:
|
||
print("Keine Bars"); return
|
||
|
||
rows = []
|
||
for b in bars:
|
||
d = dt.datetime.utcfromtimestamp(int(b["time"]) - OFF).date()
|
||
rows.append({"date": d, "o": float(b["open"]), "h": float(b["high"]),
|
||
"l": float(b["low"]), "c": float(b["close"])})
|
||
rows = [r for r in rows if r["date"].year == 2026]
|
||
if len(rows) < 2:
|
||
print("Zu wenig 2026-Daten"); return
|
||
|
||
gaps = []
|
||
for i in range(1, len(rows)):
|
||
p, cur = rows[i - 1], rows[i]
|
||
if cur["l"] > p["h"]: # Gap UP
|
||
gaps.append({"i": i, "date": cur["date"], "dir": "UP",
|
||
"lo": p["h"], "hi": cur["l"], "ref_close": p["c"]})
|
||
elif cur["h"] < p["l"]: # Gap DOWN
|
||
gaps.append({"i": i, "date": cur["date"], "dir": "DOWN",
|
||
"lo": cur["h"], "hi": p["l"], "ref_close": p["c"]})
|
||
|
||
# Fill-Status: handelt ein späterer Bar in das Vakuum zurück?
|
||
for g in gaps:
|
||
later = rows[g["i"] + 1:]
|
||
if g["dir"] == "UP":
|
||
# gefüllt, sobald ein späterer Low <= Gap-Unterkante (zurück in die Lücke)
|
||
mn = min((r["l"] for r in later), default=price or g["hi"])
|
||
g["filled"] = mn <= g["lo"]
|
||
g["fill_target"] = g["lo"] # Niveau, das die Lücke schließt
|
||
g["partial"] = (not g["filled"]) and mn < g["hi"]
|
||
else:
|
||
mx = max((r["h"] for r in later), default=price or g["lo"])
|
||
g["filled"] = mx >= g["hi"]
|
||
g["fill_target"] = g["hi"]
|
||
g["partial"] = (not g["filled"]) and mx > g["lo"]
|
||
g["size"] = g["hi"] - g["lo"]
|
||
|
||
openg = [g for g in gaps if not g["filled"]]
|
||
print("=" * 78)
|
||
print(f" WTI ({sym}) 2026 — Gap-Analyse (D1) aktueller Kurs ≈ {price:.2f}"
|
||
if price else f" WTI ({sym}) 2026 — Gap-Analyse (D1)")
|
||
print(f" {len(rows)} Handelstage · {len(gaps)} Gaps gesamt · "
|
||
f"{len(openg)} NOCH OFFEN")
|
||
print("=" * 78)
|
||
if not openg:
|
||
print(" Keine offenen Gaps — alle 2026er Lücken wurden gefüllt.")
|
||
return
|
||
print(f" {'Datum':<12}{'Richtg':<7}{'Lücke von–bis':<18}{'Größe':>7}"
|
||
f"{'Schließt bei':>14}{'Abstand':>10} Status")
|
||
for g in sorted(openg, key=lambda x: x["date"]):
|
||
dist = (g["fill_target"] - price) if price else 0.0
|
||
tag = "teilw. angelaufen" if g.get("partial") else "unberührt"
|
||
zone = f"{g['lo']:.2f}–{g['hi']:.2f}"
|
||
print(f" {g['date'].isoformat():<12}{g['dir']:<7}{zone:<18}"
|
||
f"{g['size']:>7.2f}{g['fill_target']:>14.2f}{dist:>+10.2f} {tag}")
|
||
print("\n „Schließt bei\" = Kursniveau, das die Lücke füllt · "
|
||
"Abstand = von dort zum aktuellen Kurs (+ = darüber, − = darunter)")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|