Stufe 1: voller Entscheidungskontext beim Einstieg + Karte "Manuelle Trades"
Frage: kann der Bot aus den manuellen Trades lernen? Vorpruefung: noch NICHT.
1) Die entscheidenden Merkmale wurden nie gespeichert - beim Einstieg nur 5
Felder, davon ai_sentiment in 1,4 % und news_score in 34 % befuellt.
2) Das naive Lernziel ("wann steigt er ein") braucht unbeschriftete Negative;
brauchbar ist nur "welche Einstiege liefen gut" (Labels ueber P&L).
3) Die scheinbaren Muster sind Einzeltage: Stunde 13:00 zeigt +478 EUR ueber
n=69, davon +382 EUR (80 %) aus den fuenf Trades des 04.08.
Gebaut wurde Stufe 1 - reine Telemetrie, kein Verhalten:
- trades um 16 ctx_*-Spalten erweitert (DB-Backup .bak-2026-08-04, 1.148 Zeilen
unveraendert): p_break_target/-stop, dist_res_atr/dist_sup_atr, structure,
squeeze, htf_trend/h1_trend, spread_atr, session, block_reason, conf_pct,
atr_m5, bias, cone_pos, hour
- log_trade_open haengt die Spalten dynamisch an und ignoriert unbekannte
Schluessel -> aeltere DB ohne Migration laeuft weiter (fail-open)
- Kette: engine._run_analysis -> trader.set_open_context(ctx=) -> log_trade_open
Fallstrick beim Bau: wave_snap existiert in _run_analysis NICHT (nur in
snapshot()). Der erste Entwurf haette einen NameError erzeugt, den das umgebende
except still geschluckt haette - der Kontext waere dauerhaft leer geblieben ohne
dass es auffaellt. Jetzt self.wave.snapshot(); vd zusaetzlich per locals()-Guard.
Verifiziert auf einer DB-KOPIE: alle 16 Spalten korrekt geschrieben, unbekannter
Schluessel ignoriert, Basisfelder unberuehrt. Snapshot liefert manual_stats.
Dashboard-Karte "Manuelle Trades" (#card-manual, v=134): heute / 30 Tage /
gesamt / Bot-Vergleich plus Split mit vs ohne Signal-Deckung. Backend
history.manual_stats(30) mit ~120-s-Cache wie _alignment_cached. Bewusst neutral
gefaerbt und deskriptiv; eine Reifegrad-Zeile nennt die ctx-Abdeckung (heute
0 %), damit die Karte nicht ueberschaetzt wird.
Stufe 2 NICHT gebaut: erst bei ausreichender ctx-Abdeckung messen, was Gewinner
von Verlierern unterscheidet (Fit H1, validiert H2, AUC + Kalibrierung). Ergebnis
waere ein Hinweis im Order-Dialog, ausdruecklich KEIN Auto-Entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7432be0e8c
commit
da7d8f5b90
+80
-13
@@ -270,26 +270,45 @@ class HistoryLogger:
|
||||
setup: str | None = None,
|
||||
regime: str | None = None,
|
||||
rsi_at_entry: float | None = None,
|
||||
news_score: float | None = None):
|
||||
"""Loggt das Öffnen eines Trades. Idempotent über UNIQUE(ticket)."""
|
||||
news_score: float | None = None,
|
||||
ctx: dict | None = None):
|
||||
"""Loggt das Öffnen eines Trades. Idempotent über UNIQUE(ticket).
|
||||
|
||||
`ctx` = vollständiger Entscheidungszustand beim Einstieg (Stufe 1 des
|
||||
„Manuelle Trades"-Moduls, 2026-08-04). Bis dahin wurden nur 5 Felder
|
||||
gespeichert — davon `ai_sentiment` in 1,4 % und `news_score` in 34 % der
|
||||
Fälle befüllt. Damit war NICHT rekonstruierbar, in welcher Marktlage der
|
||||
User eingestiegen ist, und jede Auswertung seiner Diskretion blieb
|
||||
Spekulation. Alle `ctx_*`-Werte werden im Snapshot ohnehin berechnet;
|
||||
hier werden sie nur mitgeschrieben (reine Telemetrie, kein Verhalten).
|
||||
Unbekannte Schlüssel werden ignoriert — so bricht ein späterer Ausbau
|
||||
nichts."""
|
||||
# Guard (Fix 2026-07-19): 0-Lot-/Preis-lose Einträge sind Reconcile-
|
||||
# Artefakte (real: Ticket 47966457, 0.0L, closed_by=unknown) — sie
|
||||
# verfälschen WR/Statistik. Nicht loggen.
|
||||
if not lots or lots <= 0 or not entry_price or entry_price <= 0:
|
||||
return
|
||||
now_ts = int(time.time())
|
||||
# Kontext-Spalten dynamisch anhängen — nur solche, die es in der Tabelle
|
||||
# wirklich gibt. So läuft eine ältere DB ohne Migration weiter (fail-open),
|
||||
# statt beim INSERT zu werfen.
|
||||
ctx = ctx or {}
|
||||
with self._lock, self._connect() as conn:
|
||||
conn.execute("""
|
||||
INSERT OR IGNORE INTO trades
|
||||
(ticket, symbol, direction, lots,
|
||||
entry_time, entry_price, sl_at_entry, tp_at_entry,
|
||||
ai_sentiment, ai_confidence, rec_signal, rec_score,
|
||||
setup, regime, rsi_at_entry, news_score)
|
||||
VALUES (?,?,?,?, ?,?,?,?, ?,?,?,?, ?,?,?,?)
|
||||
""", (ticket, symbol, direction, lots,
|
||||
now_ts, entry_price, sl_at_entry, tp_at_entry,
|
||||
ai_sentiment, ai_confidence, rec_signal, rec_score,
|
||||
setup, regime, rsi_at_entry, news_score))
|
||||
vorhanden = {r[1] for r in conn.execute("PRAGMA table_info(trades)")}
|
||||
extra = [(f"ctx_{k}", v) for k, v in ctx.items()
|
||||
if f"ctx_{k}" in vorhanden and v is not None]
|
||||
spalten = ("ticket, symbol, direction, lots, entry_time, entry_price, "
|
||||
"sl_at_entry, tp_at_entry, ai_sentiment, ai_confidence, "
|
||||
"rec_signal, rec_score, setup, regime, rsi_at_entry, news_score")
|
||||
werte = [ticket, symbol, direction, lots, now_ts, entry_price,
|
||||
sl_at_entry, tp_at_entry, ai_sentiment, ai_confidence,
|
||||
rec_signal, rec_score, setup, regime, rsi_at_entry, news_score]
|
||||
for name, v in extra:
|
||||
spalten += f", {name}"
|
||||
werte.append(v)
|
||||
platz = ",".join("?" * len(werte))
|
||||
conn.execute(f"INSERT OR IGNORE INTO trades ({spalten}) VALUES ({platz})",
|
||||
werte)
|
||||
conn.commit()
|
||||
# Kein Telegram bei Einstieg — nur Ergebnis (Close) wird gesendet
|
||||
|
||||
@@ -454,6 +473,54 @@ class HistoryLogger:
|
||||
pattern, pattern_w, orderbook, orderbook_w, liqtrend, liqtrend_w))
|
||||
conn.commit()
|
||||
|
||||
def manual_stats(self, tage: int = 30) -> dict:
|
||||
"""Kennzahlen NUR der manuellen Trades — Datengrundlage der Dashboard-Karte
|
||||
„Manuelle Trades" (2026-08-04).
|
||||
|
||||
⚠ Bewusst DESKRIPTIV, nicht prädiktiv. Der volle Entscheidungszustand wird
|
||||
erst seit dem 04.08. mitgeschrieben (`ctx_*`); bis genug davon vorliegt,
|
||||
wäre jede Vorhersage aus diesen Zahlen Rauschen. Die Karte zeigt deshalb,
|
||||
was die eigene Historie SAGT, und rechnet nichts hoch.
|
||||
⚠ `ctx_abdeckung` macht sichtbar, wie weit der neue Kontext schon reicht —
|
||||
ohne diese Zahl könnte man die Karte für aussagekräftiger halten, als sie ist.
|
||||
"""
|
||||
MAN = ("(setup IS NULL OR (setup NOT LIKE 'AUTOSIG%' "
|
||||
"AND setup NOT LIKE 'SQUEEZE%'))")
|
||||
|
||||
def agg(row):
|
||||
n = row["n"] or 0
|
||||
return {"n": n, "wr": round(100.0 * (row["w"] or 0) / n, 1) if n else 0.0,
|
||||
"sum": round(row["s"] or 0.0, 2),
|
||||
"avg": round((row["s"] or 0.0) / n, 2) if n else 0.0}
|
||||
|
||||
out = {"tage": tage}
|
||||
with self._connect() as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
q = (f"SELECT COUNT(*) n, SUM(CASE WHEN pnl>0 THEN 1 ELSE 0 END) w, "
|
||||
f"SUM(pnl) s FROM trades WHERE pnl IS NOT NULL AND {MAN}")
|
||||
seit = f"AND exit_time >= strftime('%s','now','-{int(tage)} days')"
|
||||
out["fenster"] = agg(conn.execute(f"{q} {seit}").fetchone())
|
||||
out["gesamt"] = agg(conn.execute(q).fetchone())
|
||||
out["heute"] = agg(conn.execute(
|
||||
f"{q} AND exit_time >= strftime('%s','now','start of day')").fetchone())
|
||||
# Mit / ohne Signal-Deckung — die dokumentierte Kern-Frage
|
||||
for lbl, cond in (("mit_signal", "rec_signal IN ('LONG','SHORT')"),
|
||||
("ohne_signal", "rec_signal='WARTEN'")):
|
||||
out[lbl] = agg(conn.execute(f"{q} {seit} AND {cond}").fetchone())
|
||||
# Bot zum Vergleich (dieselbe Fensterlogik)
|
||||
qb = ("SELECT COUNT(*) n, SUM(CASE WHEN pnl>0 THEN 1 ELSE 0 END) w, "
|
||||
"SUM(pnl) s FROM trades WHERE pnl IS NOT NULL AND "
|
||||
"(setup LIKE 'AUTOSIG%' OR setup LIKE 'SQUEEZE%')")
|
||||
out["bot"] = agg(conn.execute(f"{qb} {seit}").fetchone())
|
||||
# Abdeckung des NEUEN Kontexts — ehrlicher Reifegrad-Indikator
|
||||
r = conn.execute(
|
||||
f"SELECT COUNT(*) n, SUM(CASE WHEN ctx_atr_m5 IS NOT NULL THEN 1 ELSE 0 END) c "
|
||||
f"FROM trades WHERE {MAN}").fetchone()
|
||||
out["ctx_abdeckung"] = {
|
||||
"n": r["n"] or 0, "mit_ctx": r["c"] or 0,
|
||||
"pct": round(100.0 * (r["c"] or 0) / (r["n"] or 1), 1)}
|
||||
return out
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user