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
+74
-1
@@ -1474,6 +1474,59 @@ class TradingEngine:
|
||||
log.debug(f"log_verdict_votes: {e}")
|
||||
|
||||
# Kontext für Trade-Logging (set_open_context beim Öffnen)
|
||||
# ── Voller Entscheidungszustand beim Einstieg (Stufe 1 „Manuelle Trades",
|
||||
# 2026-08-04). Bis dahin wurden 5 Felder gespeichert, davon zwei fast nie
|
||||
# befüllt — die Marktlage eines manuellen Einstiegs war damit NICHT
|
||||
# rekonstruierbar. Alles hier ist bereits berechnet (Snapshot-Teile), es
|
||||
# wird nur eingesammelt. Reine Telemetrie, kein Verhalten.
|
||||
_ctx = {}
|
||||
try:
|
||||
# ⚠ `wave_snap` existiert in dieser Methode NICHT (anders als in
|
||||
# `snapshot()`) — hier den Snapshot selbst holen. Ein NameError wäre vom
|
||||
# `except` unten still geschluckt worden und hätte den Kontext dauerhaft
|
||||
# leer gelassen, ohne dass es jemand merkt.
|
||||
_ws = self.wave.snapshot() or {}
|
||||
_bid = s.get("bid")
|
||||
_atr5 = (_ws.get("pb_feats") or {}).get("atr")
|
||||
_sh = self._last_sr_close_hint or {}
|
||||
_sa = self._last_stop_approach or {}
|
||||
_dl = self._draw_levels() or {}
|
||||
_res = (_dl.get("res") or [None])[0]
|
||||
_sup = (_dl.get("sup") or [None])[0]
|
||||
_st = (self.structure.snapshot() or {}).get("trend")
|
||||
_sq = (_ws.get("squeeze") or {}).get("state")
|
||||
_ses = (session_state() or {}).get("phase")
|
||||
_cone = self._last_cone or {}
|
||||
_c120 = next((l for l in (_cone.get("levels") or [])
|
||||
if l.get("band") == 80 and l.get("minutes") == 120), None)
|
||||
_ctx = {
|
||||
"p_break_target": _sh.get("p_break"),
|
||||
"p_break_stop": _sa.get("p_break"),
|
||||
"dist_res_atr": (round((_res - _bid) / _atr5, 2)
|
||||
if (_res and _bid and _atr5) else None),
|
||||
"dist_sup_atr": (round((_bid - _sup) / _atr5, 2)
|
||||
if (_sup and _bid and _atr5) else None),
|
||||
"structure": _st,
|
||||
"squeeze": _sq,
|
||||
"htf_trend": _ws.get("htf_trend"),
|
||||
"h1_trend": _ws.get("h1_trend"),
|
||||
"spread_atr": (round(float(s.get("spread") or 0) / _atr5, 3)
|
||||
if _atr5 else None),
|
||||
"session": _ses,
|
||||
"block_reason": rec.get("block"),
|
||||
"conf_pct": rec.get("conf_pct"),
|
||||
"atr_m5": (round(_atr5, 4) if _atr5 else None),
|
||||
# `vd` entsteht oben in einem try — bei einem Fehler dort wäre es
|
||||
# gar nicht definiert. `locals()`-Guard statt NameError.
|
||||
"bias": (locals().get("vd") or {}).get("bias"),
|
||||
# Position im 120-min-Kegel: 0 = unteres Band, 1 = oberes
|
||||
"cone_pos": (round((_bid - _c120["lo"])
|
||||
/ max(_c120["hi"] - _c120["lo"], 1e-9), 3)
|
||||
if (_c120 and _bid) else None),
|
||||
"hour": datetime.now(_BERLIN).hour,
|
||||
}
|
||||
except Exception as e:
|
||||
log.debug(f"open-context: {e}")
|
||||
self._last_rec = {
|
||||
"rec_signal": rec.get("signal"),
|
||||
"rec_score": rec.get("score"),
|
||||
@@ -1482,7 +1535,8 @@ class TradingEngine:
|
||||
"setup": rec.get("setup"),
|
||||
"regime": None,
|
||||
"rsi": s.get("rsi_m15"),
|
||||
"news_score": None,
|
||||
"news_score": _news,
|
||||
"ctx": _ctx,
|
||||
}
|
||||
|
||||
def _check_flip_close(self):
|
||||
@@ -2682,6 +2736,21 @@ class TradingEngine:
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
_SNAP_TTL_S = 0.8 # geteilter Snapshot gilt so lange als frisch
|
||||
|
||||
def _manual_stats_cached(self) -> dict | None:
|
||||
"""Kennzahlen der manuellen Trades für die Karte „Manuelle Trades"
|
||||
(2026-08-04). Gleiches Cache-Muster wie `_alignment_cached` — die
|
||||
Aggregation läuft über die ganze `trades`-Tabelle und hat im 1-s-Snapshot
|
||||
nichts zu suchen."""
|
||||
now = time.time()
|
||||
if now - getattr(self, "_manual_ts", 0.0) > 120:
|
||||
try:
|
||||
self._manual_cache = self.history.manual_stats(30)
|
||||
except Exception as e:
|
||||
log.debug(f"manual_stats: {e}")
|
||||
self._manual_cache = None
|
||||
self._manual_ts = now
|
||||
return getattr(self, "_manual_cache", None)
|
||||
|
||||
def _alignment_cached(self) -> dict | None:
|
||||
"""Ausrichtungs-Split (mit/gegen/ohne Empfehlung, letzte 40 Trades) für den
|
||||
Order-Bestätigungsdialog — ehrliche eigene Zahlen statt generischem Hinweis
|
||||
@@ -3220,6 +3289,10 @@ class TradingEngine:
|
||||
"news_sentiment": (lambda sc: round(float(sc), 2) if sc is not None else None)(
|
||||
(self.news.sentiment or {}).get("score")),
|
||||
"alignment": self._alignment_cached(),
|
||||
# Kennzahlen der MANUELLEN Trades für die gleichnamige Karte
|
||||
# (2026-08-04). Rein deskriptiv; ~120 s gecacht, damit der heiße
|
||||
# Snapshot-Pfad nicht je Abruf die DB aggregiert.
|
||||
"manual_stats": self._manual_stats_cached(),
|
||||
# Live-Handelskosten relativ zur Vola (2026-07-24): Spread/ATR(M5).
|
||||
# Gemessen (`backtest_realcosts.py`): Ø 0,265×ATR, Nacht 0,32–0,50 =
|
||||
# Kostenfalle, 15–17 Uhr ~0,13 = günstig. Reine Anzeige (Chip) — die
|
||||
|
||||
Reference in New Issue
Block a user