diff --git a/core/exit_model.py b/core/exit_model.py index d4187d4..9344d42 100644 --- a/core/exit_model.py +++ b/core/exit_model.py @@ -40,7 +40,21 @@ from dataclasses import dataclass, replace class ExitParams: """Alle Größen in ×ATR, Zeiten in Bars der jeweiligen Basis-TF.""" sl_atr: float = 2.0 # Initial-SL (Live-Band 1,8–2,2 → Ziel 2,0) - trail_start: float = 0.3 # Phase Init→Trail + # ⚠⚠ 0,3 → 1,3 am 2026-08-20 (User-Entscheidung nach drei Trades in Folge, + # die per nachgezogenem Stop schlossen). BEGRUENDUNG AUS DER MESSUNG: + # `backtest_trail_start.py` (14.08.) hat den Anteil "war im Plus, schloss + # im Minus" ausgezaehlt — 0,3 → 47,6 % / 38,2 %, bei 1,3 → 0,0 % / 0,5 %. + # ⚠ Der ERTRAG unterscheidet sich dabei NICHT messbar: der gepaarte Test + # (gleiche Einstiege, nur der Exit variiert) liefert fuer alle zwoelf + # Zellen ein 95-%-KI, das die Null enthaelt. Es ist also eine KOMFORT-, + # keine Edge-Entscheidung — und sie ist gemessen gratis. + # ⚠ MECHANIK, die man kennen muss: der Trail sitzt `mult`×ATR (=1,0) + # hinter dem Bestkurs und erreicht den Einstand daher erst bei 1,0×ATR + # Gewinn. Startete er schon bei 0,3, lag der Stop im Band dazwischen + # zwangslaeufig auf der VERLUSTSEITE. Mit 1,3 greift er erst oberhalb des + # Einstands; bis dahin schuetzt der Initial-SL. + # ZURUECK: 0.3 + trail_start: float = 1.3 # Phase Init→Trail mult: float = 1.0 # Trail-Abstand HW ∓ mult×ATR (1,5 → 1,0 am 2026-07-31) be: float = 1.3 # Breakeven-Boden (Entry) ab diesem Profit lock_start: float = 3.5 # Phase Trail→Lock @@ -70,7 +84,19 @@ class ExitParams: # Grund: die Sim modelliert **keine Exit-Slippage**; ein engerer Trail # löst deutlich häufiger aus und ist damit stärker davon betroffen # (real gemessen: bis 0,75 ATR über den Stop hinaus). -# start 0,3 `backtest_trailing.py` (0,6/1,0 schlechter) +# start 1,3 ⚠ WAR 0,3 (`backtest_trailing.py`: 0,6/1,0 schlechter). Am +# 2026-08-20 auf 1,3 gesetzt — s. Begruendung an `trail_start`. +# Der alte Befund ist NICHT widerlegt: er mass den ERTRAG, und der +# unterscheidet sich gepaart nicht messbar (alle 12 KI enthalten +# die Null). Geaendert wurde aus KOMFORT — der Anteil "war im +# Plus, schloss im Minus" faellt von 47,6 % auf 0,0 %. +# ⚠⚠ FOLGE FUER BACKTESTS: `LIVE` ist die geteilte Quelle, alle +# Skripte, die sie nutzen, rechnen ab jetzt mit 1,3. Ihre +# dokumentierten Zahlen wurden mit 0,3 erzeugt und reproduzieren +# daher NICHT mehr. Wer eine alte Zahl nachrechnen will, muss +# `LIVE.with_(trail_start=0.3)` pinnen. Das ist der Preis der +# geteilten Quelle — und genau ihr Zweck: Live und Messung +# bewegen sich gemeinsam (Deployment-Drift Fall 3). # be 1,3 `backtest_exit.py be` (0,6 → 21 % Breakeven-Scratches) # timestop 24 `backtest_timestop.py` (120 min; 30/60 min kippten) LIVE = ExitParams() diff --git a/core/trailing.py b/core/trailing.py index 5f21206..58fd526 100644 --- a/core/trailing.py +++ b/core/trailing.py @@ -225,14 +225,34 @@ class TrailingManager: angle = self._trend_angle if override is not None: return override - if angle is None: - return _TF_RANGE - strength = abs(angle - 90.0) - if strength > 45: - return _TF_STRONG # starker Trend → H1 - elif strength > 20: - return _TF_MODERATE # moderater Trend → M30 - return _TF_RANGE # Seitwärts → M15 + # ⚠⚠ FEST AUF M5 (2026-08-20) — DEPLOYMENT-DRIFT BEHOBEN. + # `backtest_trailmult.py` misst `mult = 1,0` auf M5-Bars mit M5-ATR. + # Live kam der ATR bis heute aus der WELLEN-Zeitebene, und die wechselt. + # Gemessen an 90 echten Aktivierungen: + # M5 Ø ATR 0,1877 (n=51) → 1,00× wie validiert + # M15 Ø ATR 0,2586 (n= 9) → 1,38× + # M30 Ø ATR 0,4751 (n=30) → 2,53× + # In 43 % der Faelle lief der Trail also mit einem Abstand, der NIE + # gemessen wurde — auf M30 entspricht `mult 1,0` effektiv ≈2,5 in + # M5-Einheiten, und 2,5 ist gemessen KLAR schlechter (H1 ΣR −1174 + # gegen −73 bei 1,0). + # ⚠ Am 31.07. wurde `_MULT_BY_TF` genau gegen diese Fehlerklasse auf + # durchgehend 1,0 geglaettet. Der Multiplikator war aber nie die + # Ursache — es ist der ATR. Die Variabilitaet kam durch die Hintertuer + # zurueck; das hier schliesst sie. + # ⚠ `_atr_tf_override` bleibt unberuehrt: `engine` setzt ihn bewusst + # (u. a. fuer Squeeze-Trades), und das ist eine EXPLIZITE Vorgabe, kein + # Automatismus. + # ZURUECK auf das alte Verhalten: die vier Zeilen unten einkommentieren. + return mt5.TIMEFRAME_M5 + # if angle is None: + # return _TF_RANGE + # strength = abs(angle - 90.0) + # if strength > 45: + # return _TF_STRONG # starker Trend → H1 + # elif strength > 20: + # return _TF_MODERATE # moderater Trend → M30 + # return _TF_RANGE # Seitwärts → M15 # ── ATR (gecacht, refresht nur bei neuer Kerze des gewählten TF) ───────── def _refresh_atr(self, sym: str, tf: int) -> bool: