From e9653a842a506c78e0a95dbc81cd28e441ff2094 Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Wed, 29 Jul 2026 20:24:18 +0200 Subject: [PATCH] Trailing neustart-fest: Zustand per-Ticket persistieren + bei Wiedererkennung reaktivieren Problem: trail.enabled lebte nur im Speicher -> nach jedem restart_server.bat war das Trailing aus, die (auch per Auto-Squeeze eroeffnete) Position lief nur mit dem Broker-SL (kein Breakeven/Time-Stop). Die magic-match-Wiedererkennung reaktivierte es nie; der User musste es manuell einschalten (real 2026-07-29). Fix: - emergency_state.json persistiert zusaetzlich "trail" (=trail.enabled) per-Ticket. - _check_auto_close-Restore-Zweig: erkannte Position + trail war AN -> trail.toggle, Log "Trailing wiederhergestellt ... nach Neustart". War es AUS (manuelles SL/TP), bleibt es aus (persistierter Zustand entscheidet, kein TP-Ueberschreiben). - Change-Detection im _pos_loop persistiert bei jeder Aenderung (auch Selbst- Deaktivierung bei manueller SL/TP-Erkennung), nur bei Aenderung -> kein I/O je Tick. Verifiziert: Live-Position 48763789 nach Deploy-Neustart automatisch mit Trailing wiederhergestellt (Log + Snapshot trail.enabled=True). Keine Loop-Fehler. Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 12 ++++++++++++ core/engine.py | 20 +++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index f1547fa..312b0ac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -915,6 +915,18 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: real am 2026-07-17 14:30 setzte ein Neustart bei offenem Trade die von Hand gesetzten −25 € auf −19,42 € (2 %) zurück → Notfall-Close bei −19,64 statt bei −25. Eine echt neue Position (anderes Ticket) armiert normal aus pct/Merkwert und persistiert neu. + ⚠ **Trailing-Zustand ebenfalls neustart-fest (Fix 2026-07-29):** `emergency_state.json` + persistiert jetzt zusätzlich `trail` (= `trail.enabled`) per-Ticket. Erkennt der Server + dieselbe Position wieder und war das Trailing vor dem Neustart AN, **reaktiviert er es + automatisch** (`_check_auto_close`-Restore-Zweig → `trail.toggle`, Log „Trailing + wiederhergestellt … nach Neustart"). Grund: real am 2026-07-29 lief eine Auto-Squeeze- + Position nach mehreren Server-Neustarts nur mit dem **Broker-SL** (Trailing/Breakeven/ + Time-Stop weg), weil `trail.enabled` nur im Speicher lebte und die Wiedererkennung + (magic-match) es NIE reaktivierte — der User musste es von Hand einschalten. War das + Trailing AUS (z. B. nach manuellem SL/TP via `/api/sltp` → `trail.deactivate`), bleibt + es aus (der persistierte Zustand entscheidet — kein Überschreiben eines manuellen TP). + Der Zustand wird bei **jeder Änderung** persistiert (Change-Detection im `_pos_loop`, + fängt auch die Selbst-Deaktivierung des Trailings bei manueller SL/TP-Erkennung). **„aus"-Button schaltet auch den Auto-Arm ab** (setzt `_emergency_remember=0` — vorher blieb der gemerkte Wert und re-armte beim nächsten Trade, „aus" wirkte nur bis dahin; Bug gefixt 2026-07-07). Gilt genauso fürs Gewinn-Ziel. (Auto-Arm 1× auf manuell-only gestellt, nach Fehl-Trade diff --git a/core/engine.py b/core/engine.py index bd2e394..8317798 100644 --- a/core/engine.py +++ b/core/engine.py @@ -340,6 +340,7 @@ class TradingEngine: self._emergency_state_ticket = None self._emergency_state_loss = None self._emergency_state_tp = None + self._emergency_state_trail = None # Trailing-Zustand per-Ticket (neustart-fest) self._load_emergency_state() # Startup-Schonfrist: bis zu diesem Zeitpunkt kein server-seitiger Auto-Close # (in start() gesetzt). Konfigurierbar via [trading] startup_close_grace_s. @@ -719,6 +720,14 @@ class TradingEngine: log.info(f"Auto-Close wiederhergestellt (Pos {ticket}): Stop " f"{('-'+format(self._emergency_loss,'.2f')) if self._emergency_loss else 'aus'} · TP " f"{('+'+format(self._takeprofit,'.2f')) if self._takeprofit else 'aus'}") + # Trailing wiederherstellen: war es vor dem Neustart AN, wieder aktivieren + # (Fix 2026-07-29: Trailing lebte nur im Speicher → nach jedem Neustart + # aus, die Position lief nur mit Broker-SL, ohne Breakeven/Time-Stop; der + # User musste es manuell einschalten). War es AUS (z. B. manuelles SL/TP + # → deaktiviert), bleibt es aus — der persistierte Zustand entscheidet. + if self._emergency_state_trail and not self.trail.enabled and self.data.symbol: + self.trail.toggle(self.data.symbol) + log.info(f"Trailing wiederhergestellt (Pos {ticket}) nach Neustart") else: # echt neue Position → armieren if self._emergency_margin_pct > 0: pass # wird unten margin-basiert (retry-sicher) gesetzt @@ -767,6 +776,12 @@ class TradingEngine: self._save_emergency_state() log.info(f"Notfall-Stop armiert (Pos {ticket}): " f"-{self._emergency_loss:.2f} ({self._emergency_margin_pct:.0f}% Margin {margin:.2f})") + # Trailing-Zustand per-Ticket neustart-fest halten: nur bei ÄNDERUNG schreiben + # (kein Datei-I/O je Tick). Fängt auch die Selbst-Deaktivierung des Trailings + # (manuelles SL/TP im Terminal, `_do_modify`) — sonst würde der Neustart ein vom + # User abgeschaltetes Trailing wieder anwerfen. + if bool(self.trail.enabled) != bool(self._emergency_state_trail): + self._save_emergency_state() if self._in_startup_grace(): # Startup-Schonfrist: armieren ja, schließen nein return pnl = ps.get("pnl") @@ -1326,6 +1341,7 @@ class TradingEngine: self._emergency_state_ticket = st.get("ticket") self._emergency_state_loss = st.get("loss") self._emergency_state_tp = st.get("tp") + self._emergency_state_trail = st.get("trail") except FileNotFoundError: pass except Exception as e: @@ -1341,11 +1357,13 @@ class TradingEngine: with open(tmp, "w", encoding="utf-8") as f: json.dump({"ticket": self._last_pos_ticket, "loss": self._emergency_loss, - "tp": self._takeprofit}, f) + "tp": self._takeprofit, + "trail": bool(self.trail.enabled)}, f) os.replace(tmp, self._emergency_state_path) self._emergency_state_ticket = self._last_pos_ticket self._emergency_state_loss = self._emergency_loss self._emergency_state_tp = self._takeprofit + self._emergency_state_trail = bool(self.trail.enabled) except Exception as e: log.warning(f"emergency_state speichern: {e}")