Files
Axel HocksandClaude Opus 5 d9cb54a52c 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>
2026-07-31 08:25:29 +02:00

118 lines
4.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()