From 908e20a9d068e4cba8069fc9098a9449a7c2421f Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Fri, 7 Aug 2026 07:43:24 +0200 Subject: [PATCH] P(break) v2 live geprueft: Kalibrierung daneben, Trennschaerfe unentschieden Faellige B4-Messung nach dem Nachtrainieren vom 31.07. (878 Vorhersagen ab 01.08.). Drei Befunde. 1) Das Skript mischte zwei Modelle. Es trennte bei der M30-Umstellung (30.07.), nicht beim Nachtrainieren (31.07.) - "GESAMT" las altes und neues Modell zusammen. Derselbe Fehler wie am 02.08. in analyze_divergence.py. Neu: MODELL_V2 mit demselben Stichtag wie die Faelligkeitsbedingung. 2) Kalibrierung messbar daneben. Die Basisrate ist gewandert: Training 37,8 %, live 53,2 %, stabil ueber vier volle Tage (51,4 / 54,1 / 56,4 / 53,8 % bei n=122-276). Ein Modell, dessen Niveau an 37,8 % verankert ist, muss in einem 53-%-Regime systematisch zu niedrig sagen - genau das passiert. Oberste Klasse invertiert (69 % vorhergesagt, 17 % real). 3) Trennschaerfe NICHT entscheidbar - und das ist der wichtigere Befund. Entkoppelt n=87, AUC 0,556, 95-%-Bootstrap-KI [0,434 ... 0,674]. Das Intervall enthaelt den Zufall (0,50) UND die Messlatte (0,654). Die Faelligkeitsbedingung zaehlte die falsche Groesse: 800 ROHE Vorhersagen, die aber nicht unabhaengig sind (dieselbe Position pendelt um dasselbe Level). Aus 878 rohen werden 87 entkoppelte - die Bedingung ueberschaetzte die Beweismenge um rund das Zehnfache und meldete "faellig", obwohl die Messung nichts entscheiden konnte. Jetzt 350 ENTKOPPELTE (halbiert die KI-Breite). Stand 87/350. Operativ kein Notfall: auto_sr_close ist seit 06.08. false, das Modell schliesst keine Trades. Es speist nur Anzeige, Chart-Linien und Copilot-Kontext. Nichts umgestellt - ein drittes Fitten derselben Bauart wuerde denselben Weg gehen; der Befund zeigt aufs Niveau, nicht zwingend auf die Rangfolge. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 63 +++++++++++++++++++++++++++++++++++++++++ analyze_pbreak_live.py | 39 +++++++++++++++++++++++-- measurement_reminder.py | 20 +++++++++++-- 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 11c9bc4..0d29978 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4785,6 +4785,69 @@ Signal-Eingriffe sind gescheitert, und die gemessene Stärke des Users liegt in AUSWAHL, nicht in einer Regel. Ein Modul, das ihm seine eigene Historie zur aktuellen Lage zeigt, spielt auf diese Stärke; eines das für ihn einsteigt, ersetzt sie. +## ⚠⚠ P(break) v2 LIVE GEPRÜFT (2026-08-07) — KALIBRIERUNG DANEBEN, TRENNSCHÄRFE UNENTSCHIEDEN + +Fällige B4-Messung nach dem Nachtrainieren vom 31.07. (`analyze_pbreak_live.py`, +878 ausgewertete Vorhersagen ab 01.08.). Zwei Befunde, die auseinandergehalten +gehören — und einer davon betrifft die Messung selbst. + +⚠⚠ **ZUERST: DAS SKRIPT MISCHTE ZWEI MODELLE.** Es trennte bei der +M30-Level-Umstellung (30.07.), **nicht** beim Nachtrainieren (31.07.); „GESAMT" +las damit altes und neues Modell zusammen. Genau der Fehler, der am 02.08. in +`analyze_divergence.py` schon behoben wurde (`_EPOCHS`) — hier stand er noch. +Neu: `MODELL_V2` mit demselben Stichtag wie die Fälligkeitsbedingung (01.08.), +plus eigener Abschnitt „NACHTRAINIERTES MODELL". Die alten Abschnitte bleiben. + +**(1) KALIBRIERUNG — messbar daneben, und die Ursache ist benennbar.** +| P(break) | n | vorhergesagt | real | +|---|---|---|---| +| 0–19 | 56 | 15,1 % | **53,6 %** | +| 20–34 | 199 | 29,8 % | 43,2 % | +| 35–49 | 414 | 42,2 % | 56,8 % | +| 50–64 | 180 | 54,1 % | 61,7 % | +| **65–100** | 29 | **69,0 %** | **17,2 %** ⚠ invertiert | +⚠ **Die Basisrate ist gewandert** — Training **37,8 %**, live **53,2 %**, und das +**stabil über vier volle Tage** (03.08. 51,4 % · 04.08. 54,1 % · 05.08. 56,4 % · +06.08. 53,8 %, je n=122–276). Das ist kein Rauschen. Ein Modell, dessen Niveau an +einer 37,8-%-Basisrate verankert ist, muss in einem 53-%-Regime systematisch zu +niedrig sagen — genau das steht in der Tabelle. Dieselbe Signatur wie beim alten +Modell (alle unteren Klassen „zu niedrig", oberste invertiert). + +**(2) TRENNSCHÄRFE — NICHT entscheidbar, und das ist der wichtigere Befund.** +| | | +|---|---| +| AUC roh (n=878) | 0,541 | +| **AUC entkoppelt (n=87)** | **0,556** | +| **95-%-Bootstrap-KI** | **[0,434 … 0,674]** | +Das Intervall enthält **den Zufall (0,50) UND die Messlatte (0,654)**. Es ist +also **nicht** belegt, dass das Modell schlechter rankt als beim Nachtrainieren — +es ist nur nichts belegt. „AUC 0,54" als Durchfall zu lesen wäre falsch. + +⚠⚠ **DIE FÄLLIGKEITSBEDINGUNG ZÄHLTE DIE FALSCHE GRÖSSE — behoben.** Sie stand +auf **800 ROHEN** Vorhersagen; die sind nicht unabhängig (dieselbe Position +pendelt vielfach um dasselbe Level). Aus 878 rohen werden **87 entkoppelte** — +die Bedingung hat die Beweismenge um **rund das Zehnfache** überschätzt und die +Messung als „fällig" gemeldet, obwohl sie nichts entscheiden konnte. Die +Abhängigkeit war im Skript seit jeher dokumentiert („entkoppelte Sicht"), nur die +Fälligkeit zählte daran vorbei. **Jetzt: 350 ENTKOPPELTE** (halbiert die +KI-Breite 0,12 → 0,06, erst dann sind 0,50 und 0,65 trennbar; bei ~17/Tag ≈ 3 +Wochen). Stand: **87/350**. +⚠ Verwandt mit der Bootstrap-Grenze bei `backtest_cost_gate.ci95` +(„überlappende Beobachtungen → effektives n ist ~n/24") — dieselbe Klasse Fehler, +diesmal in einer Fälligkeitsbedingung statt in einem Konfidenzintervall. + +✅ **Operativ kein Notfall:** `auto_sr_close = false` seit 06.08. — das Modell +schliesst **keine** Trades. Es speist nur den Hinweis in der App, die +Chart-Linien, den Copilot-Kontext und `stop_approach`. Die Fehlkalibrierung +kostet damit aktuell **kein Geld**, sie macht nur die Anzeige zu pessimistisch +(sie sagt „Level hält", wo es in 53 % der Fälle bricht). +⚠ **Vor einem erneuten Nachtrainieren:** der letzte Versuch hat live auch nicht +gehalten. Der Befund zeigt auf das **Niveau** (Basisrate/Achsenabschnitt), nicht +zwingend auf die Rangfolge — ein drittes Fitten derselben Bauart würde denselben +Weg gehen. Naheliegender wäre, das Niveau **laufend** an die jüngste Basisrate zu +koppeln oder ein Vola-/Regime-Merkmal aufzunehmen. **Nicht gebaut, nicht +gemessen.** + ## ⚠⚠ LIVE-CODE HING AN EINEM BACKTEST-SKRIPT — behoben 2026-08-07 **Befund:** `core/engine.py` (`squeeze_monitor`) und `weekly_review.py` importierten diff --git a/analyze_pbreak_live.py b/analyze_pbreak_live.py index 966a47e..9b6c9db 100644 --- a/analyze_pbreak_live.py +++ b/analyze_pbreak_live.py @@ -28,6 +28,17 @@ from datetime import datetime DB = "oil_widget_history.db" M30_SWITCH = datetime(2026, 7, 30).timestamp() +# ⚠⚠ MODELL-EPOCHE (ergänzt 2026-08-07). Am 31.07. wurde das P(break)-Modell +# NACHTRAINIERT (`backtest_pbreak_retrain.py`) und die Schwelle von 0,55 auf 0,35 +# gesenkt. Die Tabelle `pbreak_predictions` enthält beides — Vorhersagen des ALTEN +# und des NEUEN Modells. Wer sie zusammen auswertet, misst eine Mischung und liest +# die Fehlkalibrierung des alten Modells als aktuellen Alarm. +# Derselbe Fehler wurde am 02.08. in `analyze_divergence.py` behoben (`_EPOCHS`); +# hier stand er noch. Stichtag identisch zur Fälligkeitsbedingung in +# `measurement_reminder.py` ("pbreak_accuracy_v2", ts >= 2026-08-01), damit +# Erinnerung und Auswertung dieselbe Grundmenge zählen. +MODELL_V2 = datetime(2026, 8, 1).timestamp() + def auc(ps, ys): """Rangbasiert, mit Bindungskorrektur.""" @@ -106,8 +117,32 @@ def main(): print(f" {len(rows)} ausgewertete Vorhersagen, {t0:%d.%m.%Y} bis {t1:%d.%m.%Y}") print("=" * 84) - report(rows, "GESAMT") - calib(rows, "gesamt") + # ⚠ GESAMT mischt ALTES und NEUES Modell (s. MODELL_V2 oben) — bleibt als + # Historie stehen, ist aber NICHT der B4-Maßstab. + report(rows, "GESAMT ⚠ mischt altes + nachtrainiertes Modell") + calib(rows, "gesamt (gemischt)") + + # ── DAS ist die fällige B4-Messung: nur das nachtrainierte Modell ────── + v2 = [(float(p), 1 if o == "break" else 0, pr) + for ts, _d, _l, p, pr, o in raw if ts >= MODELL_V2] + print("\n" + "=" * 84) + print(" NACHTRAINIERTES MODELL (ab 01.08.) — die fällige B4-Prüfung") + print(" Messlatte: AUC 0,654 (out-of-sample beim Nachtrainieren erreicht)") + print("=" * 84) + report(v2, "MODELL v2") + if len(v2) >= 30: + calib(v2, "Modell v2") + # entkoppelt auch hier — der Cluster-Effekt gilt unabhängig vom Modell + seen2 = set(); dd2 = [] + for ts, _d, lvl, p, pr, o in raw: + if ts < MODELL_V2: + continue + key = (round(float(lvl or 0), 2), ts // 3600) + if key in seen2: + continue + seen2.add(key) + dd2.append((float(p), 1 if o == "break" else 0, pr)) + report(dd2, "MODELL v2 · ENTKOPPELT (max. 1 je Level und Stunde)") # ── entkoppelt: max. 1 Vorhersage je (Level, Stunde) seen = set(); dedup = [] diff --git a/measurement_reminder.py b/measurement_reminder.py index 0147e8a..5ad36aa 100644 --- a/measurement_reminder.py +++ b/measurement_reminder.py @@ -62,9 +62,23 @@ CHECKS = [ "backtest_srclose_prob.py aufnehmen und neu fitten (live in beiden " "Hälften +6/+4 Pp für die 2.–4. Berührung, 5.+ fällt zurück). " "Danach mit frischen Live-Daten gegenprüfen.", - "have": lambda: _q("SELECT COUNT(*) FROM pbreak_predictions " - "WHERE outcome IS NOT NULL AND ts >= strftime('%s','2026-08-01')"), - "need": 800, "unit": "Vorhersagen seit 01.08.", + # ⚠⚠ EINHEIT KORRIGIERT 2026-08-07 — die alte Bedingung zählte die FALSCHE + # Größe. Sie stand auf 800 ROHEN Vorhersagen; die sind aber NICHT + # unabhängig (dieselbe Position pendelt vielfach um dasselbe Level). Am + # 07.08. war sie mit 878 „fällig" — entkoppelt (max. 1 je Level und + # Stunde) waren es **87**, und das 95-%-Bootstrap-Intervall der AUC ging + # von 0,434 bis 0,674: es enthält sowohl den Zufall (0,50) als auch die + # Messlatte (0,654). Die Messung konnte also gar nichts entscheiden. + # Die Abhängigkeit war im Skript seit jeher dokumentiert („entkoppelte + # Sicht"), nur die Fälligkeit zählte daran vorbei — sie überschätzte die + # Beweismenge um rund das Zehnfache. + # 350 entkoppelte Zeilen ≈ halbe KI-Breite (0,12 → 0,06), erst dann sind + # 0,50 und 0,65 überhaupt trennbar. Bei ~17/Tag sind das ~3 Wochen. + "have": lambda: _q( + "SELECT COUNT(*) FROM (SELECT DISTINCT ROUND(level,2), ts/3600 " + "FROM pbreak_predictions WHERE outcome IS NOT NULL " + "AND ts >= strftime('%s','2026-08-01'))"), + "need": 350, "unit": "ENTKOPPELTE Vorhersagen seit 01.08. (max. 1 je Level und Stunde)", "cmd": "python analyze_pbreak_live.py", }, {