Kegel mit Adaptive Conformal Inference nachkalibriert (v=158)

Der Kegel war seit dem 31.07. dokumentiert 3-5 Pp ZU ENG - 80 % nominal gegen
real 76,7 / 76,6 / 75,3 % - und blieb es, weil Nachfitten das Problem nur
verschiebt. ACI justiert stattdessen ONLINE nach jedem aufgeloesten Fenster und
konvergiert nachweislich gegen die Zielabdeckung, auch unter Drift.

EHRLICH ZUR BAUART: die Lehrbuch-Form justiert alpha und schlaegt das Quantil
nach. Hier liegt nur eine feste Quantil-Tabelle vor, kein Verteilungsobjekt -
umgesetzt ist die multiplikative Variante:
    s <- s * exp(gamma * (1{verfehlt} - alpha_ziel)),  gamma 0,02, Deckel 0,7-2,0
Dieselbe Regelungsidee, aber die Konvergenz-GARANTIE der Originalform ist damit
nicht woertlich uebertragen.

Verifiziert: bei exakt 20 % Verfehlern bleibt die Skala bei 1,0000 - der
Fixpunkt stimmt. Simulation aus der dokumentierten Lage (76,6 %): nach ~50
Checks um 80 %. Die Skala pendelt um das Ziel statt exakt zu treffen - das ist
das bekannte ACI-Verhalten (garantiert ist die Langfrist-Abdeckung).

NICHT-UEBERLAPPEND geloggt (neue Tabelle cone_checks): ein 120-min-Fenster alle
5 min waere 24-fach ueberlappend - genau die Cluster-Verzerrung, an der
analyze_hl_funding.py aufgelaufen ist und die die Faelligkeitsbedingung
pbreak_accuracy_v2 um das Zehnfache danebenliegen liess. Je Horizont startet ein
neuer Check erst, wenn der vorige ablief -> 48/24/12 Checks je Tag.

Neustart-fest ueber runtime_state.json. build() ohne skalen liefert bitgenau die
alten Werte (additiv geprueft).

ZWEI EIGENE PLATZIERUNGSFEHLER, beide gefangen: der Lade-Block landete zuerst
HINTER den except-Klauseln von _load_runtime_state, wo st nicht mehr existiert;
und beim M15-Setup landete ein Block mitten in einem mehrzeiligen Aufruf. Der
erste waere stillschweigend nie gelaufen - gefunden nur, weil ich die Funktion
danach gelesen habe statt dem gruenen py_compile zu trauen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-08 23:45:26 +02:00
co-authored by Claude Opus 5
parent 86c432f791
commit b8705e297f
6 changed files with 244 additions and 11 deletions
+101 -2
View File
@@ -698,6 +698,9 @@ class TradingEngine:
# geschlossen hat.
try: self._evaluate_m15_states() # ~alle 5 min, self-throttled
except Exception as me: log.warning(f"m15_states: {me}")
# Kegel-Abdeckung + ACI-Nachjustierung (2026-08-08)
try: self._evaluate_cone_checks()
except Exception as ce: log.warning(f"cone_checks: {ce}")
# Wirtschaftskalender (self-throttled ~1 h, außerhalb des mt5_lock
# unkritisch — reiner HTTP-Fetch mit Timeout)
# HL-Kontext (Funding/OI/Premium) — reine Anzeige.
@@ -2101,6 +2104,13 @@ class TradingEngine:
from core.config import set_margin_buffer as _setp, get_margin_buffer
_setp(float(st["margin_pct"] or 0) or 95.0)
self._margin_pct = round(get_margin_buffer() * 100.0, 1)
if "cone_skalen" in st:
# ⚠ JSON liefert String-Schluessel → auf int zurueckholen, sonst
# findet `cone.build` sie nicht (es sucht nach `bars` als int).
# ⚠ Der Block MUSS im `try` stehen: der erste Entwurf landete
# hinter den except-Klauseln, wo `st` gar nicht mehr existiert.
self._cone_skalen = {int(k): float(v)
for k, v in (st["cone_skalen"] or {}).items()}
log.info(f"Runtime-State geladen: BRK={'AN' if self._auto_squeeze else 'aus'} · "
f"S/R-Close={'AN' if self._auto_sr_close else 'aus'} · "
f"MinGewinn={self._sr_close_min_gain:+.2f} · "
@@ -2121,7 +2131,15 @@ class TradingEngine:
"auto_sr_close": self._auto_sr_close,
"sr_close_min_gain": self._sr_close_min_gain,
"manual_margin": getattr(self, "_manual_margin", 0.0),
"margin_pct": getattr(self, "_margin_pct", 0.0)}, f)
"margin_pct": getattr(self, "_margin_pct", 0.0),
# Kegel-ACI-Skalen (2026-08-08): ohne Persistenz waere
# die muehsam erlernte Kalibrierung nach jedem Neustart
# weg und liefe von 1,0 neu an. ⚠ JSON-Schluessel sind
# Strings — `cone.build` und der ACI-Schritt lesen
# deshalb beide Formen (int UND str).
"cone_skalen": {str(k): v for k, v in
(getattr(self, "_cone_skalen", None)
or {}).items()}}, f)
os.replace(tmp, self._runtime_state_path)
except Exception as e:
log.warning(f"runtime_state speichern: {e}")
@@ -3503,6 +3521,85 @@ class TradingEngine:
except Exception as e:
log.debug(f"m15_state log: {e}")
def _log_cone_check(self, cone: dict | None):
"""Legt je Horizont EINEN nicht-ueberlappenden Abdeckungs-Check an.
⚠⚠ NICHT-UEBERLAPPEND ist hier kein Detail: ein 120-min-Fenster alle
5 min waere 24-fach ueberlappend. Genau daran ist `analyze_hl_funding.py`
(06.08.) aufgelaufen, und die Faelligkeitsbedingung `pbreak_accuracy_v2`
ueberschaetzte damit die Beweismenge um das Zehnfache (07.08.). Ein neuer
Check startet je Horizont erst, wenn der vorige abgelaufen ist."""
try:
if not cone or not self.history:
return
now = time.time()
letzte = getattr(self, "_cone_check_ts", None)
if letzte is None:
letzte = self._cone_check_ts = {}
for lv in cone.get("levels") or []:
if lv.get("band") != 80:
continue
b = lv.get("bars")
fenster = (lv.get("minutes") or 0) * 60
if not b or not fenster:
continue
if now - letzte.get(b, 0.0) < fenster:
continue # voriger Check laeuft noch
letzte[b] = now
self.history.log_cone_check(
bars=b, band=80, price=cone.get("price") or 0.0,
lo=lv["lo"], hi=lv["hi"], skala=lv.get("skala") or 1.0)
except Exception as e:
log.debug(f"cone_check log: {e}")
def _evaluate_cone_checks(self):
"""Loest faellige Kegel-Checks auf und fuehrt den ACI-Schritt aus.
⚠ Der Kegel war dokumentiert **35 Pp zu eng** (80 % nominal → real
76,7 / 76,6 / 75,3 %) und blieb es seit dem 31.07., weil Nachfitten das
Problem nur verschiebt. ACI justiert stattdessen laufend nach.
⚠ `candles_m1.time` ist ROHE Broker-Zeit — Offset noetig."""
now = time.time()
if now - getattr(self, "_cone_eval_ts", 0.0) < 300:
return
self._cone_eval_ts = now
try:
import sqlite3
from core.cone import MINUTES, aci_update
off = self.trader._broker_offset_s()
con = sqlite3.connect(HISTORY_DB_FILE, timeout=5.0)
con.row_factory = sqlite3.Row
offen = con.execute(
"SELECT id, ts, bars, band, lo, hi, skala FROM cone_checks "
"WHERE done IS NULL OR done = 0").fetchall()
skalen = dict(getattr(self, "_cone_skalen", {}) or {})
for r in offen:
ziel = r["ts"] + MINUTES.get(r["bars"], 30) * 60
if now < ziel:
continue
row = con.execute(
"SELECT c FROM candles_m1 WHERE time <= ? AND time > ? "
"ORDER BY time DESC LIMIT 1",
(ziel + off, ziel + off - 900)).fetchone()
if not row:
if now >= r["ts"] + 6 * 3600: # keine Bars mehr zu erwarten
con.execute("UPDATE cone_checks SET done=1 WHERE id=?", (r["id"],))
continue
drin = 1 if r["lo"] <= row["c"] <= r["hi"] else 0
con.execute("UPDATE cone_checks SET drin=?, done=1 WHERE id=?",
(drin, r["id"]))
s = float(skalen.get(r["bars"]) or 1.0)
skalen[r["bars"]] = aci_update(s, verfehlt=(drin == 0),
band=r["band"] or 80)
con.commit(); con.close()
if skalen != getattr(self, "_cone_skalen", None):
self._cone_skalen = skalen
self._save_runtime_state() # Neustart-fest
log.info("Kegel-ACI: Skalen " + ", ".join(
f"{k}{v:.3f}" for k, v in sorted(skalen.items())))
except Exception as e:
log.debug(f"cone_checks auswerten: {e}")
def _evaluate_m15_states(self):
"""Traegt den Forward-Return offener M15-Zustaende nach.
@@ -3771,7 +3868,8 @@ class TradingEngine:
stop_approach = self._stop_approach_hint(market, position, wave_snap)
_cone = _cone_build(
(market or {}).get("bid"),
float(((wave_snap or {}).get("pb_feats") or {}).get("atr") or 0.0))
float(((wave_snap or {}).get("pb_feats") or {}).get("atr") or 0.0),
skalen=getattr(self, "_cone_skalen", None))
# M15-Setup EINMAL rechnen (nicht im Dict), damit die Selbstmessung
# daran haengen kann — ein zweiter Aufruf waere Arbeit fuer dieselben
# Zahlen (der Konsens-Pfeil-Fehler).
@@ -3783,6 +3881,7 @@ class TradingEngine:
_m15 = self._m15_setup(market, wave_snap, _cone,
self._alignment_cached())
self._log_m15_state(_m15, market)
self._log_cone_check(_cone)
# Für den KI-Copiloten festhalten (2026-08-02) — er bekam bis dahin nur
# das Wellen-Signal und sah ausgerechnet die beiden kalibrierten Größen
# des Projekts nicht. Hier nur ZUWEISEN, nicht neu rechnen.