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:
co-authored by
Claude Opus 5
parent
9ad1b6d8d4
commit
5f70d58828
@@ -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,196–0,201, PF 1,38–1,39, Worst −2,2×ATR): der **Band-Cap
|
||||
|
||||
+61
-5
@@ -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)}")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user