From 621c82bab8ca7280ada104a90fa1dfcebe6e488b Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Wed, 19 Aug 2026 23:00:53 +0200 Subject: [PATCH] Walk-Forward auf dem Live-Trail: 1,0 gegen 1,5 stabil, gegen 0,8 unentschieden MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zweite der drei benannten offenen Messungen. Der Trail betrifft JEDEN Trade - nach dem Walk-Forward-Befund vom selben Tag (Squeeze nur 4 von 8 Bloecken positiv, obwohl "beidhaelftig positiv") war offen, ob LIVE.mult = 1,0 ueber die Zeitachse haelt oder nur ueber zwei Haelften. GEPAART gerechnet: alle Multiplikatoren auf DENSELBEN Einstiegen, verglichen wird die Differenz je Trade. Der sequentielle Vergleich waere 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). ERGEBNIS ueber 8 Bloecke (~35 Tage je Block): 1,5 gegen 1,0 Vorzeichen - - + + - - - - nur 2 von 8 fuer 1,5 0,8 gegen 1,0 Vorzeichen - + + + + - + - 5 von 8 fuer 0,8 ✅ Gegen den WEITEREN Trail ist 1,0 stabil bestaetigt: 6 von 8 Bloecken, und die beiden Gegenbloecke liegen beieinander (3 und 4). Die Entscheidung vom 31.07. haelt also nicht nur ueber zwei Haelften, sondern ueber die Zeitachse. ⚠ Gegen den ENGEREN Trail ist es unentschieden - 5:3 fuer 0,8, aber das Vorzeichen kippt dreimal, und ALLE Deltas liegen zwischen -0,026 und +0,097 R. Damit ist die dokumentierte Aussage "enger ist monoton besser" (backtest_trailmult.py) als Blocksicht NICHT bestaetigt: sie beschreibt zwei Regime, keine Stabilitaet. Eine Umstellung auf 0,8 waere nicht gedeckt. ⚠ Effektgroesse: typisch ±0,03 R je Trade. Der Trail ist damit keine Stellschraube mit Hebel, sondern eine, die man richtig stehen lassen sollte. Keine Aenderung. Co-Authored-By: Claude Opus 5 --- backtest_trail_wf.py | 117 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 backtest_trail_wf.py diff --git a/backtest_trail_wf.py b/backtest_trail_wf.py new file mode 100644 index 0000000..b1a7c36 --- /dev/null +++ b/backtest_trail_wf.py @@ -0,0 +1,117 @@ +"""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()