Gesamtempfehlung-Paket: 2 Rollouts, 3 Anzeige-Features, 2 Messungen (v=114)

- Entry-Raum-Gate 0,6→1,0 (Messung lag vor: beide Hälften besser)
- Konfidenz-Kalibrierung gemessen (analyze_verdict_calibration.py): conf
  INVERTIERT zwischen Regimen → keine P(Erfolg)-Aufwertung, kein Konf-Sizing
- verdict_votes-Logging (1×/min) für spätere Copilot/Elliott-Entscheidung
- Order-Dialog zeigt eigenen Ausrichtungs-Split (36/40 Trades ohne Signal: −423€)
- Live-Kosten-Chip (Spread/ATR) im Verdict
- P(break)×Entry-Raum-Freigabe gemessen VERWORFEN (Flip in jeder Schwelle,
  backtest_entryroom_pbreak.py) — 13. verworfener Signal-Eingriff
- News-Konflikt-Chip + recommendations.news_score wieder befüllt

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-07-24 20:57:56 +02:00
co-authored by Claude Opus 4.8
parent c3c6a8ce5f
commit d61c4a3634
10 changed files with 544 additions and 21 deletions
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Kalibrierung der Gesamtempfehlung (2026-07-24, Idee 2 „Verdict ans eigene
Erfolgsrezept halten"): Ist die angezeigte Konfidenz (conf_pct) PRÄDIKTIV — sagt
conf=80 mehr Erfolg voraus als conf=55? Und: sind die unbelegten Verdict-Stimmen
(KI-Copilot ai_sentiment, news_score) prädiktiv?
Methodik wie beim P(break)-Modell (dem kalibrierten Erfolgsfall):
- `recommendations` (LONG/SHORT) aus der DB, Sampling ≥15 min Abstand
(Autokorrelation dämpfen — die Empfehlung ändert sich minütlich kaum).
- Forward-Return = Signalrichtung × (Close[t+H] Close[t0]) / ATR(M5, t0)
für Horizonte 1 h und 2 h (M5-Bars von MT5; rec.timestamp = lokale Epoch,
Bar-Zeit = Broker-Zeit → 3 h).
- Bins über conf_pct → n / WR(fwd>0) / Ø-fwdR je Bin, in ZWEI Zeitraum-Hälften
(Sign-Stabilität wie immer).
- Copilot: ai_sentiment (LONG/bullish=+1, SHORT/bearish=1) → Ø-fwdR in
Copilot-Richtung (unabhängig vom Wave-Signal!). News: Vorzeichen news_score.
Kalibriert = monoton steigende Ø-fwdR über die conf-Bins, in BEIDEN Hälften.
"""
import sqlite3, datetime as dt
import MetaTrader5 as mt5
_BROKER_OFF = 3*3600
_SAMPLE_S = 15*60 # Mindestabstand zwischen zwei gewerteten Empfehlungen
_H1, _H2 = 12, 24 # Forward-Horizonte in M5-Bars (1 h / 2 h)
_ATR_P = 14
_BINS = [(0,45),(45,55),(55,65),(65,75),(75,85),(85,101)]
def _atr_series(H, L, C, p=_ATR_P):
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])))
return [(sum(t[max(1,i-p+1):i+1])/max(1,len(t[max(1,i-p+1):i+1]))) if i else None for i in range(len(C))]
def _dirnum(s):
if not s: return 0
s = str(s).strip().upper()
if s in ("LONG","BULLISH"): return 1
if s in ("SHORT","BEARISH"): return -1
return 0
def line(lbl, rows, key):
v=[r[key] for r in rows]
if not v: return f" {lbl:<22}"
n=len(v); wr=100*sum(1 for x in v if x>0)/n
return f" {lbl:<22} n={n:>5} WR={wr:>3.0f}% ØR={sum(v)/n:+.3f}"
def main():
mt5.initialize()
sym = next((c for c in ("SpotCrude","USOIL","WTI","XTIUSD") if mt5.symbol_info(c)), None)
bars = None
for req in (100000, 80000, 60000, 40000):
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, req)
if bars is not None and len(bars) > 2000: break
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]
T=[int(b["time"])-_BROKER_OFF for b in bars] # → echte UTC-Epoch
A=_atr_series(H,L,C)
t2i = {t:i for i,t in enumerate(T)}
con = sqlite3.connect("oil_widget_history.db"); con.row_factory=sqlite3.Row
recs = con.execute(
"SELECT timestamp, signal, conf_pct, ai_sentiment, news_score "
"FROM recommendations WHERE signal IN ('LONG','SHORT') ORDER BY timestamp"
).fetchall()
ev=[]; last_ts=0
for r in recs:
ts=r["timestamp"]
if ts-last_ts < _SAMPLE_S: continue
bar_t = (ts//300)*300 # auf M5-Raster runden
i = t2i.get(bar_t)
if i is None or i+_H2 >= len(C) or not A[i]: continue
last_ts=ts
d = 1 if r["signal"]=="LONG" else -1
atr=max(A[i],0.06)
ev.append(dict(ts=ts, conf=r["conf_pct"] or 0, d=d,
ai=_dirnum(r["ai_sentiment"]),
news=(r["news_score"] if r["news_score"] is not None else None),
f1=(C[i+_H1]-C[i])*d/atr, f2=(C[i+_H2]-C[i])*d/atr))
if not ev:
print("Keine matchbaren Empfehlungen (History-Überlappung prüfen)."); return
mid_ts = ev[len(ev)//2]["ts"]
halves=[("H1 (alt)",[e for e in ev if e["ts"]<mid_ts]),
("H2 (neu)",[e for e in ev if e["ts"]>=mid_ts])]
def span(rows):
return (f"{dt.datetime.fromtimestamp(rows[0]['ts']):%d.%m.%y}"
f"{dt.datetime.fromtimestamp(rows[-1]['ts']):%d.%m.%y}")
print("="*84)
print(f" Verdict-Kalibrierung — {sym}, {len(ev)} gesampelte Signale (≥15 min Abstand)")
print(f" fwd-Return in Signalrichtung, ×ATR(M5) · Horizonte 1 h/2 h")
print("="*84)
for lbl, rows in halves:
print(f"\n{lbl} ({span(rows)}, n={len(rows)}):")
print(" conf_pct-Bins (Horizont 2 h) — kalibriert = ØR steigt monoton:")
for lo,hi in _BINS:
sub=[e for e in rows if lo<=e["conf"]<hi]
print(line(f"conf {lo}{hi-1}", sub, "f2"))
print(" KI-Copilot (eigene Richtung, unabhängig vom Wave-Signal):")
for tag,dd in (("Copilot LONG",1),("Copilot SHORT",-1)):
sub=[dict(f2=e["f2"]*e["d"]*dd) for e in rows if e["ai"]==dd]
print(line(tag, sub, "f2"))
print(" News-Score (Richtung des Scores, |score|≥0.3):")
for tag,dd in (("News bullisch",1),("News bärisch",-1)):
sub=[dict(f2=e["f2"]*e["d"]*dd) for e in rows
if e["news"] is not None and (e["news"]>=0.3 if dd>0 else e["news"]<=-0.3)]
print(line(tag, sub, "f2"))
print("\n Lesart: conf kalibriert → höhere Bins klar bessere ØR in BEIDEN Hälften.")
print(" Copilot/News prädiktiv → 'LONG'-Zeile positiv UND 'SHORT'-Zeile positiv")
print(" (jeweils fwd in der EIGENEN Richtung gemessen).")
if __name__ == "__main__":
main()