diff --git a/CLAUDE.md b/CLAUDE.md index b4ca380..54a6a24 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4942,6 +4942,66 @@ Modell schliesst keine Trades. „die Rangfolge ist kaputt" ist **nicht** belegt; belegt ist nur die Basisraten-/Kalibrierungslücke. +## ⚠⚠ VERLUSTBEGRENZUNG BEI „FALSCHEN" EINSTIEGEN — BEIDE IDEEN GEMESSEN, BEIDE DURCHGEFALLEN (2026-08-07) + +User: „um meine Verluste bei falschen Einstiegen zu begrenzen würde ich gerne den +Notfall-Close automatisch setzen — 30 %?" und „oder doch lieber ein Flip-Close +bei entgegengerichtetem Trend?" + +**(1) AUTOMATISCHER NOTFALL-STOP — 30 % wäre ohnehin wirkungslos gewesen.** +Maßstab zuerst: Einsatz **670 €/Lot**, Median-Verlust **20 €/Lot = 3 % der +Margin**. 30 % (=201 €/Lot) erreichen **1 %** aller Verluste. +**Kontrafaktisch über 1.022 echte Trades** (MAE aus M5-Bars zwischen Ein- und +Ausstieg; 87 €/$/Lot als Median aus den Trades selbst geschätzt): +| Schwelle | gefeuert | Σ P&L | Δ gegen IST | +|---|---|---|---| +| **ohne Stop (IST)** | — | **+358 €** | — | +| 3 % | 492 | +65 € | −293 € | +| 5 % | 317 | −280 € | −639 € | +| 8 % | 165 | +345 € | −14 € | +| 15 % | 70 | −58 € | −416 € | +| 30 % | 28 | +27 € | −331 € | +**Keine Schwelle schlägt „gar kein Stop".** Nach Einstiegsart (Δ gegen IST): +mit Signal −162…−278 · gegen Signal −69…−148 · **ohne Signal −292 / +298 / +290 +/ −106** (5/8/10/15 %). Die +298 sehen gut aus, aber **beide Nachbarn kippen** → +Rauschen, dieselbe Regel, an der ORB und die SMC-Sweeps gescheitert sind. + +**(2) FLIP-CLOSE AUF DEN HTF-TREND — war nie gemessen, jetzt schon +(`backtest_flipclose_htf.py`).** Abzugrenzen vom Flip auf das +**Empfehlungs**-Signal (gemessen, am 05.08. abgeschaltet): der HTF ist träger, +dreht seltener, und als FILTER ist er belegt (Edge ×2). Als EXIT nicht: +| Variante | H1 ΣR (Δ) | H2 ΣR (Δ) | +|---|---|---| +| **BASIS (kein Flip)** | **−548** | **−154** | +| M30 dagegen, ab +0,0R | −629 (−80) | −247 (−93) | +| M30 dagegen, ab +0,5R | −574 (−25) | −234 (−79) | +| M30 dagegen, ab +1,0R | −549 (−0) | −183 (−29) | +| M30 dagegen, auch im Minus | −616 (−68) | −327 (−172) | +| M30+H1, ab +0,5R | −570 (−22) | −222 (−67) | +| M30+H1, auch im Minus | −616 (−68) | −290 (−136) | +**Alle sechs Varianten in BEIDEN Hälften schlechter.** Die „mildeste" ist die, +die kaum feuert (ab +1,0R, Δ −0/−29) — exakt das Muster der 15-Minuten-Regel. +✅ **Kontrolle:** die Basis (−548/−154) reproduziert `backtest_flipclose2.py` +(−537/−140) — die Pipeline ist in Ordnung. +⚠ Methodik: **feste, geteilte Entry-Liste** über alle Varianten. +`backtest_flipclose2.py` läuft nach jedem Exit bei `xb+1` weiter, wodurch jede +Variante eine ANDERE Trade-Folge bekommt (der am 31.07. bei +`backtest_exit_combo.py` korrigierte Fehler). Hier von vornherein vermieden. + +⚠⚠ **DER MECHANISMUS IST IN ALLEN FÄLLEN DERSELBE: Trefferquote STEIGT +(38→44 %, 42→51 %), Ertrag FÄLLT = Gewinner-Kappen.** Ein Früh-Ausstieg kappt +einige Trades vor dem vollen SL, schneidet aber weit öfter Trades ab, die sich +erholt hätten. Damit ist es die **fünfte** unabhängige Messung dieser Klasse +(15-Minuten-Regel · `backtest_adverse_nosignal` · Flip auf das Signal · +Notfall-Stop · Flip auf den HTF-Trend) — **alle negativ.** + +✅ **Was stattdessen belegt ist:** der Verlust-Hebel liegt in der **SL-Breite** +(live 2,0×ATR_M15 ≈ 3,6×ATR_M5, Worst bis −5,6×ATR; per Broker-SL geschlossene +Trades hätten mit dem kanonischen Exit **1.229 €** weniger verloren) und damit +im **Sizing**, nicht im Exit. Gegen „falsche Einstiege" ist die gemessene Bremse +das **Veto** (gegen das Signal −6,60 €/Lot, beidhälftig) — es steht bereits im +Order-Dialog. **Nichts gebaut.** + ## ⚠⚠ DIE PRÜFUNG HING AM COMMIT, NICHT AM DEPLOYMENT — behoben 2026-08-07 User-Frage „wird die Pipeline bei jedem Deployment ausgeführt?". **Antwort war diff --git a/backtest_flipclose_htf.py b/backtest_flipclose_htf.py new file mode 100644 index 0000000..33b7681 --- /dev/null +++ b/backtest_flipclose_htf.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""Flip-Close auf dem HÖHEREN TREND (M30 / H1) — trägt er als EXIT? + +ANLASS (2026-08-07, User: „oder doch lieber ein Flip-Close bei entgegen- +gerichtetem Trend?"). Zu unterscheiden sind ZWEI Dinge: + · Flip auf das EMPFEHLUNGS-Signal — gemessen (`backtest_flipclose2.py`), in + JEDER Variante und BEIDEN Hälften schlechter, am 05.08. abgeschaltet. + Mechanik: die nachlaufende EMA dreht oft mitten im Pullback → Gewinner-Kappen. + · Flip auf den HTF-TREND (M30/H1) — **war nie gemessen**. Der HTF ist träger, + dreht viel seltener, und als FILTER ist er im Projekt belegt (Edge ×2). Ob er + auch als EXIT trägt, ist damit keineswegs entschieden. + +⚠⚠ ENTSCHEIDUNGSREGEL, VORAB FESTGELEGT: +Eine Variante gilt nur dann als besser, wenn sie + (1) den ΣR in BEIDEN Halbjahren erhöht, UND + (2) den Worst-Case nicht verschlechtert, UND + (3) bei den Nachbar-Einstellungen (minR) nicht das Vorzeichen kippt. +Sonst: kein Einbau. Punkt (3), weil in diesem Projekt schon mehrfach eine +einzelne Zelle gut aussah, deren Nachbarn kippten (ORB, SMC-Sweeps, und heute +der Notfall-Stop bei 8 % gegen −292/−106 bei 5 %/15 %). + +⚠ FESTE, GETEILTE ENTRY-LISTE. `backtest_flipclose2.py` lässt die sequentielle +Sim nach jedem Exit bei `xb+1` weiterlaufen — dadurch bekommt JEDE Variante eine +andere Trade-Folge (steigt eine früher aus, verschiebt sich alles Nachfolgende) +und die Zahlen sind nicht vergleichbar. Genau dieser Fehler wurde am 31.07. bei +`backtest_exit_combo.py` schon einmal korrigiert; hier wird er von vornherein +vermieden: alle Varianten laufen auf DENSELBEN Einstiegen. + +Exit-Kern = `core/exit_model.simulate()` mit LIVE-Parametern (geteilte Quelle, +kein Nachbau) + `stop_when`-Hook für den Flip. Kosten = echter Bar-Spread/ATR. + +Aufruf: python backtest_flipclose_htf.py [n_bars] (Default 80000) +""" +from __future__ import annotations + +import sys + +import MetaTrader5 as mt5 + +from backtest_auto_signal import _atr_series, _ema_series, _htf_signs +# ⚠ `_ATRMIN` aus `backtest_flipclose2` uebernehmen, NICHT neu setzen — der +# Vergleich mit dem dortigen Befund gilt nur bei identischem ATR-Floor. +from backtest_flipclose2 import _ATRMIN, signal_series +from core.exit_model import LIVE, simulate +from core.wave_rec import _EMA_FAST, _EMA_SLOW, _N_BARS + +_M30_STEP = 6 # 6 M5-Bars = M30 +_H1_STEP = 12 # 12 M5-Bars = H1 + + +def entries(sig, lo, hi, H, L, C, AT, SP): + """EINE feste Einstiegsliste, sequentiell mit dem BASIS-Exit ermittelt. + + ⚠ Bewusst mit der Basis (ohne Flip) bestimmt: nur so handeln alle Varianten + dieselben Trades und der Vergleich misst den EXIT, nicht eine verschobene + Trade-Folge.""" + out = [] + i = lo + while i < hi: + d = sig[i] + if d == 0: + i += 1 + continue + atr = max(AT[i] or 0, _ATRMIN) + kosten = (SP[i] if SP[i] > 0 else 0.0225) / atr + _, xb = simulate(C[i], d, atr, H, L, C, i + 1, LIVE, ret_bar=True) + out.append((i, d, atr, kosten)) + i = xb + 1 + return out + + +def lauf(ein, H, L, C, htf, h1, *, flip=None, min_r=0.0, auch_im_minus=False): + """`flip`: None = Basis · "m30" · "m30+h1" (beide müssen dagegen stehen).""" + Rs = [] + for i, d, atr, kosten in ein: + haken = None + if flip: + def haken(j, prof_atr, _d=d): + gegen = htf[j] == -_d + if flip == "m30+h1": + gegen = gegen and h1[j] == -_d + if not gegen: + return False + return auch_im_minus or prof_atr > min_r + R = simulate(C[i], d, atr, H, L, C, i + 1, LIVE, stop_when=haken) + Rs.append(R - kosten) + return Rs + + +def kz(Rs): + if not Rs: + return "—" + n = len(Rs); w = sum(1 for x in Rs if x > 0); s = sum(Rs) + g = sum(x for x in Rs if x > 0); v = -sum(x for x in Rs if x < 0) + return (f"n={n:>4} WR={100*w/n:>3.0f}% ØR={s/n:+.3f} " + f"PF={(g/v if v > 0 else 9.99):>4.2f} ΣR={s:>+6.0f} Worst={min(Rs):+.2f}") + + +def main(): + n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000 + 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) + 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) + sig = signal_series(H, L, C, EF, ES, AT) + htf = _htf_signs(H, L, C, _M30_STEP) + h1 = _htf_signs(H, L, C, _H1_STEP) + + mid = len(C) // 2 + print("=" * 96) + print(f" FLIP-CLOSE auf dem HTF-TREND — {sym} M5, {len(C)} Bars") + print(" Regel vorab: ΣR in BEIDEN Hälften besser · Worst nicht schlechter · " + "Nachbarn kippen nicht") + print("=" * 96) + + for name, (lo, hi) in (("H1", (_N_BARS, mid)), ("H2", (mid, len(C) - LIVE.max_hold - 2))): + ein = entries(sig, lo, hi, H, L, C, AT, SP) + print(f"\n ── {name} ── {len(ein)} feste Einstiege, alle Varianten identisch") + basis = lauf(ein, H, L, C, htf, h1) + b_sum = sum(basis) + print(f" {'BASIS (kein Flip-Close)':<34}{kz(basis)}") + for etikett, kw in ( + ("M30 dagegen, ab +0,0R", dict(flip="m30", min_r=0.0)), + ("M30 dagegen, ab +0,5R", dict(flip="m30", min_r=0.5)), + ("M30 dagegen, ab +1,0R", dict(flip="m30", min_r=1.0)), + ("M30 dagegen, AUCH im Minus", dict(flip="m30", auch_im_minus=True)), + ("M30+H1 dagegen, ab +0,5R", dict(flip="m30+h1", min_r=0.5)), + ("M30+H1, AUCH im Minus", dict(flip="m30+h1", auch_im_minus=True))): + Rs = lauf(ein, H, L, C, htf, h1, **kw) + d = sum(Rs) - b_sum + print(f" {etikett:<34}{kz(Rs)} Δ{d:>+6.0f}") + + +if __name__ == "__main__": + main()