C: Wahrscheinlichkeits-Kegel (kalibriert) + Config-Waechter im Reminder
C - core/cone.py + Kachel #card-cone + MT5 v1.31 (CN;<min>;<lo>;<hi>): "Erwartete Spanne" statt "Kursvorhersage". Der User wollte eingezeichnete Verlaufspfade; bewusst NICHT gebaut - sie suggerieren eine Praezision, die es nicht gibt (Lehre aus analyze_verdict_calibration.py). Der Kegel sagt wie WEIT der Kurs in 30/60/120 min plausibel laeuft, nicht wohin. analyze_cone.py: r(h)=(C[i+h]-C[i])/ATR[i], Fit auf H1, Abdeckung gemessen auf H2 (echtes out-of-sample): 80%-Band -> 76,7 / 76,6 / 75,3 % 90%-Band -> 87,5 / 87,2 / 86,5 % Konsistent ~3-5 Pp ZU ENG. Das wird NICHT weggefittet - Kachel und Tooltip nennen die REAL gemessene Abdeckung, nicht den Nennwert. Damit nach dem P(break)-Modell die zweite kalibriert geprueft Komponente im Projekt. Zentriert auf den aktuellen Kurs, KEINE Drift addiert (Median +-0,0..0,16 xATR = klein gegen die Bandbreite; eine Drift-Korrektur waere eine Richtungsaussage und die ist nicht belegt). Farbe neutral grau, kein gruen/rot. Kein Signal, kein Verdict-Gewicht. MQL5 v1.31: zwei sich oeffnende gepunktete Pfade, verkettet 30->60->120. Startpunkt aus der PX-Zeile - die setzt g_conePx jetzt auch, wenn InpShowPrice aus ist (sonst verliert der Faecher seinen Anker). Config-Waechter in measurement_reminder.py (Antwort auf "sollten wir alle verworfenen Backtests woechentlich neu rechnen?"): NEIN - eine Woche sind ~2000 Bars, und 18 Ideen x 52 Wochen = 936 Tests/Jahr erzeugen bei 5 % Fehlalarmquote ~47 falsche "funktioniert jetzt!" pro Jahr. Stattdessen EREIGNIS-gesteuert: CONFIG_DEPS verankert 8 config-abhaengige Messungen an den Wert, gegen den sie validiert wurden (entry_room_atr 0,6 / sr_close_pbreak 0,55 / breakout_k 0,3 / risk_pct 0 / margin_buffer_pct 95 / adverse_15min_atr 0,5 / auto_signal_min_conf 75 / dead_hours leer). Weicht einer ab, meldet der Timer welche Messung veraltet ist. Numerischer Vergleich, damit 0.60 == 0.6. Mit 5 synthetischen Szenarien getestet. Erledigt ausgetragen: Chartmuster-Kontrolltest (30.07., Muster schlagen die Kontrolle in beiden Haelften +0,022/+0,108). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
03be3b8f5f
commit
d9cb54a52c
+151
-16
@@ -94,16 +94,8 @@ CHECKS = [
|
||||
"need": 30, "unit": "S/R-Closes seit der Umstellung",
|
||||
"cmd": "sr_close-Trades vor/nach 2026-07-30 vergleichen (ØEUR, Trefferquote)",
|
||||
},
|
||||
{
|
||||
"key": "patterns_control",
|
||||
"title": "Chartmuster: Kontrolltest gegen generischen Breakout",
|
||||
"why": "backtest_patterns.py zeigte positiven ØR — aber die Ziel-Trefferquote "
|
||||
"ist nur 13–38 %, der Ertrag kommt vom Trailing-Exit. Es fehlt die "
|
||||
"Kontrolle: gleicher Exit auf einem BELIEBIGEN Breakout. Erst danach "
|
||||
"ist 'Muster wirkt' belegt oder widerlegt.",
|
||||
"have": lambda: 1, "need": 1, "unit": "jederzeit machbar (nur Rechenzeit)",
|
||||
"cmd": "backtest_patterns.py um eine Kontrollgruppe erweitern",
|
||||
},
|
||||
# (erledigt 2026-07-30: Chartmuster-Kontrolltest gegen generischen Swing-Bruch —
|
||||
# Muster schlagen die Kontrolle in beiden Hälften (+0,022/+0,108) → Gewicht 1,0)
|
||||
{
|
||||
"key": "m1_squeeze",
|
||||
"title": "M1-Squeeze (Variante B) backtesten",
|
||||
@@ -128,6 +120,107 @@ CHECKS = [
|
||||
]
|
||||
|
||||
|
||||
# ── Config-abhängige Backtests ───────────────────────────────────────────────
|
||||
# User-Frage 2026-07-31: „sollten wir alle verworfenen Backtests wöchentlich neu
|
||||
# laufen lassen, da wir laufend die Config anpassen?"
|
||||
#
|
||||
# ⚠ NEIN — wöchentlich wäre eine Fehlalarm-Maschine: eine Woche M5 sind ~2000 Bars,
|
||||
# und das Projekt hat dreimal erlebt, was daraus wird (Auto-Signal-Vorlauf auf 6k
|
||||
# Bars zeigte „trägt", der 80k-Lauf drehte es; Std 13; ORB mit ØR +1,3/PF 10 als
|
||||
# Selektions-Artefakt). Dazu der Mehrfachvergleich: 18 verworfene Ideen × 52 Wochen
|
||||
# = 936 Tests/Jahr → bei 5 % Fehlalarmquote ~47 falsche „funktioniert jetzt!"/Jahr.
|
||||
#
|
||||
# ABER das Anliegen dahinter stimmt: einige Messungen sind gegen einen KONKRETEN
|
||||
# Config-Wert kalibriert und werden ungültig, wenn der sich ändert. Das ist
|
||||
# EREIGNIS-gesteuert, nicht kalendergesteuert — und genau das prüft dieser Block.
|
||||
# (Die meisten verworfenen Tests sind config-UNABHÄNGIG: PDH/PDL, Volume Profile,
|
||||
# Chartmuster, Liquidity Sweeps testen Signalklassen auf rohen Preisdaten.)
|
||||
CONFIG_DEPS = [
|
||||
{
|
||||
"key": "entry_room_atr", "test": "backtest_entryroom.py",
|
||||
"validated": "0.6",
|
||||
"why": "Das Raum-Gate ist auf diesen Schwellwert kalibriert (gemessen war 1,0 "
|
||||
"netto besser, 0,6 ist die bewusste Frequenz-Entscheidung). Es nutzt "
|
||||
"weiterhin die M5-`pb_levels` — eine Umstellung auf M30 wäre ungemessen.",
|
||||
},
|
||||
{
|
||||
"key": "sr_close_pbreak", "test": "backtest_srclose_prob.py",
|
||||
"validated": "0.55",
|
||||
"why": "Optimum-Plateau 0,50–0,60. Außerhalb ist der S/R-Auto-Close nicht mehr "
|
||||
"durch die Messung gedeckt (die Hysterese ±5 Pp hängt ebenfalls daran).",
|
||||
},
|
||||
{
|
||||
"key": "breakout_k", "test": "backtest_breakout.py",
|
||||
"validated": "0.3",
|
||||
"why": "Split-validiert: 0,3 ist in BEIDEN Hälften das Beste; 0,5 und 1,0 waren "
|
||||
"in H1 netto NEGATIV. Ein anderer Wert ist nicht belegt.",
|
||||
},
|
||||
{
|
||||
"key": "risk_pct", "test": "backtest_sizing.py",
|
||||
"validated": "0",
|
||||
"why": "0 = margin-basiert. Wechsel auf >0 aktiviert einen anderen Sizing-Pfad "
|
||||
"(calc_lots_risk) — und koppelt den %-Notfall-Stop anders.",
|
||||
},
|
||||
{
|
||||
"key": "margin_buffer_pct", "test": "backtest_sizing.py",
|
||||
"validated": "95",
|
||||
"why": "Die Ruin-Simulation ist gegen den Margin-Anteil gerechnet (90 % = 36 % "
|
||||
"P(DD>80 %)). Ändert sich er, ändert sich das Ruin-Risiko.",
|
||||
},
|
||||
{
|
||||
"key": "adverse_15min_atr", "test": "backtest_auto_signal.py (15-Min-Achse)",
|
||||
"validated": "0.5",
|
||||
"why": "0,5 war die beste Regel-Variante; 0,0 (sofort im Minus schliessen) war "
|
||||
"die SCHLECHTESTE (WR bricht 39→31 %). Andere Werte sind ungemessen.",
|
||||
},
|
||||
{
|
||||
"key": "auto_signal_min_conf", "test": "backtest_auto_signal.py",
|
||||
"validated": "75",
|
||||
"why": "Empirisch bester Punkt (NICHT weil höhere Konfidenz besser wäre — "
|
||||
"conf_pct ist unkalibriert). Nachbarwerte sind nicht besser.",
|
||||
},
|
||||
{
|
||||
"key": "dead_hours", "test": "backtest_hourly_split.py / backtest_realcosts.py",
|
||||
"validated": "",
|
||||
"why": "Leer = Gate AUS (User-Vorgabe, bewusst GEGEN die Messung). Wird es "
|
||||
"wieder gefüllt, gelten die gemessenen Stunden 0–7 + 12 + 16.",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _cfg_now() -> dict:
|
||||
"""Liest NUR die überwachten [trading]-Keys. ⚠ Die ini enthält Live-Secrets —
|
||||
hier wird bewusst nichts anderes gelesen oder ausgegeben."""
|
||||
import configparser
|
||||
out: dict = {}
|
||||
try:
|
||||
c = configparser.ConfigParser(inline_comment_prefixes=("#", ";"))
|
||||
c.read(os.path.join(HERE, "oil_widget_config.ini"), encoding="utf-8")
|
||||
t = c["trading"] if c.has_section("trading") else {}
|
||||
for d in CONFIG_DEPS:
|
||||
out[d["key"]] = (t.get(d["key"], "") or "").strip()
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def config_drift() -> list:
|
||||
"""→ [(dep, ist, soll)] für alle Keys, deren Wert von der Validierung abweicht."""
|
||||
now = _cfg_now()
|
||||
drift = []
|
||||
for d in CONFIG_DEPS:
|
||||
cur = now.get(d["key"])
|
||||
if cur is None:
|
||||
continue
|
||||
try: # numerisch vergleichen, damit 0.60 == 0.6
|
||||
same = abs(float(cur) - float(d["validated"])) < 1e-9
|
||||
except (TypeError, ValueError):
|
||||
same = cur.lower() == d["validated"].lower()
|
||||
if not same:
|
||||
drift.append((d, cur, d["validated"]))
|
||||
return drift
|
||||
|
||||
|
||||
def _popup(title: str, text: str) -> None:
|
||||
if DRY or STATUS:
|
||||
print(f"[POPUP] {title}\n{text}")
|
||||
@@ -156,6 +249,21 @@ def main() -> None:
|
||||
else:
|
||||
pending.append((c, have, "sammelt"))
|
||||
|
||||
# ── Config-Drift: welche Messung ist durch eine Config-Änderung veraltet?
|
||||
# Marker enthält den Wert → ändert der User später erneut, wird neu gemeldet.
|
||||
drift = config_drift()
|
||||
drift_new = []
|
||||
for d, cur, val in drift:
|
||||
marker = os.path.join(MARKER_DIR, f"cfg_{d['key']}")
|
||||
seen = ""
|
||||
try:
|
||||
with open(marker, encoding="utf-8") as f:
|
||||
seen = f.read().strip()
|
||||
except Exception:
|
||||
pass
|
||||
if STATUS or DRY or seen != cur:
|
||||
drift_new.append((d, cur, val, marker))
|
||||
|
||||
if STATUS or DRY:
|
||||
print("=" * 78)
|
||||
print(" AUSSTEHENDE MESSUNGEN — Status")
|
||||
@@ -170,24 +278,51 @@ def main() -> None:
|
||||
print(f" {have:.0f}/{c['need']} {c['unit']} ({pct:.0f} %)")
|
||||
if not due:
|
||||
print("\n (nichts fällig)")
|
||||
print("\n" + "=" * 78)
|
||||
print(" CONFIG-VERANKERUNG — sind die Messungen noch gültig?")
|
||||
print("=" * 78)
|
||||
if not drift:
|
||||
print(f"\n ✅ alle {len(CONFIG_DEPS)} überwachten Werte stehen auf dem "
|
||||
f"Stand, gegen den gemessen wurde")
|
||||
for d, cur, val, _m in drift_new:
|
||||
print(f"\n ⚠ {d['key']}: ist '{cur}' — gemessen/validiert gegen '{val}'")
|
||||
print(f" betroffen: {d['test']}")
|
||||
print(f" {d['why']}")
|
||||
return
|
||||
|
||||
if not due:
|
||||
if not due and not drift_new:
|
||||
return
|
||||
lines = []
|
||||
for c, have in due:
|
||||
lines.append(f"• {c['title']}\n {have:.0f}/{c['need']} {c['unit']}\n"
|
||||
f" {c['why']}\n -> {c['cmd']}")
|
||||
text = ("Diese Messungen haben jetzt genug Daten:\n\n"
|
||||
+ "\n\n".join(lines)
|
||||
+ "\n\nStatus aller Punkte:\n"
|
||||
" python measurement_reminder.py --status")
|
||||
_popup(f"Oil-Bot: {len(due)} Messung(en) faellig", text)
|
||||
for d, cur, val, _m in drift_new:
|
||||
lines.append(f"• CONFIG geaendert: {d['key']} = '{cur}' "
|
||||
f"(validiert gegen '{val}')\n"
|
||||
f" {d['why']}\n -> neu rechnen: {d['test']}")
|
||||
head = []
|
||||
if due:
|
||||
head.append(f"{len(due)} Messung(en) faellig")
|
||||
if drift_new:
|
||||
head.append(f"{len(drift_new)} Config-Abweichung(en)")
|
||||
text = ("Offene Punkte:\n\n" + "\n\n".join(lines)
|
||||
+ "\n\n⚠ Verworfene Backtests NICHT woechentlich neu rechnen — eine Woche "
|
||||
"ist Rauschen (~2000 Bars) und 936 Tests/Jahr erzeugen Fehlalarme.\n"
|
||||
"Neu rechnen nur bei Config-Aenderung (oben) oder wenn genug WIRKLICH "
|
||||
"neue Daten fuer eine dritte Stichprobe da sind (~quartalsweise).\n\n"
|
||||
+ "Status: python measurement_reminder.py --status")
|
||||
_popup("Oil-Bot: " + " + ".join(head), text)
|
||||
for c, _ in due:
|
||||
try:
|
||||
open(os.path.join(MARKER_DIR, c["key"]), "w").close()
|
||||
except Exception:
|
||||
pass
|
||||
for _d, cur, _val, marker in drift_new:
|
||||
try:
|
||||
with open(marker, "w", encoding="utf-8") as f:
|
||||
f.write(cur)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user