#!/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()