User will Auto-Eroeffnung bei Empfehlung trotz negativem Backtest -> gebaut, Default AUS, mit den GEMESSEN besten Parametern statt geratenen: Entry (_check_auto_signal, [trading] auto_signal=false): - min_conf=75 + Nacht-Sperre = beste Kombi (H1 -0.053 mildester Verlust, H2 +0.141 bestes Netto). NICHT weil "mehr Konfidenz besser" -- conf_pct ist unkalibriert, 75 ist empirisch. - Nur FLAT, 1x je Signal-Episode (Dedup, re-armt bei WARTEN/Flip), kein Nachkauf, kein Drehen einer Gegen-Position; erbt Circuit-Breaker + beide Cooldowns. - Setup-Tag AUTOSIG_<dir> -> in der DB separierbar fuer B4-Monitor. - Toggle POST /api/autosignal, Button "SIG" (bernstein = gemessen nicht tragfaehig), neustart-fest via runtime_state.json. 15-Min-Regel (_check_adverse15, adverse_15min_atr=0.5): - Schwelle GEMESSEN kalibriert: "sobald im Minus" ist die SCHLECHTESTE Variante (WR 39->31%, Netto -0.067 vs +0.026 ohne); 0.5xATR ist die beste (H1 -0.111 -> -0.079). Netto bringt die Regel ~nichts = Regime-Schutz. - Nur BOT-eroeffnete Trades (_bot_open_ticket) -- manuelle Diskretion bleibt. - 1x je Ticket geprueft, Startup-Grace respektiert, closed_by=adverse15. Verifiziert: 18 synthetische Szenarien (9 Entry + 9 Adverse) alle korrekt; Live-Neustart ohne Loop-Fehler, Toggle + Persistenz geprueft. v=119. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
464 lines
23 KiB
Python
464 lines
23 KiB
Python
"""
|
||
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
|
||
|
||
|
||
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",
|
||
},
|
||
# (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}")
|