"""
core/config.py — Globale Konstanten + Config-Loader
=====================================================
Zentrale Stelle für alle Magic-Numbers, Farben, Zeitintervalle
und das Laden der `oil_widget_config.ini`.
"""

from __future__ import annotations
import configparser
from pathlib import Path

import MetaTrader5 as mt5

from core.logger import get_logger

log = get_logger("config")


# ══════════════════════════════════════════════
#  PFADE
# ══════════════════════════════════════════════
PROJECT_ROOT    = Path(__file__).parent.parent
CONFIG_FILE     = PROJECT_ROOT / "oil_widget_config.ini"
HISTORY_DB_FILE = PROJECT_ROOT / "oil_widget_history.db"


# ══════════════════════════════════════════════
#  TRADING-KONSTANTEN
# ══════════════════════════════════════════════
SYMBOL_CANDIDATES = [
    # WTI / USOIL — gehandeltes Instrument (SpotCrude). Reine WTI-Kandidatenliste.
    # (Brent-Umstellung 2026-07-09 wurde am selben Tag zurückgenommen.)
    "SpotCrude", "USOIL", "WTI", "XTIUSD", "WTICOUSD", "OIL.WTI", "CRUDE.WTI",
    "USOilSpot", "SpotWTI", "WTISpot", "USCrude", "USOilCash",
]
MARGIN_BUFFER     = 0.90      # Default; Laufzeitwert aus `[trading] margin_buffer_pct`
                              # (aktuell 90). Lot-Größe = ~90 % der freien Margin
                              # (User-Vorgabe) via `calc_lots`.
RISK_PER_TRADE    = 0.015     # AKTIV: Verlust beim Initial-SL ≈ 1,5 % der Equity je
                              # Trade (`calc_lots_risk`). Laufzeitwert aus
                              # `[trading] risk_pct`. 0 = aus → Fallback margin-basiert
                              # (MARGIN_BUFFER). Behebt die großen EUR-Verluste
                              # (90 %-Margin × 2×ATR-SL) — `docs/risiko-management.md`.
DEVIATION         = 30
MAGIC             = 230002


def set_margin_buffer(pct: float) -> float:
    """Setzt den globalen Margin-Buffer zur Laufzeit (1-99 %)."""
    global MARGIN_BUFFER
    MARGIN_BUFFER = max(0.01, min(0.99, pct / 100.0))
    log.info(f"Margin-Buffer geändert: {MARGIN_BUFFER*100:.0f}%")
    return MARGIN_BUFFER


def get_margin_buffer() -> float:
    """Aktuellen Margin-Buffer abrufen (Module-Level globals sind Snapshot-frei)."""
    return MARGIN_BUFFER


# ── Manuell vorgegebene Einsatz-Margin (User-Wunsch 2026-08-04) ───────────────
# 0 = AUS → automatische Größe wie bisher (MARGIN_BUFFER % der freien Margin bzw.
# risiko-basiert bei RISK_PER_TRADE > 0). > 0 = die Position wird auf GENAU diesen
# Margin-Einsatz in Kontowährung gerechnet.
# ⚠ Die freie Margin bleibt IMMER die Obergrenze — ein zu hoher Wunschwert wird
# gedeckelt, nicht durchgereicht (sonst Broker-Reject mit kryptischem retcode).
MANUAL_MARGIN = 0.0


def set_manual_margin(eur: float) -> float:
    """Feste Einsatz-Margin in Kontowährung setzen. 0 = automatisch."""
    global MANUAL_MARGIN
    try:
        MANUAL_MARGIN = max(0.0, float(eur or 0))
    except (TypeError, ValueError):
        MANUAL_MARGIN = 0.0
    log.info(f"Einsatz-Margin: "
             + (f"{MANUAL_MARGIN:.2f} (manuell)" if MANUAL_MARGIN > 0
                else "automatisch"))
    return MANUAL_MARGIN


def get_manual_margin() -> float:
    return MANUAL_MARGIN


def set_risk_per_trade(pct: float) -> float:
    """Setzt das Risiko pro Trade zur Laufzeit (% der Equity). 0 = aus (Fallback
    margin-basiert). Sinnvoll 0,5–3 %."""
    global RISK_PER_TRADE
    RISK_PER_TRADE = max(0.0, min(0.10, pct / 100.0))
    log.info(f"Risiko/Trade geändert: {RISK_PER_TRADE*100:.2f}% "
             f"({'risiko-basiert' if RISK_PER_TRADE > 0 else 'aus → margin-basiert'})")
    return RISK_PER_TRADE


def get_risk_per_trade() -> float:
    return RISK_PER_TRADE


# ══════════════════════════════════════════════
#  STOP-LOSS / TAKE-PROFIT
# ══════════════════════════════════════════════
SL_TF             = mt5.TIMEFRAME_M15
SL_LOOKBACK       = 60
SL_WINDOW         = 4
SL_BUFFER_TICKS   = 5
INIT_SL_FALLBACK  = 0.012     # 1.2 %
INIT_SL_MIN_ATR   = 1.8       # Initial-SL-Distanz MIN 1.8×ATR(M15) — verhindert
                              # zu enge Stops, wenn der nächste Pivot direkt am
                              # Einstieg liegt (sonst Stop nach Sekunden auf Rauschen).
INIT_SL_MAX_ATR   = 2.2       # Initial-SL-Distanz MAX 2.2×ATR(M15). Band [1.8 … 2.2]
                              # (Ziel 2,0): Exit-Simulation (backtest_exit.py) zeigte,
                              # dass 1,2–1,5 zu eng war (MAE ~1,7×ATR, 31–37 % Früh-
                              # stopps); 2,0 hebt Ø-R +32 %, PF 1,33→1,39, Worst-Case
                              # auf 2,0×ATR begrenzt. Doku: docs/exit-simulation.md.

INIT_TP_RR        = 2.0


# ══════════════════════════════════════════════
#  ANALYSE
# ══════════════════════════════════════════════
M15_BARS          = 60
EMA_FAST          = 5
EMA_SLOW          = 13
PIVOT_WINDOW      = 3
ANGLE_LR_BARS     = 14

SR_LOOKBACK       = 150
SR_PIVOT_WIN      = 4
SR_MIN_TOUCHES    = 3
SR_TOL_ATR_FACTOR = 0.5
SR_MAX_LINES      = 2
TL_MIN_PIVOTS     = 2


# ══════════════════════════════════════════════
#  ANALYSE-DATENFENSTER
# ══════════════════════════════════════════════
CHART_BARS        = 50    # Bar-Fenster der M15-Analyse (Trendlinien-Mapping)


# ══════════════════════════════════════════════
#  TIMINGS
# ══════════════════════════════════════════════
PRICE_REFRESH_MS  = 500
TREND_REFRESH_MS  = 5_000
POS_REFRESH_MS    = 1_000
BLINK_INTERVAL_MS = 380
BLINK_COUNT_MAX   = 3
AUTO_BTN_ON_BG    = "#1f6feb"   # Blau – AUTO-Button aktiv
AUTO_BTN_ON_FG    = "#ffffff"
AUTO_BTN_ON_ACT   = "#3081f0"
AUTO_BTN_OFF_BG   = "#1a1e24"
AUTO_BTN_OFF_FG   = "#8b949e"

WIDGET_X          = 20
WIDGET_Y          = 55


# ══════════════════════════════════════════════
#  FARBEN
# ══════════════════════════════════════════════
BG             = "#0d1117"
BORDER_NEUTRAL = "#30363d"
BORDER_POS     = "#1a4731"
BORDER_NEG     = "#3d1a1a"
BORDER_BLINK   = "#1f6feb"
BLINK_BG       = "#0d1f3c"
ACCENT         = "#e8a000"
UP_COLOR       = "#3fb950"
DOWN_COLOR     = "#f85149"
SPREAD_COLOR   = "#58a6ff"
TEXT_DIM       = "#8b949e"
TEXT_BRIGHT    = "#e6edf3"
GRID_COLOR     = "#161b22"
EMA_F_COLOR    = "#e8a000"
EMA_S_COLOR    = "#58a6ff"

# Buttons
BTN_LONG_BG    = "#1a6e35"; BTN_LONG_FG  = "#ffffff"; BTN_LONG_ACT  = "#26a050"
BTN_SHORT_BG   = "#8b1a1a"; BTN_SHORT_FG = "#ffffff"; BTN_SHORT_ACT = "#b02020"
BTN_CLOSE_BG   = "#2d2000"; BTN_CLOSE_FG = "#e8a000"; BTN_CLOSE_ACT = "#4a3600"
BTN_DIS_BG     = "#1a1e24"; BTN_DIS_FG   = "#3a4049"


# ══════════════════════════════════════════════
#  CONFIG-FILE LOADER
# ══════════════════════════════════════════════
DEFAULT_CONFIG = {
    "openai": {
        "api_key":         "",
        "model":           "gpt-4o-mini-search-preview",
        "auto_refresh":    "true",
        "refresh_min":     "60",
        "search_context":  "low",
    },
    "gemini": {
        # Key gehört NUR in die lokale oil_widget_config.ini, nie in den Code
        "api_key":  "",
        "model":    "gemini-2.0-flash",
        "enabled":  "true",
    },
    "anthropic": {
        # Claude-Key (sk-ant-…) — nur in die lokale config.ini, nie in den Code
        "api_key":  "",
        "model":    "claude-opus-4-8",
    },
    "ollama": {
        # Lokales LLM via Ollama (kein Key, kein Quota, läuft offline)
        "base_url":   "http://localhost:11434",
        "model":      "qwen2.5:7b",
        # Modell zwischen den Ticks geladen halten (GPU-resident, ~4,7 GB VRAM).
        # "0" entlädt sofort nach jedem Aufruf, "-1" hält unbegrenzt.
        "keep_alive": "30m",
    },
    "agent": {
        "enabled":     "true",      # KI-Copilot automatisch laufen lassen
        "provider":    "local",     # local (Ollama) | claude | gemini | openai | zai | kimi
        "model":       "",          # leer = Provider-Default
        "refresh_min": "5",         # Intervall der Lagebeurteilung
        "telegram":    "false",     # Beurteilung zusätzlich per Telegram pushen
    },
    "kimi": {
        # Kimi / Moonshot AI (OpenAI-kompatibel). Key gehört in die ini (Secret).
        # Endpoint = international .ai (der .cn-Endpoint akzeptiert diesen Key NICHT).
        # kimi-k2.6 = Reasoning-Modell → verbraucht ~400 reasoning_tokens VOR dem
        # content; _call_kimi setzt max_tokens großzügig + temperature=1 (Pflicht).
        "api_key":  "",
        "base_url": "https://api.moonshot.ai/v1",
        "model":    "kimi-k2.6",
    },
    "deepseek": {
        # DeepSeek (OpenAI-kompatibel, api.deepseek.com). deepseek-v4-flash ist ein
        # Reasoning-Modell (content nach reasoning_content → max_tokens großzügig).
        # ⚠ KEINE Web-Suche (rein Chat) → NICHT für daily_levels geeignet, nur Copilot.
        "api_key":  "",
        "base_url": "https://api.deepseek.com",
        "model":    "deepseek-v4-flash",
    },
    "web": {
        # Mobile-Web-Backend (server.py). Lese-Endpoints (Snapshot/WebSocket)
        # sind offen; Trade-Aktionen brauchen diesen Token im X-Auth-Token-Header.
        # Leer = wird beim ersten Start automatisch generiert und geloggt.
        "api_token":       "",
        "require_confirm": "true",   # Bestätigungs-Dialog vor jeder Order im UI
        # Token-Pflicht für Trade-Aktionen. "false" = kein Token nötig
        # (nur sinnvoll, wenn der Zugang anderweitig abgesichert ist, z.B. WireGuard).
        "require_token":   "true",
    },
    "news": {
        "enabled":     "true",
        "refresh_min": "10",
    },
    "translation": {
        "enabled":         "true",
        "target_language": "de",
        "provider":        "google",
        "show_original":   "false",
    },
    "telegram": {
        "enabled":   "false",
        "bot_token": "",
        "chat_id":   "",
    },
    "trading": {
        "margin_buffer_pct": "90",
        # Risiko-basiertes Sizing: Verlust beim Initial-SL ≈ risk_pct % der Equity.
        # 0 = aus → margin-basiert (margin_buffer_pct). Behebt große EUR-Verluste.
        "risk_pct": "1.5",
        "last_symbol":       "",
        "atr_tf":            "Auto",
        # Quellensteuer auf Gewinn-Trades (Broker behält pro Gewinn ein).
        # DE: Abgeltungsteuer 25% + Soli 5,5% = 26.375. 0 = keine WHT.
        "wht_pct":           "0",
        # Wer wählt den Analyse-Timeframe der Empfehlung?
        #   heuristic = schnelle Volatilitäts-/Trend-Heuristik (jede Minute, kein LLM)
        #   agent     = der KI-Agent (LLM) wählt
        #   M1/M5/M15/M30/H1 = fest
        "tf_select":         "heuristic",
        # Band, aus dem die Heuristik die Basis-TF wählt. Daytrading = M1–M15
        # (M30/H1 bleiben nur Kontext: Filter/Konfluenz). tf_min = niedrigste,
        # tf_max = höchste erlaubte TF.
        "tf_min":            "M5",
        "tf_max":            "H1",
        # ATR-Breakout-Bestätigung: Signal erst handeln, wenn der Kurs k×ATR in
        # Signalrichtung lief (gemessen ~2× Edge/Trade). 0 = aus (Sofort-Einstieg).
        "breakout_k":        "1.0",
        # Fixwert-Notfall-Stop = aus (0). Der frühere 2%-Auto-Arm war ~0,76×ATR = 3×
        # enger als der Squeeze-SL und schüttelte validierte Ausbrüche raus (real
        # 2026-07-17: −48,94 €). Seit 2026-07-20 läuft stattdessen das %-GAP-NETZ
        # (auto_emergency_pct=3.0, s. u.) — nur in Kombination mit risk_pct=1.5 sinnvoll.
        "auto_emergency_loss": "0",
        # Mindest-Notfall-Stop (Kontowährung): ein manuell gesetzter Wert DARUNTER
        # wird als „aus" behandelt (Vertipper-Schutz — ein Sub-Spread-Wert schließt
        # sofort und vererbt sich per _emergency_remember auf alle Folge-Trades;
        # real 2026-07-27 killte ein −1-€-Stop jeden Trade in Sekunden). 0 = kein Floor.
        "emergency_min_eur": "3",
        # Circuit Breaker (Tagesverlust-Stopp, User-Vorgabe 2026-07-27): erreicht der
        # Tages-P&L (realisiert + offen) −pct% der Balance, schließt der Bot die
        # Position und sperrt neue Auto-Trades bis zum nächsten Tag. Überlebens-Netz
        # gegen Verlust-Kaskaden (früher abgelehnt, mit dem Autonomie-Ziel reaktiviert).
        # ⚠ Unter 80 %-Margin schützt es v. a. gegen die Serie, nicht den Einzeltrade.
        # 0 = aus.
        "daily_loss_limit_pct": "0",
        # Gewinn-mitnehmen automatisch beim Öffnen setzen (Kontowährung). 0 = aus
        # (dann nur manuell im UI). Merkt den zuletzt gesetzten Wert.
        "auto_takeprofit": "0",
        # Automatischer S/R-Close (gemessen `backtest_srclose_prob.py`): schließt eine
        # Position im PLUS selbstständig, wenn der Kurs am gegenüberliegenden Level ist
        # und das kalibrierte P(Durchbruch) unter `sr_close_pbreak` liegt (Level prallt
        # wahrscheinlich ab). true/false.
        "auto_sr_close": "true",
        # Schwelle der User-Regel „laufen lassen wenn P(Durchbruch) ≥ X, sonst close".
        # 0.60 = validiertes Optimum-Plateau (0,50–0,60 ~gleich). Band [0.30 … 0.90].
        "sr_close_pbreak": "0.60",
        # S/R-Level als CSV nach MQL5\Files schreiben (für den SR_Levels.mq5-Indikator,
        # der sie im Desktop-Terminal zeichnet). true/false.
        "export_mql5_levels": "true",
        # Mindestgewinn (Kontowährung) für den S/R-Auto-Close: Trade wird am Level
        # nur geschlossen, wenn P&L ≥ diesem Betrag. Wird bei JEDER neuen Position
        # automatisch neu gesetzt, s. `sr_close_min_gain_pct` (0 dort = aus, dann bleibt
        # dieser Wert manuell/persistiert maßgeblich). ⚠ Gemessen ist ein Mindestgewinn
        # tendenziell SCHLECHTER als ohne (`backtest_srclose_prob.py`, Mindestgewinn-
        # Sektion: verliert monoton in beiden Hälften) — bewusster User-Opt-in.
        "sr_close_min_gain": "0",
        # Gewinn-Close automatisch auf X % des EINSATZES (Margin) der jeweiligen Position
        # setzen, sobald sie eröffnet wird. Verlauf: 1,0→3,0 (2026-07-23) → 0 (2026-07-24
        # früh, „nichts beim Öffnen setzen") → **3,0 wieder AN (2026-07-24 abends,
        # User-Vorgabe — gilt NUR für den Gewinn-Close; der Notfall-Stop
        # `auto_emergency_margin_pct` bleibt 0/aus)**. 0 = aus (dann bleibt der manuell
        # gesetzte/gemerkte `sr_close_min_gain` maßgeblich).
        "sr_close_min_gain_pct": "3.0",
        # Auto-Notfall-Stop als % der Balance beim Öffnen (skaliert mit dem Konto).
        # >0 → Stop = pct% × Balance; 0 = aus. ⚠ Nur zusammen mit risk_pct>0 als
        # GAP-NETZ sinnvoll (dann pct ≈ 2× risk_pct → feuert nur bei Slippage/Gap
        # ÜBER den SL hinaus): bei Margin-Sizing ist ein %-Stop in hoher Vola enger
        # als der 2×ATR-SL und zerschießt die Squeeze-Strategie (gemessen 2026-07-17,
        # −48,94-€-Tag). 2026-07-20 kurz als 3%-Netz mit risk_pct=1.5 aktiv, mit der
        # Rückkehr zu Margin-Sizing (80 %) am selben Tag wieder AUS. Bleibt 0 — ersetzt
        # durch `auto_emergency_margin_pct` (s. u.), hat Vorrang wenn >0.
        "auto_emergency_pct": "0",
        # Notfall-Stop als % der EINSATZ-Margin der Position (analog zu
        # `sr_close_min_gain_pct`). >0 → Stop = pct% × Margin dieser Position, hat Vorrang
        # vor `auto_emergency_pct` (Balance) und dem Fixwert. 0 = AUS (User-Vorgabe
        # 2026-07-24: beim Öffnen NICHTS setzen). War kurz 3,0 (2026-07-23), wieder 0.
        # ⚠ Kopplungs-Warnung (falls reaktiviert): unter Margin-Sizing kann ein enger
        # %-Stop schneller greifen als der 2×ATR-SL.
        "auto_emergency_margin_pct": "0",
        # Entry-Raum-Gate: Signal → WARTEN, wenn das Gegenlevel (M5-Pivot in Trade-
        # Richtung) näher als X×ATR liegt (gemessen `backtest_entryroom.py` — Raum
        # <0,6 in beiden Hälften negativ: Ertrag am Level gedeckelt, Kosten fressen
        # den Rest). 0 = aus. **0,6→1,0 am 2026-07-24 (User: „baue alles ein"):**
        # 1,0 war von Anfang an in BEIDEN Hälften besser (H1 +415/H2 +266 R), Start
        # 0,6 war nur die konservative Stufe (1,0 filtert ~¾ der Signale → deutlich
        # mehr WARTEN-Phasen; gemessen trotzdem mehr Gesamtertrag).
        "entry_room_atr": "0.6",
        # Tageszeit-Gate: Komma-Liste der Stunden (Berlin), zu denen die Wellen-
        # Empfehlung WARTEN erzwingt. **Gemessener Default = 0–7,12,16** (Nacht-
        # Kostenfalle + 12/16 Uhr beidhälftig negativ, `backtest_hourly_split.py`).
        # LEER = Gate AUS. Die ini setzt es leer (User-Vorgabe 2026-07-22, bewusst
        # gegen die Messung). EIA-Blackout (Mi 15:30–16:30) ist separat, bleibt aktiv.
        "dead_hours": "0,1,2,3,4,5,6,7,12,16",
        # Autonomer Entry auf den Squeeze-Ausbruch (echte Order, nur flat). ⚠ Default
        # AUS — Autonomie nie stillschweigend an. true = Bot eröffnet selbständig.
        "auto_squeeze": "false",
        # Startup-Schonfrist (Sek.): nach Bot-Start schließt der Bot so lange KEINEN
        # laufenden Trade selbst (S/R-Close/Notfall/Time-Stop/Reverse) — verhindert
        # den Sofort-Close eines adoptierten Trades auf noch-instabilen Daten direkt
        # nach restart_server.bat. Broker-SL unberührt. 0 = aus. (User-Vorgabe 2026-07-20)
        "startup_close_grace_s": "60",
        # Stop-&-Reverse für den Auto-Squeeze = AUS (Default, User-Vorgabe 2026-07-17):
        # NUR der flat-Entry ist gemessen-validiert (2 Halbjahre, ØR +0,14…+0,23). Der
        # Reverse (Gegen-Trade bei Ausbruch sofort schließen + drehen) ist UNBELEGT,
        # verdoppelt Kosten und dreht am Ausbruch-Extrem (real 2026-07-17 18:55: Short
        # −29,64 geschlossen, dann LONG am Erschöpfungs-Top). true = Reverse an.
        "auto_squeeze_reverse": "false",
        # Dead-Hour-Guard für den Auto-Squeeze (keine Nacht-Entries 0–7 Uhr Berlin) =
        # AUS (User-Vorgabe 2026-07-17). Das live-Verlust-Argument war emergency-getrieben
        # und mit dem Notfall-Stop-Ausbau moot; der gemessene Nacht-Kosten-Befund
        # (`backtest_realcosts.py`, Spread÷Nacht-ATR 0,32–0,50) bleibt gültig → per
        # B4-Wochenmonitor prüfen, ob Nacht-Squeezes tragen. Code (`_SQUEEZE_NIGHT`,
        # `_squeeze_skip_night`) bleibt dormant/reaktivierbar (true = wieder an).
        "auto_squeeze_skip_night": "true",
        # ── Autonomer Entry auf die EMPFEHLUNG (Wave-Signal) — User-Wunsch 2026-07-30
        # („probieren wir es trotzdem mal"). ⚠ GEMESSEN NICHT TRAGFÄHIG
        # (`backtest_auto_signal.py`, 80k M5, 2 Halbjahre): H1 durchgehend NEGATIV
        # (ØR −0,05…−0,18, PF 0,78–0,93), H2 positiv (+0,06…+0,15) = Regime-Kippen;
        # deckt sich mit dem Live-Dry-Run von 2026-07-23 (WR 29 %/PF 0,64). Default AUS.
        # Der User hat das nach vollständiger Information bewusst gewollt → Schalter
        # existiert, aber mit den gemessen BESTEN Parametern und Live-Monitor (B4/B5:
        # driftet es weg → zurückbauen).
        "auto_signal": "false",
        # Mindest-Konfidenz für den Auto-Signal-Entry. 75 = gemessen beste Kombi
        # (mit Nacht-Sperre: H1 −0,053 = mildester Verlust, H2 +0,141). ⚠ conf_pct ist
        # NICHT kalibriert (`analyze_verdict_calibration.py`) — höher ist nicht monoton
        # besser, 75 ist empirisch, nicht „mehr ist mehr".
        "auto_signal_min_conf": "75",
        # Nacht-Sperre 0–7 Uhr Berlin für den Auto-Signal-Entry (gemessene Kostenfalle,
        # verbessert JEDE Variante: H1 −0,157→−0,111 bzw. −0,127→−0,053). true = an.
        "auto_signal_skip_night": "true",
        # 15-Minuten-Regel (User-Wunsch 2026-07-30 „wenn der Trade nach 15 Minuten
        # gegen die Richtung läuft schließe ihn"): steht ein BOT-eröffneter Trade
        # 15 min nach Entry mehr als X×ATR im Minus → schließen (`closed_by=adverse15`).
        # ⚠ Wert GEMESSEN kalibriert (`backtest_auto_signal.py`): „sobald im Minus"
        # (=0,0) ist die SCHLECHTESTE Variante (WR bricht 39→31 %, ØR-Netto −0,067);
        # **0,5 ist die beste Regel-Variante** (H1 −0,111→−0,079). Netto über beide
        # Hälften bringt die Regel ~nichts (+0,001 vs +0,026 ohne) — sie ist ein
        # REGIME-Schutz (hilft im schlechten, kostet im guten). 2. Bestätigung von
        # `backtest_adverse_nosignal.py`. 0 = aus.
        "adverse_15min_atr": "0.5",
        # ECHTE Liquiditäts-Wände von Hyperliquid im MT5-Chart zeichnen (User-Wunsch
        # 2026-07-30). Holt `GET /api/walls` vom HL-Dashboard (Port 8001) — das größte
        # L2-Orderbuch-Level je Seite, BASIS-KORRIGIERT auf CFD-Niveau (HL notiert
        # ~0,45 $ tiefer). Anders als R/S nicht aus Pivots geschätzt, sondern echte
        # ruhende Orders. REINE ANZEIGE — Übertragbarkeit auf den CFD wird gerade
        # gemessen (`analyze_bookflow.py` im HL-Projekt). Fail-safe: HL-Server aus →
        # keine Linien, kein Fehler. false = aus.
        "export_hl_walls": "true",
        "hl_walls_url": "http://127.0.0.1:8001/api/walls",
    },
    # (Der frühere `[setups]`-Block — Per-Setup-Toggles für den Dry-Run-Auto-Trader —
    # ist entfernt, 2026-07-23: verwaist seit dem Entfernen des Dry-Run-Traders, wurde
    # aber auch davor nirgends im Code gelesen. Die `[setups]`-Sektion kann in einer
    # bestehenden oil_widget_config.ini stehen bleiben, wird nur nicht mehr neu erzeugt.)
    # Stehende S/R-Zonen für den KI-Agenten — frei editierbar.
    # Format je Eintrag:  name = lo, hi, kind   (kind: support | demand |
    # resistance | supply | invalidation;  Einzel-Level: lo == hi)
    "zones": {
        "demand":  "74.98, 76.75, demand",
        "support": "76.40, 77.00, support",
        "res_low": "81.24, 82.64, resistance",
        "fvg":     "88.00, 90.00, resistance",
        "res_top": "94.72, 94.72, resistance",
        "invalid": "75.40, 75.80, invalidation",
    },
}


def parse_zones(cfg) -> list[dict]:
    """Liest die [zones]-Sektion in eine Liste {name, lo, hi, kind}."""
    out: list[dict] = []
    try:
        if not cfg.has_section("zones"):
            return out
        for name, val in cfg["zones"].items():
            parts = [p.strip() for p in (val or "").split(",")]
            if len(parts) < 2:
                continue
            try:
                lo, hi = float(parts[0]), float(parts[1])
            except ValueError:
                continue
            kind = parts[2].lower() if len(parts) >= 3 else "level"
            if lo > hi:
                lo, hi = hi, lo
            out.append({"name": name, "lo": lo, "hi": hi, "kind": kind})
    except Exception:
        pass
    return out


def load_config() -> configparser.ConfigParser:
    """Liest oil_widget_config.ini, ergänzt fehlende Defaults."""
    cfg = configparser.ConfigParser()
    if CONFIG_FILE.exists():
        cfg.read(CONFIG_FILE, encoding="utf-8")

    changed = False
    for section, defaults in DEFAULT_CONFIG.items():
        if section not in cfg:
            cfg[section] = {}
            changed = True
        for k, v in defaults.items():
            if k not in cfg[section]:
                cfg[section][k] = v
                changed = True

    if changed:
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            f.write("# Config für den Oil Trading Server (server.py)\n")
            f.write("# Claude-Key (KI-Copilot): https://console.anthropic.com/\n\n")
            cfg.write(f)
        log.info(f"Config aktualisiert: {CONFIG_FILE}")
        if not cfg["anthropic"]["api_key"]:
            log.warning("Trage deinen Claude-Key in [anthropic] api_key ein")

    return cfg


def save_config(cfg: configparser.ConfigParser):
    """Speichert die Config-Datei (nach Änderungen über das Widget)."""
    try:
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            cfg.write(f)
    except Exception as e:
        log.error(f"Config-Save fehlgeschlagen: {e}")
