Nachtest S/R-Trailing auf Squeeze-Entries: klar schlechter, Frage geschlossen

backtest_trailing_sr.py --squeeze: gleiche Sim, aber nur SQUEEZE-Ausbrueche
(Regel exakt wie live via core.wave_rec _SQ_N/_SQ_MULT/_SQ_K, Einstieg am
Ausbruchslevel, Echtkosten aus dem Bar-Spread).

Basis H1 +0,102 / H2 +0,108 - KEINE Variante besteht. Anders als im
Wave-Lauf ist das kein Rauschen: der reine S/R-Trail wird monoton
schlechter, je breiter der Puffer (0,10 -> 0,25 -> 0,50, in BEIDEN
Haelften). Mechanismus: der Squeeze lebt von wenigen Laeufern (43 %
Treffer, Ertrag im Tail) - ein ans naechste Level gebundener Stop holt
genau dort raus, wo der Lauf beginnt. Bei Wave-Signalen faellt das kaum
auf, daher das umgekehrte Vorzeichen zwischen beiden Laeufen.

Staerkster Gewinner-Kappen-Beleg des Projekts: die Kontrollvariante
(fester TP am Gegenlevel) dreht den Squeeze von +0,102/+0,108 auf
-0,066/-0,004 = Edge vollstaendig zerstoert.

Abgrenzung: der laufende S/R-Auto-Close ist P(break)-GEGATED und war auf
Squeeze-Trades separat ein Wash (backtest_squeeze_srclose.py). Das Gate
ist der Unterschied zwischen "Wash" und "Edge zerstoert".

Damit ist die User-Idee in allen drei Teilen durchgemessen; der
Live-Exit-Stack bleibt unveraendert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-07-31 07:54:10 +02:00
co-authored by Claude Opus 5
parent 9ad1b6d8d4
commit 5f70d58828
2 changed files with 81 additions and 8 deletions
+61 -5
View File
@@ -182,6 +182,42 @@ def st(Rs):
f"Worst={min(Rs):+.2f} ΣR={sum(Rs):+.0f}")
def squeeze_signals(H, L, C, AT, lo, hi):
"""Nur SQUEEZE-Ausbrüche (das einzige validierte Auto-Setup) — Nachtest
2026-07-31: der Squeeze ist runner-abhängig (nur 43 % Treffer, der Ertrag
kommt aus wenigen Läufern), dort könnte ein level-verankerter Stop mehr
bewegen als über ALLE Wave-Signale gemittelt.
Regel exakt wie live (`wave._squeeze_one`): Box = Spanne der letzten _SQ_N
abgeschlossenen M5-Bars ≤ _SQ_MULT×ATR, Ausbruch um _SQ_K×ATR. Einstieg AM
Ausbruchslevel (nicht am Bar-Close), Cooldown wie in den Squeeze-Backtests.
"""
from core.wave_rec import _SQ_N, _SQ_MULT, _SQ_K
_W = 24; _COOL = 12
out = []
i = max(lo, _N_BARS, _SQ_N + 15)
while i < min(hi, len(C) - _MAXH - 1):
atr = AT[i]
if not atr or atr < _ATRMIN:
i += 1; continue
boxHi = max(H[i - _SQ_N:i]); boxLo = min(L[i - _SQ_N:i])
if (boxHi - boxLo) > _SQ_MULT * atr: # keine Kompression
i += 1; continue
hit = None
for j in range(i, min(i + _W, len(C) - _MAXH - 1)):
up = boxHi + _SQ_K * atr; dn = boxLo - _SQ_K * atr
if H[j] >= up:
hit = (j, 1, up); break
if L[j] <= dn:
hit = (j, -1, dn); break
if hit is None:
i += 1; continue
j, d, lvl = hit
out.append((j, d, atr, lvl))
i = j + _COOL
return out
def signals(H, L, C, ES, EF, AT, lo, hi, step):
TH = _REVERSAL_STRETCH; out = []
for i in range(max(lo, _N_BARS), min(hi, len(C) - _MAXH - 1), step):
@@ -204,8 +240,10 @@ def signals(H, L, C, ES, EF, AT, lo, hi, step):
def main():
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
step = int(sys.argv[2]) if len(sys.argv) > 2 else 2
squeeze = "--squeeze" in sys.argv
args = [a for a in sys.argv[1:] if not a.startswith("--")]
n = int(args[0]) if args else 80000
step = int(args[1]) if len(args) > 1 else 2
mt5.initialize(); sym = None
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
if mt5.symbol_info(c):
@@ -215,22 +253,39 @@ def main():
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, req)
if bars is not None and len(bars) > 2000:
break
si = mt5.symbol_info(sym); point = si.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]
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW); AT = _atr_series(H, L, C)
LV = build_levels(H, L)
mid = len(C) // 2
halves = [("H1 (alt)", _N_BARS, mid), ("H2 (neu)", mid, len(C))]
print("=" * 96)
print(f" S/R-VERANKERTES TRAILING — {sym} M5 ({len(C)} Bars, step {step})")
kind = "NUR SQUEEZE-Ausbrüche (Echtkosten)" if squeeze else "Signal = Trend+Reversal"
print(f" S/R-VERANKERTES TRAILING — {sym} M5 ({len(C)} Bars) {kind}")
print(f" Level = M30-Pivots k={_PIV_K}, Fenster {_PIV_LOOKBACK} M30-Bars (kausal), "
f"Deckel [{_SL_MIN_ATR}..{_SL_MAX_ATR}]×ATR")
if squeeze:
print(" Erwartung Squeeze-Entry-Edge (validiert): ØR +0,14…+0,23 · PF>1")
print("=" * 96)
SIG = {lbl: signals(H, L, C, ES, EF, AT, a, b, step) for lbl, a, b in halves}
if squeeze:
SIG = {lbl: squeeze_signals(H, L, C, AT, a, b) for lbl, a, b in halves}
else:
SIG = {lbl: [(i, d, atr, C[i]) for (i, d, atr)
in signals(H, L, C, ES, EF, AT, a, b, step)] for lbl, a, b in halves}
for lbl, _, _ in halves:
print(f" {lbl}: {len(SIG[lbl])} Signale")
def cost(i, atr):
"""Echte Kosten = Bar-Spread/ATR (nur im Squeeze-Modus; für den reinen
VARIANTEN-Vergleich neutral, da alle Varianten dieselben Entries teilen —
macht das ØR aber mit der +0,14…+0,23-Erwartung vergleichbar."""
if not squeeze:
return 0.0
return (SP[i] if SP[i] > 0 else 0.0225) / atr
variants = [
("LIVE ATR-Trail 1,5 (Basis)", dict(mode="atr", mult=1.5)),
("S/R-Trail Puffer 0,10", dict(mode="sr", buf=0.10)),
@@ -243,7 +298,8 @@ def main():
for name, kw in variants:
print(f"\n{name}")
for lbl, _, _ in halves:
Rs = [sim(C[i], d, atr, H, L, C, i + 1, LV, **kw) for (i, d, atr) in SIG[lbl]]
Rs = [sim(entry, d, atr, H, L, C, i + 1, LV, **kw) - cost(i, atr)
for (i, d, atr, entry) in SIG[lbl]]
print(f" {lbl} {st(Rs)}")