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 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-07-30 19:09:01 +02:00
co-authored by Claude Opus 4.8
parent 3a7bd044c0
commit 770f994fd1
4 changed files with 237 additions and 5 deletions
+194
View File
@@ -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 1338 %, 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()