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
@@ -2503,7 +2503,7 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
||||
Signal → **Hinweis-Dialog „trotzdem eröffnen?"** (überschreibbar). CLOSE bei
|
||||
**negativem** P&L → Sicherheitsabfrage; im Plus/Breakeven direkt. Sonst keine
|
||||
Token-Dialoge (`require_token=false`). Audio braucht 1× Nutzer-Tap.
|
||||
- Asset-Version aktuell **v=133** (in `web/index.html` hochzählen, siehe Workflows).
|
||||
- Asset-Version aktuell **v=134** (in `web/index.html` hochzählen, siehe Workflows).
|
||||
Schriftgrößen 2026-07-24 global **+4px** (2× je +2px auf User-Wunsch; Body-Basis
|
||||
14→18px). ⚠ Betrifft in `style.css` sowohl `font-size:Npx` (71×) ALS AUCH die
|
||||
`font:<weight> Npx/…`-**Shorthand** (3×: `.ms-chip`/`.ms-bos-lbl`/`.sqm-badge` —
|
||||
@@ -3027,6 +3027,60 @@ PF>1 in BEIDEN Hälften, Nachbarparameter müssen mithalten).
|
||||
Begründung gegen wöchentliche Läufe (Mehrfachvergleich, ~47 Fehlalarme/Jahr)
|
||||
steht unverändert. Anlass war hier die *einmalige* Signaländerung.
|
||||
|
||||
## Modul „Manuelle Trades" + voller Entscheidungskontext (Stufe 1, 2026-08-04)
|
||||
|
||||
**User-Frage:** „Kann der Bot aus meinen manuellen Trades lernen — evtl. ein neues
|
||||
Modul?" Auslöser: am 04.08. machten 18 manuelle Trades **+728 €**, der Bot −16,53 €,
|
||||
bei 93 % WARTEN.
|
||||
|
||||
⚠ **Die exploratorische Vorprüfung ergab: noch NICHT lernbar** — und zwar aus drei
|
||||
Gründen, die alle benannt gehören:
|
||||
1. **Die entscheidenden Merkmale wurden nie gespeichert.** Beim Einstieg schrieb der
|
||||
Bot 5 Felder; davon war `ai_sentiment` in **1,4 %** und `news_score` in **34 %** der
|
||||
Trades befüllt. Die Marktlage eines manuellen Einstiegs war damit nicht
|
||||
rekonstruierbar.
|
||||
2. **Das naive Lernziel ist untauglich.** „Wann steigt er ein" bräuchte Gegenbeispiele
|
||||
(Bars ohne Einstieg) — Millionen unbeschriftete Negative, und „nicht eingestiegen"
|
||||
heißt nicht „hätte man nicht sollen". Die brauchbare Frage lautet: **welche seiner
|
||||
Einstiege liefen gut** (saubere Labels über P&L).
|
||||
3. **Die scheinbaren Muster sind Einzeltage.** Stunde 13:00 zeigt +478 € über n=69 —
|
||||
davon stammen **+382 € (80 %) aus den fünf Trades des 04.08.** Genau die Falle, an
|
||||
der ORB und die Dead-Hours gescheitert sind.
|
||||
|
||||
**Gebaut wurde deshalb Stufe 1 — Telemetrie, kein Verhalten:**
|
||||
`trades` um **16 `ctx_*`-Spalten** erweitert (DB-Backup `…bak-2026-08-04`, 1.148
|
||||
Zeilen unverändert): `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`. Alle Werte
|
||||
werden im Snapshot ohnehin berechnet und nur eingesammelt
|
||||
(`engine._run_analysis` → `trader.set_open_context(ctx=…)` → `history.log_trade_open`).
|
||||
`log_trade_open` hängt die Spalten **dynamisch** an und ignoriert unbekannte Schlüssel
|
||||
— eine ältere DB ohne Migration läuft weiter (fail-open).
|
||||
⚠ **Fallstrick beim Bau:** `wave_snap` existiert in `_run_analysis` NICHT (nur in
|
||||
`snapshot()`). Der erste Entwurf hätte einen `NameError` erzeugt, den das umgebende
|
||||
`except` still geschluckt hätte — der Kontext wäre dauerhaft leer geblieben, ohne dass
|
||||
es auffällt. Jetzt `self.wave.snapshot()`; `vd` zusätzlich per `locals()`-Guard
|
||||
abgesichert (es entsteht in einem eigenen `try`).
|
||||
Verifiziert auf einer DB-**Kopie**: alle 16 Spalten korrekt geschrieben, unbekannter
|
||||
Schlüssel ignoriert, Basisfelder unberührt.
|
||||
|
||||
**Dashboard-Karte „Manuelle Trades"** (`#card-manual`, v=134): heute / 30 Tage /
|
||||
gesamt / Bot-Vergleich, dazu der Split **mit vs. ohne Signal-Deckung**. Backend
|
||||
`history.manual_stats(30)` → `engine._manual_stats_cached()` (~120 s Cache, wie
|
||||
`_alignment_cached` — die Aggregation läuft über die ganze Tabelle und hat im
|
||||
1-s-Snapshot nichts zu suchen). Bewusst **neutral gefärbt** (keine Ampel) und
|
||||
ausdrücklich **deskriptiv**: die Karte zeigt eine Reifegrad-Zeile mit der
|
||||
`ctx`-Abdeckung (heute 0 %), damit niemand sie für aussagekräftiger hält, als sie ist.
|
||||
|
||||
**Stufe 2 (später, NICHT gebaut):** wenn genug `ctx`-Daten vorliegen (~2–3 Monate),
|
||||
messen was Gewinner von Verlierern unterscheidet — Fit auf H1, validiert auf H2, AUC
|
||||
und Kalibrierung berichtet, wie beim P(break)-Modell. Ergebnis wäre ein **Hinweis im
|
||||
Order-Dialog** („deine Trades in dieser Konstellation: X % WR, n=Y"), analog zum
|
||||
bestehenden Ausrichtungs-Split. ⚠ **Ausdrücklich KEIN Auto-Entry daraus** — 22
|
||||
Signal-Eingriffe sind gescheitert, und die gemessene Stärke des Users liegt in der
|
||||
AUSWAHL, nicht in einer Regel. Ein Modul, das ihm seine eigene Historie zur aktuellen
|
||||
Lage zeigt, spielt auf diese Stärke; eines das für ihn einsteigt, ersetzt sie.
|
||||
|
||||
## Backtest-Audit 2026-08-04 — rechnen die Skripte noch korrekt?
|
||||
|
||||
**Anlass:** User-Frage nach dem `angle=`-Fund in `backtest_auto_signal.py`. Prüfung
|
||||
|
||||
+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
|
||||
|
||||
+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
|
||||
|
||||
+6
-1
@@ -315,11 +315,14 @@ class TradeManager:
|
||||
|
||||
def set_open_context(self, *, ai_sentiment=None, ai_confidence=None,
|
||||
rec_signal=None, rec_score=None,
|
||||
setup=None, regime=None, rsi=None, news_score=None):
|
||||
setup=None, regime=None, rsi=None, news_score=None,
|
||||
ctx=None):
|
||||
# `ctx` = voller Entscheidungszustand (2026-08-04, s. `history.log_trade_open`)
|
||||
self._open_context = {
|
||||
"ai_sentiment": ai_sentiment, "ai_confidence": ai_confidence,
|
||||
"rec_signal": rec_signal, "rec_score": rec_score,
|
||||
"setup": setup, "regime": regime, "rsi": rsi, "news_score": news_score,
|
||||
"ctx": ctx or {},
|
||||
}
|
||||
|
||||
def _log_open(self, ticket: int, sym: str, direction: str, entry_price: float):
|
||||
@@ -351,6 +354,8 @@ class TradeManager:
|
||||
rec_score=ctx.get("rec_score"),
|
||||
setup=ctx.get("setup"), regime=ctx.get("regime"),
|
||||
rsi_at_entry=ctx.get("rsi"), news_score=ctx.get("news_score"),
|
||||
# Voller Entscheidungszustand (2026-08-04, „Manuelle Trades")
|
||||
ctx=ctx.get("ctx"),
|
||||
)
|
||||
except Exception as e:
|
||||
log_hist.error(f"log_trade_open: {e}")
|
||||
|
||||
+34
@@ -226,6 +226,39 @@ function renderGaps(g, m) {
|
||||
}).join("");
|
||||
}
|
||||
|
||||
// Manuelle Trades (2026-08-04) — DESKRIPTIV. Zeigt die eigene Historie, macht
|
||||
// keine Vorhersage: der volle Entscheidungskontext wird erst seit dem 04.08.
|
||||
// mitgeschrieben, vorher wären Aussagen daraus Rauschen. Die Reifegrad-Zeile
|
||||
// nennt die Abdeckung ehrlich, damit die Karte nicht überschätzt wird.
|
||||
function renderManual(m) {
|
||||
const grid = $("mt-grid"); if (!grid) return; // null-sicher (Cache-Regel)
|
||||
if (!m || !m.gesamt) { grid.innerHTML = '<span class="dim">—</span>'; return; }
|
||||
const cls = v => v > 0 ? "up" : v < 0 ? "down" : "";
|
||||
const box = (lbl, b) => !b || !b.n ? "" :
|
||||
`<div class="mt-box"><div class="mt-lbl">${lbl}</div>`
|
||||
+ `<div class="mt-val ${cls(b.sum)}">${b.sum >= 0 ? "+" : ""}${fmt(b.sum, 2)}</div>`
|
||||
+ `<div class="mt-sub2">${b.n} Trades · ${fmt(b.wr, 1)} % · Ø ${b.avg >= 0 ? "+" : ""}${fmt(b.avg, 2)}</div></div>`;
|
||||
grid.innerHTML = box("heute", m.heute) + box(`${m.tage} Tage`, m.fenster)
|
||||
+ box("gesamt", m.gesamt) + box("Bot (Vergleich)", m.bot);
|
||||
|
||||
// Signal-Deckung: die dokumentierte Kern-Frage — trägt Handeln OHNE Empfehlung?
|
||||
const sp = $("mt-split");
|
||||
if (sp) {
|
||||
const z = (lbl, b) => !b || !b.n ? "" :
|
||||
`<span class="mt-chip"><b>${lbl}</b> ${b.n}× · ${fmt(b.wr, 1)} % · `
|
||||
+ `<span class="${cls(b.sum)}">${b.sum >= 0 ? "+" : ""}${fmt(b.sum, 2)}</span></span>`;
|
||||
sp.innerHTML = z("mit Signal", m.mit_signal) + z("ohne Signal", m.ohne_signal);
|
||||
}
|
||||
const nt = $("mt-note");
|
||||
if (nt) {
|
||||
const c = m.ctx_abdeckung || {};
|
||||
nt.textContent = `Entscheidungs-Kontext gespeichert für ${c.mit_ctx || 0} von `
|
||||
+ `${c.n || 0} Trades (${fmt(c.pct || 0, 1)} %). Erst ab ausreichender `
|
||||
+ `Abdeckung lässt sich messen, WORAN gute von schlechten Einstiegen zu `
|
||||
+ `unterscheiden sind — bis dahin ist diese Karte reine Beschreibung.`;
|
||||
}
|
||||
}
|
||||
|
||||
// Marktstruktur (M30) — reine Anzeige (HH/HL/LH/LL, BOS, Regressionskanal).
|
||||
function renderStructure(s) {
|
||||
s = s || {};
|
||||
@@ -571,6 +604,7 @@ function render(d) {
|
||||
renderCandles(d.candle_anat);
|
||||
renderCone(d.cone);
|
||||
renderStructure(d.structure);
|
||||
renderManual(d.manual_stats);
|
||||
renderStatsCompact(d.stats_compact, d.market);
|
||||
renderGaps(d.gaps, d.market);
|
||||
|
||||
|
||||
+12
-2
@@ -6,7 +6,7 @@
|
||||
<meta name="theme-color" content="#0d1117">
|
||||
<title>Oil · MT5</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="stylesheet" href="style.css?v=133">
|
||||
<link rel="stylesheet" href="style.css?v=134">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modul-Menü (ganz oben) -->
|
||||
@@ -124,6 +124,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Manuelle Trades — deskriptiv (2026-08-04). Zeigt die EIGENE Historie,
|
||||
rechnet nichts hoch. Der volle Entscheidungskontext (ctx_*) wird erst
|
||||
seit dem 04.08. mitgeschrieben; die Reifegrad-Zeile macht das sichtbar. -->
|
||||
<section class="card wide" id="card-manual">
|
||||
<h2>Manuelle Trades <span class="mt-sub">deine Historie · 30 Tage</span></h2>
|
||||
<div class="mt-grid" id="mt-grid"></div>
|
||||
<div class="mt-split" id="mt-split"></div>
|
||||
<div class="mt-note dim" id="mt-note"></div>
|
||||
</section>
|
||||
|
||||
<!-- Chartmuster (visuell) — reine Anzeige, kein Signal (noch nicht backtestet) -->
|
||||
<section class="card wide" id="card-patterns">
|
||||
<h2>Chartmuster <span class="pt-tf">M30 · visuell</span></h2>
|
||||
@@ -249,6 +259,6 @@
|
||||
<div id="toast" class="toast hidden"></div>
|
||||
|
||||
<script src="lightweight-charts.standalone.production.js?v=114"></script>
|
||||
<script src="app.js?v=133"></script>
|
||||
<script src="app.js?v=134"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -489,3 +489,26 @@ main{padding-bottom:74px} /* Platz für die fixe Leiste */
|
||||
gefaerbt als der Broker-Kurs, damit man ihn nicht verwechselt. */
|
||||
#hdr-price.hl-price{color:var(--accent)}
|
||||
#hdr-price-lbl{letter-spacing:.3px}
|
||||
|
||||
/* ── Manuelle Trades (2026-08-04) ─────────────────────────────────────────
|
||||
Bewusst NEUTRAL gehalten: die Karte beschreibt die eigene Historie und
|
||||
sagt nichts voraus. Farbe nur am Vorzeichen der Summe, keine Ampel. */
|
||||
#card-manual .mt-sub { font-size: 13px; color: var(--dim); font-weight: 400; }
|
||||
.mt-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 10px; margin-bottom: 10px;
|
||||
}
|
||||
.mt-box {
|
||||
background: var(--surface-2); border: 1px solid var(--border);
|
||||
border-radius: var(--r-sm); padding: 9px 11px;
|
||||
}
|
||||
.mt-lbl { font-size: 13px; color: var(--dim); margin-bottom: 3px; }
|
||||
.mt-val { font-family: var(--mono); font-size: 21px; font-weight: 600; }
|
||||
.mt-sub2 { font-size: 12px; color: var(--dim); font-family: var(--mono); margin-top: 2px; }
|
||||
.mt-split { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
|
||||
.mt-chip {
|
||||
background: var(--surface-2); border: 1px solid var(--line);
|
||||
border-radius: var(--r-sm); padding: 5px 10px;
|
||||
font-size: 13px; font-family: var(--mono);
|
||||
}
|
||||
.mt-note { font-size: 12px; line-height: 1.45; }
|
||||
|
||||
Reference in New Issue
Block a user