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
|
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 %,
|
KONTROLLVARIANTE „TP am Gegenlevel" reproduziert den alten Befund exakt** (WR 40→54 %,
|
||||||
ΣR H2 +1261→+740 = Gewinner-Kappen) — damit ist die Simulation gegen
|
ΣR H2 +1261→+740 = Gewinner-Kappen) — damit ist die Simulation gegen
|
||||||
`backtest_srclose.py` validiert und der TP-Teil endgültig erledigt. **Offener
|
`backtest_srclose.py` validiert und der TP-Teil endgültig erledigt.
|
||||||
Nachtest:** dieselbe Messung NUR auf Squeeze-Entries (runner-abhängig → dort könnte
|
**NACHTEST auf NUR Squeeze-Entries (`--squeeze`, 2026-07-31, Echtkosten): ERLEDIGT —
|
||||||
ein Level-Stop mehr bewegen als über alle Wave-Signale gemittelt).
|
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`):**
|
- **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
|
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
|
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}")
|
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):
|
def signals(H, L, C, ES, EF, AT, lo, hi, step):
|
||||||
TH = _REVERSAL_STRETCH; out = []
|
TH = _REVERSAL_STRETCH; out = []
|
||||||
for i in range(max(lo, _N_BARS), min(hi, len(C) - _MAXH - 1), step):
|
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():
|
def main():
|
||||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
|
squeeze = "--squeeze" in sys.argv
|
||||||
step = int(sys.argv[2]) if len(sys.argv) > 2 else 2
|
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
|
mt5.initialize(); sym = None
|
||||||
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
|
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
|
||||||
if mt5.symbol_info(c):
|
if mt5.symbol_info(c):
|
||||||
@@ -215,22 +253,39 @@ def main():
|
|||||||
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, req)
|
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, req)
|
||||||
if bars is not None and len(bars) > 2000:
|
if bars is not None and len(bars) > 2000:
|
||||||
break
|
break
|
||||||
|
si = mt5.symbol_info(sym); point = si.point
|
||||||
mt5.shutdown()
|
mt5.shutdown()
|
||||||
H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars]
|
H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars]
|
||||||
C = [float(b["close"]) 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)
|
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW); AT = _atr_series(H, L, C)
|
||||||
LV = build_levels(H, L)
|
LV = build_levels(H, L)
|
||||||
mid = len(C) // 2
|
mid = len(C) // 2
|
||||||
halves = [("H1 (alt)", _N_BARS, mid), ("H2 (neu)", mid, len(C))]
|
halves = [("H1 (alt)", _N_BARS, mid), ("H2 (neu)", mid, len(C))]
|
||||||
print("=" * 96)
|
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), "
|
print(f" Level = M30-Pivots k={_PIV_K}, Fenster {_PIV_LOOKBACK} M30-Bars (kausal), "
|
||||||
f"Deckel [{_SL_MIN_ATR}..{_SL_MAX_ATR}]×ATR")
|
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)
|
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:
|
for lbl, _, _ in halves:
|
||||||
print(f" {lbl}: {len(SIG[lbl])} Signale")
|
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 = [
|
variants = [
|
||||||
("LIVE ATR-Trail 1,5 (Basis)", dict(mode="atr", mult=1.5)),
|
("LIVE ATR-Trail 1,5 (Basis)", dict(mode="atr", mult=1.5)),
|
||||||
("S/R-Trail Puffer 0,10", dict(mode="sr", buf=0.10)),
|
("S/R-Trail Puffer 0,10", dict(mode="sr", buf=0.10)),
|
||||||
@@ -243,7 +298,8 @@ def main():
|
|||||||
for name, kw in variants:
|
for name, kw in variants:
|
||||||
print(f"\n{name}")
|
print(f"\n{name}")
|
||||||
for lbl, _, _ in halves:
|
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)}")
|
print(f" {lbl} {st(Rs)}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user