From 4ebc55c49b5415ea3714374da6b42fc58be1602b Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Thu, 20 Aug 2026 08:01:20 +0200 Subject: [PATCH] Trailing: ATR fest auf M5 (Drift behoben) + trail_start 0,3 -> 1,3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User: "ueberpruefe die trailing logik und passe ggf an", nach drei Trades in Folge, die per nachgezogenem Stop schlossen. Beide Aenderungen auf Zustimmung. (1) DEPLOYMENT-DRIFT: der Trail-ATR kam aus der WELLEN-Zeitebene, und die wechselt. Gemessen an 90 echten Aktivierungen im Log: M5 Ø ATR 0,1877 n=51 -> 1,00x wie validiert M15 Ø ATR 0,2586 n= 9 -> 1,38x M30 Ø ATR 0,4751 n=30 -> 2,53x In 43 % der Faelle lief der Trail also mit einem Abstand, der NIE gemessen wurde. `backtest_trailmult.py` misst mult=1,0 auf M5-Bars mit M5-ATR; auf M30 entspricht das effektiv ~2,5 in M5-Einheiten, und 2,5 ist dort KLAR schlechter gemessen (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. `_atr_tf_override` bleibt unberuehrt: den setzt engine bewusst, das ist eine explizite Vorgabe und kein Automatismus. Zurueck: vier auskommentierte Zeilen in `_pick_tf` wieder aktivieren. (2) trail_start 0,3 -> 1,3. `backtest_trail_start.py` (14.08.) zaehlt den Anteil "war im Plus, schloss im Minus": 0,3 -> 47,6 % / 38,2 %, bei 1,3 -> 0,0 % / 0,5 %. ⚠ Der ERTRAG unterscheidet sich gepaart NICHT messbar - alle zwoelf KI enthalten die Null. Es ist also eine KOMFORT-Entscheidung, und sie ist gemessen gratis. Mechanik: der Trail sitzt mult×ATR (=1,0) hinter dem Bestkurs und erreicht den Einstand erst bei 1,0×ATR Gewinn; mit Start bei 0,3 lag der Stop im Band dazwischen zwangslaeufig auf der VERLUSTSEITE. ⚠⚠ FOLGE FUER DIE BACKTESTS, ausdruecklich benannt: `LIVE` ist die GETEILTE Quelle. Alle Skripte, die sie nutzen, rechnen ab jetzt mit trail_start 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 zugleich ihr Zweck: Live und Messung bewegen sich gemeinsam. Der veraltete Kommentar "start 0,3" in exit_model.py wurde mitgezogen, damit die Begruendung nicht gegen die Einstellung steht. ⚠ Am Verlust-Trade von heute frueh (-22,36) haette KEINE der beiden etwas geaendert: er war nie im Plus und traf den regulaeren Initial-SL. Dort ist die Schraube die Positionsgroesse, nicht der Exit. 91 Tests gruen, Deploy ueber tools/deploy.py --feld position_brk. Co-Authored-By: Claude Opus 5 --- core/exit_model.py | 30 ++++++++++++++++++++++++++++-- core/trailing.py | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 10 deletions(-) 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: