"""DIE Live-Parameter — EINE Quelle für Backtests. ⚠⚠ WARUM ES DAS GIBT. Ein Backtest, der einen geteilten Kern aufruft, aber dessen **Modul-Defaults** übernimmt, misst eine andere Welt als die laufende. Das ist im Projekt DREIMAL passiert: · 2026-08-05 `set_entry_room(0.6)` vergessen → `_room_gate` war ein No-op; die Population schrumpfte von 967 auf 295, als es korrigiert war. · 2026-08-07 der Level-Cluster war NACHGEBAUT und wich ab (4,5 % der Zeitpunkte hatten einen anderen Widerstand). · 2026-08-13 `squeeze_scan.ATRMIN_STD` = 0,12 genommen statt der **live** gültigen 0,06 → H1 127 gegen H2 483 Einstiege, also eine völlig andere Population. Aufgefallen nur an der Schieflage. **Ein aufgerufenes Gate ist nicht dasselbe wie ein aktives Gate.** Deshalb: wer einen Backtest schreibt, holt die Werte HIER — nicht aus dem Gedächtnis, nicht aus dem Modul-Default, nicht aus CLAUDE.md. from core.live_config import LIVE_CFG scan(..., atr_min=LIVE_CFG.squeeze_atr_min, box_n=LIVE_CFG.squeeze_box_n) ⚠ Gelesen wird die ECHTE `oil_widget_config.ini` PLUS `runtime_state.json` — und zwar in derselben Vorrangfolge wie beim Engine-Start (runtime gewinnt). Genau diese Reihenfolge hat am 31.07. dafür gesorgt, dass `auto_signal` unbemerkt lief, obwohl die ini `false` sagte. ⚠ Diese Datei trifft KEINE Entscheidungen und ändert nichts. Sie liest nur. """ from __future__ import annotations import configparser import json import os from dataclasses import dataclass, field _HIER = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _INI = os.path.join(_HIER, "oil_widget_config.ini") _RT = os.path.join(_HIER, "runtime_state.json") def _lies() -> tuple[dict, dict]: c = configparser.ConfigParser(inline_comment_prefixes=("#", ";")) try: c.read(_INI, encoding="utf-8") t = dict(c["trading"]) if c.has_section("trading") else {} except Exception: t = {} try: with open(_RT, encoding="utf-8") as f: rt = json.load(f) except Exception: rt = {} return t, rt def _f(d: dict, k: str, std: float) -> float: try: return float(d.get(k, std)) except (TypeError, ValueError): return std def _i(d: dict, k: str, std: int) -> int: try: return int(float(d.get(k, std))) except (TypeError, ValueError): return std def _b(d: dict, rt: dict, k: str, std: bool) -> bool: """⚠ runtime_state gewinnt — genau wie beim Engine-Start.""" if k in rt: return bool(rt[k]) v = str(d.get(k, str(std))).strip().lower() return v in ("true", "1", "yes", "ja") @dataclass(frozen=True) class LiveConfig: # ── Squeeze (der einzige validierte Auto-Entry) ─────────────────────── squeeze_box_n: int = 10 # `wave_rec._SQ_N` — NICHT squeeze_scan.BOX_N_STD (12) squeeze_k: float = 0.1 squeeze_mult: float = 2.5 squeeze_atr_min: float = 0.06 # ⚠ Modul-Default ist 0,12 — live gilt 0,06 # ── Signal-Gates ───────────────────────────────────────────────────── breakout_k: float = 0.3 entry_room_atr: float = 0.6 min_conf: int = 55 stretch_max: float = 3.5 reversal_enabled: bool = False # ── Exit ───────────────────────────────────────────────────────────── sr_close_pbreak: float = 0.35 adverse_15min_atr: float = 0.0 auto_flip_close: bool = False # ── Ausfuehrung / Risiko ───────────────────────────────────────────── squeeze_pending_entry: bool = True squeeze_max_chase_atr: float = 0.20 margin_buffer_pct: float = 95.0 risk_pct: float = 0.0 daily_loss_limit_pct: float = 0.0 # ── Schalter (runtime_state hat Vorrang) ───────────────────────────── auto_squeeze: bool = False auto_sr_close: bool = True squeeze_night_hours: tuple = field(default_factory=tuple) # ── Abgeleitete Groessen, die Backtests brauchen ───────────────────── # Der Live-SL kommt aus ATR_M15; ueber 121 echte Trades (13.08.) betraegt # er im Median 3,4xATR_M5. Backtests rechnen in ATR_M5 — deshalb hier. sl_atr_m5_aequivalent: float = 3.4 eur_pro_dollar_lot: float = 86.90 # aus 1.051 Trades geschaetzt (13.08.) @classmethod def laden(cls) -> "LiveConfig": t, rt = _lies() std = cls() nh = str(t.get("auto_squeeze_night_hours", "")).strip() stunden = tuple(int(x) for x in nh.split(",") if x.strip().isdigit()) if nh else () return cls( squeeze_atr_min=std.squeeze_atr_min, # lebt im Code, nicht in der ini squeeze_box_n=std.squeeze_box_n, squeeze_k=std.squeeze_k, squeeze_mult=std.squeeze_mult, breakout_k=_f(t, "breakout_k", std.breakout_k), entry_room_atr=_f(t, "entry_room_atr", std.entry_room_atr), min_conf=std.min_conf, stretch_max=std.stretch_max, reversal_enabled=_b(t, {}, "reversal_enabled", std.reversal_enabled), sr_close_pbreak=_f(t, "sr_close_pbreak", std.sr_close_pbreak), adverse_15min_atr=_f(t, "adverse_15min_atr", std.adverse_15min_atr), auto_flip_close=_b(t, {}, "auto_flip_close", std.auto_flip_close), squeeze_pending_entry=_b(t, {}, "squeeze_pending_entry", std.squeeze_pending_entry), squeeze_max_chase_atr=_f(t, "squeeze_max_chase_atr", std.squeeze_max_chase_atr), margin_buffer_pct=_f(t, "margin_buffer_pct", std.margin_buffer_pct), risk_pct=_f(t, "risk_pct", std.risk_pct), daily_loss_limit_pct=_f(t, "daily_loss_limit_pct", std.daily_loss_limit_pct), auto_squeeze=_b(t, rt, "auto_squeeze", std.auto_squeeze), auto_sr_close=_b(t, rt, "auto_sr_close", std.auto_sr_close), squeeze_night_hours=stunden, sl_atr_m5_aequivalent=std.sl_atr_m5_aequivalent, eur_pro_dollar_lot=std.eur_pro_dollar_lot, ) def zeig(self) -> str: z = [" LIVE-KONFIGURATION (aus ini + runtime_state, Vorrang wie beim Start)"] for k, v in sorted(self.__dict__.items()): z.append(f" {k:<26} {v}") return "\n".join(z) LIVE_CFG = LiveConfig.laden() if __name__ == "__main__": print(LIVE_CFG.zeig())