Files
AH-Oil-Trader/tools/check_konstanten.py
T
Axel HocksandClaude Opus 5 792355e8f9 Sieben Verbesserungen umgesetzt: Breaker scharf, B5 entschieden, 5 Werkzeuge
1) CIRCUIT-BREAKER SCHARF -- daily_loss_limit_pct 0 -> 8. Live verifiziert:
   enabled=True, limit_eur 89,44. Steht nicht in runtime_state.json, die ini
   greift direkt. Begruendung: MaxDD 82,8 % auf dem gemessenen IST-Pfad,
   schlechtester Einzeltrade -381 EUR.

2) B5-DEADLOCK RECHNERISCH AUFGELOEST -- die Regel ist ENTSCHIEDEN, nicht
   vertagt. Stand 11/20: Verhaeltnis 0,14 (Latte 1,0), PF 0,39 (Latte 1,0).
   Was die 9 Rest-Trades leisten muessten:
     9/0 Gewinner -> 165,72 EUR je Trade = 12,2x der bisherige Schnitt
     7/2          -> 186,17 EUR
     5/4          -> 222,99 EUR
   Der groesste Squeeze-Gewinn des Zeitraums war 77,34 EUR -- selbst der beste
   Fall verlangt 2,1x den Rekord, neunmal in Folge. Latte nicht mehr erreichbar.
   auto_squeeze bleibt FALSE. Wiederaufnahme waere eine NEUE Entscheidung mit
   NEUER, vorab fixierter Latte.

3) core/kontrolle.py -- Kontroll-Fixture mit den Referenzwerten. Bricht bewusst
   NICHT ab: ein Backtest darf abweichen, er muss es nur wissen und sagen.

4) core/live_config.py -- EINE Quelle der Live-Parameter (ini + runtime_state in
   der Vorrangfolge des Engine-Starts). Dritte Instanz derselben Fehlerklasse:
   ATRMIN_STD 0,12 statt 0,06, fehlendes set_entry_room, nachgebauter Cluster.

5) stichprobe.paarweise()/zeig_paarweise() -- gepaarter Vergleich. Anlass:
   trail_start sah sequentiell besser aus, gepaart kippte das Vorzeichen
   (Selektions- statt Exit-Effekt). Selbsttest in drei Richtungen bestanden.

6) tools/check_konstanten.py -- Waechter fuer veraltete Zahlen.
   Erster Lauf: zwei Befunde, BEIDE Fehler in meiner eigenen Ableitung (Margin
   aus Balance geschaetzt statt vom Broker gelesen; Elliott ohne
   Gueltigkeitsfenster gezaehlt). Beide korrigiert.
   Danach ein ECHTER Fund: meine am 13.08. notierten "~405 EUR/Lot" sind falsch,
   der exakte Broker-Wert ist 712,84 -- die urspruenglich notierten 670 lagen
   naeher als meine Korrektur. Lehre: eine abgeleitete Zahl ist keine gemessene.
   Stand jetzt 0 Befunde.

7) Puls-Waechter bedingungs-bewusst: PULS traegt je Zeile die Vorbedingung,
   bedingte Tabellen erscheinen als Hinweis statt als Ausfall. 0 Zeilen bleibt
   IMMER ein Befund (der rec_outcomes-Fall). Grund: ein Waechter mit
   Fehlalarmen wird ignoriert -- so ist der Divergenz-Waechter verstummt.

Pipeline gruen (85 Tests), Deployment ueber deploy.py --feld circuit_breaker
verifiziert, genau eine Instanz je Port.
Nicht angefasst: Sizing (User-Entscheidung) und die 44 Backtests mit eigener
Exit-Kopie (Migrations-Regel).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 00:57:38 +02:00

211 lines
8.3 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
"""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())