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
+20 -3
View File
@@ -701,9 +701,26 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
Tail-Risiko hinzu (das war die Sorge aus `backtest_sl_method.py`). ⚠ **Die
KONTROLLVARIANTE „TP am Gegenlevel" reproduziert den alten Befund exakt** (WR 40→54 %,
ΣR H2 +1261→+740 = Gewinner-Kappen) — damit ist die Simulation gegen
`backtest_srclose.py` validiert und der TP-Teil endgültig erledigt. **Offener
Nachtest:** dieselbe Messung NUR auf Squeeze-Entries (runner-abhängig → dort könnte
ein Level-Stop mehr bewegen als über alle Wave-Signale gemittelt).
`backtest_srclose.py` validiert und der TP-Teil endgültig erledigt.
**NACHTEST auf NUR Squeeze-Entries (`--squeeze`, 2026-07-31, Echtkosten): ERLEDIGT —
dort ist der S/R-Trail nicht nur zu klein, sondern klar SCHLECHTER.** Basis
H1 +0,102/H2 +0,108; **keine einzige Variante besteht** (S/R 0,10: +0,096/+0,093 ·
0,25: +0,083/+0,098 · 0,50: +0,052/+0,092 · Hybrid: +0,093/+0,122 = kippt ·
Hybrid weit: +0,091/+0,084). Anders als im Wave-Lauf ist das **kein Rauschen**: der
reine S/R-Trail wird **monoton schlechter, je breiter der Puffer** (in BEIDEN
Hälften) — sauberer monotoner Verlauf, nur gegen die Idee. **Mechanismus:** der
Squeeze lebt von wenigen Läufern (43 % Treffer, Ertrag im Tail); ein an das
nächstgelegene Level gebundener Stop holt genau dort raus, wo der Lauf beginnt. Bei
Wave-Signalen fällt das kaum auf (selten große Runner) — daher das umgekehrte
Vorzeichen zwischen beiden Läufen. ⚠ **Stärkster 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 vollständig zerstört. ⚠ **Abgrenzung
gegen den laufenden S/R-Auto-Close:** der ist **P(break)-gegated** (schließt nur,
wenn das Level wahrscheinlich HÄLT) und war auf Squeeze-Trades separat gemessen ein
**Wash** (`backtest_squeeze_srclose.py`, +0,190→+0,224 / +0,394→+0,377). **Das Gate
ist der Unterschied zwischen „Wash" und „Edge zerstört"** — ein ungegateter Level-TP
ist etwas grundlegend anderes. Damit ist die User-Idee in allen DREI Teilen
durchgemessen; der Live-Exit-Stack bleibt unverändert.
- **S/R-verankerter SL = KEIN Edge (gemessen, `backtest_sl_method.py`):**
Pivot+Band vs. „S/R ∓ ATR/0,10-Puffer" vs. fest 2,0×ATR — **gedeckelt alle
identisch** (Ø-R 0,1960,201, PF 1,381,39, Worst 2,2×ATR): der **Band-Cap
+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)}")