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>
211 lines
8.3 KiB
Python
211 lines
8.3 KiB
Python
#!/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())
|