Files
AH-Oil-Trader/measurement_reminder.py
T
Axel HocksandClaude Opus 5 c5190e77fd M15-Auto-Entry: Richtung aus dem Veto + Abbruchregel verankert
(1) Der Auto-Entry nimmt die Richtung jetzt aus bias.verboten (= die ERLAUBTE
Richtung), also genau das, was die Karte anzeigt (app.js:504 nimmt erl). Kein
Veto -> kein Entry, weil die Karte dort ausdruecklich keine Richtung gibt.
Gegenprobe an der Live-Lage: verboten=SHORT, lesart=ab -> alt SHORT (die
verbotene!), neu LONG = identisch zur Anzeige. Damit entspricht der Pfad der
gemessenen Kontrolle C (-0,085/-0,030), der am wenigsten schlechten Variante --
aber weiterhin negativ. Die Korrektur behebt den Widerspruch, nicht den
fehlenden Edge.

(2) measurement_reminder.py: neue Messung m15_abbruch (>=20 AUTOM15_*-Trades,
Latte Verhaeltnis < 1,0 ODER PF < 1) und Config-Anker auto_m15 (validiert
false), dazu der Helfer _zaehle_setup(). Der Zaehler zaehlt nur geschlossene
Trades. Damit kann die vorab fixierte Regel nicht mehr unbemerkt verfallen --
genau das war bei B5 wochenlang der Fall.

Aktivierung steht aus: Backend-Aenderung, braucht einen Neustart. auto_m15 ist
aus, also keine Eile -- der Neustart wartet, bis das Konto flat ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 18:10:07 +02:00

791 lines
44 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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
def _zaehle_setup(muster: str) -> int:
"""Geschlossene Trades mit `setup LIKE muster` (fuer die Abbruchregeln)."""
try:
import sqlite3
with sqlite3.connect(DB) as c:
return c.execute(
"SELECT COUNT(*) FROM trades WHERE setup LIKE ?"
" AND exit_time IS NOT NULL AND pnl IS NOT NULL", (muster,)
).fetchone()[0]
except Exception:
return 0
CHECKS = [
{
# ⚠⚠ WIEDERVORLAGE aus dem Backtest vom 2026-08-12
# (`backtest_srclose_reentry.py`). Die Frage des Users: wenn das
# P(break)-Gate schliesst („Level haelt") und die Linie bricht doch —
# lohnt der Wiedereinstieg? Ergebnis ueber 2 Halbjahre:
# 0,3×ATR H1 +0,119 [0,138 … +0,387] · H2 +0,196 [+0,065 … +0,332]
# 0,5×ATR H1 0,043 · H2 +0,043
# 0,7×ATR H1 +0,079 · H2 +0,014
# Die vorab fixierte Regel ist verfehlt (H1s KI enthaelt die Null, und
# der Nachbar 0,5 dreht H1 negativ). ABER: der OeR ist bei 0,3 in BEIDEN
# Haelften positiv, und zwei gleichgerichtete Haelften sind mehr als eine
# — „KI enthaelt 0" heisst nicht widerlegt, sondern nicht belegt.
# ⚠ Der Backtest kann NICHT besser werden: H1 ist feste Historie, und die
# M5-Tiefe des Brokers ist bei 80k Bars gedeckelt. Was waechst, sind
# LIVE-Faelle — und die stehen bereits in `pbreak_predictions`
# (predicted='bounce' + outcome='break' = genau dieser Fall).
# ⚠⚠ ENTKOPPELT ZAEHLEN — und das ist nicht kosmetisch. Beim Aufsetzen
# dieser Bedingung habe ich zuerst „roh 193 = entkoppelt 193" gemessen
# und daraus ~17 Faelle/Tag geschlossen. Falsch: `schluessel_level_stunde`
# ist eine FABRIK und muss AUFGERUFEN werden; uebergibt man sie selbst,
# ist jeder Schluessel ein neues Funktionsobjekt und es wird nichts
# dedupliziert. Richtig sind **55** Faelle = **~4,9/Tag**.
# (Die zwei echten Verwendungen in `analyze_divergence.py` rufen sie
# korrekt auf — nur die Ad-hoc-Auswertung war betroffen.)
# SCHWELLE 500: loest einen wahren OeR ab ~0,118 auf (n > (1,96·1,35/OeR)²
# bei der gemessenen SD ~1,35) und deckt damit auch die UNTERE
# Backtest-Schaetzung (+0,12) ab. Bei ~4,9/Tag sind das **~3 Monate**.
# ⚠ Bewusst NICHT kleiner: bei n=175 (~25 Tage) waere nur ein OeR ≥0,20
# aufloesbar — die Bedingung liefe ab, ohne entscheiden zu koennen.
# Genau dieser Fehler steckte in `pbreak_accuracy_v2` (Faktor 10 zu
# frueh faellig). Lieber spaet und entscheidbar als frueh und wertlos.
"key": "srclose_reentry",
"title": "Wiedereinstieg nach S/R-Close erneut prüfen (Live-Daten)",
"why": "Backtest 2026-08-12 nicht entscheidbar: bei 0,3×ATR "
"Bestätigung ØR +0,119/+0,196 — beidhälftig positiv, aber H1s "
"95-%-KI enthält die Null und der Nachbar 0,5 kippt H1. Der "
"Backtest kann nicht besser werden (H1 ist feste Historie); "
"was wächst, sind Live-Fälle. Auswerten: die Fälle mit "
"predicted='bounce' und outcome='break' gegen den kanonischen "
"Exit rechnen (Vorlage: analyze_srclose_reentry.py) und gegen "
"die Backtest-Erwartung +0,12…+0,20 halten.",
"have": lambda: _q(
"SELECT COUNT(*) FROM (SELECT DISTINCT ROUND(level,2), ts/3600 "
"FROM pbreak_predictions WHERE predicted='bounce' "
"AND outcome='break' AND ts >= 1785492000)"), # 31.07.2026 12:00
"need": 500,
"unit": "entkoppelte Fälle 'Gate schliesst, Level bricht doch' ab 31.07.",
"cmd": "python analyze_srclose_reentry.py (bzw. auf pbreak_predictions erweitern)",
},
{
# ERLEDIGT 2026-07-31 (analyze_pbreak_live.py, 2275 Vorhersagen): das Modell
# hält live NICHT — AUC 0,539 statt 0,71, Kalibrierung sagt 8 % wo 39 %
# eintreten, und die Bruchrate der geschlossenen Gruppe ist bei JEDER
# Schwelle ~38 % → das Gate gatet nicht. Wiedervorlage mit HÖHERER Schwelle,
# damit ein evtl. nachtrainiertes Modell auf frischen Daten geprüft wird.
"key": "pbreak_accuracy_v2",
"title": "P(break) erneut auswerten (nach Nachtraining)",
"why": "Erste Auswertung 2026-07-31 fiel durch (AUC 0,539, Gate ohne "
"Trennschärfe). Offene Baustelle: Touch-Zahl als 5. Merkmal in "
"backtest_srclose_prob.py aufnehmen und neu fitten (live in beiden "
"Hälften +6/+4 Pp für die 2.4. Berührung, 5.+ fällt zurück). "
"Danach mit frischen Live-Daten gegenprüfen.",
# ⚠⚠ EINHEIT KORRIGIERT 2026-08-07 — die alte Bedingung zählte die FALSCHE
# Größe. Sie stand auf 800 ROHEN Vorhersagen; die sind aber NICHT
# unabhängig (dieselbe Position pendelt vielfach um dasselbe Level). Am
# 07.08. war sie mit 878 „fällig" — entkoppelt (max. 1 je Level und
# Stunde) waren es **87**, und das 95-%-Bootstrap-Intervall der AUC ging
# von 0,434 bis 0,674: es enthält sowohl den Zufall (0,50) als auch die
# Messlatte (0,654). Die Messung konnte also gar nichts entscheiden.
# Die Abhängigkeit war im Skript seit jeher dokumentiert („entkoppelte
# Sicht"), nur die Fälligkeit zählte daran vorbei — sie überschätzte die
# Beweismenge um rund das Zehnfache.
# 350 entkoppelte Zeilen ≈ halbe KI-Breite (0,12 → 0,06), erst dann sind
# 0,50 und 0,65 überhaupt trennbar. Bei ~17/Tag sind das ~3 Wochen.
"have": lambda: _q(
"SELECT COUNT(*) FROM (SELECT DISTINCT ROUND(level,2), ts/3600 "
"FROM pbreak_predictions WHERE outcome IS NOT NULL "
"AND ts >= strftime('%s','2026-08-01'))"),
"need": 350, "unit": "ENTKOPPELTE Vorhersagen seit 01.08. (max. 1 je Level und Stunde)",
"cmd": "python analyze_pbreak_live.py",
},
{
"key": "modul_audit_2",
"title": "Modul-Audit: zweiter Durchgang",
"why": "Am 2026-07-31 wurden an EINEM Tag 5 Deployment-Drift-Fälle (gemessen "
"unter X, betrieben unter Y) UND 5 Modul-Inkonsistenzen gefunden — bei "
"gezielter Suche in wenigen Stunden. Die Trefferquote spricht dafür, "
"dass weitere existieren. `analyze_divergence.py` meldet die Klasse "
"'Betrieb ≠ Messung' inzwischen selbst; die Klasse 'Modul-Inkonsistenz' "
"(Gewichte, stale Quellen, tote Config, Sonderfälle) braucht dagegen "
"einen bewussten Durchgang.",
"have": lambda: _days_since("2026-07-31"),
"need": 28, "unit": "Tage seit dem 1. Audit",
"cmd": "python analyze_divergence.py 14 · dann Verdict-Gewichte gegen die "
"Regel 'keine Aussage → Gewicht 0' prüfen, Config-Schlüssel gegen den "
"Code, Level-Quellen gegen den aktuellen Kurs",
},
{
"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. "
"⚠ OFFENE PUNKTE für diesen Lauf (Stand 2026-08-07, aus dem "
"Zwischenstand über 15.544 Zeilen): "
"(a) Liq-Trend (Gewicht 0,5, HL-abgeleitet) — +0,20/+0,32 über nur "
"132 Episoden, also dünn. ⚠ Es stammt aus DERSELBEN Quelle wie das am "
"06.08. entmachtete Orderbuch-Modul, und HL-Preisdaten laufen gemessen "
"hinter Pepperstone her (Lead-Lag: 51 % Treffer nach 6 s). Als einziges "
"verbliebenes HL-Modul mit Stimmrecht gehört es zuerst geprüft. "
"(b) Muster (0,25) — +1,27/+0,67 bei n=88, zu dünn für ein Urteil. "
"(c) H1 (1,5) — nur 19 Episoden, wechselt zu selten; trägt aber 38 % "
"des Nadel-Einflusses, prädiktiv bisher NICHT beurteilbar. "
"(d) Elliott und Orderbuch sind bereits entmachtet (used=False), werden "
"aber WEITER geloggt — die Reihe darf für diese Messung nicht abreißen.",
# ⚠⚠ EINHEIT KORRIGIERT 2026-08-17 — sie zählte die FALSCHE Größe.
# Vorher: 25.000 ZEILEN. Die sind bei Minutentakt in 12 Tagen erreicht,
# die auswertbare Einheit sind aber EPISODEN — und davon gab es je
# Modul nur 50 (H1) bis 2.712 (Orderbuch). Dieselbe Klasse wie bei
# `pbreak_accuracy_v2`, die roh statt entkoppelt zählte und die
# Beweismenge um das Zehnfache überschätzte.
# Gezählt werden jetzt Headline-WECHSEL als billiger Episoden-Proxy.
# ⚠ Die Episodenzahl JE MODUL ist kleiner (17.08.: H1 nur 50 bei 254
# Headline-Episoden) — deshalb liegt die Schwelle höher als das Ziel
# je Modul. Und der Vorbehalt des Skripts bleibt: wenige Wochen sind
# EIN Regime, also Sichtung statt Urteil.
"have": lambda: _q(
"SELECT COUNT(*) FROM (SELECT headline, LAG(headline) OVER "
"(ORDER BY ts) prev FROM verdict_votes) WHERE headline IS NOT prev"),
"need": 400, "unit": "Headline-Episoden (Proxy; je Modul deutlich weniger)",
"cmd": "python analyze_verdict_modules.py (Episoden-Bildung beachten: "
"die Rohzeilen zählen dieselbe Marktlage dutzendfach)",
},
# (ausgetragen 2026-08-01: „Auto-Signal-Entry B4" — der Pfad wurde ABGESCHALTET
# (`auto_signal=false`, Toggle + runtime_state). Gründe: gemessen H1 durchgehend
# negativ, und er konkurriert mit dem Auto-Squeeze um den EINEN Positions-Slot →
# solange beide laufen, ist die Squeeze-Erholung unten nicht sauber messbar.
# Wird er je reaktiviert, gilt wieder: ≥20 AUTOSIG-Trades, ØR gegen +0,14 halten.
# Der Zustand wird jetzt über CONFIG_DEPS['auto_signal'] überwacht.)
{
"key": "manual_ctx",
"title": "Manuelle Trades: Stufe 2 — was trennt Gewinner von Verlierern?",
"why": "Seit 2026-08-04 wird bei JEDEM Einstieg der volle Entscheidungszustand "
"mitgeschrieben (16 `ctx_*`-Spalten: P(break) Ziel/Stop, Abstand zu S/R, "
"Struktur, Squeeze, M30/H1, Spread/ATR, Session, block_reason, Konfidenz, "
"ATR, Bias, Kegel-Position, Stunde). Vorher war die Marktlage eines "
"manuellen Einstiegs NICHT rekonstruierbar — jede Aussage über die "
"Diskretion des Users blieb Spekulation. ⚠ Die Vorprüfung zeigte: die "
"sichtbaren Muster sind Einzeltage (Stunde 13:00 = +478 € über n=69, "
"davon 80 % aus FÜNF Trades des 04.08.). Erst mit genug Kontext ist "
"messbar, WORAN gute von schlechten Einstiegen zu unterscheiden sind.",
"have": lambda: _q("SELECT COUNT(*) FROM trades WHERE ctx_atr_m5 IS NOT NULL "
"AND pnl IS NOT NULL AND (setup IS NULL OR (setup NOT LIKE "
"'AUTOSIG%' AND setup NOT LIKE 'SQUEEZE%'))"),
"need": 300, "unit": "manuelle Trades mit Kontext",
"cmd": "Fit auf der ERSTEN Hälfte, validiert auf der ZWEITEN; AUC und "
"Kalibrierung berichten (Methode wie backtest_pbreak_retrain.py). "
"⚠ 300 reichen für eine ERSTE SICHTUNG, nicht für ein Urteil — mit "
"~150 je Hälfte und mehreren Merkmalen ist die Overfit-Gefahr hoch; "
"wenige Merkmale nehmen und das Ergebnis entsprechend vorsichtig lesen. "
"Ziel ist ein HINWEIS im Order-Dialog ('deine Trades in dieser "
"Konstellation: X % WR, n=Y'), ausdrücklich KEIN Auto-Entry.",
},
{
# Diese Regel MUSS hier stehen: sie wurde am 19.08. vorab fixiert, und
# genau so eine Regel blieb bei B5 wochenlang unbemerkt, weil der Zaehler
# nirgends lief. Ohne Ueberwachung ist eine Abbruchregel keine Regel.
"key": "m15_abbruch",
"title": "M15-Auto-Entry: Abbruchpruefung (Latte vorab fixiert)",
"why": "Am 19.08. auf User-Wunsch GEGEN die Messung gebaut. "
"backtest_m15_auto.py: die Karte ist in BEIDEN Halbjahren negativ "
"(Lesart -0,162/-0,094; die jetzt gehandelte ERLAUBTE Richtung "
"entspricht Kontrolle C mit -0,085/-0,030). Zusaetzlich ist der "
"gebaute MARKT-Einstieg schlechter als der gemessene "
"Level-Einstieg. Erwartung: die Latte greift.",
"have": lambda: _zaehle_setup("AUTOM15%"),
"need": 20, "unit": "AUTOM15_*-Trades",
"cmd": "LATTE (vorab fixiert, NICHT verschiebbar): Verhaeltnis "
"(OeGewinn/OeVerlust) < 1,0 ODER PF < 1 -> auto_m15 = false "
"(Knopf M15). Dieselbe Latte wie squeeze_b5. Rechnung analog, "
"nur setup LIKE 'AUTOM15%'.",
},
{
# ⚠ VORAB festgelegte Abbruchregel (User-Zustimmung 2026-08-01) — bewusst VOR
# der Beobachtung fixiert, damit die Latte hinterher nicht verschoben wird.
"key": "squeeze_b5",
"title": "Auto-Squeeze: B5-Abbruchprüfung (Latte vorab fixiert)",
"why": "Live-Bilanz 17.31.07.: n=31, ΣEUR 162,99, PF 0,49, Verhältnis 0,56 — "
"während der B4-Monitor den EINSTIEG als intakt ausweist (Trefferquote "
"live 45 % vs. Mechanik 46 %, ØR Mechanik +0,102). Die Lücke von ~0,34 R "
"entstand also im EXIT, und alle drei Ursachen wurden am 31.07. behoben: "
"P(break)-Modell nachtrainiert (schloss vorher 91 % aller Level-"
"Berührungen = faktisch pauschaler S/R-Close), 15-Min-Regel aus, Trailing "
"einheitlich 1,0 statt 1,53,0 je TF. Deshalb NICHT sofort zurückgebaut, "
"sondern ein sauberes Fenster mit harter Latte.",
"have": lambda: 20, # ⚠ ENTSCHIEDEN — s. `cmd`
"need": 20, "unit": "Squeeze-Trades seit 01.08. (ENTSCHIEDEN bei 11)",
"cmd": "✅ ERLEDIGT 2026-08-17 — die Regel ist RECHNERISCH entschieden, "
"nicht vertagt. Stand war 11/20: ØGewinn 13,62 gegen ØVerlust 94,15 "
"= Verhältnis 0,14 (Latte 1,0), PF 0,39 (Latte 1,0), Σ 173,46 €. "
"Nachgerechnet, was die 9 Rest-Trades leisten müssten: selbst im "
"BESTEN Fall (9 von 9 Gewinnern, kein weiterer Verlust) bräuchte es "
"Ø 165,72 € je Trade — das 12-fache des bisherigen Durchschnitts und "
"2,1× den bisherigen REKORD (77,34 €). Mit Verlusten darunter steigt "
"die Anforderung auf 175223 €. ⚠ Die Latte ist damit nicht mehr "
"erreichbar; auf 20 zu warten hätte den Zustand nie entschieden — "
"und der Trader stand seit 11.08. ohnehin AUS, wodurch der Zähler "
"eingefroren war (Deadlock). `auto_squeeze` bleibt FALSE. "
"Wiederaufnahme wäre eine NEUE Entscheidung mit NEUER, vorab "
"fixierter Latte — nicht die Fortsetzung dieser.",
},
{
"key": "squeeze_entry_gap",
"title": "Stop-Order-Umbau: kommt der Einstieg jetzt AM Level an?",
"why": "Am 05.08. wurde der Auto-Squeeze von der Market-Order auf ruhende "
"Stop-Orders am Ausbruchs-Level umgestellt. Grund: der Squeeze hat "
"seinen GESAMTEN Edge im Einstiegspreis — am Level ØR +0,456/+0,611, "
"hinterhergelaufen 0,158/0,006 (`backtest_squeeze_entry.py`). Live "
"lag der Abstand vorher im Median +0,275×ATR (Mittel +0,686, Max "
"+4,339). Greift die Pending-Order nicht wie gedacht, ist der ganze "
"Umbau wirkungslos — und das fiele sonst NICHT auf, weil der Bot "
"weiterhin Trades macht.",
# ⚠ Stichtag exakt auf den Umbau (05.08. 08:55 Berlin = 06:55 UTC), NICHT auf
# Mitternacht: der Squeeze-Trade von 08:33 lief noch über die Market-Order und
# würde die Zählung verfälschen. `strftime('%s', …)` liest UTC, `entry_time`
# ist lokale Epoch — deshalb 07:00 UTC als sicherer Schnitt.
# ⚠ Seit 05.08. laeuft AUCH der Signal-Pfad ueber Pending-Orders — beide
# zaehlen, sonst wartet der Reminder auf eine Population, die vielleicht
# nie kommt (der Squeeze feuert nur ~2-3x/Woche).
"have": lambda: _q("SELECT COUNT(*) FROM trades WHERE "
"(setup LIKE 'SQUEEZE%' OR setup LIKE 'AUTOSIG%') "
"AND entry_time >= strftime('%s','2026-08-05 07:00:00')"),
"need": 12, "unit": "Bot-Trades (Squeeze+Signal) seit dem Umbau (05.08. 08:55)",
"cmd": "python analyze_squeeze_entry_gap.py 60 → Der Median-Abstand muss "
"gegen 0 gehen und die Market-Log-Zeilen ('AUTO-SQUEEZE-ENTRY') "
"sollten weitgehend verschwinden. Bleibt der Abstand bei ~0,3×ATR, "
"füllt die Pending-Order nicht — dann Broker-Mindestabstand und die "
"Log-Zeile 'Squeeze-Pending nicht platzierbar' prüfen.",
},
{
"key": "night_window_0127",
"title": "Nacht-Fenster 36: tragen die freigegebenen Stunden 0/1/2/7?",
"why": "Am 05.08. wurde die Auto-Squeeze-Nachtsperre von 07 auf 36 verkürzt "
"(`backtest_cost_gate.py`: keine Nachtstunde ist beidhälftig negativ, "
"das alte Gate kostete ΣR 42/243 R). Der Backtest modelliert aber "
"KEINE Slippage — und genau die freigegebenen Stunden liegen in der "
"dünnsten Liquidität (gemessener Tail: 0,75×ATR über den Stop hinaus). "
"Das ist die einzige offene Bedrohung des Befunds, und sie lässt sich "
"nur live prüfen.",
"have": lambda: _q("SELECT COUNT(*) FROM trades WHERE setup LIKE 'SQUEEZE%' "
"AND pnl IS NOT NULL AND entry_time >= "
"strftime('%s','2026-08-05') AND "
"CAST(strftime('%H', entry_time, 'unixepoch', 'localtime') "
"AS INTEGER) IN (0,1,2,7)"),
"need": 15, "unit": "Squeeze-Trades aus 0/1/2/7 Uhr seit 05.08.",
"cmd": "Erwartung aus dem Backtest: diese Stunden gehören zu den besseren "
"(Std 0 ist die STÄRKSTE des Tages, ØR +1,48/+1,51). Liegen sie live "
"deutlich darunter oder im Minus, war die Slippage der Grund → "
"`auto_squeeze_night_hours = 0,1,2,3,4,5,6,7` zurücksetzen.",
},
# (erledigt 2026-08-04: „M30-Level-Umstellung live gegenprüfen" — Ertrag JE LOT
# +17,53 → +36,92 €, Ø-Move je Trade 0,202 → 0,427 $, also verdoppelt wie im
# Backtest erwartet. ⚠ Normierung war entscheidend: unnormiert sah HEUTE wie der
# Treiber aus (Ø +55 € vs +19 €), je Lot ist heute aber praktisch identisch zum
# Rest (+38,81 vs +35,87) — der Unterschied war reine Positionsgröße.
# ⚠ NICHT der Level-Quelle allein zuzuordnen: am 31.07. kamen P(break)-Nachtraining
# und Schwelle 0,55→0,35 dazu; das Fenster mit NUR der Umstellung hat n=8.)
# (erledigt 2026-07-30: Chartmuster-Kontrolltest gegen generischen Swing-Bruch —
# Muster schlagen die Kontrolle in beiden Hälften (+0,022/+0,108) → Gewicht 1,0)
{
"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",
},
]
# ── Config-abhängige Backtests ───────────────────────────────────────────────
# User-Frage 2026-07-31: „sollten wir alle verworfenen Backtests wöchentlich neu
# laufen lassen, da wir laufend die Config anpassen?"
#
# ⚠ NEIN — wöchentlich wäre eine Fehlalarm-Maschine: eine Woche M5 sind ~2000 Bars,
# und das Projekt hat dreimal erlebt, was daraus wird (Auto-Signal-Vorlauf auf 6k
# Bars zeigte „trägt", der 80k-Lauf drehte es; Std 13; ORB mit ØR +1,3/PF 10 als
# Selektions-Artefakt). Dazu der Mehrfachvergleich: 18 verworfene Ideen × 52 Wochen
# = 936 Tests/Jahr → bei 5 % Fehlalarmquote ~47 falsche „funktioniert jetzt!"/Jahr.
#
# ABER das Anliegen dahinter stimmt: einige Messungen sind gegen einen KONKRETEN
# Config-Wert kalibriert und werden ungültig, wenn der sich ändert. Das ist
# EREIGNIS-gesteuert, nicht kalendergesteuert — und genau das prüft dieser Block.
# (Die meisten verworfenen Tests sind config-UNABHÄNGIG: PDH/PDL, Volume Profile,
# Chartmuster, Liquidity Sweeps testen Signalklassen auf rohen Preisdaten.)
CONFIG_DEPS = [
{
# ⚠ Bewusste Abweichung von der Messung (User-Entscheidung 2026-08-07),
# deshalb hier überwacht — wie `auto_sr_close`.
"key": "auto_emergency_margin_pct", "test": "kontrafaktisch, 1022 Trades",
"validated": "0",
"why": "AKTIV auf 8 seit 2026-08-07 auf User-Wunsch. Kontrafaktisch über "
"1022 echte Trades ist KEINE Schwelle besser als gar kein Stop: "
"ohne Stop +358 € · 3 % 293 · 5 % 639 · 8 % 14 · 15 % 416 · "
"30 % 331 (Δ gegen ohne). 8 % ist die mildeste Variante und "
"praktisch neutral — sie schützt wenig und kostet wenig. Auch je "
"Einstiegsart trägt keine Schwelle robust (bei 'ohne Signal' sieht "
"810 % gut aus, aber beide Nachbarn kippen = Rauschen). "
"⚠ Mechanismus aller Früh-Ausstiege hier: Trefferquote steigt, "
"Ertrag fällt (Gewinner-Kappen) — fünfmal unabhängig gemessen. "
"Zurück: auto_emergency_margin_pct = 0.",
},
{
"key": "entry_room_atr", "test": "backtest_entryroom.py",
"validated": "0.6",
"why": "Das Raum-Gate ist auf diesen Schwellwert kalibriert (gemessen war 1,0 "
"netto besser, 0,6 ist die bewusste Frequenz-Entscheidung). Es nutzt "
"weiterhin die M5-`pb_levels` — eine Umstellung auf M30 wäre ungemessen.",
},
{
"key": "sr_close_pbreak", "test": "backtest_pbreak_rvalue.py",
"validated": "0.35",
"why": "Seit dem Modell-Nachtraining 2026-07-31 (live-spiegelnde Stichprobe) "
"ist 0,35 der in BEIDEN Hälften beste Wert gegen die Trailing-Baseline "
"(H1 +357, H2 +1418 R). Bei 0,60 kippt H1. Die alte 0,55 gehörte zum "
"alten Modell und ist mit den neuen _PB_W nicht mehr gültig.",
},
{
"key": "breakout_k", "test": "backtest_breakout.py",
"validated": "0.3",
"why": "Split-validiert: 0,3 ist in BEIDEN Hälften das Beste; 0,5 und 1,0 waren "
"in H1 netto NEGATIV. Ein anderer Wert ist nicht belegt.",
},
{
"key": "risk_pct", "test": "backtest_sizing.py",
"validated": "0",
"why": "0 = margin-basiert. Wechsel auf >0 aktiviert einen anderen Sizing-Pfad "
"(calc_lots_risk) — und koppelt den %-Notfall-Stop anders.",
},
{
"key": "margin_buffer_pct", "test": "backtest_sizing.py",
"validated": "95",
"why": "Die Ruin-Simulation ist gegen den Margin-Anteil gerechnet (90 % = 36 % "
"P(DD>80 %)). Ändert sich er, ändert sich das Ruin-Risiko.",
},
{
"key": "adverse_15min_atr", "test": "backtest_adverse15_squeeze.py",
"validated": "0",
"why": "0 = AUS (2026-07-31). Nachgemessen mit dem kanonischen Exit über die "
"ZEIT- und Schwellen-Achse (15/30/45/60 min × 0,5/0,8×ATR) auf BEIDEN "
"Populationen: **alle 16 Kombinationen sind in beiden Hälften negativ**, "
"nur monoton weniger schädlich je später/lockerer. Es gibt keine "
"Einstellung, bei der die Regel hilft. Wiedereinschalten wäre nicht "
"gedeckt — der Schutz läuft über Broker-SL 2×ATR, Trailing und Time-Stop.",
},
{
"key": "auto_signal_min_conf", "test": "backtest_auto_signal.py",
"validated": "75",
"why": "Empirisch bester Punkt (NICHT weil höhere Konfidenz besser wäre — "
"conf_pct ist unkalibriert). Nachbarwerte sind nicht besser.",
},
{
"key": "reversal_enabled", "test": "backtest_reversal_angle.py",
"validated": "false",
"why": "AUS seit 2026-08-04. Der Reversal-Trigger ist in JEDER Variante "
"beidhälftig negativ (IST 0,122/0,031 · vorzeichenkorrigiert "
"0,173/0,061 · ohne Winkelbedingung 0,099/0,023) und hebelt dabei "
"ZWEI gemessen positive Schutzmechanismen aus (Anti-Überdehnung und den "
"M30-Gegen-Trend-Filter, Edge ×2). ⚠ Der Modul-Default in `wave_rec` ist "
"bewusst True — die Backtests rufen dasselbe `_build`; abgeschaltet wird "
"nur im Live-Pfad über diese Config.",
},
{
# ⚠⚠ GEGENSTANDSLOS seit 2026-08-12: der Auto-Signal-Pfad ist KOMPLETT
# entfernt (Code, Endpunkt, Button, Snapshot-Felder). Der Schlüssel
# wird nirgends mehr gelesen — ein Wächter darüber könnte also nur
# noch Fehlalarm oder falsche Beruhigung liefern.
# Der Eintrag bleibt als STUMME Notiz stehen (`validated` = der
# Ist-Zustand), damit die Historie lesbar bleibt und niemand den
# Schlüssel versehentlich reaktiviert, ohne die Messlage zu kennen.
"key": "auto_signal", "test": "backtest_signal_touchfill.py",
"validated": "false",
"why": "ENTFERNT 2026-08-12 (Pfad gelöscht, nicht nur abgeschaltet). "
"Gemessen negativ auf BEIDEN Ausführungs-Achsen: Markt-Einstieg "
"in allen 8 Feldern (backtest_auto_signal.py 30.07.), ruhende "
"Order mit Touch-Fill 0,392/0,338 in allen 6 Feldern und damit "
"SCHLECHTER als Markt (backtest_signal_touchfill.py 06.08.). "
"Ursache strukturell: das Bestätigungs-Level `_pend` WANDERT. "
"⚠ Die ini-Schlüssel bleiben stehen, werden aber nicht mehr "
"gelesen — Reaktivieren ginge nur über die Git-Historie bzw. "
".removed_backup/auto_signal_2026-08-12.py.",
},
{
"key": "auto_m15", "test": "backtest_m15_auto.py", "soll": "false",
"why": "Gemessen negativ (Lesart -0,162/-0,094; die gehandelte erlaubte "
"Richtung entspricht Kontrolle C, -0,085/-0,030 -- ebenfalls "
"negativ). Steht er auf 'true', laeuft ein gemessen negativer "
"Auto-Entry auf echtem Geld. Das soll sichtbar bleiben.",
},
{
"key": "auto_squeeze", "test": "backtest_breakout_squeeze.py",
# ⚠⚠ ERWARTUNG GEDREHT 2026-08-17: stand auf "true" und meldete deshalb
# seit dem 11.08. taeglich eine Abweichung — obwohl FALSE inzwischen
# der begruendete Zustand ist. Ein Waechter, der dauerhaft warnt, wird
# ignoriert (dieselbe Lehre wie beim Divergenz-Waechter, 6 Tage
# Dauerwarnung). Die B5-Regel ist am 17.08. RECHNERISCH gefallen:
# Verhaeltnis 0,14 / PF 0,39 bei 11 Trades, und selbst 9 perfekte
# Rest-Trades braeuchten Ø 165,72 EUR = 2,1x den bisherigen Rekord.
"validated": "false",
"why": "Der einzige 2-Stichproben-validierte Auto-Einstieg (ØR +0,14…+0,23) "
"— aber die vorab fixierte B5-Abbruchregel ist am 2026-08-17 "
"GEFALLEN (Verhaeltnis 0,14 gegen Latte 1,0, PF 0,39 gegen 1,0, "
"Latte rechnerisch unerreichbar). Deshalb ist FALSE jetzt der "
"validierte Zustand. ⚠ Ein Wiedereinschalten waere eine NEUE "
"Entscheidung mit NEUER, vorab fixierter Latte — dann gehoert "
"dieser Eintrag zurueck auf 'true' gedreht.",
},
{
"key": "auto_flip_close", "test": "backtest_flipclose2.py",
"validated": "false",
"why": "ABGESCHALTET 2026-08-05. Jede Variante in BEIDEN Halbjahren negativ "
"(Live-Wert 0,5xATR: -92 R / -160 R); Mechanismus = Gewinner-Kappen, "
"die nachlaufende EMA dreht oft mitten im Pullback. Die Live-Bilanz "
"sprach dagegen (+284 EUR aus 4 Ausloesungen), aber +101 EUR davon "
"steckten in EINEM Trade am Ausreissertag 04.08. Wird der Schalter "
"wieder auf true gesetzt, laeuft ein gemessen negativer Exit auf "
"echtem Geld - deshalb hier ueberwacht.",
},
{
"key": "signal_pending_entry", "test": "backtest_signal_touchfill.py",
"validated": "false",
"why": "ABGESCHALTET 2026-08-06. Gebaut am 05.08. auf +0,39/+0,36 — diese "
"Zahl unterstellt aber Fill NUR dort, wo der Bar-CLOSE das Level "
"bricht. Eine liegende Order fuellt bei der ERSTEN BERUEHRUNG. Mit "
"der realistischen Annahme ist der Pfad in ALLEN SECHS Feldern "
"negativ (-0,18 bis -0,48, PF 0,38-0,71) und damit sogar schlechter "
"als der Markt-Einstieg — er faengt an einem WANDERNDEN Level "
"systematisch die Fehlausbrueche ein (441 statt 328 Fills). "
"⚠ NICHT verwechseln mit `squeeze_pending_entry`: dort STEHT die Box "
"still, und der gleiche Test ist bestanden (+0,124/+0,292). "
"Wieder auf true zu setzen hiesse, SIG ueber den gemessen "
"schlechtesten Einstiegsweg zu handeln.",
},
{
"key": "auto_sr_close", "test": "backtest_pbreak_rvalue.py",
"validated": "true",
"why": "ABGESCHALTET 2026-08-06 auf User-Wunsch — und das ist eine Abweichung "
"von der Messung, deshalb hier überwacht. Mit dem am 31.07. "
"nachtrainierten P(break)-Modell schlägt der gegatete S/R-Close die "
"Trailing-Baseline in BEIDEN Hälften (bei der Live-Schwelle 0,35: "
"H1 -149 gegen -506 = +357 R, H2 +1308 gegen -110 = +1418 R). "
"⚠ Nicht zu verwechseln mit dem PAUSCHALEN S/R-Close, der 2x verworfen "
"wurde — das P(break)-Gate ist der Unterschied. Was weiter schützt: "
"Broker-SL 2xATR, Trailing, Time-Stop. Der Hinweis in der App und die "
"Chart-Linien bleiben (nur die automatische Ausführung entfällt). "
"⚠ Lebt AUCH in runtime_state.json (UI-Toggle) und wird von dort "
"überschrieben — beim Zurückschalten beide Stellen prüfen.",
},
{
"key": "dead_hours", "test": "backtest_hourly_split.py / backtest_realcosts.py",
"validated": "",
"why": "Leer = Gate AUS (User-Vorgabe, bewusst GEGEN die Messung). Wird es "
"wieder gefüllt, gelten die gemessenen Stunden 07 + 12 + 16.",
},
]
# ── TELEMETRIE-PULS ───────────────────────────────────────────────────────────
# ⚠⚠ ANLASS (2026-08-11, Review-Durchgang 4): `rec_outcomes` — die
# Selbst-Kalibrierung der Empfehlung — stand seit dem Einbau am 06.08. bei
# **0 Zeilen**. Ursache war ein falsches dict (`s.get("pb_feats")` auf dem
# MT5Data- statt dem Wave-Snapshot) → `_atr` immer 0 → die Schreibbedingung
# nie erfüllt. **Kein NameError** (ruff sieht nichts), und das umgebende
# `except` loggt auf DEBUG, der Logger steht auf INFO. Fünf Tage still tot.
# Aufgefallen NUR, weil die Tabelle mit 0 Zeilen neben ihren Schwestern stand.
# ➜ Diese Prüfung generalisiert den Fund: jede Telemetrie-Tabelle muss einen
# PULS haben. Schweigt eine, während die anderen schreiben, ist der Logger tot.
# ⚠ Die Toleranz ist grosszügig (Faktor 20 der erwarteten Kadenz), damit ein
# ruhiger Markt keinen Fehlalarm auslöst — ein Wächter, der oft warnt, wird
# ignoriert (Lehre vom 02.08.).
# ⚠ Die Implementierung liegt in `core/history.py` (DB-Schicht) — BEIDE Nutzer
# (dieses Popup und der Tagesreport in `engine._send_daily_report`) rufen
# dieselbe Funktion. Zwei Kopien waeren die Divergenz-Falle, die im Projekt
# dreimal zugeschlagen hat. Richtungsregel: Skripte importieren `core`.
def telemetrie_puls() -> list:
"""Welche Telemetrie-Tabelle schreibt nicht mehr? → Klartext-Zeilen."""
try:
from core.history import HistoryLogger
return HistoryLogger(DB).telemetrie_puls()
except Exception as e:
return [f"Puls-Pruefung nicht moeglich: {e}"]
_PULS = () # Rueckwaerts-Kompatibilitaet fuer die Zaehl-Ausgabe unten
def _cfg_now() -> dict:
"""Liest NUR die überwachten [trading]-Keys. ⚠ Die ini enthält Live-Secrets —
hier wird bewusst nichts anderes gelesen oder ausgegeben.
⚠⚠ WICHTIG (Fix 2026-08-01): Die ini ist NICHT die Quelle der Wahrheit für die
UI-Schalter. `runtime_state.json` wird beim Start über die ini gelegt (engine
`_load_runtime_state`) — der Wächter muss denselben Vorrang abbilden, sonst meldet
er „alles auf dem gemessenen Stand", während live etwas anderes läuft.
Real passiert: `auto_signal` stand in der ini auf `false`, lief aber wochenlang,
weil ein Dashboard-Toggle `true` persistiert hatte — 4 der 7 Bot-Trades vom 31.07.
stammten aus diesem Pfad, der gemessen in H1 negativ ist.
"""
import configparser
import json
out: dict = {}
try:
c = configparser.ConfigParser(inline_comment_prefixes=("#", ";"))
c.read(os.path.join(HERE, "oil_widget_config.ini"), encoding="utf-8")
t = c["trading"] if c.has_section("trading") else {}
for d in CONFIG_DEPS:
out[d["key"]] = (t.get(d["key"], "") or "").strip()
except Exception:
pass
# runtime_state.json überschreibt — exakt wie im Engine-Start.
try:
with open(os.path.join(HERE, "runtime_state.json"), encoding="utf-8") as f:
rt = json.load(f) or {}
for k, v in rt.items():
if k in out:
out[k] = ("true" if v else "false") if isinstance(v, bool) else str(v)
except Exception:
pass
return out
def config_drift() -> list:
"""→ [(dep, ist, soll)] für alle Keys, deren Wert von der Validierung abweicht."""
now = _cfg_now()
drift = []
for d in CONFIG_DEPS:
cur = now.get(d["key"])
if cur is None:
continue
try: # numerisch vergleichen, damit 0.60 == 0.6
same = abs(float(cur) - float(d["validated"])) < 1e-9
except (TypeError, ValueError):
same = cur.lower() == d["validated"].lower()
if not same:
drift.append((d, cur, d["validated"]))
return drift
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 _auto_squeeze_an() -> bool:
"""Laeuft der Auto-Squeeze? (ini + runtime_state, Vorrang wie beim Start)"""
try:
from core.live_config import LiveConfig
return bool(LiveConfig.laden().auto_squeeze)
except Exception:
return True # im Zweifel NICHT als ruhend melden
# ── RUHENDE Messungen ────────────────────────────────────────────────────────
# ⚠⚠ ANLASS (2026-08-18): zwei Messungen brauchen SQUEEZE-Trades — und
# `auto_squeeze` ist seit dem 11.08. aus, seit dem 17.08. auch begruendet
# (B5-Regel gefallen). Ihre Zaehler stehen seither still: 8/12 und 3/15.
# Als „sammelt (67 %)" angezeigt suggerieren sie einen Fortschritt, den es
# nicht gibt — sie laufen NIE ab.
# RUHEND heisst: die Messung ist nicht langsam, sie ist BLOCKIERT — und die
# Blockade ist eine ENTSCHEIDUNG, keine Datenfrage. Wer den Trader wieder
# einschaltet, laesst beide automatisch weiterlaufen; hier ist nichts zu tun.
RUHT = {
"squeeze_entry_gap": (
_auto_squeeze_an,
"braucht SQUEEZE-Trades — `auto_squeeze` ist AUS (B5-Regel am 17.08. "
"gefallen). Zaehler steht seit dem 11.08. still."),
"night_window_0127": (
_auto_squeeze_an,
"braucht SQUEEZE-Trades aus 0/1/2/7 Uhr — `auto_squeeze` ist AUS. "
"Zaehler steht seit dem 11.08. still."),
}
def ruht(key: str):
"""(True, Grund) wenn die Messung blockiert ist, sonst (False, '')."""
e = RUHT.get(key)
if not e:
return False, ""
pruef, grund = e
try:
return (not pruef()), grund
except Exception:
return False, ""
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:
_sch, _ = ruht(c["key"])
pending.append((c, have, "RUHT" if _sch else "sammelt"))
# ── Config-Drift: welche Messung ist durch eine Config-Änderung veraltet?
# Marker enthält den Wert → ändert der User später erneut, wird neu gemeldet.
drift = config_drift()
drift_new = []
for d, cur, val in drift:
marker = os.path.join(MARKER_DIR, f"cfg_{d['key']}")
seen = ""
try:
with open(marker, encoding="utf-8") as f:
seen = f.read().strip()
except Exception:
pass
if STATUS or DRY or seen != cur:
drift_new.append((d, cur, val, marker))
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:
if st == "RUHT":
_, _grund = ruht(c["key"])
print(f"\n{c['title']} [RUHT]")
print(f" {have:.0f}/{c['need']} {c['unit']} — steht STILL")
print(f" ⚠ {_grund}")
continue
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)")
print("\n" + "=" * 78)
print(" CONFIG-VERANKERUNG — sind die Messungen noch gültig?")
print("=" * 78)
if not drift:
print(f"\n ✅ alle {len(CONFIG_DEPS)} überwachten Werte stehen auf dem "
f"Stand, gegen den gemessen wurde")
for d, cur, val, _m in drift_new:
print(f"\n{d['key']}: ist '{cur}' — gemessen/validiert gegen '{val}'")
print(f" betroffen: {d['test']}")
print(f" {d['why']}")
print("\n" + "=" * 78)
print(" TELEMETRIE-PULS — schreibt jeder Logger noch?")
print("=" * 78)
puls = telemetrie_puls()
if not puls:
from core.history import HistoryLogger as _HL
print(f"\n ✅ alle {len(_HL.PULS)} Telemetrie-Tabellen schreiben")
for x in puls:
print(f"\n{x}")
return
if not due and not drift_new:
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']}")
for d, cur, val, _m in drift_new:
lines.append(f"• CONFIG geaendert: {d['key']} = '{cur}' "
f"(validiert gegen '{val}')\n"
f" {d['why']}\n -> neu rechnen: {d['test']}")
head = []
if due:
head.append(f"{len(due)} Messung(en) faellig")
if drift_new:
head.append(f"{len(drift_new)} Config-Abweichung(en)")
text = ("Offene Punkte:\n\n" + "\n\n".join(lines)
+ "\n\n⚠ Verworfene Backtests NICHT woechentlich neu rechnen — eine Woche "
"ist Rauschen (~2000 Bars) und 936 Tests/Jahr erzeugen Fehlalarme.\n"
"Neu rechnen nur bei Config-Aenderung (oben) oder wenn genug WIRKLICH "
"neue Daten fuer eine dritte Stichprobe da sind (~quartalsweise).\n\n"
+ "Status: python measurement_reminder.py --status")
_popup("Oil-Bot: " + " + ".join(head), text)
for c, _ in due:
try:
open(os.path.join(MARKER_DIR, c["key"]), "w").close()
except Exception:
pass
for _d, cur, _val, marker in drift_new:
try:
with open(marker, "w", encoding="utf-8") as f:
f.write(cur)
except Exception:
pass
if __name__ == "__main__":
main()