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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9dbc5d0452
commit
792355e8f9
@@ -0,0 +1,210 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user