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:
Axel Hocks
2026-07-31 08:25:29 +02:00
co-authored by Claude Opus 5
parent 03be3b8f5f
commit d9cb54a52c
9 changed files with 508 additions and 21 deletions
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""Wahrscheinlichkeits-Kegel: empirische Quantile fitten UND out-of-sample prüfen.
User-Wunsch 2026-07-31 war ein „Kursvorhersagemodul, das mögliche Verläufe in MT5
einzeichnet". Eingezeichnete PFADE würden eine Präzision suggerieren, die es nicht
gibt — das Projekt hat mit `analyze_verdict_calibration.py` teuer gelernt, dass eine
Zahl ohne Kalibrierungsprüfung in die Irre führt (conf_pct war in H2 invertiert).
STATTDESSEN: ein **Streuungs-Kegel**. Er sagt NICHT, wohin der Kurs geht, sondern
**wie weit** er in 30/60/120 min plausibel gelaufen sein kann. Der entscheidende
Unterschied: das ist ÜBERPRÜFBAR — landen wirklich ~80 % der Ausgänge im 80 %-Band?
METHODE
r(h) = (C[i+h] C[i]) / ATR[i] (ATR-normiert → über Vola-Regime vergleichbar)
Quantile auf H1 fitten, Abdeckung auf H2 messen (echtes out-of-sample).
Ein Kegel ist nur dann brauchbar, wenn die gemessene Abdeckung nah am Nominalwert
liegt. Weicht sie stark ab, wird er NICHT gebaut — dann ist die Streuung nicht
stabil ATR-skalierbar.
"""
import sys
import MetaTrader5 as mt5
_H = (6, 12, 24) # 30 / 60 / 120 min auf M5
_ATRMIN = 0.06
# Nominale Bänder: (unteres Quantil, oberes Quantil, Nennabdeckung)
_BANDS = ((0.25, 0.75, 50), (0.10, 0.90, 80), (0.05, 0.95, 90))
def _atr_series(H, L, C, p=14):
t = [0.0]
for i in range(1, len(C)):
t.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1])))
out = []
for i in range(len(C)):
if not i:
out.append(None); continue
w = t[max(1, i - p + 1):i + 1]
out.append(sum(w) / max(1, len(w)))
return out
def quantile(sorted_xs, q):
if not sorted_xs:
return 0.0
k = q * (len(sorted_xs) - 1)
lo = int(k); hi = min(lo + 1, len(sorted_xs) - 1)
return sorted_xs[lo] + (sorted_xs[hi] - sorted_xs[lo]) * (k - lo)
def collect(C, AT, a, b, h):
out = []
for i in range(a, min(b, len(C) - h)):
atr = AT[i]
if not atr:
continue
out.append((C[i + h] - C[i]) / max(atr, _ATRMIN))
return out
def main():
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
mt5.initialize(); sym = None
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
if mt5.symbol_info(c):
sym = c; break
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
mt5.shutdown()
H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars]
C = [float(b["close"]) for b in bars]
AT = _atr_series(H, L, C)
N = len(C); mid = N // 2
print("=" * 92)
print(f" WAHRSCHEINLICHKEITS-KEGEL — {sym} M5 ({N} Bars)")
print(" Fit auf H1, Abdeckung gemessen auf H2 (out-of-sample). Werte in ×ATR.")
print("=" * 92)
fitted = {}
for h in _H:
tr = sorted(collect(C, AT, 100, mid, h))
te = collect(C, AT, mid, N, h)
print(f"\n Horizont {h} Bars = {h*5} min (fit n={len(tr)}, test n={len(te)})")
print(f" Median H1 {quantile(tr,0.5):+.3f} · Median H2 "
f"{sorted(te)[len(te)//2]:+.3f} (Drift-Kontrolle)")
row = {}
for qlo, qhi, nominal in _BANDS:
lo = quantile(tr, qlo); hi = quantile(tr, qhi)
cov = 100.0 * sum(1 for x in te if lo <= x <= hi) / len(te)
flag = "OK" if abs(cov - nominal) <= 5 else ("eng" if cov < nominal else "weit")
print(f" {nominal}%-Band [{lo:+.2f}{hi:+.2f}]×ATR "
f"-> Abdeckung H2 {cov:>5.1f}% [{flag}]")
row[nominal] = (round(lo, 3), round(hi, 3))
fitted[h] = row
# ── Final auf ALLEN Bars fitten (wie beim P(break)-Modell: erst out-of-sample
# validieren, dann für den Live-Einsatz auf der vollen History fitten)
print("\n" + "=" * 92)
print(" FINAL (auf allen Bars gefittet) — zum Einbetten in core/cone.py")
print("=" * 92)
final = {}
for h in _H:
allx = sorted(collect(C, AT, 100, N, h))
row = {}
for qlo, qhi, nominal in _BANDS:
row[nominal] = (round(quantile(allx, qlo), 3), round(quantile(allx, qhi), 3))
final[h] = row
print("_CONE = {")
for h in _H:
parts = ", ".join(f"{k}: ({v[0]:+.3f}, {v[1]:+.3f})" for k, v in final[h].items())
print(f" {h}: {{{parts}}},")
print("}")
if __name__ == "__main__":
main()