"""Traegt Trail 1,0 ueber die ZEITACHSE - oder nur ueber zwei Haelften? (2026-08-19) Der Walk-Forward-Lauf vom selben Tag hat gezeigt, dass "beidhaelftig positiv" Instabilitaet verdecken kann: der Squeeze war in nur 4 von 8 Bloecken positiv, und die einzigen zwei Bloecke mit Intervall ohne Null waren BEIDE negativ. Diese Sicht fehlt fuer den LIVE-Exit. `LIVE.mult` steht seit dem 31.07. auf 1,0, validiert ueber zwei Halbjahre (`backtest_trailmult.py`: enger ist monoton besser). Der Multiplikator betrifft JEDEN Trade - ist er ueber die Zeitachse instabil, ist das wichtiger als jede Signal-Idee. ⚠ GEPAART gerechnet: alle Multiplikatoren laufen auf DENSELBEN Einstiegen, und verglichen wird die Differenz je Trade. Der sequentielle Vergleich waere hier ungueltig - ein anderer Exit laesst ANDERE Trades in den einen Slot, das ist ein Selektions- und kein Exit-Effekt (die Falle vom 14.08. beim trail_start-Sweep). ⚠ GRENZE: kuerzere Bloecke -> breitere KI. Ausgewertet wird das VORZEICHENMUSTER der Differenz, nicht der Einzelblock. Aufruf: python backtest_trail_wf.py [bars] [bloecke] """ import sys import MetaTrader5 as mt5 from core.exit_model import LIVE, simulate from core.squeeze_scan import atr_series, scan from core.stichprobe import paarweise BOX_N, K, MULT_BOX, ATRMIN = 10, 0.1, 2.5, 0.06 KANDIDATEN = [0.8, 1.0, 1.5] def einstiege(H, L, C, A, SP, lo, hi): out = [] scan(H, L, C, A, SP, 1.0, lo, hi, MULT_BOX, box_n=BOX_N, k=K, atr_min=ATRMIN, exit_fn=lambda *a: 0.0, on_entry=lambda j, d, lvl, atr, komp: ( out.append((j, d, lvl, atr)) if komp else None)) return out def r_werte(ein, H, L, C, SP, ende, mult): par = LIVE.with_(mult=mult) out = [] for j, d, lvl, atr in ein: 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: out.append(None); continue r = simulate(lvl, d, atr, H, L, C, f + 1, par, timestop=True, use_tp=True) out.append(r - ((SP[f] if SP[f] > 0 else 0.0225) / atr)) return out 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("=" * 88) print(" TRAIL 1,0 im WALK-FORWARD - {} M5 ({} Bars, {} Bloecke)".format(sym, N, nb)) print(" gepaart: gleiche Einstiege, nur der Trail variiert") print("=" * 88) for m in KANDIDATEN: if m == LIVE.mult: continue print("\n {} gegen LIVE {} (Differenz je Trade, + = Kandidat besser)" .format(m, LIVE.mult)) print(" " + "-" * 84) vz = [] for b in range(nb): lo, hi = int(b * ende / nb), int((b + 1) * ende / nb) ein = einstiege(H, L, C, A, SP, lo, hi) a = r_werte(ein, H, L, C, SP, ende, LIVE.mult) k = r_werte(ein, H, L, C, SP, ende, m) paare = [(y, x) for x, y in zip(k, a) if x is not None and y is not None] if len(paare) < 10: print(" Block {}/{} n={:<4} zu duenn".format(b+1, nb, len(paare))) continue basis = [p0 for p0, _ in paare]; kand = [p1 for _, p1 in paare] d = [p1 - p0 for p0, p1 in paare] mw = sum(d) / len(d) try: st = paarweise(basis, kand) ci = st.get("ci") if isinstance(st, dict) else None except Exception: ci = None m2 = "" if ci: m2 = " [{:+.3f} .. {:+.3f}]{}".format( ci[0], ci[1], "" if ci[0]*ci[1] > 0 else " ~0") print(" Block {}/{} n={:<4} Δ={:+.3f}{}".format(b+1, nb, len(d), mw, m2)) vz.append(mw) if vz: print(" Vorzeichen: " + " ".join("+" if x > 0 else "-" for x in vz) + " ({} von {} fuer den Kandidaten)".format( sum(1 for x in vz if x > 0), len(vz))) print("\n " + "=" * 84) print(" LESART: kippt das Vorzeichen ueber die Bloecke, ist 'enger ist besser'") print(" eine Aussage ueber ZWEI Regime - keine ueber Stabilitaet.") if __name__ == "__main__": main()