#!/usr/bin/env python3 """Ist die Richtung der NAECHSTEN Kerze vorhersagbar? (User-Frage 2026-08-17) Der Pfeil an der aktuellen Kerze zeigt heute die ABPRALL-Wahrscheinlichkeit des naechsten S/R-Levels (`AR;L;…`) — so ist er seit v1.32 (31.07.) entworfen. Der User moechte stattdessen die Richtung der naechsten Kerze. ⚠⚠ WARUM DAS NICHT EINFACH UMGEBAUT WIRD. Die freie Richtungsfrage ist im Projekt mehrfach als Muenzwurf gemessen (`analyze_reversal.py`: AUC 0,499-0,509 out-of-sample und **in-sample nur 0,52** — die Merkmale enthalten keine Richtungsinformation; Live-Richtung 48-51 % ueber 1.071 Episoden). Genau deshalb zeigt der Pfeil eine BEDINGTE Groesse (haelt DIESES Level?), die out-of-sample kalibriert ist (AUC 0,65). ⚠ ABER: gemessen wurden bisher Horizonte von 60-360 Minuten. Die **naechste M5-Kerze** ist ein anderer, kuerzerer Horizont — auf Ein-Bar-Ebene gibt es Mikrostruktur-Effekte (Mean Reversion), die dort anders wirken koennen. Das ist offen und wird hier gemessen, statt aus Analogie abgelehnt. VORAB FIXIERTE REGEL: ein Praediktor taugt, wenn er (1) in BEIDEN Haelften eine Trefferquote > 50 % hat, (2) deren 95-%-Intervall die 50 % ausschliesst, und (3) die mittlere Bewegung in Vorhersagerichtung den SPREAD schlaegt. Punkt (3) ist die haerteste Huerde: eine M5-Kerze bewegt sich im Median ~0,10 $, der Spread ist ~0,0225 $ — eine Trefferquote knapp ueber 50 % zahlt ihn nicht. Aufruf: python backtest_naechste_kerze.py [bars] """ from __future__ import annotations import random import sys import MetaTrader5 as mt5 from core.squeeze_scan import atr_series def ema(v, p): k = 2 / (p + 1); o = [v[0]] for x in v[1:]: o.append(o[-1] + k * (x - o[-1])) return o def anteil_ci(tr, n, seed=5, w=2000): if n < 30: return None, None r = random.Random(seed); p = tr / n b = sorted(sum(1 if r.random() < p else 0 for _ in range(n)) / n for _ in range(w)) return 100 * b[int(.025 * w)], 100 * b[int(.975 * w)] def main(): n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000 mt5.initialize() sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD") if mt5.symbol_info(c)), None) bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n) point = mt5.symbol_info(sym).point mt5.shutdown() H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars] C = [float(b["close"]) for b in bars]; O = [float(b["open"]) for b in bars] SP = [float(b["spread"]) * point for b in bars] A = atr_series(H, L, C) ef, es = ema(C, 12), ema(C, 50) beweg = sorted(abs(C[i + 1] - C[i]) for i in range(len(C) - 1)) med_bew = beweg[len(beweg) // 2] med_sp = sorted(SP)[len(SP) // 2] print(f"\n {sym} · {len(C)} M5-Bars") print(f" Median-Bewegung einer M5-Kerze: {med_bew:.4f} $ · Median-Spread " f"{med_sp:.4f} $ = {100*med_sp/med_bew:.0f} % davon") print(f" ⚠ Eine Trefferquote muss diesen Anteil SCHLAGEN, nicht nur > 50 % sein.\n") # Praediktoren: jeder gibt +1 / -1 / 0 fuer die Richtung der NAECHSTEN Kerze def p_ema(i): # Trendfolge (das Wellen-Signal) d = ef[i] - es[i] return 0 if not A[i] or abs(d) < 0.15 * A[i] else (1 if d > 0 else -1) def p_mom3(i): # Momentum-Fortsetzung m = C[i] - C[i - 3] return 0 if not A[i] or abs(m) < 0.1 * A[i] else (1 if m > 0 else -1) def p_kerze(i): # letzte Kerze setzt sich fort b = C[i] - O[i] return 0 if b == 0 else (1 if b > 0 else -1) def p_kerze_geg(i): # letzte Kerze kehrt um (Mean Reversion auf 1 Bar) return -p_kerze(i) def p_docht(i): # langer Docht oben -> abwaerts (Lehrbuch) sp = H[i] - L[i] if sp <= 0 or not A[i] or sp < 0.5 * A[i]: return 0 ob = H[i] - max(O[i], C[i]); un = min(O[i], C[i]) - L[i] if ob > 2 * un and ob > 0.3 * sp: return -1 if un > 2 * ob and un > 0.3 * sp: return 1 return 0 praed = (("EMA-Trend (Wellen-Signal)", p_ema), ("Momentum 3 Bars", p_mom3), ("letzte Kerze setzt fort", p_kerze), ("letzte Kerze kehrt um", p_kerze_geg), ("langer Docht (Lehrbuch)", p_docht)) mid = len(C) // 2 print(f" {'Praediktor':<28}{'Haelfte':>8}{'n':>8}{'Treffer':>9}" f"{'95-%-KI':>16}{'Ø Bew. in Richtung':>20}") for name, fn in praed: for lbl, lo, hi in (("H1", 60, mid), ("H2", mid, len(C) - 2)): tr = ges = unentschieden = 0; summe = 0.0 for i in range(lo, hi): if not A[i]: continue d = fn(i) if d == 0: continue bew = (C[i + 1] - C[i]) * d # ⚠⚠ UNVERAENDERTE Schlusskurse AUSSCHLIESSEN. Der erste Entwurf # zaehlte sie als Fehltreffer — dadurch lagen ein Praediktor UND # sein eigenes Gegenteil beide unter 50 % (43,4 + 45,8 = 89,2), # was arithmetisch unmoeglich ist und den Fehler verriet. # Real sind ~11 % der M5-Kerzen unveraendert. if bew == 0: unentschieden += 1 continue ges += 1; summe += bew if bew > 0: tr += 1 if ges < 100: continue u, o = anteil_ci(tr, ges) q = 100 * tr / ges flag = "" if u is None or (u <= 50 <= o) else (" ✅" if u > 50 else " ❌") print(f" {name:<28}{lbl:>8}{ges:>8}{q:>8.1f}%" f" [{u:>4.1f} … {o:>4.1f}]{summe/ges:>+17.4f} $" + flag) print() print(" ── LESEHILFE ──────────────────────────────────────────────────") print(f" Die Spalte ganz rechts ist die Ø-Bewegung in Vorhersagerichtung.") print(f" Sie muss den halben Spread ({med_sp/2:.4f} $) schlagen, damit ein") print(f" Trade ueberhaupt kostendeckend waere — und den ganzen ({med_sp:.4f} $)") print(f" fuer Ein- UND Ausstieg.") return 0 if __name__ == "__main__": raise SystemExit(main())