"""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())
