#!/usr/bin/env python3 """measurement_reminder.py — erinnert per Windows-Popup an FÄLLIGE Messungen. Im Projekt sammeln mehrere Module still Daten für spätere Auswertungen (`pbreak_predictions`, `verdict_votes`, `candles_m1`, AUTOSIG-/SQUEEZE-Trades …). Ohne Erinnerung bleiben die liegen — genau das ist passiert: die P(break)-Prognose- Genauigkeit war seit 2026-07-23 „später auswertbar" und hatte am 2026-07-30 bereits 2213 ausgewertete Zeilen. Jeder Eintrag hat eine PRÜFBARE Fälligkeitsbedingung (Datenmenge oder Datum), nicht nur ein Datum — so wird gemeldet, wenn die Messung wirklich belastbar ist. Gemeldet wird je Messung genau einmal (Marker-Datei); ist sie erledigt, trägt man sie hier aus oder löscht den Marker für eine Wiedervorlage. Nutzung: python measurement_reminder.py (echt, mit Popup) python measurement_reminder.py --dry-run (Statusliste, kein Popup/Marker) python measurement_reminder.py --status (alle Punkte + Fortschritt) """ from __future__ import annotations import os import sqlite3 import sys from datetime import datetime, timezone HERE = os.path.dirname(os.path.abspath(__file__)) DB = os.path.join(HERE, "oil_widget_history.db") MARKER_DIR = os.path.join(HERE, ".reminders") DRY = "--dry-run" in sys.argv STATUS = "--status" in sys.argv def _q(sql: str, default=0): try: with sqlite3.connect(DB, timeout=5) as c: r = c.execute(sql).fetchone() return r[0] if r and r[0] is not None else default except Exception: return default def _days_since(iso: str) -> float: try: return (datetime.now() - datetime.fromisoformat(iso)).total_seconds() / 86400 except Exception: return 0.0 # ── Die offenen Messungen ──────────────────────────────────────────────────── # have = aktueller Stand · need = Schwelle · unit = Anzeigeeinheit # cmd = was zu tun ist, wenn fällig CHECKS = [ { "key": "pbreak_accuracy", "title": "P(break)-Prognose-Genauigkeit auswerten", "why": "Seit 2026-07-23 wird jede Level-Vorhersage mitgeloggt und gegen den " "echten Ausgang geprüft. Zeigt, ob das Modell live so trifft wie im " "Backtest (AUC 0,72) — und ob die M30-Umstellung es verbessert hat.", "have": lambda: _q("SELECT COUNT(*) FROM pbreak_predictions WHERE outcome IS NOT NULL"), "need": 500, "unit": "ausgewertete Vorhersagen", "cmd": "GET /api/pbreak_accuracy?period=all · history.pbreak_accuracy()", }, { "key": "verdict_votes", "title": "Verdict-Module auf Prädiktivität prüfen", "why": "`verdict_votes` loggt seit 2026-07-24 jede Modul-Stimme + Forward-" "Verlauf. Damit lässt sich datenbasiert entscheiden, welches Modul " "im Konsens bleibt, welches Gewicht bekommt — und welches raus muss " "(wie TU 2026-07-06). Auch die Doppelzählung M30/H1 wird hier messbar.", "have": lambda: _q("SELECT COUNT(*) FROM verdict_votes"), "need": 25000, "unit": "geloggte Verdicts (~3 Wochen)", "cmd": "analog analyze_verdict_calibration.py, aber auf verdict_votes", }, { "key": "autosig_b4", "title": "Auto-Signal-Entry: B4-Monitor (trägt er live?)", "why": "Gemessen NICHT tragfähig (H1 −0,05…−0,18, H2 positiv = Regime-Kippen), " "auf ausdrücklichen Wunsch trotzdem gebaut. Erwartung: im aktuellen " "Regime ~+0,14 R/Trade, im ungünstigen −0,05. Driftet es negativ weg → " "`auto_signal=false` (B5-Rückbau).", "have": lambda: _q("SELECT COUNT(*) FROM trades WHERE setup LIKE 'AUTOSIG%' " "AND pnl IS NOT NULL"), "need": 20, "unit": "AUTOSIG-Trades", "cmd": "Statistik-Tab · setup LIKE 'AUTOSIG%' gegen ØR +0,14 halten", }, { "key": "m30_levels", "title": "M30-Level-Umstellung live gegenprüfen", "why": "Seit 2026-07-30 kommen S/R-Level + P(break) aus M30 statt M5 " "(Backtest: R-Ertrag ~2×, AUC 0,721). Live gegenprüfen: bringt der " "S/R-Auto-Close jetzt mehr pro Trade als vorher?", "have": lambda: _q("SELECT COUNT(*) FROM trades WHERE closed_by='sr_close' " "AND pnl IS NOT NULL AND exit_time >= " "strftime('%s','2026-07-30')"), "need": 30, "unit": "S/R-Closes seit der Umstellung", "cmd": "sr_close-Trades vor/nach 2026-07-30 vergleichen (ØEUR, Trefferquote)", }, { "key": "patterns_control", "title": "Chartmuster: Kontrolltest gegen generischen Breakout", "why": "backtest_patterns.py zeigte positiven ØR — aber die Ziel-Trefferquote " "ist nur 13–38 %, der Ertrag kommt vom Trailing-Exit. Es fehlt die " "Kontrolle: gleicher Exit auf einem BELIEBIGEN Breakout. Erst danach " "ist 'Muster wirkt' belegt oder widerlegt.", "have": lambda: 1, "need": 1, "unit": "jederzeit machbar (nur Rechenzeit)", "cmd": "backtest_patterns.py um eine Kontrollgruppe erweitern", }, { "key": "m1_squeeze", "title": "M1-Squeeze (Variante B) backtesten", "why": "Der Squeeze läuft auf M5. Ob M1 früher/besser erkennt, ist offen — " "braucht genug eigene M1-History (candles_m1, Broker-History rollt weg).", "have": lambda: (lambda mn, mx: (mx - mn) / 86400 if mn else 0)( _q("SELECT MIN(time) FROM candles_m1"), _q("SELECT MAX(time) FROM candles_m1")), "need": 180, "unit": "Tage M1-History", "cmd": "backtest_breakout_squeeze.py auf M1-Basis aus candles_m1", }, { "key": "news_score", "title": "News-Score auf Prädiktivität prüfen", "why": "`recommendations.news_score` wird seit 2026-07-24 wieder befüllt. " "Die Kalibrier-Messung hatte damals zu wenige Fälle (n=4/6). Mit " "genug Zeilen ist messbar, ob der News-Flow überhaupt etwas vorhersagt " "— aktuell ist er nur Kontext-Chip.", "have": lambda: _days_since("2026-07-24"), "need": 90, "unit": "Tage Sammlung", "cmd": "Forward-Return nach news_score-Bucket, 2 Hälften", }, ] def _popup(title: str, text: str) -> None: if DRY or STATUS: print(f"[POPUP] {title}\n{text}") return try: import ctypes ctypes.windll.user32.MessageBoxW(0, text, title, 0x40 | 0x1000) except Exception as e: print(f"Popup fehlgeschlagen: {e}\n{text}") def main() -> None: os.makedirs(MARKER_DIR, exist_ok=True) due, pending = [], [] for c in CHECKS: try: have = float(c["have"]()) except Exception: have = 0.0 ready = have >= c["need"] marker = os.path.join(MARKER_DIR, c["key"]) if ready and (STATUS or DRY or not os.path.exists(marker)): due.append((c, have)) elif ready: pending.append((c, have, "gemeldet")) else: pending.append((c, have, "sammelt")) if STATUS or DRY: print("=" * 78) print(" AUSSTEHENDE MESSUNGEN — Status") print("=" * 78) for c, have in due: print(f"\n ✅ FÄLLIG: {c['title']}") print(f" {have:.0f}/{c['need']} {c['unit']}") print(f" → {c['cmd']}") for c, have, st in pending: pct = min(100, have / c["need"] * 100) if c["need"] else 0 print(f"\n ⏳ {c['title']} [{st}]") print(f" {have:.0f}/{c['need']} {c['unit']} ({pct:.0f} %)") if not due: print("\n (nichts fällig)") return if not due: return lines = [] for c, have in due: lines.append(f"• {c['title']}\n {have:.0f}/{c['need']} {c['unit']}\n" f" {c['why']}\n -> {c['cmd']}") text = ("Diese Messungen haben jetzt genug Daten:\n\n" + "\n\n".join(lines) + "\n\nStatus aller Punkte:\n" " python measurement_reminder.py --status") _popup(f"Oil-Bot: {len(due)} Messung(en) faellig", text) for c, _ in due: try: open(os.path.join(MARKER_DIR, c["key"]), "w").close() except Exception: pass if __name__ == "__main__": main()