Live-Backtest-Vergleich in die Pipeline: core/stichprobe.py + Abschnitt E

Konsequenz aus dem Messfehler vom 07.08.: der Vergleich lief nur, wenn jemand
daran dachte - und als er lief, war er falsch gebaut.

1) core/stichprobe.py - die Praevention ist KONSTRUKTIV, nicht ermahnend:
   entkoppeln() / anteil_ci() / unterscheidbar() / schluessel_level_stunde().
   Regel: vor jeder berichteten Rate entkoppeln, jede Aussage ueber einen
   Unterschied mit Konfidenzintervall belegen. Die Zeilen in
   pbreak_predictions und recommendations sind nicht unabhaengig.
   8 Tests, darunter einer, der die reale Konstellation nachspielt
   (roh 77 %, entkoppelt 33 %).
   Fallstrick beim Bau, gefunden weil ich es laufen liess: ein sqlite3-Cursor
   liefert Tupel, keine dicts - die Feldangabe akzeptiert jetzt Namen UND
   Spaltennummern.

2) analyze_divergence.sec_d0 war selbst der Fehler. Es mittelte AVG(p_break)
   ueber ALLE Rohzeilen und flaggte bei Delta > 8 Pp:
     vorher  1058 rohe Zeilen, Delta +8,7 Pp -> ALARM
     jetzt   110 entkoppelte, 37,6 % liegt in [37,3 ... 55,5] % -> OK
   Der Waechter haette heute also einen Fehlalarm produziert - genau den, dem
   ich elf Hypothesen lang nachgegangen bin.

3) NEU: Abschnitt E "GLEICHES FENSTER" (--backtest). Rechnet den Backtest ueber
   genau den Live-Zeitraum und haelt ihn gegen das Intervall der entkoppelten
   Live-Rate. Erster Lauf: live 46,4 % [37,3 ... 55,5] gegen Backtest 43,9 %
   -> im Intervall, kein Befund.

4) Eingebaut in den Wochenreport (weekly_review._divergenz_section, montags
   07:30 per Mail + Telegram), mit demselben Fail-safe wie der
   Squeeze-Abschnitt. Bewusst NICHT in den pre-commit-Hook: der braucht MT5 und
   Minuten, Stufe 1 muss bei ~5 s bleiben.

Ausserdem ein Rechenfehler in meinem ersten Entwurf behoben (p_break steht in
der DB bereits in Prozent, ich hatte zusaetzlich skaliert -> "3763,9 %") und
ein \n-Heredoc-Fall, der genau gegen die dokumentierte Regel verstiess.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-07 11:55:05 +02:00
co-authored by Claude Opus 5
parent 1f58049049
commit 3f1f19087d
5 changed files with 430 additions and 15 deletions
+55
View File
@@ -6,6 +6,7 @@ Aufruf: python weekly_review.py (letzte 6 Wochen + diese Woche im Detail
Zeitspalten der DB sind LOKALE Epoch (Broker-Offset bereits korrigiert).
"""
import sqlite3, datetime as dt
from pathlib import Path
DB = "oil_widget_history.db"
# ── Backtest-Erwartung (R-basiert, exit-sim; s. CLAUDE.md / backtest_*.py) ──
@@ -103,6 +104,58 @@ def _fetch_m5(days=30):
return None
def _divergenz_section() -> list:
"""LIVE gegen BACKTEST auf DEMSELBEN Fenster — Pipeline-Einbau 2026-08-07.
⚠⚠ WARUM DAS HIER STEHT. Track B prüft, ob ein Edge existiert; der
Wochenreport prüft, ob er live noch trägt. Was BEIDE nicht abdeckten: ob
live überhaupt DASSELBE gemessen wird wie im Backtest. `analyze_divergence.py`
gibt es dafür seit dem 31.07., aber es lief nur, wenn jemand daran dachte —
und am 07.08. hat genau diese Lücke zugeschlagen: eine geclusterte
6-Tage-Live-Stichprobe wurde gegen ein 208-Tage-Backtest-Mittel gehalten,
Ergebnis eine „Lücke" von 14,1 Pp, die es nicht gab (34 % Zeitraum,
53 % Clusterung, Rest Rauschen — elf Hypothesen umsonst geprüft).
Der Abschnitt beantwortet deshalb genau zwei Fragen, beide mit
Konfidenzintervall statt Punktabstand:
· sagt das Modell live das voraus, was eintritt? (entkoppelt!)
· liefert der Backtest auf DEMSELBEN Zeitfenster dasselbe?
⚠ Fail-safe wie beim Squeeze-Abschnitt: schlägt der Aufruf fehl (kein MT5,
keine DB), fehlt der Abschnitt — der Report darf daran nicht scheitern.
"""
import subprocess
import sys
out = ["", "=" * 92,
"DIVERGENZ — läuft der Bot unter den Bedingungen, unter denen gemessen wurde?",
"=" * 92]
try:
r = subprocess.run(
[sys.executable, "-X", "utf8", "analyze_divergence.py", "7", "--backtest"],
cwd=Path(__file__).resolve().parent, capture_output=True, text=True,
timeout=900)
txt = r.stdout or ""
# Nur die beiden entscheidenden Abschnitte übernehmen — der Rest ist im
# Wochenreport Rauschen und würde die Mail unlesbar machen.
nimm = False
for ln in txt.splitlines():
if ln.strip().startswith(("D0)", "E)")):
nimm = True
elif ln.strip().startswith(("D)", "A)", "B)", "C)")):
nimm = False
if nimm and ln.strip() and not ln.startswith("==="):
out.append(" " + ln.strip())
if len(out) <= 4:
out.append(" (keine auswertbaren Daten)")
except Exception as e:
out.append(f" Divergenz-Prüfung nicht möglich ({e})")
out.append("")
out.append(" ⚠ Gelesen wird gegen das KONFIDENZINTERVALL, nicht gegen den")
out.append(" Punktabstand: „liegt der Backtest-Wert im Intervall der")
out.append(" entkoppelten Live-Rate?" + '"' + " Nur ein NEIN ist ein Befund.")
return out
def build_report(db=DB, m5=None) -> str:
"""Baut den B4-Wochenreport als reinen Text (für Konsole, Mail-<pre>, Telegram).
m5 = vorab geholte M5-Bars für den Squeeze-Monitor (Server reicht sie unter
@@ -166,6 +219,8 @@ def build_report(db=DB, m5=None) -> str:
P("erst Ursache trennen (Regime vs. Disziplin vs. Sizing), nicht am Signal drehen.")
for ln in _squeeze_section(m5):
P(ln)
for ln in _divergenz_section():
P(ln)
return "\n".join(out)
def main():