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:
co-authored by
Claude Opus 5
parent
e3e8a6b9fd
commit
621c82bab8
@@ -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()
|
||||||
Reference in New Issue
Block a user