"""WALK-FORWARD statt 2 Stichproben -- versteckt unsere Methode Instabilitaet? (User-Auftrag 2026-08-19: "nutze das PDF fuer neue Ideen", Illab Kap. 9.2) DIE IDEE AUS DEM BUCH. Es empfiehlt fuer JEDEN neuen Indikator einen **Walk-Forward-Backtest** (rollierendes Fenster, Zeitreihen-Kreuzvalidierung) statt eines einmaligen Splits -- ausdruecklich gegen Overfitting. Dieses Projekt prueft dagegen seit Monaten mit **ZWEI Haelften** ("2-Stichproben-Regel"). ⚠⚠ WARUM DAS EINE ECHTE LUECKE IST, und nicht bloss eine andere Schreibweise: zwei Haelften haben genau ZWEI Vorzeichen. Ein Setup, das in 4 von 8 aufeinanderfolgenden Bloecken negativ ist, kann trotzdem "beidhaelftig positiv" aussehen -- es reicht, wenn die Gewinner in beiden Haelften gleich verteilt liegen. Die Regel wuerde es durchwinken. Das ist keine Theorie: der Live-Ausfall des P(break)-Modells (31.07. auf H1 gefittet, auf H2 mit AUC 0,715 bestaetigt -- dann live 0,368) ist exakt dieser Fall. Der Split hat gehalten, die Zeit nicht. WAS HIER GEMESSEN WIRD. Der **Squeeze** -- das einzige live laufende, 2-Stichproben-validierte Setup. Dieselben Daten, dieselbe Regel, derselbe Exit; variiert wird NUR die Aufteilung der Zeitachse: (a) 2 Haelften = die Projekt-Methode (b) 8 Bloecke = walk-forward-artig, ~35 Tage je Block Wenn (b) dasselbe Bild zeigt, ist die 2-Stichproben-Regel fuer diesen Fall ausreichend und der Buch-Einwand traegt hier nicht. Zeigt (b) kippende Vorzeichen, ist unsere Latte zu niedrig -- und zwar rueckwirkend fuer ALLES, was je nach ihr freigegeben wurde. AUFBAU. Echte `core.squeeze_scan`-Regel (kein Nachbau), LIVE-Parameter, Fill bei BERUEHRUNG (die seit 06.08. als einzig ehrliche belegte Annahme), kanonischer Exit `exit_model.LIVE`, Echtkosten aus dem Bar-Spread, 95-%-Bootstrap je Zelle. ⚠ GRENZE, vorab benannt: die Bloecke sind kuerzer, also ist jedes einzelne KI breiter -- ein Block mit KI ueber der Null ist NICHT dasselbe wie eine Haelfte mit KI ueber der Null. Ausgewertet wird deshalb das **Vorzeichenmuster** und die Frage, ob die Haelften-Sicht es verdeckt, nicht der Einzelblock. Aufruf: python backtest_walkforward.py [bars] [bloecke] """ import sys import MetaTrader5 as mt5 from backtest_cost_gate import kennzahlen from core.exit_model import LIVE, simulate from core.squeeze_scan import atr_series, scan # LIVE-Parameter (nicht die Modul-Defaults -- der ATR-Floor wurde gemessen von # 0,12 auf 0,06 gesenkt; ihn hier zu vergessen erzeugt eine ANDERE Population, # real passiert am 13.08.). BOX_N = 10 K = 0.1 MULT = 2.5 ATRMIN = 0.06 def lauf(H, L, C, A, SP, lo, hi, ende): """Squeeze-Einstiege in [lo,hi), Fill bei Beruehrung, kanonischer Exit.""" entries = [] scan(H, L, C, A, SP, 1.0, lo, hi, MULT, box_n=BOX_N, k=K, atr_min=ATRMIN, exit_fn=lambda *a: 0.0, on_entry=lambda j, d, lvl, atr, komp: ( entries.append((j, d, lvl, atr)) if komp else None)) rs = [] for j, d, lvl, atr in entries: # ruhende Stop-Order am Level: fuellt, sobald der Kurs es BERUEHRT f = None for x in range(j, min(j + 13, ende)): if (H[x] >= lvl) if d > 0 else (L[x] <= lvl): f = x break if f is None or f + 1 >= ende: continue r = simulate(lvl, d, atr, H, L, C, f + 1, LIVE, timestop=True, use_tp=True) rs.append(r - ((SP[f] if SP[f] > 0 else 0.0225) / atr)) return rs def zeile(lbl, k): if not k: print(" {:<22} --".format(lbl)) return None ci = k.get("ci") m = "" if ci: m = (" [{:+.3f} .. {:+.3f}]".format(ci[0], ci[1]) + ("" if ci[0] * ci[1] > 0 else " ~0")) print(" {:<22} n={:>4} WR={:>3.0f}% OeR={:+.3f} PF={:.2f}{}" .format(lbl, k["n"], k["wr"], k["oer"], k["pf"], m)) return k["oer"] def main(): n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000 nb = int(sys.argv[2]) if len(sys.argv) > 2 else 8 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] SP = [float(b["spread"]) * point for b in bars] A = atr_series(H, L, C) N = len(C); ende = N - LIVE.max_hold - 1 print("=" * 92) print(" WALK-FORWARD gegen 2-STICHPROBEN -- Squeeze, {} M5 ({} Bars)".format(sym, N)) print(" Fill bei BERUEHRUNG - kanonischer Exit - Echtkosten - 95-%-KI") print("=" * 92) print("\n (a) DIE PROJEKT-METHODE: 2 Haelften") print(" " + "-" * 88) mid = N // 2 zeile("H1", kennzahlen(lauf(H, L, C, A, SP, 0, mid, ende))) zeile("H2", kennzahlen(lauf(H, L, C, A, SP, mid, ende, ende))) print("\n (b) WALK-FORWARD: {} aufeinanderfolgende Bloecke (~{} Tage)" .format(nb, round(ende / nb * 5 / 1440))) print(" " + "-" * 88) vz = [] for b in range(nb): lo = int(b * ende / nb); hi = int((b + 1) * ende / nb) o = zeile("Block {}/{}".format(b + 1, nb), kennzahlen(lauf(H, L, C, A, SP, lo, hi, ende))) if o is not None: vz.append(o) pos = sum(1 for x in vz if x > 0) print("\n " + "=" * 88) print(" Bloecke mit positivem OeR: {} von {}".format(pos, len(vz))) print(" Vorzeichenfolge: " + " ".join("+" if x > 0 else "-" for x in vz)) if vz: print(" Spanne OeR: {:+.3f} .. {:+.3f}" .format(min(vz), max(vz))) print(" " + "=" * 88) print("\n LESART: 2 Haelften kennen nur ZWEI Vorzeichen. Kippen die Bloecke,") print(" ist 'beidhaelftig positiv' erfuellbar, ohne dass das Setup stabil ist.") if __name__ == "__main__": main()