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

88 lines
3.9 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.
#!/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 vonbis':<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()