#!/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()