1) CIRCUIT-BREAKER SCHARF -- daily_loss_limit_pct 0 -> 8. Live verifiziert:
enabled=True, limit_eur 89,44. Steht nicht in runtime_state.json, die ini
greift direkt. Begruendung: MaxDD 82,8 % auf dem gemessenen IST-Pfad,
schlechtester Einzeltrade -381 EUR.
2) B5-DEADLOCK RECHNERISCH AUFGELOEST -- die Regel ist ENTSCHIEDEN, nicht
vertagt. Stand 11/20: Verhaeltnis 0,14 (Latte 1,0), PF 0,39 (Latte 1,0).
Was die 9 Rest-Trades leisten muessten:
9/0 Gewinner -> 165,72 EUR je Trade = 12,2x der bisherige Schnitt
7/2 -> 186,17 EUR
5/4 -> 222,99 EUR
Der groesste Squeeze-Gewinn des Zeitraums war 77,34 EUR -- selbst der beste
Fall verlangt 2,1x den Rekord, neunmal in Folge. Latte nicht mehr erreichbar.
auto_squeeze bleibt FALSE. Wiederaufnahme waere eine NEUE Entscheidung mit
NEUER, vorab fixierter Latte.
3) core/kontrolle.py -- Kontroll-Fixture mit den Referenzwerten. Bricht bewusst
NICHT ab: ein Backtest darf abweichen, er muss es nur wissen und sagen.
4) core/live_config.py -- EINE Quelle der Live-Parameter (ini + runtime_state in
der Vorrangfolge des Engine-Starts). Dritte Instanz derselben Fehlerklasse:
ATRMIN_STD 0,12 statt 0,06, fehlendes set_entry_room, nachgebauter Cluster.
5) stichprobe.paarweise()/zeig_paarweise() -- gepaarter Vergleich. Anlass:
trail_start sah sequentiell besser aus, gepaart kippte das Vorzeichen
(Selektions- statt Exit-Effekt). Selbsttest in drei Richtungen bestanden.
6) tools/check_konstanten.py -- Waechter fuer veraltete Zahlen.
Erster Lauf: zwei Befunde, BEIDE Fehler in meiner eigenen Ableitung (Margin
aus Balance geschaetzt statt vom Broker gelesen; Elliott ohne
Gueltigkeitsfenster gezaehlt). Beide korrigiert.
Danach ein ECHTER Fund: meine am 13.08. notierten "~405 EUR/Lot" sind falsch,
der exakte Broker-Wert ist 712,84 -- die urspruenglich notierten 670 lagen
naeher als meine Korrektur. Lehre: eine abgeleitete Zahl ist keine gemessene.
Stand jetzt 0 Befunde.
7) Puls-Waechter bedingungs-bewusst: PULS traegt je Zeile die Vorbedingung,
bedingte Tabellen erscheinen als Hinweis statt als Ausfall. 0 Zeilen bleibt
IMMER ein Befund (der rec_outcomes-Fall). Grund: ein Waechter mit
Fehlalarmen wird ignoriert -- so ist der Divergenz-Waechter verstummt.
Pipeline gruen (85 Tests), Deployment ueber deploy.py --feld circuit_breaker
verifiziert, genau eine Instanz je Port.
Nicht angefasst: Sizing (User-Entscheidung) und die 44 Backtests mit eigener
Exit-Kopie (Migrations-Regel).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
156 lines
6.8 KiB
Python
156 lines
6.8 KiB
Python
"""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())
|