From 0386497a791cdaa21e410928912d5d70969e3b68 Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Thu, 20 Aug 2026 19:45:05 +0200 Subject: [PATCH] Slot-Pruefung 5. Durchgang: drei Luecken, eine davon gravierend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Diesmal systematisch statt punktuell: alle Methoden von TradingEngine per AST klassifiziert, welche Slot-Objekte sie beruehren. 36 Funktionen fassen einen Slot an, 20 davon NUR Slot 1. ⚠⚠⚠ LUECKE 1 (gravierend): EINE FRISCH GEFUELLTE BRK-POSITION HATTE KEIN TRAILING. trail_brk.toggle() wurde an genau ZWEI Stellen gerufen - _rebind_brk (nach Neustart) und toggle_trail (der Knopf). Der frische Pending-Fill kam in keiner davon vor, und das ist der haeufigste Weg, auf dem eine BRK-Position entsteht (7 von 8 Trades seit dem Pending-Umbau). Sie lief damit nur mit dem Broker-SL - exakt die Variante, die backtest_brk_slonly.py verworfen hat (alle zehn KI enthalten die Null, Trefferquote 40 -> 21 %). Von aussen unsichtbar: die Position sieht in jeder Anzeige normal aus, ihr fehlt nur der Schutz. Ironie: _rebind_brk loggt "Trailing wiederhergestellt" - wiederhergestellt wurde etwas, das fuer einen frischen Fill nie an war. ⚠⚠ LUECKE 2: der Market-Fallback schaltete das FALSCHE Trailing ein. _open rief pauschal self.trail.toggle(). Zwei Schaeden auf einmal: die BRK-Position bleibt ungeschuetzt UND auf der manuellen Position wird das Trailing wieder eingeschaltet, auch wenn der Nutzer es dort bewusst abgeschaltet hatte (nach einer SL-Handeingabe). Sein handgesetzter Stop waere weitergezogen worden. Noch nicht eingetreten, weil alle acht Fallback-Versuche des 20.08. schon an der Order scheiterten. ⚠⚠ LUECKE 3: ein geschlossener BRK-Trade loeste KEINE Telegram-Meldung aus. _close_notify_pending wird in _check_auto_close armiert, das self.trader liest. Wiegt schwer, weil seit dem 19.08. NUR NOCH "Trade geschlossen" durch den Telegram-Filter kommt - der autonome Pfad haette vollstaendig still gehandelt. Behoben in _check_brk_close_cooldown, das den BRK-Flat-Uebergang ohnehin kennt. Dazu die Reichweiten-Warnung korrigiert: sie sagte "Broker-SL, Trailing und Circuit-Breaker greifen weiter" - das Trailing tat es eben NICHT. ABGESICHERT auf zwei Ebenen, beide mutationsgeprueft: tests/test_pending_fill.py +2: der Fill MUSS trail_brk einschalten und DARF das Trailing von Slot 1 nicht anfassen. Mutation -> Test faellt. Pipeline-Stufe E, Regel E: jeder Pfad, der eine BRK-Position eroeffnet (_check_pending_fill, _open), MUSS trail_brk erwaehnen. Mutation -> Exit 1. Die Test-Fixture bekam dafuer erstmals BEIDE Trailing-Attrappen - ohne die war der gefaehrlichste Slot-Fehler ueberhaupt nicht pruefbar. GEPRUEFT UND SAUBER: Circuit-Breaker, close(), _check_open_notify, _price_loop, _write_levels_file, snapshot, die Fill-Bindung, die _pos_loop-Reihenfolge. BEWUSST NICHT GEAENDERT (Slot-1-only, aber alle Features AUS): _check_auto_close, _check_sr_close, _check_adverse15, _check_flip_close, _check_auto_m15, set_sltp, _check_close_alert. _check_slot_reichweite meldet es, sobald eines eingeschaltet wird. Zweislot-faehig waere ein Umbau der Notfall-Zustandsmaschine - offen. ⚠⚠ ZWEI CONFIG-WERTE SIND AUF 0 ZURUECKGEDRIFTET (nur berichtet): close_notify_min_eur dokumentiert 50 -> ist 0 (Telegram ganz still) auto_emergency_margin_pct dokumentiert 8 -> ist 0 Bei close_notify_min_eur = 0 nuetzt auch Luecke 3 nichts - die Vormerkung haengt an _close_notify_min > 0. Der Bot meldet derzeit gar keinen Trade-Abschluss. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 78 ++++++++++++++++++++++++++++++++++++++ core/engine.py | 48 ++++++++++++++++++++--- tests/conftest.py | 20 ++++++++++ tests/test_pending_fill.py | 45 ++++++++++++++++++++++ tools/check_slots.py | 34 ++++++++++++++++- 5 files changed, 219 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8babccf..ff4965c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9106,6 +9106,84 @@ drosselt, ist schlimmer als keine — sie verdeckt genau die Meldung, die zählt | offene Position kleiner halten | die einzige Variante, die BRK sofort Luft gibt | ⚠ **Nichts davon wurde eigenmächtig gesetzt.** +## ⚠⚠⚠ SLOT-PRÜFUNG, FÜNFTER DURCHGANG — drei Lücken, eine davon gravierend (2026-08-20) + +User-Auftrag (zum fünften Mal). Diesmal **systematisch statt punktuell**: alle +Methoden von `TradingEngine` per AST danach klassifiziert, welche Slot-Objekte +sie berühren. **36 Funktionen** fassen einen Slot an; 20 davon **nur Slot 1**. + +⚠⚠⚠ **LÜCKE 1 — DIE GRAVIERENDSTE: EINE FRISCH GEFÜLLTE BRK-POSITION HATTE +KEIN TRAILING.** `trail_brk.toggle()` wurde an genau **zwei** Stellen gerufen — +`_rebind_brk` (nach einem Neustart) und `toggle_trail` (der TRAIL-Knopf). Der +**frische Pending-Fill kam in keiner davon vor** — und das ist der häufigste +Weg, auf dem eine BRK-Position überhaupt entsteht (**7 von 8** Trades seit dem +Pending-Umbau vom 05.08.). +Eine so gefüllte Position lief damit **nur mit dem Broker-SL**, also exakt in +der Variante, die `backtest_brk_slonly.py` am 19.08. **verworfen** hat: alle +zehn Konfidenzintervalle enthalten die Null, die Trefferquote fällt **40 → +21 %**, vier von fünf Trades laufen in den vollen −2R-Stop. +⚠ **Von aussen unsichtbar** — die Position sieht in jeder Anzeige normal aus, +ihr fehlt nur der Schutz. ⚠ Ironie: `_rebind_brk` loggt „Trailing des BRK-Slots +wiederhergestellt"; wiederhergestellt wurde etwas, das für einen frischen Fill +**nie an war**. + +⚠⚠ **LÜCKE 2 — der Market-Fallback schaltete das FALSCHE Trailing ein.** `_open` +rief pauschal `self.trail.toggle(sym)`. Bei `source == "auto_squeeze"` heisst +das: **zwei Schäden auf einmal** — (a) die BRK-Position bleibt ungeschützt und +(b) auf der **manuellen** Position wird das Trailing wieder **eingeschaltet**, +auch wenn der Nutzer es dort gerade bewusst abgeschaltet hatte (etwa nach einer +SL-Handeingabe, die `deactivate()` auslöst). Sein handgesetzter Stop wäre danach +vom Trailing weitergezogen worden. +⚠ Noch nicht eingetreten, weil alle acht Market-Fallback-Versuche des 20.08. +schon an der Order scheiterten (`Lot-Fehler`) — der Pfad bricht davor ab. + +⚠⚠ **LÜCKE 3 — ein geschlossener BRK-Trade löste KEINE Telegram-Meldung aus.** +`_close_notify_pending` wird in `_check_auto_close` armiert, und das liest +`self.trader.snapshot()`. Das wiegt schwer, weil seit dem 19.08. **nur noch +„Trade geschlossen"** durch den Telegram-Filter kommt: der autonome Pfad hätte +also **vollständig still** gehandelt — genau der Zustand, den der Befund vom +11.08. („alle neun Auslöser waren aus") beseitigen sollte. +✅ Behoben in `_check_brk_close_cooldown`, das den BRK-Flat-Übergang ohnehin +schon erkennt. + +✅ **ALLE DREI BEHOBEN**, dazu die **Reichweiten-Warnung korrigiert**: sie sagte +„Broker-SL, **Trailing** und Circuit-Breaker greifen weiter" — das Trailing tat +es eben NICHT. Die Warnung selbst war also irreführend. + +**ABGESICHERT — zwei Ebenen, beide mutationsgeprüft:** +· **`tests/test_pending_fill.py`** + 2 Tests: der Fill **muss** `trail_brk` + einschalten, und er **darf** das Trailing von Slot 1 **nicht** anfassen. + Mutation (Fix zurückgebaut) → Test fällt. +· **Pipeline-Stufe E** (`tools/check_slots.py`, Regel **E**): jeder Pfad, der + eine BRK-Position eröffnet (`_check_pending_fill`, `_open`), **muss** + `trail_brk` erwähnen. Mutation → **Exit 1**. +⚠ Dafür bekam die Test-Fixture erstmals **beide** Trailing-Attrappen — ohne die +war der gefährlichste Slot-Fehler überhaupt nicht prüfbar. + +✅ **GEPRÜFT UND SAUBER:** Circuit-Breaker (schliesst beide), `close()` (beide), +`_check_open_notify` (beide), `_price_loop` (füttert beide), `_write_levels_file` +(Marker beider Slots), `snapshot` (beide), `_check_pending_fill` (Bindung +korrekt), `_pos_loop`-Reihenfolge (Fill **vor** Pending-Manager). + +⚠ **BEWUSST NICHT GEÄNDERT — Slot-1-only, aber alle Features AUS:** +`_check_auto_close` (Notfall-Stop · Gewinn-Ziel · Trail-Restore), +`_check_sr_close`, `_check_adverse15`, `_check_flip_close`, `_check_auto_m15`, +`set_sltp` (die SL/TP-Felder der Trade-Leiste wirken nur auf Slot 1), +`_check_close_alert`. `_check_slot_reichweite` meldet das im Log, sobald eines +davon eingeschaltet wird und gleichzeitig eine BRK-Position läuft. Sie +zweislot-fähig zu machen ist ein Umbau der Notfall-Zustandsmaschine +(`_emergency_*_ticket`), kein Einzeiler — **offen, benannt**. + +⚠⚠ **ZWEI CONFIG-WERTE SIND AUF 0 ZURÜCKGEDRIFTET** (nur berichtet, nicht +geändert — Sizing/Alarm sind User-Entscheidungen): +| Schlüssel | dokumentiert | ist | +|---|---|---| +| `close_notify_min_eur` | **50** (11.08.) | **0 = Telegram ganz still** | +| `auto_emergency_margin_pct` | **8** (07.08.) | **0** | +⚠ Bei `close_notify_min_eur = 0` nützt auch die soeben geschlossene Lücke 3 +nichts: die Vormerkung hängt an `_close_notify_min > 0`. **Der Bot meldet +derzeit gar keinen Trade-Abschluss.** + ## ⚠⚠⚠ P(break) IST LIVE **INVERTIERT** — und es steuert echtes Geld (2026-08-19) Die fällige Messung ist entscheidbar geworden: **n=530 entkoppelt** gegen die diff --git a/core/engine.py b/core/engine.py index a0e568b..b2f77aa 100644 --- a/core/engine.py +++ b/core/engine.py @@ -1031,6 +1031,16 @@ class TradingEngine: self._brk_letzte = None self._pos_close_ts = time.time() self._pos_close_dir = "LONG" if vorher[1] == 0 else "SHORT" + # ⚠⚠ AUCH die Close-Benachrichtigung vormerken (Fix 2026-08-20). + # `_check_auto_close` armiert sie aus `self.trader.snapshot()`, + # sieht den BRK-Slot also nicht — ein geschlossener BRK-Trade + # loeste KEINE Telegram-Meldung aus. Das wiegt schwer, weil seit + # dem 19.08. NUR NOCH "Trade geschlossen" durch den + # Telegram-Filter kommt: der autonome Pfad haette also voellig + # still gehandelt — genau der Zustand, den der Befund vom 11.08. + # ("alle neun Ausloeser waren aus") beseitigen sollte. + if self._close_notify_min > 0: + self._close_notify_pending = [vorher[0], 0] log.info("BRK-Slot geschlossen (T=%s) — Wiedereinstiegs-Sperre " "%s armiert.", vorher[0], self._pos_close_dir) except Exception as e: @@ -1150,8 +1160,9 @@ class TradingEngine: 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).", + "die BRK-Position T=%s läuft ohne. Es greifen: " + "Broker-SL, Circuit-Breaker und — seit dem Fix vom " + "20.08. — das eigene Trailing (`trail_brk`).", ", ".join(offen), tk) return offen except Exception as e: @@ -1636,6 +1647,21 @@ class TradingEngine: # Dieselbe Buchfuehrung wie beim Market-Pfad, damit B4/B5 zaehlen self._bot_open_ticket = tk self._bot_open_source = "auto_squeeze" + # ⚠⚠ TRAILING EINSCHALTEN — ohne das lief der HAEUFIGSTE BRK-Pfad + # voellig ungeschuetzt (Fix 2026-08-20). `trail_brk.toggle()` wurde + # bis dahin an genau ZWEI Stellen gerufen: beim Rueckbinden nach + # einem Neustart und ueber den TRAIL-Knopf. Der frische Fill — und + # das sind 7 von 8 BRK-Trades, seit der Pending-Umbau laeuft — + # kam in keiner davon vor. Eine so gefuellte Position hatte nur + # den Broker-SL, also exakt die Variante, die + # `backtest_brk_slonly.py` verworfen hat. + # ⚠ Ironie am Rande: `_rebind_brk` loggt "Trailing des BRK-Slots + # wiederhergestellt" — wiederhergestellt wurde etwas, das fuer + # einen frischen Fill nie an war. + if not self.trail_brk.enabled and self.data.symbol: + self.trail_brk.set_setup(setup) + self.trail_brk.toggle(self.data.symbol) + log.info(f"Trailing des BRK-Slots aktiviert (T={tk})") self._squeeze_entry_count += 1 log.info(f"🎯 PENDING-FILL ({quelle}): {d} @ {ps.get('entry_price')} " f"— Position {tk} als {setup} getaggt" @@ -2995,9 +3021,21 @@ class TradingEngine: except Exception: pass # Trailing automatisch aktivieren (wie _after_trade im Widget) - if not self.trail.enabled: - self.trail.set_setup(self._last_rec.get("setup")) - self.trail.toggle(sym) + # ⚠⚠ DAS TRAILING DES *SLOTS*, nicht pauschal `self.trail` (Fix 2026-08-20). + # Vorher lief hier IMMER Slot 1 — mit zwei Schaeden auf einmal: + # (a) die BRK-Position bekam KEIN Trailing und lief damit in die + # gemessen DURCHGEFALLENE SL-only-Variante + # (`backtest_brk_slonly.py`: alle zehn KI enthalten die Null, + # Trefferquote faellt 40 -> 21 %); + # (b) schlimmer noch: ein BRK-Entry schaltete das Trailing auf der + # MANUELLEN Position wieder EIN — auch wenn der Nutzer es dort + # gerade bewusst abgeschaltet hatte (etwa nach einer SL-Handeingabe, + # die `deactivate()` ausloest). Sein Stop waere danach vom Trailing + # weitergezogen worden. + _tr = self.trail_brk if source == "auto_squeeze" else self.trail + if not _tr.enabled: + _tr.set_setup(self._last_rec.get("setup")) + _tr.toggle(sym) return None def close(self, slot: str | None = None) -> str | None: diff --git a/tests/conftest.py b/tests/conftest.py index 39ffef9..808a913 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -103,6 +103,26 @@ def engine(history): e.trader = FakeTrader() # Zweiter, strikt ticket-gebundener Slot fuer BRK (Umbau 2026-08-19). e.trader_brk = FakeTrader() + # ⚠⚠ BEIDE Trailing-Manager — sonst ist der gefaehrlichste Slot-Fehler + # ueberhaupt nicht pruefbar: bis zum 2026-08-20 hat der Pending-Fill das + # BRK-Trailing NICHT eingeschaltet, die Position lief nur mit dem + # Broker-SL (gemessen durchgefallen, `backtest_brk_slonly.py`). Die + # Attrappe merkt sich lediglich, OB `toggle()` gerufen wurde — die + # Trailing-Mechanik selbst gehoert nicht in diesen Test. + class _FakeTrail: + def __init__(self): + self.enabled = False + self.setup = None + def set_setup(self, s): + self.setup = s + def toggle(self, sym): + self.enabled = not self.enabled + return self.enabled + def deactivate(self): + was, self.enabled = self.enabled, False + return was + e.trail = _FakeTrail() + e.trail_brk = _FakeTrail() e.data = types.SimpleNamespace(symbol="XTIUSD") e.cfg = {"telegram": {"enabled": "false"}} # Zustand der geprüften Pfade diff --git a/tests/test_pending_fill.py b/tests/test_pending_fill.py index 519dc67..7bac4da 100644 --- a/tests/test_pending_fill.py +++ b/tests/test_pending_fill.py @@ -88,3 +88,48 @@ def test_ohne_pending_merker_sofort_raus(engine): engine._check_pending_fill() assert engine._bot_open_ticket is None + + +# ── Trailing am BRK-Slot (Fix 2026-08-20) ─────────────────────────────────── +def test_fill_schaltet_das_BRK_TRAILING_ein(engine): + """⚠⚠ DER GEFAEHRLICHSTE FUND DER SLOT-PRUEFUNG. + + `trail_brk.toggle()` wurde bis zum 20.08. an genau ZWEI Stellen gerufen: + beim Rueckbinden nach einem Neustart und ueber den TRAIL-Knopf. Der + FRISCHE Pending-Fill kam in keiner davon vor - und das ist der haeufigste + Weg, auf dem eine BRK-Position entsteht (7 von 8 Trades seit dem + Pending-Umbau). Eine so gefuellte Position hatte damit nur den Broker-SL: + exakt die SL-only-Variante, die `backtest_brk_slonly.py` verworfen hat + (alle zehn KI enthalten die Null, Trefferquote faellt 40 -> 21 %). + + Ohne diesen Test faellt ein Rueckbau nicht auf - die Position sieht in + jeder Anzeige normal aus, ihr fehlt nur der Schutz. + """ + engine._pending_tickets = {49483982: "squeeze"} + engine.trader_brk.snap = {"ticket": 49483982, "order_type": 0, + "entry_price": 75.5} + engine.trader_brk.ticket = 49483982 + assert not engine.trail_brk.enabled, "Vorbedingung: Trailing ist AUS" + + engine._check_pending_fill() + + assert engine.trail_brk.enabled, ( + "Der BRK-Slot laeuft nach einem Pending-Fill OHNE Trailing - " + "nur mit dem Broker-SL (gemessen durchgefallen)") + + +def test_fill_laesst_SLOT1_trailing_in_ruhe(engine): + """Die Gegenprobe: der Fill darf das Trailing der MANUELLEN Position nicht + anfassen. Der Nutzer schaltet es dort bewusst ab (z. B. nach einer + SL-Handeingabe, die `deactivate()` ausloest) - ein BRK-Entry darf es ihm + nicht wieder einschalten und seinen Stop weiterziehen.""" + engine.trail.enabled = False + engine._pending_tickets = {49483983: "squeeze"} + engine.trader_brk.snap = {"ticket": 49483983, "order_type": 0, + "entry_price": 75.6} + engine.trader_brk.ticket = 49483983 + + engine._check_pending_fill() + + assert not engine.trail.enabled, ( + "Ein BRK-Fill hat das Trailing der manuellen Position eingeschaltet") diff --git a/tools/check_slots.py b/tools/check_slots.py index 1cefe07..e88503f 100644 --- a/tools/check_slots.py +++ b/tools/check_slots.py @@ -159,12 +159,44 @@ def _sperrliste_vollstaendig(baum: ast.AST) -> list: return [] +def _brk_bekommt_trailing(baum: ast.AST) -> list: + """(E) Jeder Pfad, der eine BRK-Position EROEFFNET, muss `trail_brk` anfassen. + + ⚠⚠ DER GEFAEHRLICHSTE FUND DER SLOT-PRUEFUNG (2026-08-20): `trail_brk.toggle()` + wurde an genau ZWEI Stellen gerufen — beim Rueckbinden nach einem Neustart + (`_rebind_brk`) und ueber den TRAIL-Knopf (`toggle_trail`). Der FRISCHE + Pending-Fill kam in keiner davon vor, und das ist der haeufigste Weg, auf dem + eine BRK-Position entsteht (7 von 8 Trades seit dem Pending-Umbau). Der + Market-Fallback (`_open`) war noch schlechter: er schaltete `self.trail` ein, + also das Trailing der MANUELLEN Position — die BRK-Position blieb ungeschuetzt + UND dem Nutzer wurde auf seinem eigenen Trade das Trailing wieder angemacht. + Eine nur mit dem Broker-SL laufende BRK-Position ist genau die Variante, die + `backtest_brk_slonly.py` verworfen hat (alle zehn KI enthalten die Null, + Trefferquote faellt 40 -> 21 %). + ⚠ Von aussen unsichtbar: die Position sieht in jeder Anzeige normal aus, ihr + fehlt nur der Schutz. Deshalb diese statische Regel. + """ + OEFFNER = {"_check_pending_fill": "der Pending-Fill", + "_open": "der Market-Fallback"} + befunde = [] + for fn in [n for n in ast.walk(baum) if isinstance(n, ast.FunctionDef)]: + if fn.name not in OEFFNER: + continue + if "trail_brk" not in ast.unparse(fn): + befunde.append( + f"{fn.name}: erwaehnt `trail_brk` NICHT — {OEFFNER[fn.name]} " + f"wuerde eine BRK-Position OHNE Trailing eroeffnen " + f"(nur Broker-SL = gemessen durchgefallen)") + return sorted(befunde) + + def pruefe() -> list: p = WURZEL / "core" / "engine.py" quelle = p.read_text(encoding="utf-8") baum = ast.parse(quelle) return (_init_reihenfolge(baum, quelle) + _slot_trennung(baum) - + _close_beide(baum) + _sperrliste_vollstaendig(baum)) + + _close_beide(baum) + _sperrliste_vollstaendig(baum) + + _brk_bekommt_trailing(baum)) if __name__ == "__main__":