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
+117
@@ -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()
|
||||
Reference in New Issue
Block a user