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
+75
View File
@@ -138,6 +138,25 @@ SCHEMA = [
correct INTEGER -- NULL bis ausgewertet, dann 0/1
)
""",
"""
CREATE TABLE IF NOT EXISTS verdict_votes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts INTEGER, -- lokale Epoch
headline TEXT, -- Wave-Signal (LONG/SHORT/WARTEN)
conf INTEGER, -- conf_pct der Welle
bias REAL, -- gewichteter Konsens-Bias 1..+1
tf TEXT, -- Zeitebene der Empfehlung
wave INTEGER, -- Votes je Modul: +1/1/0
m30 INTEGER,
h1 INTEGER,
ki INTEGER, -- KI-Copilot-Vote (0 auch wenn ohne Aussage)
ki_w REAL, -- Copilot-Gewicht (0 = hatte keine Aussage)
elliott INTEGER,
elliott_w REAL,
squeeze INTEGER,
news_score REAL -- News-Sentiment zum Zeitpunkt (Kontext)
)
""",
]
# Indizes — werden NACH den Migrationen ausgeführt, weil sie Spalten
@@ -156,6 +175,7 @@ INDEXES = [
"CREATE INDEX IF NOT EXISTS idx_intended_close ON intended_trades(close_ts)",
"CREATE INDEX IF NOT EXISTS idx_pbreak_ts ON pbreak_predictions(ts)",
"CREATE INDEX IF NOT EXISTS idx_pbreak_open ON pbreak_predictions(outcome) WHERE outcome IS NULL",
"CREATE INDEX IF NOT EXISTS idx_verdict_ts ON verdict_votes(ts)",
]
# Migrations: Spalten, die in alten DBs evtl. fehlen.
@@ -394,6 +414,61 @@ class HistoryLogger:
"pending": pending,
}
def log_verdict_votes(self, *, headline: str, conf: int, bias: float, tf: str,
wave: int, m30: int, h1: int, ki: int, ki_w: float,
elliott: int, elliott_w: float, squeeze: int,
news_score=None) -> None:
"""Verdict-Modul-Stimmen für die spätere Kalibrier-Auswertung loggen
(Copilot/Elliott sind unbelegte Stimmen — nach ein paar Wochen kann
`analyze_verdict_calibration.py`-artig entschieden werden, ob sie
Gewicht behalten). Intern gedrosselt auf max. 1×/60 s."""
now = time.time()
if now - getattr(self, "_verdict_log_ts", 0.0) < 60:
return
self._verdict_log_ts = now
with self._lock, self._connect() as conn:
conn.execute("""
INSERT INTO verdict_votes
(ts, headline, conf, bias, tf, wave, m30, h1, ki, ki_w,
elliott, elliott_w, squeeze, news_score)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
""", (int(now), headline, conf, bias, tf, wave, m30, h1, ki, ki_w,
elliott, elliott_w, squeeze, news_score))
conn.commit()
def alignment_stats(self, last_n: int = 40) -> dict:
"""Ausrichtungs-Split der letzten N geschlossenen Trades: liefen sie MIT,
GEGEN oder OHNE die Empfehlung (rec_signal beim Entry)? Für die ehrliche
Verhaltens-Anzeige im Order-Bestätigungsdialog (B0 als Software — die
gemessene Kern-Leckage sind diskretionäre Gegen-Signal-Trades)."""
with self._connect() as conn:
rows = conn.execute("""
SELECT direction, rec_signal, pnl FROM trades
WHERE exit_time IS NOT NULL AND pnl IS NOT NULL
ORDER BY exit_time DESC LIMIT ?
""", (last_n,)).fetchall()
out = {}
for key, match in (("with", True), ("against", False)):
grp = []
for r in rows:
rec = (r["rec_signal"] or "").upper()
if rec not in ("LONG", "SHORT"):
continue
aligned = (rec == "LONG") == (r["direction"] == "BUY")
if aligned == match:
grp.append(r["pnl"])
n = len(grp)
out[key] = {"n": n,
"wr": round(100 * sum(1 for p in grp if p > 0) / n) if n else None,
"pnl": round(sum(grp), 2)}
none_grp = [r["pnl"] for r in rows
if (r["rec_signal"] or "").upper() not in ("LONG", "SHORT")]
n = len(none_grp)
out["none"] = {"n": n,
"wr": round(100 * sum(1 for p in none_grp if p > 0) / n) if n else None,
"pnl": round(sum(none_grp), 2)}
return out
# ══════════════════════════════════════════
# STATISTIK-QUERIES
# ══════════════════════════════════════════