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:
co-authored by
Claude Opus 4.8
parent
3a7bd044c0
commit
770f994fd1
@@ -42,3 +42,6 @@ desktop.ini
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# Reminder-Marker (gemeldete Messungen)
|
||||||
|
.reminders/
|
||||||
|
|||||||
@@ -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
|
v=108. `GET /api/snapshot` ist ohnehin öffentlich/token-frei nutzbar — externe
|
||||||
Tools (Shortcuts, Home Assistant, …) können ihn direkt pollen.
|
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
|
## Auch beachten
|
||||||
- **Startup-Schonfrist gegen Sofort-Close nach Neustart (User-Vorgabe 2026-07-20,
|
- **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
|
`[trading] startup_close_grace_s=60`):** In den ersten 60 s nach Bot-Start schließt
|
||||||
|
|||||||
@@ -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()
|
||||||
+15
-5
@@ -12,7 +12,12 @@
|
|||||||
//| 4) Der Bot muss laufen (schreibt die CSV alle ~5 s). |
|
//| 4) Der Bot muss laufen (schreibt die CSV alle ~5 s). |
|
||||||
//+------------------------------------------------------------------+
|
//+------------------------------------------------------------------+
|
||||||
#property copyright "Oil Trading Bot"
|
#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
|
// 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).
|
// (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
|
// Quelle = dasselbe kalibrierte P(break)-Modell wie der S/R-Auto-Close, auf
|
||||||
@@ -245,7 +250,13 @@ datetime CenterTime()
|
|||||||
//+------------------------------------------------------------------+
|
//+------------------------------------------------------------------+
|
||||||
datetime RightTime()
|
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 |
|
//| 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
|
// 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
|
// 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.
|
// 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);
|
int na = ObjectsTotal(0, 0, OBJ_ARROW);
|
||||||
for(int i = 0; i < na; i++)
|
for(int i = 0; i < na; i++)
|
||||||
{
|
{
|
||||||
@@ -476,8 +487,7 @@ void Redraw()
|
|||||||
string an = PFX + "ALQ_" + side;
|
string an = PFX + "ALQ_" + side;
|
||||||
// Rechter Rand = aktuelle (rechteste) Kerze + kleiner Versatz, damit der
|
// Rechter Rand = aktuelle (rechteste) Kerze + kleiner Versatz, damit der
|
||||||
// Pfeil frei neben dem Kursverlauf steht und beim Scrollen rechts bleibt.
|
// Pfeil frei neben dem Kursverlauf steht und beim Scrollen rechts bleibt.
|
||||||
datetime atime = iTime(_Symbol, PERIOD_CURRENT, 0)
|
datetime atime = RightTime();
|
||||||
+ (datetime)(PeriodSeconds() * 2);
|
|
||||||
if(ObjectCreate(0, an, OBJ_ARROW, 0, atime, lpx))
|
if(ObjectCreate(0, an, OBJ_ARROW, 0, atime, lpx))
|
||||||
{
|
{
|
||||||
ObjectSetInteger(0, an, OBJPROP_ARROWCODE, (dirc == "U" ? 233 : 234));
|
ObjectSetInteger(0, an, OBJPROP_ARROWCODE, (dirc == "U" ? 233 : 234));
|
||||||
|
|||||||
Reference in New Issue
Block a user