#!/usr/bin/env python3 """Waechter fuer VERALTETE KONSTANTEN — leitet die tragenden Zahlen aus den Daten neu ab und meldet Abweichungen von der Dokumentation. ⚠⚠ ANLASS (2026-08-17). An EINEM Tag sind vier dokumentierte Zahlen als veraltet aufgefallen — jede war Entscheidungsgrundlage gewesen: · „Margin ~670 EUR/Lot" → real ~405 (bei 1.279 EUR Konto und ~3 Lots) · „87,6 EUR je $ und Lot" → real 86,90 (aus 1.051 Trades geschaetzt) · „X-API Basic 200 $/Monat" → seit Februar 2026 pay-per-use, ~45 $/Monat · „Elliott hat Gewicht nur in 1,3 % der Zeilen" → real 99,9 % CLAUDE.md hat 7.149 Zeilen. Dass dort Zahlen altern, ist unvermeidlich — dass Entscheidungen darauf gestuetzt werden, ist das Problem. ⚠ Der Waechter meldet NICHT jede kleine Abweichung. Eine Pruefung mit Dauer-Treffern wird ignoriert, und mit ihr die eine echte Meldung (die Lehre aus `pyflakes`: kosmetische Hinweise unterdruecken). Gemeldet wird erst ab einer Toleranz, die je Zahl begruendet ist. ⚠ Er AENDERT nichts. Er sagt nur, wo Doku und Daten auseinanderlaufen. Aufruf: python tools/check_konstanten.py """ from __future__ import annotations import os import re import sqlite3 import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) HIER = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DB = os.path.join(HIER, "oil_widget_history.db") DOK = os.path.join(HIER, "CLAUDE.md") def _db(): c = sqlite3.connect(DB); c.row_factory = sqlite3.Row return c # ── Die einzelnen Ableitungen ──────────────────────────────────────────────── def eur_pro_dollar_lot(): """EUR je 1,00 $ Kursbewegung und Lot — aus den echten Trades.""" db = _db() f = [] for r in db.execute("""SELECT pnl,lots,entry_price,exit_price,direction FROM trades WHERE exit_time IS NOT NULL AND pnl IS NOT NULL AND lots>0 AND entry_price>0 AND exit_price>0"""): d = 1 if r["direction"] == "BUY" else -1 mv = (r["exit_price"] - r["entry_price"]) * d if abs(mv) > 0.05: f.append(r["pnl"] / (mv * r["lots"])) if len(f) < 50: return None f.sort() return f[len(f) // 2], len(f) def sl_breite_atr_m5(): """Wie breit ist der Initial-SL WIRKLICH, in ATR_M5?""" db = _db() v = [abs(r["entry_price"] - r["sl_at_entry"]) / r["ctx_atr_m5"] for r in db.execute("""SELECT entry_price,sl_at_entry,ctx_atr_m5 FROM trades WHERE sl_at_entry IS NOT NULL AND entry_price>0 AND ctx_atr_m5 IS NOT NULL AND ctx_atr_m5>0 AND exit_time >= strftime('%s','now')-30*86400""")] if len(v) < 30: return None v.sort() return v[len(v) // 2], len(v) def margin_pro_lot(): """Gebundene Margin je Lot — EXAKT aus der offenen Position. ⚠⚠ EIGENER FEHLER, korrigiert (2026-08-17): der erste Entwurf rechnete `Balance × margin_pct / Median-Lots`. Das unterstellt, jede Position sei auf exakt 95 % der Balance gesized — und lieferte prompt 564,92 gegen die dokumentierten 405, obwohl die Broker-Eigenschaft (Hebel) sich gar nicht geaendert hat. Ein Waechter, der seine eigene Herleitung falsch macht, erzeugt genau die Fehlalarme, gegen die er gebaut ist. ➜ Jetzt der exakte Wert: `position.margin / position.lots` aus dem Snapshot (der Broker rechnet ihn selbst). Ohne offene Position: nicht pruefbar — das ist ehrlicher als eine Schaetzung. """ try: import json import urllib.request d = json.load(urllib.request.urlopen( "http://127.0.0.1:8000/api/snapshot", timeout=5)) p = d.get("position") or {} m, l = p.get("margin"), p.get("lots") except Exception: return None if not m or not l or l <= 0: return None return m / l, 1 _ELLIOTT_ENTMACHTET = "2026-08-06" # seither ist das Gewicht per Definition 0 def elliott_stimmanteil(): """Anteil der Verdict-Zeilen MIT Elliott-Gewicht — nur im dokumentierten Fenster. ⚠⚠ EIGENER FEHLER, korrigiert: der erste Entwurf zaehlte ueber ALLE Zeilen und meldete 53,07 % gegen die dokumentierten 99,9 %. Beide Zahlen stimmen — nur fuer verschiedene Zeitraeume: das Modul wurde am 06.08. entmachtet (Gewicht 1,0 → 0), seither ist `elliott_w > 0` per Konstruktion falsch. Der Kennzahl fehlte schlicht ihr Gueltigkeitsfenster. """ db = _db() try: a, b = db.execute( "SELECT COUNT(*), SUM(elliott_w > 0) FROM verdict_votes " "WHERE ts < strftime('%s',?)", (_ELLIOTT_ENTMACHTET,)).fetchone() except sqlite3.OperationalError: return None if not a: return None return 100.0 * (b or 0) / a, a # ── Was in der Doku steht ──────────────────────────────────────────────────── def dok_zahl(muster: str): try: s = open(DOK, encoding="utf-8").read() except OSError: return None m = re.search(muster, s) if not m: return None return float(m.group(1).replace(",", ".")) PRUEFUNGEN = [ # (Name, Ableitung, Doku-Muster, Toleranz in %, Begruendung der Toleranz) ("EUR je $ und Lot", eur_pro_dollar_lot, r"EUR-Faktor (\d+[,.]\d+) €/\$/Lot", 3.0, "3 % — Swap und Waehrungskurs schwanken leicht"), ("Initial-SL in ATR_M5", sl_breite_atr_m5, r"durchgehend (\d+[,.]\d+)×ATR_M5 \(= der Live-Stop", 10.0, "10 % — der ATR selbst schwankt, der Median ist traege"), ("Margin je Lot (EUR)", margin_pro_lot, r"beträgt \*\*~(\d+) €/Lot\*\*", 15.0, "15 % — haengt an Hebel und Kurs"), ("Elliott-Stimmanteil (%)", elliott_stimmanteil, r"`elliott_w > 0` in [\d.]+ von [\d.]+ Zeilen \((\d+[,.]\d+) %\)", 5.0, "5 Pp — reine Auszaehlung, sollte stabil sein"), ] def main() -> int: print("\n ── KONSTANTEN-WAECHTER ────────────────────────────────────────") print(" Leitet die tragenden Zahlen aus den DATEN ab und haelt sie") print(" gegen CLAUDE.md. Aendert nichts.\n") befunde = 0 for name, fn, muster, tol, warum in PRUEFUNGEN: try: erg = fn() except Exception as e: print(f" {name:<26} ⚠ nicht pruefbar ({type(e).__name__})") continue if erg is None: print(f" {name:<26} — zu wenige Daten / Server aus") continue wert, n = erg dok = dok_zahl(muster) if dok is None: print(f" {name:<26} Daten {wert:>9.2f} (n={n}) " f"⚠ in CLAUDE.md nicht gefunden") continue abw = abs(wert - dok) / dok * 100 if dok else 0 flag = "✅" if abw <= tol else "❌ VERALTET" if abw > tol: befunde += 1 print(f" {name:<26} Daten {wert:>9.2f} · Doku {dok:>9.2f} · " f"Abw {abw:>5.1f} % {flag}") if abw > tol: print(f" → Toleranz {tol} % ({warum})") print(f"\n ── SCHALTER: ini gegen CLAUDE.md ──────────────────────────────") try: from core.live_config import LIVE_CFG s = open(DOK, encoding="utf-8").read() paare = [("auto_emergency_margin_pct", r"auto_emergency_margin_pct\s*=\s*(\d+)")] import configparser c = configparser.ConfigParser(inline_comment_prefixes=("#", ";")) c.read(os.path.join(HIER, "oil_widget_config.ini"), encoding="utf-8") for key, mus in paare: ist = c["trading"].get(key) treffer = re.findall(mus, s) soll = treffer[-1] if treffer else None gleich = soll is None or str(ist) == str(soll) if not gleich: befunde += 1 print(f" {key:<26} ini={ist} Doku={soll} " f"{'✅' if gleich else '❌ WIDERSPRUCH'}") print(f" (Live-Schalter siehe `python -m core.live_config`)") except Exception as e: print(f" ⚠ Schalter-Vergleich nicht moeglich: {e}") print(f"\n ── ERGEBNIS: {befunde} Befund(e)") return 1 if befunde else 0 if __name__ == "__main__": raise SystemExit(main())