Walk-Forward auf dem Live-Trail: 1,0 gegen 1,5 stabil, gegen 0,8 unentschieden

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 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-19 23:00:53 +02:00
co-authored by Claude Opus 5
parent e3e8a6b9fd
commit 621c82bab8
+117
View File
@@ -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()