From 770f994fd1731d25e0d0f9f4660b6f030ccbdaba Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Thu, 30 Jul 2026 19:09:01 +0200 Subject: [PATCH] Timer-Erinnerung fuer alle ausstehenden Messungen + MQL5 v1.26 1) measurement_reminder.py + Task OilMeasurementReminder (taeglich 18:00): meldet per Popup, welche Messung genug Daten hat. Jeder Punkt mit PRUEFBARER Bedingung (Datenmenge/Datum), Meldung je Punkt 1x (Marker in .reminders/). Status: python measurement_reminder.py --status Direkt zwei FAELLIGE Punkte gefunden: - P(break)-Prognose-Genauigkeit: 2213 ausgewertete Vorhersagen (Schwelle 500), lag seit 2026-07-23 unbeachtet herum - Chartmuster-Kontrolltest (generischer Breakout): jederzeit machbar Noch sammelnd: verdict_votes 24%, AUTOSIG-Trades 0, M30-Level-Validierung 27%, M1-History 7%, News-Score 8%. 2) MQL5 v1.26: Beschriftungen UND Pfeile stehen jetzt ganz rechts am Chartrand, auf einer Linie mit dem Kurs-Label (User: "genau so angeordnet wie der aktuelle Kurs"). RightTime() rechnet das Ende des Shift-Bereichs aus (CHART_WIDTH_IN_BARS x InpShiftPct) statt fix 2 Kerzen hinter der letzten Bar. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 3 + CLAUDE.md | 25 ++++++ measurement_reminder.py | 194 ++++++++++++++++++++++++++++++++++++++++ mql5/SR_Levels.mq5 | 20 +++-- 4 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 measurement_reminder.py diff --git a/.gitignore b/.gitignore index 8657799..1937dcb 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ desktop.ini .DS_Store .vscode/ .idea/ + +# Reminder-Marker (gemeldete Messungen) +.reminders/ diff --git a/CLAUDE.md b/CLAUDE.md index c338006..7c993cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1599,6 +1599,31 @@ Aktivierung genullt (kein Altwert-Fehlalarm); Erkennung erst ab dem 2. Modify. T v=108. `GET /api/snapshot` ist ohnehin öffentlich/token-frei nutzbar — externe Tools (Shortcuts, Home Assistant, …) können ihn direkt pollen. +## Ausstehende Messungen — Erinnerung per Timer + +`measurement_reminder.py` + Windows-Task **`OilMeasurementReminder`** (täglich 18:00) +meldet per Popup, welche Messung **genug Daten** hat. Jeder Punkt hat eine PRÜFBARE +Fälligkeitsbedingung (Datenmenge/Datum), nicht nur ein Datum — gemeldet wird je Punkt +1× (Marker in `.reminders/`, gitignoriert; Marker löschen = Wiedervorlage). +Status jederzeit: `python measurement_reminder.py --status`. + +⚠ **Warum es das gibt:** mehrere Module sammeln still Daten für „später" — und das +blieb liegen. Die P(break)-Prognose-Genauigkeit war seit 2026-07-23 auswertbar und +hatte am 2026-07-30 bereits **2213 ausgewertete Zeilen**, ohne dass jemand hinsah. + +| Messung | Bedingung | Stand 2026-07-30 | +|---|---|---| +| P(break)-Prognose-Genauigkeit | ≥500 ausgewertete Vorhersagen | **2213 → FÄLLIG** | +| Chartmuster-Kontrolltest (generischer Breakout) | jederzeit | **FÄLLIG** | +| Verdict-Module auf Prädiktivität (`verdict_votes`) | ≥25 000 Verdicts | 5961 (24 %) | +| Auto-Signal-Entry B4/B5 | ≥20 AUTOSIG-Trades | 0 | +| M30-Level-Umstellung live gegenprüfen | ≥30 S/R-Closes seit 30.07. | 8 (27 %) | +| M1-Squeeze Variante B | ≥180 Tage `candles_m1` | 13 (7 %) | +| News-Score-Prädiktivität | ≥90 Tage Sammlung | 7 (8 %) | + +Verwandte Timer: **`HyperliquidBookflow`** (Order-Flow, 2 Stufen ~3 Tage/~2 Wochen), +**`HyperliquidLeadLag`** (erledigt: Pepperstone führt), Wochenreport montags 07:30 (B4). + ## Auch beachten - **Startup-Schonfrist gegen Sofort-Close nach Neustart (User-Vorgabe 2026-07-20, `[trading] startup_close_grace_s=60`):** In den ersten 60 s nach Bot-Start schließt diff --git a/measurement_reminder.py b/measurement_reminder.py new file mode 100644 index 0000000..59db835 --- /dev/null +++ b/measurement_reminder.py @@ -0,0 +1,194 @@ +#!/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() diff --git a/mql5/SR_Levels.mq5 b/mql5/SR_Levels.mq5 index 1756008..69f1f69 100644 --- a/mql5/SR_Levels.mq5 +++ b/mql5/SR_Levels.mq5 @@ -12,7 +12,12 @@ //| 4) Der Bot muss laufen (schreibt die CSV alle ~5 s). | //+------------------------------------------------------------------+ #property copyright "Oil Trading Bot" -#property version "1.25" +#property version "1.26" +// v1.26 (2026-07-30): Beschriftungen + Pfeile stehen jetzt GANZ rechts am +// Chartrand — auf Hoehe des Kurs-Labels (User: „genau so angeordnet wie der +// aktuelle Kurs"). RightTime() rechnet dafuer das Ende des Shift-Bereichs +// aus (CHART_WIDTH_IN_BARS x InpShiftPct) statt fix 2 Kerzen hinter die +// letzte Bar. Gilt beim Erzeugen UND beim Nachfuehren. // v1.25 (2026-07-30): S/R-Labels zeigen die DURCHBRUCHWAHRSCHEINLICHKEIT // (User-Wunsch) — der Bot haengt sie als drittes CSV-Feld an (R;preis;pct). // Quelle = dasselbe kalibrierte P(break)-Modell wie der S/R-Auto-Close, auf @@ -245,7 +250,13 @@ datetime CenterTime() //+------------------------------------------------------------------+ datetime RightTime() { - return iTime(_Symbol, PERIOD_CURRENT, 0) + (datetime)(PeriodSeconds() * 2); + // Ganz an den RECHTEN Chartrand — also ans Ende des Shift-Bereichs, nicht nur + // 2 Kerzen hinter die letzte (User 2026-07-30: „genau so angeordnet wie der + // aktuelle Kurs"). Der Shift-Bereich ist InpShiftPct % der sichtbaren Breite; + // ein Balken Reserve, damit der Text nicht am Rand abgeschnitten wird. + long width = ChartGetInteger(0, CHART_WIDTH_IN_BARS); + int bars = (int)MathMax(2, (double)width * InpShiftPct / 100.0 - 1.0); + return iTime(_Symbol, PERIOD_CURRENT, 0) + (datetime)(PeriodSeconds() * bars); } //+------------------------------------------------------------------+ //| Hält die Beschriftungen beim Scrollen/Zoomen am rechten Rand | @@ -268,7 +279,7 @@ void RepositionLabels() // Liquiditäts-PFEILE rechtsbündig halten: die CSV ändert sich nicht bei jedem // Tick, ohne Nachführung würden die Pfeile mit jeder neuen Kerze nach links // wandern. Preis bleibt, nur die Zeit wird an den rechten Rand gezogen. - datetime rt = iTime(_Symbol, PERIOD_CURRENT, 0) + (datetime)(PeriodSeconds() * 2); + datetime rt = RightTime(); int na = ObjectsTotal(0, 0, OBJ_ARROW); for(int i = 0; i < na; i++) { @@ -476,8 +487,7 @@ void Redraw() string an = PFX + "ALQ_" + side; // Rechter Rand = aktuelle (rechteste) Kerze + kleiner Versatz, damit der // Pfeil frei neben dem Kursverlauf steht und beim Scrollen rechts bleibt. - datetime atime = iTime(_Symbol, PERIOD_CURRENT, 0) - + (datetime)(PeriodSeconds() * 2); + datetime atime = RightTime(); if(ObjectCreate(0, an, OBJ_ARROW, 0, atime, lpx)) { ObjectSetInteger(0, an, OBJPROP_ARROWCODE, (dirc == "U" ? 233 : 234));