#!/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())
