From 44955bf70a712f9a816e5fef584073841988baaf Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Sat, 8 Aug 2026 15:18:29 +0200 Subject: [PATCH] M15-Karte "Setup-Bereitschaft" gebaut - Ersatz der Gesamtempfehlung (v=153) Nach drei negativen M15-Messungen (Squeeze-Einstieg, VWAP-Baender, Exit-ATR) steht fest: auf M15 traegt keine Richtungsquelle. Gebaut ist deshalb bewusst KEINE Empfehlung, sondern eine sequenzielle Gate-Kette: [1 HTF-BIAS M30/H1] -> [2 KOSTEN Spread/ATR_M15] -> [3 LEVEL & KEGEL] -> Order AM LEVEL Der Unterschied zur alten Karte ist grundsaetzlich: dort wurden Stimmen zu einem Bias VERRECHNET (gemessen 48-51 % Treffer), hier muss jedes Gate EINZELN passieren. Ein gefallenes Gate laesst sich nicht durch ein anderes ausgleichen. Nur belegte Bausteine: HTF-Trend (einziger robuster Filter, Edge x2), Kosten (M15 gemessen 0,104 gegen M5 0,193), naechstes stehendes Level + P(break), Kegel (out-of-sample kalibriert, 80-%-Band trifft 77 %), Veto (-6,60 EUR/Lot). Status-Banner: SETUP BEREIT / WARTEN AUF LEVEL / KEIN TRADE. Es bestaetigt ausdruecklich auch das NICHT-Handeln - die gemessene Kern-Leckage sind diskretionaere Abweichungen. Die Order-Buttons werden BEWUSST NICHT gesperrt (gegen den Vorschlag "ausgrauen"): hartes Blockieren wurde im Projekt verworfen, und User-Uebersteuerungen liefen gemessen 68 % WR. Umsetzung: engine._m15_setup() setzt nur zusammen, was der Snapshot ohnehin erzeugt. Neu gerechnet wird allein das M15-Kosten-Verhaeltnis und P(break) fuer das naechste Level. Dafuer veroeffentlicht wave_rec jetzt tf_atr (ADDITIV) - der ATR je Zeitebene wurde im Turn-Fetch laengst berechnet, war aber nirgends abrufbar. P(break)-Merkmale bleiben auf M5 (Modell ist darauf trainiert, Fix 2026-07-13). Mit 5 Szenarien getestet (alles passt, Bias uneinig, Kosten teuer, Level zu weit, Kaltstart ohne ATR_M15 -> kein Absturz), live verifiziert ueber deploy.py --feld m15_setup. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 41 ++++++++++++++++++++++- core/engine.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ core/wave_rec.py | 9 ++++++ web/app.js | 79 +++++++++++++++++++++++++++++++++++++++++++++ web/index.html | 30 +++++++++++++++-- web/style.css | 19 +++++++++++ 6 files changed, 259 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a8ef40b..0a6ec96 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4182,7 +4182,7 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: („⚠ die Welle (steuert die Order) schweigt"). Live gegengerechnet, beide Zeilen erscheinen wie beabsichtigt. ⚠ Reine Anzeige — an Gewichten, Gates und Order-Logik ist nichts geändert. -- Asset-Version aktuell **v=151** (in `web/index.html` hochzählen, siehe Workflows). +- Asset-Version aktuell **v=153** (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: Npx/…`-**Shorthand** (3×: `.ms-chip`/`.ms-bos-lbl`/`.sqm-badge` — @@ -4942,6 +4942,45 @@ Modell schliesst keine Trades. „die Rangfolge ist kaputt" ist **nicht** belegt; belegt ist nur die Basisraten-/Kalibrierungslücke. +## ✅ M15-KARTE „SETUP-BEREITSCHAFT" GEBAUT (2026-08-08, v=153) — Ersatz der Gesamtempfehlung + +Nach dem Entfernen der Gesamtempfehlung und drei negativen M15-Messungen +(Squeeze-Einstieg, VWAP-Bänder, Exit-ATR) steht fest: **auf M15 trägt keine +Richtungsquelle.** Gebaut ist deshalb bewusst **keine Empfehlung**, sondern eine +**sequenzielle Gate-Kette** nach dem Schema des Users: +``` +[① HTF-BIAS M30/H1] → [② KOSTEN Spread/ATR_M15] → [③ LEVEL & KEGEL] → Order AM LEVEL +``` +⚠ **Der Unterschied zur alten Karte ist grundsätzlich**: dort wurden Stimmen zu +einem Bias **verrechnet** (gemessen 48–51 % Treffer über 1.071 Episoden), hier +muss jedes Gate **einzeln** passieren. Ein gefallenes Gate lässt sich nicht durch +ein anderes ausgleichen. + +**Nur BELEGTE Bausteine:** ① HTF-Trend (einziger robuster Filter, Edge ×2) · +② Kosten (M15 gemessen **0,104** gegen M5 0,193 — hier gewinnt M15 wirklich) · +③ nächstes STEHENDES Level aus `_draw_levels` + P(break) · ④ Kegel +(out-of-sample kalibriert, 80 %-Band trifft 77 %) · ⑤ Veto (gegen das Signal +−6,60 €/Lot, beidhälftig). +**Status-Banner:** 🟢 SETUP BEREIT @ Level · 🟡 WARTEN AUF LEVEL · 🔴 KEIN TRADE. +⚠ Das Banner bestätigt ausdrücklich auch das **Nicht**-Handeln — die gemessene +Kern-Leckage sind diskretionäre Abweichungen, nicht verpasste Gelegenheiten. +⚠⚠ **Die Order-Buttons werden BEWUSST NICHT gesperrt** (gegen den Vorschlag +„ausgrauen"): hartes Blockieren wurde im Projekt verworfen, und +User-Übersteuerungen liefen gemessen **68 % WR**. Sichtbar machen, nicht +bevormunden. + +**Umsetzung:** `engine._m15_setup()` setzt nur zusammen, was der Snapshot ohnehin +erzeugt; NEU gerechnet wird allein das M15-Kosten-Verhältnis und P(break) für das +nächste Level. ⚠ Dafür veröffentlicht `wave_rec` jetzt **`tf_atr`** (ADDITIV) — +der ATR je Zeitebene wurde im Turn-Fetch längst berechnet, war aber nirgends +abrufbar; ihn neu zu holen wäre ein zweiter MT5-Fetch für vorhandene Zahlen +(derselbe Fehler wie beim Konsens-Pfeil). +⚠ P(break)-Merkmale bleiben auf **M5** — das Modell ist darauf trainiert (Fix +2026-07-13); auf M15 umzustellen würde es falsch skalieren. +Mit **5 Szenarien getestet** (alles passt · Bias uneinig · Kosten teuer · Level +zu weit · Kaltstart ohne ATR_M15 → kein Absturz), live verifiziert über +`deploy.py --feld m15_setup`. + ## ⚠⚠ EXIT-ABSTAND AUS M15-ATR = FÄLLT DURCH (2026-08-08) Zweiter Teil der M15-Idee: M5-Einstieg behalten, nur den EXIT gröber führen. diff --git a/core/engine.py b/core/engine.py index 9561f38..39ef97f 100644 --- a/core/engine.py +++ b/core/engine.py @@ -3795,6 +3795,12 @@ class TradingEngine: if (b and a and atr) else None)( (market or {}).get("bid"), (market or {}).get("ask"), ((wave_snap or {}).get("pb_feats") or {}).get("atr")), + # M15-SETUP-BEREITSCHAFT (2026-08-08) — Ersatz fuer die entfernte + # Gesamtempfehlung. ⚠ KEINE Richtungsaussage: auf M15 traegt gemessen + # keine (Squeeze n=33/Nachbarn kippen, VWAP-Baender ohne Edge). + # Zeigt Bedingungen, nicht Prognosen. + "m15_setup": self._m15_setup(market, wave_snap, _cone, + self._alignment_cached()), "sr_close_hint": sr_close_hint, "stop_approach": stop_approach, "auto_sr_close": self._auto_sr_close, @@ -3968,6 +3974,84 @@ class TradingEngine: log.debug(f"sr_close_hint: {e}") return None + def _m15_setup(self, market, wave_snap, cone, alignment) -> dict | None: + """SETUP-BEREITSCHAFT auf M15 — der Ersatz für die entfernte Gesamtempfehlung. + + ⚠⚠ BEWUSST KEINE RICHTUNGS-EMPFEHLUNG. Gemessen (2026-08-08) trägt auf + M15 KEINE Richtungsquelle: der Squeeze fällt dort durch (n=33 in H1, + Nachbarn kippen), VWAP-Bänder ebenso (Zufallslinie hält gleich gut, alle + Konfidenzintervalle enthalten die Null), und 24 Signal-Eingriffe sind + insgesamt gescheitert. Was BELEGT ist, sind Bedingungen — nicht Prognosen. + Die Karte zeigt deshalb das Schema des Users (Bias → Zone → Reichweite → + Veto) und überlässt den Auslöser dem Menschen, dessen Auswahl-Vorteil + unabhängig belegt ist. + + Zusammengesetzt aus bereits berechneten Teilen — NEU gerechnet wird nur + das Kosten-Verhältnis auf M15 und P(break) für das nächste Level. Der + Konsens-Pfeil hat einmal ein zweites `_verdict()` gezogen; das ist die + Lehre daraus. + """ + try: + bid = (market or {}).get("bid"); ask = (market or {}).get("ask") + ws = wave_snap or {} + atr15 = (ws.get("tf_atr") or {}).get("M15") + out: dict = {"atr_m15": round(atr15, 4) if atr15 else None} + + # ── 1) BIAS — der einzige belegte Filter (M30-Gegen-Trend: Edge ×2) + h30 = ws.get("htf_trend"); h1 = ws.get("h1_trend") + einig = (h30 and h1 and h30 == h1) + out["bias"] = {"m30": h30, "h1": h1, + "richtung": h30 if einig else None, + "einig": bool(einig)} + + # ── 2) KOSTEN auf M15 — hier gewinnt M15 wirklich (gemessen 0,104 + # gegen M5 0,193). Schwellen wie beim M5-Chip (0,20 / 0,32). + kr = None + if bid and ask and atr15 and atr15 > 0: + kr = round((ask - bid) / atr15, 3) + out["kosten"] = {"ratio": kr, + "stufe": (None if kr is None else + "guenstig" if kr < 0.20 else + "normal" if kr < 0.32 else "teuer")} + + # ── 3) ZONE — nächstes STEHENDES Level (M30-Pivots, dieselbe Quelle + # wie Chart und S/R-Close) + P(break). ⚠ Die P(break)-Merkmale + # kommen aus M5, weil das Modell darauf trainiert ist (Fix + # 2026-07-13) — NICHT auf M15 umstellen. + dl = self._draw_levels() or {} + res = (dl.get("res") or [None])[0] + sup = (dl.get("sup") or [None])[0] + pf = ws.get("pb_feats") or {} + a5 = pf.get("atr") or 0.2 + zonen = [] + for lvl, seite in ((res, "res"), (sup, "sup")): + if not lvl or not bid or not atr15: + continue + d = 1 if seite == "res" else -1 + gap = abs(lvl - bid) + wt = 1.0 if (pf.get("ema_diff", 0.0) or 0.0) * d > 0 else 0.0 + p = _p_break(pf.get("mom6", 0.0) * d, pf.get("mom3", 0.0) * d, + wt, gap / max(a5, 0.06)) + zonen.append({"seite": seite, "level": round(lvl, 3), + "gap_atr15": round(gap / atr15, 2), + "p_break": round(100 * p)}) + out["zonen"] = zonen + + # ── 4) REICHWEITE — der Kegel (kalibriert: 80 %-Band trifft 77 %) + lv = (cone or {}).get("levels") or [] + b80 = [x for x in lv if x.get("band") == 80] + out["reichweite"] = [{"minutes": x.get("minutes"), + "lo": x.get("lo"), "hi": x.get("hi")} + for x in b80] or None + + # ── 5) VETO — die eigene Bilanz, je Lot (absolute Euro sind bei + # wechselnder Positionsgröße ungültig, Lehre vom 04.08.) + out["veto"] = alignment or None + return out + except Exception as e: + log.debug(f"m15_setup: {e}") + return None + def _stop_approach_hint(self, market, position, wave_snap) -> dict | None: """REINE ANZEIGE: P(Durchbruch) für das Level, dem sich der Kurs auf der **Gegen-/Stop-Seite** der offenen Position nähert (LONG → Support darunter, diff --git a/core/wave_rec.py b/core/wave_rec.py index b497f2f..86e526b 100644 --- a/core/wave_rec.py +++ b/core/wave_rec.py @@ -398,6 +398,14 @@ class WaveRecommender: if turn_data: # frisch geholt → neu berechnen, sonst letzten Stand halten self._last_tf_turns = {lbl: self._turn_state(c, a) for lbl, (c, a) in turn_data.items()} + # ⚠ ADDITIV (2026-08-08): den ATR je Zeitebene MITNEHMEN. Er wird im + # Turn-Fetch ohnehin berechnet (`turn_data[lbl] = (closes, atr)`), + # war aber nirgends abrufbar — die M15-Setup-Karte braucht ihn fuer + # das Kosten-Gate (Spread/ATR_M15) und die Abstandsangaben. Ihn dort + # neu zu holen waere ein zweiter MT5-Fetch fuer Zahlen, die schon da + # sind (derselbe Fehler wie beim Konsens-Pfeil, der ein zweites + # `_verdict()` rechnete). + self._tf_atr = {lbl: a for lbl, (c, a) in turn_data.items() if a} # P(break)-Features IMMER aus M5 (Modell wurde auf M5 trainiert/kalibriert # — die Wave-TF wechselt per Heuristik bis M30; damit wäre mom6 ein 3-h- # statt 30-min-Momentum und der ATR 2–3× größer → P(break) völlig falsch @@ -484,6 +492,7 @@ class WaveRecommender: else: self._last_bounce = {"state": None, "dir": None, "tf": None} snap["tf_turns"] = dict(self._last_tf_turns) + snap["tf_atr"] = dict(getattr(self, "_tf_atr", {}) or {}) snap["bounce"] = dict(self._last_bounce) # Multi-TF überschreibt Basis-TF snap["pb_feats"] = dict(self._pb_feats) if getattr(self, "_pb_feats", None) else None snap["pb_levels"] = dict(self._pb_levels) if getattr(self, "_pb_levels", None) else None diff --git a/web/app.js b/web/app.js index e33a6ff..5b34d5c 100644 --- a/web/app.js +++ b/web/app.js @@ -142,6 +142,84 @@ function beepRecommendation(sig, isNew) { // · HL-Kontext (`#hl-note`) — schreibt in die Meldungen-Karte, nicht hierher // Entfallen ist die Block-Grund-Zeile (`#vd-blocks`). // ══════════════════════════════════════════════════════════════════════════ +// ══════════════════════════════════════════════════════════════════════════ +// M15 SETUP-BEREITSCHAFT — sequenzielle GATE-KETTE, keine Score-Aggregation. +// [HTF-BIAS] → [KOSTEN] → [LEVEL & KEGEL] → AUSFÜHRUNG NUR AM LEVEL +// ⚠ Bewusst KEINE Richtungsaussage. Auf M15 trägt gemessen keine (Squeeze +// n=33/Nachbarn kippen, VWAP-Bänder ohne Edge). Die Karte sagt, ob die +// BEDINGUNGEN stimmen — der Auslöser bleibt beim Menschen. +// ⚠ Der Unterschied zur entfernten Gesamtempfehlung ist grundsätzlich: dort +// wurden Stimmen zu einem Bias VERRECHNET (gemessen 48–51 % Treffer), hier +// muss jedes Gate EINZELN passieren. Ein Gate, das fällt, lässt sich nicht +// durch ein anderes ausgleichen. +// ══════════════════════════════════════════════════════════════════════════ +function renderM15(s) { + const st = $("m15-status"); + if (!s) { st.className = "m15-status"; st.textContent = "— keine Daten"; return; } + const f = (v, d = 2) => (v == null ? "—" : (+v).toFixed(d)); + + // ── Gate 1: HTF-Bias (der einzige belegte Filter, Edge ×2) ─────────── + const b = s.bias || {}; + const bOk = !!b.einig; + $("m15-bias").className = "m15-item " + (bOk ? "ok" : "fail"); + $("m15-bias").textContent = bOk + ? `① Bias ${b.richtung === "LONG" ? "▲ LONG" : "▼ SHORT"} · M30+H1 einig` + : `① Bias unklar · M30 ${b.m30 || "—"} / H1 ${b.h1 || "—"}`; + + // ── Gate 2: Kosten auf M15 ─────────────────────────────────────────── + const k = s.kosten || {}; + const kOk = k.stufe === "guenstig" || k.stufe === "normal"; + $("m15-kosten").className = "m15-item " + (k.ratio == null ? "" : kOk ? "ok" : "fail"); + $("m15-kosten").textContent = k.ratio == null + ? "② Kosten —" + : `② Kosten ${f(k.ratio, 3)}×ATR · ${ + k.stufe === "guenstig" ? "günstig" : k.stufe === "normal" ? "normal" : "TEUER"}`; + + // ── Gate 3: Level in Reichweite ────────────────────────────────────── + const z = (s.zonen || []); + const nah = z.filter(x => x.gap_atr15 != null && x.gap_atr15 <= 0.5); + const zOk = nah.length > 0; + $("m15-zone").className = "m15-item " + (z.length ? (zOk ? "ok" : "warn") : ""); + $("m15-zone").textContent = z.length + ? "③ " + z.map(x => `${x.seite === "res" ? "Widerstand" : "Support"} ${f(x.level, 3)}` + + ` (${f(x.gap_atr15)}×ATR, Bruch ${x.p_break} %)`).join(" · ") + : "③ kein Level in Sicht"; + + // ── Reichweite (Kegel) — sagt WIE WEIT, nicht wohin ────────────────── + const r = s.reichweite || []; + $("m15-reich").className = "m15-item"; + $("m15-reich").textContent = r.length + ? "④ erwartete Spanne · " + r.map(x => `${x.minutes}′ ${f(x.lo, 2)}–${f(x.hi, 2)}`).join(" · ") + : "④ erwartete Spanne —"; + + // ── Veto: die eigene Bilanz, JE LOT ────────────────────────────────── + const v = s.veto || {}; + const g = v.against || {}; + $("m15-veto").className = "m15-item " + (g.n >= 5 ? "warn" : ""); + $("m15-veto").textContent = g.n >= 5 + ? `⑤ gegen das Signal: ${g.n} Trades, ${f(g.pnl_lot, 2)} € je Lot` + : "⑤ Veto: noch zu wenige Trades"; + + // ── Status: die Kette wird der Reihe nach ausgewertet ──────────────── + let cls, txt; + if (!bOk) { cls = "rot"; txt = "🔴 KEIN TRADE · Bias unklar (M30/H1 uneinig)"; } + else if (!kOk && k.ratio != null) { cls = "rot"; txt = "🔴 KEIN TRADE · Kosten zu hoch"; } + else if (!zOk) { cls = "gelb"; txt = "🟡 WARTEN AUF LEVEL · Bias und Kosten passen"; } + else { + const nn = nah[0]; + cls = "gruen"; + txt = `🟢 SETUP BEREIT @ ${f(nn.level, 3)} · ${b.richtung === "LONG" ? "▲" : "▼"} ` + + `${b.richtung} · Order ANS LEVEL legen, nicht zum Markt`; + } + st.className = "m15-status " + cls; + st.textContent = txt; + st.title = "Sequenzielle Kette: Bias → Kosten → Level. Ein gefallenes Gate lässt " + + "sich NICHT durch ein anderes ausgleichen. ⚠ Die Order-Buttons bleiben " + + "bewusst bedienbar — hartes Blockieren wurde im Projekt verworfen, und " + + "Übersteuerungen des Users liefen gemessen 68 % Trefferquote."; +} + + function renderKontext(snap) { // ── Selbst-Kalibrierung: die Empfehlung zeigt ihre eigene Trefferquote ── // ⚠ Gegen 50 % zu lesen, nicht gegen 0 — es ist eine Richtungsaussage. @@ -609,6 +687,7 @@ function render(d) { } // ── Gesamtempfehlung (aggregiert) ─────── + renderM15(d.m15_setup); renderKontext(d); renderCircuitBreaker(d.circuit_breaker); renderPatterns(d.patterns); diff --git a/web/index.html b/web/index.html index d3f5d63..8e1d748 100644 --- a/web/index.html +++ b/web/index.html @@ -6,7 +6,7 @@ Oil · MT5 - + @@ -87,6 +87,32 @@ GEBLIEBEN sind drei Dinge, die dort nur geparkt waren und je eine eigene, unabhaengige Beleglage haben (s. CLAUDE.md). ══════════════════════════════════════════════════════════════════ --> + +
+

M15 · Setup-Bereitschaft

+ +
+
+
+
+
+
+
+
+
+

Marktkontext

@@ -314,6 +340,6 @@ - + diff --git a/web/style.css b/web/style.css index faea3d0..973694e 100644 --- a/web/style.css +++ b/web/style.css @@ -489,3 +489,22 @@ main{padding-bottom:74px} /* Platz für die fixe Leiste */ .hl-note{font-size:13px;color:var(--dim);font-family:var(--mono); padding:5px 0;border-top:1px solid var(--line);margin-top:5px} .hl-note.hidden{display:none} + +/* ── M15 Setup-Bereitschaft (2026-08-08, Ersatz der Gesamtempfehlung) ────── + Sequenzielle Gate-Kette statt Score-Aggregation. Das Status-Banner ist + bewusst das groesste Element der Karte: die gemessene Kern-Leckage sind + diskretionaere Abweichungen, nicht verpasste Gelegenheiten - deshalb muss + auch das NICHT-Handeln ausdruecklich bestaetigt werden. */ +#card-m15{border-color:var(--accent)} +.m15-status{font-size:19px;font-weight:700;letter-spacing:.3px; + padding:10px 12px;border-radius:var(--r);margin-bottom:10px; + background:var(--surface-2);border:1px solid var(--border);color:var(--dim)} +.m15-status.gruen{color:var(--up);border-color:#1d5c2a;background:#0e2413} +.m15-status.gelb {color:var(--accent);border-color:#4a3600;background:#241d05} +.m15-status.rot {color:var(--down);border-color:#5c1a1a;background:#240f0f} +.m15-grid{display:flex;flex-direction:column;gap:5px} +.m15-item{font-size:15px;color:var(--dim);padding:4px 8px;border-radius:var(--r-sm); + background:var(--surface-2);border:1px solid var(--line)} +.m15-item.ok {color:var(--bright);border-color:#1d5c2a} +.m15-item.warn{color:var(--accent);border-color:#4a3600} +.m15-item.fail{color:var(--down);border-color:#5c1a1a}