From f02fd43b3934c5c0be6f21d4128a4a38ba9fed0d Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Wed, 19 Aug 2026 23:23:01 +0200 Subject: [PATCH] Telegram: ALLE Trade-Opens und -Closes + Reichweiten-Waechter fuer den 2. Slot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (1) User-Vorgabe: "Alle Trade Closes und Opens sollen per Telegram gemeldet werden". - close_notify_min_eur 50 -> 0, also jeder Close ohne Schwelle (Backup oil_widget_config.ini.bak-2026-08-19-tgopen) - neuer _check_open_notify: meldet jede Eroeffnung, ueber BEIDE Slots. Der BRK-Slot ist ausdruecklich dabei - ein autonomer Entry soll nicht unbemerkt bleiben, genau dafuer wurde der zweite Slot gebaut; die Meldung nennt den Slot mit. - _TG_ERLAUBT um "Trade eroeffnet" erweitert (das Gate ist EINE Filterstelle, nicht 17 Einzeleingriffe). ⚠ 1x je Ticket ueber _open_notify_gemeldet - ohne den Merker feuerte es im 1-s-Takt weiter, solange die Position offen ist. ⚠ In der Startup-Schonfrist wird nur GEMERKT, nicht gemeldet: nach einem Neustart erkennt der Trader eine BESTEHENDE Position wieder, und das ist keine Eroeffnung. (2) Reichweiten-Waechter (_check_slot_reichweite). Gemessener Befund: von den Schutz-Checks ist NUR _check_circuit_breaker slot-faehig. _check_auto_close (Notfall-Stop, haengt an 4 Ticket-Variablen), _check_sr_close, _check_adverse15 und _check_flip_close kennen nur Slot 1. Heute folgenlos, weil alle vier AUS sind - aber wird einer eingeschaltet, waehrend eine BRK-Position laeuft, gilt er stillschweigend nur fuer Slot 1, und im Dashboard saehe alles normal aus. Dieselbe Klasse wie Deployment-Drift Fall 8: nicht die Strategie driftet, sondern ihre REICHWEITE. Der Waechter macht das Loch LAUT (Log-Warnung 1x je Ticket + Snapshot-Feld `slot_luecke`), bis die vier slot-faehig sind. ⚠ Die volle Parametrisierung der vier Checks steht AUS. _check_auto_close allein haengt an vier Ticket-Variablen und ist der Notfall-Stop - das gehoert in eine eigene Sitzung mit Tests, nicht ans Ende einer langen. ⚠ EIGENER FEHLER auf dem Weg: der erste Bauversuch lief per Heredoc und riss das Telegram-Format-Literal an einem \n auseinander (SyntaxError). Das ist exakt die im Projekt dokumentierte \n-Falle - "mehrzeilige Strings mit \n NIE per Heredoc schreiben, den Edit-Weg nehmen". Ich habe die eigene Regel verletzt; engine.py wurde per git checkout zurueckgesetzt und sauber ueber Edit neu gebaut. Deploy ueber tools/deploy.py --feld position_brk, alle 5 Schritte. Co-Authored-By: Claude Opus 5 --- core/engine.py | 85 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/core/engine.py b/core/engine.py index 9498c64..4c34476 100644 --- a/core/engine.py +++ b/core/engine.py @@ -183,7 +183,9 @@ def _p_break(mom6: float, mom3: float, wt: float, dist: float) -> float: # EINE AUSNAHME mit Bypass: " FEHLGESCHLAGEN" beim Notfall-/ # Gewinn-Close. Dort ist die Position NICHT zu und es braucht eine # Handeingabe -- Stille genau dort waere die gefaehrlichste Variante. -_TG_ERLAUBT = ("Trade geschlossen",) +# ⚠ User-Vorgabe 2026-08-19: ALLE Trade-Closes UND -Opens melden. +# `close_notify_min_eur` steht dafür auf 0 (= jeder Close, keine Schwelle). +_TG_ERLAUBT = ("Trade geschlossen", "Trade eroeffnet") def send_telegram(text, *a, **kw): @@ -862,6 +864,8 @@ class TradingEngine: # ⚠ NACH `_check_auto_close`: erst dort wird der Flat-Übergang # erkannt und die Benachrichtigung vorgemerkt. self._check_close_notify() # Telegram bei jedem Close ab Schwelle + self._check_open_notify() # Telegram bei jeder Eröffnung + self._check_slot_reichweite() # meldet ungedeckte 2. Position self._check_adverse15() # 15-Min-Regel (nur Bot-Trades) self._check_flip_close() # Empfehlung dreht → schließen self._check_sr_close() @@ -945,6 +949,85 @@ class TradingEngine: return False return self._circuit_tripped_date == datetime.now(_BERLIN).date() + def _check_open_notify(self): + """Telegram bei JEDER Eröffnung — beide Slots (User-Vorgabe 2026-08-19). + + ⚠ Bewusst über BEIDE Trader: ein autonomer BRK-Entry soll nicht + unbemerkt bleiben — genau der Fall, für den der zweite Slot gebaut + wurde. Die Meldung nennt deshalb den Slot mit. + ⚠ 1× je Ticket (`_open_notify_gemeldet`); ohne den Merker feuerte sie im + 1-s-Takt weiter, solange die Position offen ist. + ⚠ In der Startup-Schonfrist wird nur GEMERKT, nicht gemeldet: nach einem + Neustart erkennt der Trader eine BESTEHENDE Position wieder, und das ist + keine Eröffnung. + """ + if not hasattr(self, "_open_notify_gemeldet"): + self._open_notify_gemeldet = set() + for tr, quelle in ((self.trader, "Slot 1"), (self.trader_brk, "BRK")): + try: + ps = tr.snapshot() or {} + tk = ps.get("ticket") + if not tk or tk in self._open_notify_gemeldet: + continue + self._open_notify_gemeldet.add(tk) + if self._in_startup_grace(): + continue + lg = ps.get("order_type") == 0 + zeilen = [ + f"📈 Trade eroeffnet ({quelle})", + f"{self.data.symbol or '?'} " + f"{'▲ LONG' if lg else '▼ SHORT'} " + f"{ps.get('lots')} Lots @ {ps.get('entry_price')}", + f"SL {ps.get('sl') or '—'} · TP {ps.get('tp') or '—'}", + ] + send_telegram("\n".join(zeilen)) + except Exception as e: + log.debug(f"_check_open_notify: {e}") + if len(self._open_notify_gemeldet) > 200: + self._open_notify_gemeldet = set(list(self._open_notify_gemeldet)[-50:]) + + # ⚠⚠ REICHWEITEN-WÄCHTER (2026-08-19). Vier Schutz-Checks kennen bislang NUR + # den ersten Slot: `_check_auto_close` (Notfall-Stop, hängt an 4 + # Ticket-Variablen), `_check_sr_close`, `_check_adverse15`, + # `_check_flip_close`. Solange sie AUS sind, ist das folgenlos — der + # BRK-Slot hat Broker-SL, `trail_brk` und den Circuit-Breaker. + # ⚠ Wird einer davon wieder eingeschaltet, WÄHREND eine BRK-Position + # läuft, gilt er stillschweigend nur für Slot 1: die zweite Position liefe + # ohne ihn, und im Dashboard sähe alles normal aus. Dieselbe Klasse wie + # Deployment-Drift Fall 8 — nicht die Strategie driftet, sondern ihre + # REICHWEITE. Bis die vier slot-fähig sind, macht dieser Wächter das Loch + # LAUT statt still (1× je Ticket, kein Dauerlärm). + def _check_slot_reichweite(self): + try: + tk = self.trader_brk.ticket + if not tk: + self._reichweite_gemeldet = None + return None + offen = [] + if ((getattr(self, "_emergency_margin_pct", 0) or 0) > 0 + or (getattr(self, "_emergency_pct", 0) or 0) > 0 + or (getattr(self, "_emergency_remember", 0) or 0) > 0): + offen.append("Notfall-Stop") + if getattr(self, "_auto_sr_close", False): + offen.append("S/R-Close") + if (getattr(self, "_adverse15_atr", 0) or 0) > 0: + offen.append("15-Min-Regel") + if getattr(self, "_flip_close", False): + offen.append("Flip-Close") + if not offen: + self._reichweite_gemeldet = None + return None + if getattr(self, "_reichweite_gemeldet", None) != tk: + self._reichweite_gemeldet = tk + log.warning("⚠ REICHWEITE: %s aktiv, gilt aber NUR für Slot 1 — " + "die BRK-Position T=%s läuft ohne (Broker-SL, " + "Trailing und Circuit-Breaker greifen weiter).", + ", ".join(offen), tk) + return offen + except Exception as e: + log.debug(f"_check_slot_reichweite: {e}") + return None + def _check_circuit_breaker(self): """Tagesverlust-Stopp (User-Vorgabe 2026-07-27): erreicht der Tages-P&L (realisiert heute + offene Position) −`daily_loss_limit_pct`% der Balance,