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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a866c9cec7
commit
dca3c11bbd
@@ -6161,6 +6161,80 @@ Neumessung. Sie bleiben unverändert und reproduzierbar; wo ihre Schlussfolgerun
|
|||||||
relevant wird, gehört sie live-treu **neu gerechnet** (Muster:
|
relevant wird, gehört sie live-treu **neu gerechnet** (Muster:
|
||||||
`backtest_auto_signal_norev.py`).
|
`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`)
|
## Gesamtprüfung 2026-08-02 (nach `docs/review-prompt.md`)
|
||||||
|
|
||||||
Erster vollständiger Durchlauf des Review-Prompts. **Keine Strategie-Änderung** — alle
|
Erster vollständiger Durchlauf des Review-Prompts. **Keine Strategie-Änderung** — alle
|
||||||
|
|||||||
+22
-2
@@ -41,7 +41,18 @@ _BROKER_OFF = 10800 # Brokerzeit = UTC+3 (candles/copy_rates sind Broker-Wallc
|
|||||||
LOG = "oil_widget.log"
|
LOG = "oil_widget.log"
|
||||||
|
|
||||||
# Erwartungswerte aus den Backtests — die Messlatte
|
# 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)
|
_EXP_WAIT_TOL = 15.0 # ab dieser Abweichung wird geflaggt (Pp)
|
||||||
|
|
||||||
# ── EPOCHEN ───────────────────────────────────────────────────────────────────
|
# ── 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.")
|
print(" noch keine Daten — das Logging läuft erst seit dem Neustart am 31.07.")
|
||||||
return
|
return
|
||||||
# Welche Gates kennt der Backtest? (nur diese dürfen den Vergleich prägen)
|
# 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,
|
im_backtest = {"deadband": True, "htf_counter": True, "stretch": True,
|
||||||
"min_conf": True, "breakout_pending": 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}
|
"no_data": False, "stale": False, None: None}
|
||||||
print(f" {'Gate':<20}{'n':>8}{'Anteil':>9} im Backtest modelliert?")
|
print(f" {'Gate':<20}{'n':>8}{'Anteil':>9} im Backtest modelliert?")
|
||||||
for br, n in rows:
|
for br, n in rows:
|
||||||
|
|||||||
+19
-2
@@ -1743,8 +1743,25 @@ class TradingEngine:
|
|||||||
try:
|
try:
|
||||||
_sig = rec.get("signal")
|
_sig = rec.get("signal")
|
||||||
if _sig in ("LONG", "SHORT") and _sig != getattr(self, "_rec_epi_sig", None):
|
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)
|
# ⚠⚠ BUG BEHOBEN 2026-08-11 (Review-Durchgang 4): hier stand
|
||||||
_px = float((self.data.snapshot() or {}).get("bid") or 0.0)
|
# `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:
|
if _atr > 0 and _px > 0 and self.history:
|
||||||
self.history.log_rec_outcome(
|
self.history.log_rec_outcome(
|
||||||
signal=_sig, conf_pct=int(rec.get("conf_pct") or 0),
|
signal=_sig, conf_pct=int(rec.get("conf_pct") or 0),
|
||||||
|
|||||||
@@ -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:
|
def _cfg_now() -> dict:
|
||||||
"""Liest NUR die überwachten [trading]-Keys. ⚠ Die ini enthält Live-Secrets —
|
"""Liest NUR die überwachten [trading]-Keys. ⚠ Die ini enthält Live-Secrets —
|
||||||
hier wird bewusst nichts anderes gelesen oder ausgegeben.
|
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"\n ⚠ {d['key']}: ist '{cur}' — gemessen/validiert gegen '{val}'")
|
||||||
print(f" betroffen: {d['test']}")
|
print(f" betroffen: {d['test']}")
|
||||||
print(f" {d['why']}")
|
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
|
return
|
||||||
|
|
||||||
if not due and not drift_new:
|
if not due and not drift_new:
|
||||||
|
|||||||
Reference in New Issue
Block a user