From dca3c11bbd4e60a7037f1acb0a1ae2271da235dc Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Tue, 11 Aug 2026 22:31:42 +0200 Subject: [PATCH] Gesamtpruefung 2026-08-11: drei Defekte behoben Zweiter vollstaendiger Durchlauf nach docs/review-prompt.md. BEFUND 1: rec_outcomes war seit dem Einbau (06.08.) still tot - 0 Zeilen in 5 Tagen. _run_analysis las s.get("pb_feats"), aber s ist der MT5Data-Snapshot; pb_feats lebt im Wave-Snapshot. _atr also immer 0,0 und die Schreibbedingung nie erfuellt. Kein NameError (ruff sieht nichts), und das umgebende except loggt auf DEBUG. Aufgefallen nur, weil die Tabelle mit 0 Zeilen neben ihren Schwestern stand. Generalisiert als measurement_reminder.telemetrie_puls(): jede Telemetrie-Tabelle muss einen Puls haben. Erster Lauf meldet genau diesen Fund, alle uebrigen gruen. BEFUND 2: der Divergenz-Waechter war selbst driftend. Abschnitt A verglich gegen 43 % WARTEN - seit 05.08. ausdruecklich ungueltig (das misst _build allein); richtig sind 80,5 %. Und entry_room galt als "live-only", obwohl es seit 04./05.08. modelliert wird. A: +50,8 Pp Warnung -> +13,3 Pp OK. B: 63,9 % -> 5,0 %. BEFUND 3: P(break) ist wieder kalibriert (Delta -1,7 Pp nach +14,3 Pp am 07.08.) - stuetzt den Schluss, dass die gewanderte Basisrate ein Zeitraum-Effekt war. Sauber: alle Standardpruefungen, 0 fehlende Frontend-IDs, mt5_lock, Broker-Zeit, Race-Klasse. Performance: Snapshot-Median 15,9 ms, alle heissen Abfragen ueber Index - keine Optimierung noetig. Kein toter Code geloescht: die vermeintlich verwaisten Snapshot-Felder sind Diagnose-Oberflaeche (vier davon heute zur Deploy-Verifikation benutzt). Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 74 +++++++++++++++++++++++++++++++++++++++++ analyze_divergence.py | 24 +++++++++++-- core/engine.py | 21 ++++++++++-- measurement_reminder.py | 61 +++++++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a9c6c1f..8cd1eed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6161,6 +6161,80 @@ Neumessung. Sie bleiben unverändert und reproduzierbar; wo ihre Schlussfolgerun relevant wird, gehört sie live-treu **neu gerechnet** (Muster: `backtest_auto_signal_norev.py`). +## Gesamtprüfung 2026-08-11 (nach `docs/review-prompt.md`) + +Zweiter vollständiger Durchlauf. **Drei echte Defekte**, alle behoben; keine +Strategie-Änderung. + +⚠⚠ **BEFUND 1 — `rec_outcomes` war seit dem Einbau (06.08.) STILL TOT: 0 Zeilen +in 5 Tagen.** `engine._run_analysis` las `s.get("pb_feats")` — `s` ist aber der +**MT5Data**-Snapshot (Preis/Trend/Konto); `pb_feats` lebt im **Wave**-Snapshot. +Ergebnis: `_atr` immer 0,0 → die Schreibbedingung `_atr > 0` nie erfüllt. +✅ **Live belegt:** `market` hat **kein** `pb_feats`, `wave` hat es (ATR 0,12). +⚠ **Warum keine Prüfung es fing:** es ist **kein** `NameError` (ruff F821 sieht +nichts), sondern ein falsches dict — syntaktisch einwandfrei. Und das umgebende +`except` loggt auf **DEBUG**, der Logger steht auf INFO. Exakt die Fehlerklasse, +vor der der `wave_snap`-Fallstrick vom 04.08. schon einmal gewarnt hat. +✅ Aufgefallen NUR, weil die Tabelle in Durchgang 4 mit 0 Zeilen **neben ihren +Schwestern** stand (`m15_states` 3.143 · `cone_checks` 263 · +`pbreak_predictions` 3.775). Damit hat die Selbst-Kalibrierung der Empfehlung +fünf Tage lang nichts gemessen. +✅✅ **GENERALISIERT: `measurement_reminder.telemetrie_puls()`** — jede +Telemetrie-Tabelle muss einen PULS haben; schweigt eine, während die anderen +schreiben, ist der Logger tot. 7 Tabellen überwacht, Toleranz Faktor 20 der +erwarteten Kadenz (ein Wächter, der oft warnt, wird ignoriert). ⚠ +`candles_m1.time` ist ROHE Brokerzeit → Offset abgezogen, sonst 3 h Fehlalarm. +**Erster Lauf meldet genau diesen einen Fund**, alle übrigen grün. + +⚠⚠ **BEFUND 2 — DER DIVERGENZ-WÄCHTER WAR SELBST DRIFTEND.** Er verglich +Abschnitt A gegen **43 % WARTEN** — ein Maßstab, den CLAUDE.md seit dem +**2026-08-05 ausdrücklich für ungültig erklärt** (`backtest_dist.py` misst +`_build` ALLEIN, ohne Breakout-Bestätigung und Entry-Raum-Gate; mit vollem +Live-Stack sind es **80,5 %**). Zusätzlich zählte er `entry_room` als +„live-only", obwohl es seit dem 04./05.08. in `backtest_breakout_gated.py` mit +dem **echten** `_room_gate` modelliert wird. +| | vorher | nachher | +|---|---|---| +| A) Abweichung WARTEN | **+50,8 Pp ⚠** | **+13,3 Pp OK** | +| B) „Nur-Live-Gates" | **63,9 %** | **5,0 %** | +Der Wächter behauptete also einen Deployment-Drift, den es dort nicht gibt — +und „ein Wächter, der immer warnt, wird ignoriert" ist die eigene Lehre vom +02.08. (damals für die Modell-Epochen, hier für die Konstanten). + +✅ **BEFUND 3 — P(break) ist wieder kalibriert.** Δ **−1,7 Pp** (Vorhersage +40,4 %, real 38,7 %, n=106 entkoppelt) nach **+14,3 Pp** am 07.08. Das stützt +den damaligen Schluss, dass die „gewanderte Basisrate" ein **Zeitraum-Effekt** +war, kein Modellversagen. + +**Sauber (keine Befunde):** `check_nfalle` · `ruff F821` · 53 Tests · +`node --check` · **0 fehlende Frontend-IDs** von 93 · der einzige Thread-Aufruf +von `refresh_market` nimmt `mt5_lock` selbst · alle `.time`-Nutzungen sind +Vergleiche unter Broker-Zeiten oder die dokumentierte `candles_m1`-Ausnahme · +die 3 Ticket-Wechsel-Blöcke haben ihre Retry-Wächter · TF-Churn Median 23 min. + +**Performance:** Snapshot-Median **15,9 ms** (p90 19,4), gegen 13 ms am 02.08. — +der Zuwachs deckt sich mit den seither gebauten Feldern und liegt weit unter dem +1-s-Budget. Alle heißen Abfragen laufen über einen Index (`recommendations` +1,2 ms · `candles_m1` über den PRIMARY KEY 1,9 ms · `trades` 0,1 ms). +**Keine Optimierung nötig** — Micro-Tuning ohne Messung wäre hier wertlos. + +⚠ **Toter Code: keine Löschempfehlung.** 13 Snapshot-Felder haben keinen Leser +in `app.js` — davon habe ich am selben Tag **vier** benutzt, um Deployments zu +verifizieren (`close_notify_min`, `squeeze_pending`, `squeeze_night_hours`, +`squeeze_max_chase_atr`). Der Snapshot ist die **Diagnose-Oberfläche** und laut +`tools/deploy.py` Schritt 4 der einzige belastbare Beweis, dass neuer Code +läuft — sie zu entfernen würde die Verifikation kaputtmachen. Einzig +`cost_ratio` ist ein echter Waise (speiste den Kosten-Chip in `vd-consensus`, +den es nicht mehr gibt); er kostet eine Division und bleibt. +⚠ **Bekannt und unverändert:** 47 Backtests haben eine eigene Exit-Kopie (36 +nutzen `core/exit_model`), `_atr_series` existiert in **86** Dateien. Umstellen +verletzt die Migrations-Regel — ihre publizierten Zahlen würden sich +verschieben. Alle 2026-08-11 neu gebauten Skripte nutzen den geteilten Kern. +⚠ **Benannt, nicht geändert:** `auto_squeeze` steht in `runtime_state.json` auf +`false`, in der ini auf `true`. Verhalten ist korrekt (runtime gewinnt), der +Zustand aber irreführend — wird `runtime_state.json` gelöscht, schaltet sich der +autonome Einstieg wieder ein. **User-Entscheidung vom 11.08., 14:10.** + ## Gesamtprüfung 2026-08-02 (nach `docs/review-prompt.md`) Erster vollständiger Durchlauf des Review-Prompts. **Keine Strategie-Änderung** — alle diff --git a/analyze_divergence.py b/analyze_divergence.py index d132727..cf53c45 100644 --- a/analyze_divergence.py +++ b/analyze_divergence.py @@ -41,7 +41,18 @@ _BROKER_OFF = 10800 # Brokerzeit = UTC+3 (candles/copy_rates sind Broker-Wallc LOG = "oil_widget.log" # Erwartungswerte aus den Backtests — die Messlatte -_EXP_WAIT = 43.0 # backtest_dist.py: ~57 % LONG/SHORT, ~43 % WARTEN +# ⚠⚠ KORRIGIERT 2026-08-11 (Review-Durchgang 2): hier standen **43,0 %** aus +# `backtest_dist.py`. Dieser Massstab ist seit dem 2026-08-05 ausdrücklich +# UNGÜLTIG — er misst `_build` ALLEIN, ohne Breakout-Bestätigung und ohne +# Entry-Raum-Gate, also ohne die beiden groessten Live-Blocker. Mit dem vollen +# Live-Stack liegt die Erwartung bei **80,5 %** (`backtest_dist_gated.py`: +# entry_room 41,4 % · breakout_pending 17,0 % · stretch 13,8 % · deadband +# 7,4 % · min_conf 0,9 %). +# ⚠ Wirkung des Fehlers: der Waechter meldete DAUERHAFT „+50,8 Pp Abweichung" — +# und ein Waechter, der immer warnt, wird ignoriert. Genau diese Lehre steht +# seit dem 02.08. im Epochen-Block darunter; sie galt fuer die Modelle, nicht +# fuer diese Konstante. +_EXP_WAIT = 80.5 # backtest_dist_gated.py (2026-08-05), VOLLER Live-Stack _EXP_WAIT_TOL = 15.0 # ab dieser Abweichung wird geflaggt (Pp) # ── EPOCHEN ─────────────────────────────────────────────────────────────────── @@ -123,9 +134,18 @@ def sec_b(c, t0): print(" noch keine Daten — das Logging läuft erst seit dem Neustart am 31.07.") return # Welche Gates kennt der Backtest? (nur diese dürfen den Vergleich prägen) + # ⚠⚠ `entry_room` KORRIGIERT 2026-08-11 (Review-Durchgang 2): stand auf + # False = „live-only". Das stimmt seit dem 2026-08-04/05 nicht mehr — + # `backtest_breakout_gated.py` und `backtest_dist_gated.py` rufen das + # ECHTE `_room_gate` (mit `set_entry_room(0.6)`) und modellieren es damit. + # Wirkung: der grösste Blocker (58,9 %) wurde als „nicht gedeckt" gezählt, + # wodurch die Zeile „Nur-Live-Gates blocken X %" massiv zu hoch auswies + # (63,9 % statt real ~5,0 %) — und damit einen Deployment-Drift behauptete, + # den es an dieser Stelle nicht gibt. im_backtest = {"deadband": True, "htf_counter": True, "stretch": True, "min_conf": True, "breakout_pending": True, - "entry_room": False, "dead_hour": False, "eia": False, + "entry_room": True, + "dead_hour": False, "eia": False, "no_data": False, "stale": False, None: None} print(f" {'Gate':<20}{'n':>8}{'Anteil':>9} im Backtest modelliert?") for br, n in rows: diff --git a/core/engine.py b/core/engine.py index a18cae6..e366a58 100644 --- a/core/engine.py +++ b/core/engine.py @@ -1743,8 +1743,25 @@ class TradingEngine: try: _sig = rec.get("signal") if _sig in ("LONG", "SHORT") and _sig != getattr(self, "_rec_epi_sig", None): - _atr = float((s.get("pb_feats") or {}).get("atr") or 0.0) - _px = float((self.data.snapshot() or {}).get("bid") or 0.0) + # ⚠⚠ BUG BEHOBEN 2026-08-11 (Review-Durchgang 4): hier stand + # `s.get("pb_feats")`. `s` ist aber der **MT5Data**-Snapshot + # (Preis/Trend/Konto) — `pb_feats` lebt im **Wave**-Snapshot. + # `s.get(...)` lieferte damit IMMER None → `_atr = 0.0` → die + # Bedingung `_atr > 0` war NIE erfüllt → `rec_outcomes` blieb + # seit dem Einbau (06.08.) bei **0 Zeilen**, also 5 Tage lang + # still tot. Die Selbst-Kalibrierung der Empfehlung hat in + # dieser Zeit nichts gemessen. + # ⚠ Warum es keine Prüfung fing: es ist KEIN NameError (ruff F821 + # sieht nichts), sondern ein falsches dict — syntaktisch + # einwandfrei. Und das umgebende `except` loggt nur auf DEBUG, + # der Logger steht auf INFO. Genau die Fehlerklasse, vor der + # der `wave_snap`-Fallstrick vom 04.08. schon einmal warnte. + # ✅ Aufgefallen ist es NUR, weil die Tabelle in Durchgang 4 mit + # 0 Zeilen neben ihren Schwestern stand (m15_states 3.143, + # cone_checks 263, pbreak_predictions 3.775). + _ws = self.wave.snapshot() or {} + _atr = float((_ws.get("pb_feats") or {}).get("atr") or 0.0) + _px = float(s.get("bid") or 0.0) if _atr > 0 and _px > 0 and self.history: self.history.log_rec_outcome( signal=_sig, conf_pct=int(rec.get("conf_pct") or 0), diff --git a/measurement_reminder.py b/measurement_reminder.py index b4075ae..ac7113b 100644 --- a/measurement_reminder.py +++ b/measurement_reminder.py @@ -414,6 +414,59 @@ CONFIG_DEPS = [ ] +# ── TELEMETRIE-PULS ─────────────────────────────────────────────────────────── +# ⚠⚠ ANLASS (2026-08-11, Review-Durchgang 4): `rec_outcomes` — die +# Selbst-Kalibrierung der Empfehlung — stand seit dem Einbau am 06.08. bei +# **0 Zeilen**. Ursache war ein falsches dict (`s.get("pb_feats")` auf dem +# MT5Data- statt dem Wave-Snapshot) → `_atr` immer 0 → die Schreibbedingung +# nie erfüllt. **Kein NameError** (ruff sieht nichts), und das umgebende +# `except` loggt auf DEBUG, der Logger steht auf INFO. Fünf Tage still tot. +# Aufgefallen NUR, weil die Tabelle mit 0 Zeilen neben ihren Schwestern stand. +# ➜ Diese Prüfung generalisiert den Fund: jede Telemetrie-Tabelle muss einen +# PULS haben. Schweigt eine, während die anderen schreiben, ist der Logger tot. +# ⚠ Die Toleranz ist grosszügig (Faktor 20 der erwarteten Kadenz), damit ein +# ruhiger Markt keinen Fehlalarm auslöst — ein Wächter, der oft warnt, wird +# ignoriert (Lehre vom 02.08.). +_PULS = [ + # (Tabelle, Zeitspalte, erwartete Kadenz in Minuten, wozu) + ("recommendations", "timestamp", 1, "Empfehlungs-Log"), + ("candles_m1", "time", 1, "M1-Kerzen-Sammlung"), + ("verdict_votes", "ts", 1, "Modul-Stimmen"), + ("m15_states", "ts", 60, "M15-Karte Selbstmessung"), + ("cone_checks", "ts", 60, "Kegel-Kalibrierung (ACI)"), + ("pbreak_predictions", "ts", 60, "P(break)-Prognose-Tracking"), + ("rec_outcomes", "ts", 180, "Selbst-Kalibrierung Empfehlung"), +] + + +def telemetrie_puls() -> list: + """Welche Telemetrie-Tabelle schreibt nicht mehr? → Liste von Klartext-Zeilen.""" + import sqlite3 + import time as _t + out = [] + try: + db = sqlite3.connect(DB) + except Exception: + return out + jetzt = _t.time() + for tab, sp, kadenz, wozu in _PULS: + try: + n = db.execute(f"SELECT COUNT(*) FROM {tab}").fetchone()[0] + letzt = db.execute(f"SELECT MAX({sp}) FROM {tab}").fetchone()[0] + except Exception: + continue # Tabelle gibt es (noch) nicht + if not n: + out.append(f"{tab}: 0 Zeilen — der Logger hat NIE geschrieben ({wozu})") + continue + # ⚠ candles_m1.time ist ROHE Brokerzeit (UTC+3) — sonst 3 h Fehlalarm. + alter_min = (jetzt - (letzt - 10800 if tab == "candles_m1" else letzt)) / 60.0 + if alter_min > kadenz * 20: + out.append(f"{tab}: letzte Zeile vor {alter_min/60:.1f} h " + f"(erwartet ~alle {kadenz} min) — {wozu}") + db.close() + return out + + def _cfg_now() -> dict: """Liest NUR die überwachten [trading]-Keys. ⚠ Die ini enthält Live-Secrets — hier wird bewusst nichts anderes gelesen oder ausgegeben. @@ -533,6 +586,14 @@ def main() -> None: print(f"\n ⚠ {d['key']}: ist '{cur}' — gemessen/validiert gegen '{val}'") print(f" betroffen: {d['test']}") print(f" {d['why']}") + print("\n" + "=" * 78) + print(" TELEMETRIE-PULS — schreibt jeder Logger noch?") + print("=" * 78) + puls = telemetrie_puls() + if not puls: + print(f"\n ✅ alle {len(_PULS)} Telemetrie-Tabellen schreiben") + for x in puls: + print(f"\n ⚠ {x}") return if not due and not drift_new: