diff --git a/core/engine.py b/core/engine.py index f282b78..9561f38 100644 --- a/core/engine.py +++ b/core/engine.py @@ -3744,8 +3744,13 @@ class TradingEngine: "elliott": elliott, "tu": tu, "agent": agent, - "verdict": self._verdict(market, wave_signal, wave_snap, - tu, agent, elliott, _pat_snap), + # ⚠ `verdict` ist am 2026-08-08 aus dem Snapshot GEFLOGEN (die + # Gesamtempfehlung wurde entfernt). `_verdict()` selbst LEBT WEITER — + # es speist 1×/min die Telemetrie `verdict_votes` (User-Entscheidung: + # weiter sammeln) und den optionalen MQL5-Konsens-Pfeil. Es hier im + # 1-s-Pfad zu rechnen waere Arbeit fuer eine Anzeige, die es nicht + # mehr gibt — genau der Fehler, der beim Konsens-Pfeil schon einmal + # dokumentiert wurde. "daily_levels": self.daily.snapshot(), "gaps": self.gaps.snapshot(), "structure": self.structure.snapshot(), @@ -3767,7 +3772,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(), - "block_mix": self._block_mix_cached(), + # `block_mix` entfallen (2026-08-08): die Zeile `#vd-blocks` gehoerte + # zur Gesamtempfehlung. `_block_mix_cached()` bleibt als Methode + # stehen — die Abfrage ist fuer eine kuenftige Anzeige nuetzlich und + # kostet ungenutzt nichts (sie laeuft nur beim Aufruf). "hl_ctx": getattr(self, "_hl_ctx", None), "rec_track": self._rec_track_cached(), # Kennzahlen der MANUELLEN Trades für die gleichnamige Karte diff --git a/docs/gesamtempfehlung.md b/docs/gesamtempfehlung.md deleted file mode 100644 index 5d13c8e..0000000 --- a/docs/gesamtempfehlung.md +++ /dev/null @@ -1,68 +0,0 @@ -# Gesamtempfehlung (Verdict-Panel) - -Grafischer Empfehlungsbereich ganz oben im Mobile-Dashboard (`web/`), der die -Module zu **einer** Aussage zusammenfasst. - -## Wer berechnet was - -- **Backend (`core/engine.py` → `_verdict`)** baut die Aggregation und legt sie - als `verdict` in den Snapshot (`/api/snapshot`, `/ws`). Eine Quelle der - Wahrheit — das Frontend rendert nur. -- **Frontend (`web/app.js` → `renderVerdict`)** zeichnet Ring, Bias-Balken und - Modul-Chips. Markup in `web/index.html` (`#card-verdict`), Stil in - `web/style.css` (`.vd-*`). - -## Logik - -**Headline = die validierte Welle** (`wave_signal`) — das einzige Modul mit per -Backtest belegtem Edge. Sie bestimmt LONG/SHORT/WARTEN und die Order-Freigabe. -Die übrigen Module bilden nur einen **gewichteten Konsens** (Anzeige, kein -Eingriff in die Order-Logik). - -Votes je Modul: `+1` LONG · `−1` SHORT · `0` neutral. Gewichte: - -| Modul | Gewicht | Quelle | -|-------|---------|--------| -| Welle | **3,0** | `wave_signal.signal` (validierter Edge) | -| M30 | 1,5 | `wave.htf_trend` (EMA12/50 der M30 — exakt das Signal, das die Welle nutzt) | -| H1 | 1,5 | `wave.h1_trend` (EMA12/50 der H1) | -| KI-Copilot | 1,0 *(nur mit Bias)* | `agent.advisory.bias` — ohne advisory/Bias **Gewicht 0** | -| Elliott/FVG | 1,0 *(nur mit Ziel)* | Richtung zum `elliott.target` — ohne Ziel **Gewicht 0** | -| Squeeze | 2,0 *(nur `active`)* | `wave.squeeze` — armed/keine Kompression **Gewicht 0** | - -(TradersUnion wurde 2026-07-06 aus dem Konsens entfernt — nur noch Anzeige.) - -``` -bias = Σ(gewicht · vote) / Σ(gewicht) → −1 … +1 -``` - -⚠ **Stimmberechtigt sind nur Module mit Aussage** (Fix 2026-07-15): Module ohne -anwendbares Signal (Squeeze ohne Ausbruch, Elliott ohne Ziel, KI aus) bekommen -Gewicht 0 und stehen NICHT im Nenner — vorher dämpfte z. B. das fast immer -neutrale Squeeze-Gewicht (2,0) die Nadel dauerhaft um ~×0,8 Richtung Mitte. -„Flach" bei Welle/M30/H1 bleibt dagegen eine ECHTE Neutral-Stimme (mit Gewicht). - -`bias` steuert die **Nadel** im Balken (−1 = ganz SHORT/links, +1 = ganz -LONG/rechts: `left% = (bias+1)/2·100`). `agree/total` = wie viele der **anderen** -stimmberechtigten Module (ohne die Welle selbst — die zählte sich bis 2026-07-15 -mit) die Headline-Richtung teilen. Der **Konfidenz-Ring** zeigt `wave.conf_pct` -als conic-gradient in Richtungsfarbe. - -> ⚠ Wichtig: Für M30/H1 werden die **EMA12/50-Signale der Welle** genutzt -> (`htf_trend`/`h1_trend`), NICHT das Regressions-Winkelmaß `market.angles` — -> die beiden können sich widersprechen, das Panel muss zur Empfehlung passen. - -## Snapshot-Feld `verdict` - -```json -{ - "headline": "SHORT", "conf": 90, "bias": -1.0, "agree": 3, "total": 3, - "votes": [{"name": "Welle", "vote": -1, "weight": 3.0, "detail": "SHORT · 90%"}, …] -} -``` - -## Pflege - -Neues Modul ins Panel → Vote/Gewicht in `_verdict` ergänzen; das Frontend rendert -`votes` generisch. Bei JS/CSS-Änderung Asset-Version `v=N` in `index.html` -hochzählen. Siehe `CLAUDE.md`. diff --git a/web/app.js b/web/app.js index 52bd8a9..e33a6ff 100644 --- a/web/app.js +++ b/web/app.js @@ -130,199 +130,62 @@ function beepRecommendation(sig, isNew) { } // ── Gesamtempfehlung: Konfidenz-Ring + Bias-Balken + Modul-Chips ─────────── -function renderVerdict(v, snap) { - const sig = v.headline || "—"; - const cls = sig === "LONG" ? "long" : sig === "SHORT" ? "short" : ""; - const arr = sig === "LONG" ? "▲" : sig === "SHORT" ? "▼" : "◌"; - $("vd-signal").className = "vd-signal " + cls; - // Zeitebene, für die die Empfehlung gilt (M1/M5/M15/M30/H1) - let head = `${arr} ${sig}` + (v.tf ? ` · ${v.tf}` : ""); - // ⚠ WARUM WARTEN? (2026-07-31, User-Fund „WARTEN, aber Konsens stark LONG"). - // Die Welle HAT meist eine Richtung — sie hängt an einem Gate. Gemessen mit der - // neuen `block_reason`-Telemetrie (3 h): breakout_pending 63 % · entry_room 20 % · - // min_conf 18 %, KEIN einziges Mal Totband oder HTF-Gegen-Trend. Der Grund wurde - // bisher NUR als Textzeile in der Meldungen-Karte gezeigt — also weit weg von der - // Stelle, an der der scheinbare Widerspruch entsteht. Jetzt steht er direkt an der - // Headline: „◌ WARTEN · M15 · LONG, noch 0.11 bis zur Bestätigung". - const bo = (snap && snap.wave && snap.wave.breakout) || null; - const wsig = (snap && snap.wave_signal) || {}; - if (sig === "WARTEN" && bo && bo.pending && bo.dir) { - head += ` · ${bo.dir}, noch ${fmt(bo.need, 2)} bis zur Bestätigung`; - } else if (sig === "WARTEN" && wsig.block) { - // ⚠ ERGÄNZUNG 2026-08-07: bisher stand der Grund NUR bei - // `breakout_pending` an der Headline — bei jedem anderen Gate blieb dort - // ein nacktes „WARTEN", während die Konsens-Zeile darunter „stark LONG" - // meldete. Genau dieser Widerspruch war der Anlass. Der Grund liegt im - // Snapshot (`wave_signal.block`), er wurde nur nie gezeigt. - const G = { - min_conf: `Konfidenz ${wsig.conf_pct || 0} % < 55 % — Setup zu schwach`, - entry_room: "kein Raum bis zum Gegenlevel", - htf_counter: "höhere Zeitebene steht dagegen", - stretch: "Kurs zu weit von der EMA entfernt", - deadband: "Totband — kein klarer Trend", - dead_hour: "gesperrte Handelsstunde", - eia: "EIA-Blackout", - stale: "Kursdaten veraltet", - no_data: "keine Daten", - }; - const g = G[wsig.block] || wsig.block; - if (g) head += ` · ${g}`; - } - $("vd-signal").textContent = head; - - // Konfidenz-Ring (conic-gradient bis conf%, Farbe nach Richtung) - const conf = v.conf || 0; - const col = sig === "LONG" ? "var(--up)" : sig === "SHORT" ? "var(--down)" : "var(--dim)"; - const deg = Math.max(0, Math.min(100, conf)) * 3.6; - $("vd-ring").style.background = - `conic-gradient(${col} ${deg}deg, #21262d ${deg}deg)`; - $("vd-conf").textContent = conf ? conf + "%" : "—"; - - // Konsens-Text + Bias-Nadel (-1..+1 → 0..100 %) - const total = v.total || 0, agree = v.agree || 0; - const bias = typeof v.bias === "number" ? v.bias : 0; - const strength = Math.abs(bias) >= 0.5 ? "stark" : Math.abs(bias) >= 0.2 ? "leicht" : "gemischt"; - const dir = bias > 0.05 ? "LONG" : bias < -0.05 ? "SHORT" : "neutral"; - // Live-Kosten-Chip (Spread/ATR — gemessen Ø 0,265; Nacht 0,32–0,50 = Falle): - // reine Info, die die abgeschaltete Dead-Hours-Sperre ehrlich ersetzt. - const cr = snap && snap.cost_ratio; - let costTxt = ""; - if (cr != null) { - const tag = cr < 0.20 ? "✅ günstig" : cr <= 0.32 ? "· normal" : "⚠ teuer"; - costTxt = ` · Kosten ${fmt(cr, 2)}×ATR ${tag}`; - } - // ⚠ Konsens-Zeile korrigiert (2026-07-31). Zwei Fehler waren drin: - // (1) „… Module für ${sig}" nahm die HEADLINE als Bezug. Seit dem Fix vom 30.07. - // zählt `agree` bei WARTEN aber die Übereinstimmung mit der BIAS-Richtung → - // es stand „stark LONG · 4/4 Module für WARTEN". Die Richtung kommt jetzt als - // `ref_dir` vom Backend, statt hier aus `bias` nachgebaut zu werden. - // (2) `total` zählt nur Module MIT Aussage (Gewicht>0, ohne die Welle) — nach den - // Gewichts-Fixes schweigen mehr Module, während 9 Chips sichtbar bleiben. - // „4/4" las sich dadurch widersprüchlich. Jetzt steht dabei, wie viele - // Module gerade nichts sagen. - const refDir = v.ref_dir || null; - const silent = typeof v.silent === "number" ? v.silent : null; - // ⚠⚠ KORREKTUR 2026-08-07. „stark LONG · 2 von 2 stimmberechtigten" las sich - // wie Einstimmigkeit — dabei sprachen nur 2 von 9 Modulen, und die WELLE, die - // als einzige die Order steuert, war nicht dabei. `bias` ist arithmetisch - // richtig (das gesamte sprechende Gewicht war einig), aber die BASIS war - // winzig. Seit dem Entmachten von Elliott und Orderbuch (06.08.) trat das - // häufiger auf. Jetzt führt die Zeile mit der Basis und nennt die stumme - // Welle ausdrücklich — das löst den scheinbaren Widerspruch zur Headline auf. - const welleStumm = (v.votes || []).some( - m => m.name === "Welle" && !Number(m.weight)); - const nMod = (v.votes || []).filter(m => m.used !== false).length; - let consensus; - if (sig === "—") { - consensus = "keine Daten"; - } else if (!refDir || !total) { - consensus = "Modul-Konsens: kein Modul mit klarer Aussage"; - } else { - // „stark/leicht" nur noch, wenn ueberhaupt die Haelfte der Module spricht — - // sonst beschreibt das Wort die Einigkeit von zwei Nebenstimmen. - const dueneBasis = nMod && total < nMod / 2; - consensus = `Modul-Konsens: ${agree} von ${nMod} Modulen sprechen, ` - + `${agree === total ? "alle" : `${agree} davon`} für ${refDir}` - + (dueneBasis ? "" : ` · ${strength} ${dir}`); - if (welleStumm) consensus += " · ⚠ die Welle (steuert die Order) schweigt"; - } - $("vd-consensus").textContent = consensus + costTxt; - $("vd-needle").style.left = ((bias + 1) / 2 * 100).toFixed(1) + "%"; - - // Modul-Chips - const vote = (x) => x > 0 ? ["long", "▲"] : x < 0 ? ["short", "▼"] : ["flat", "—"]; - // ⚠ NUR Module zeigen, die wirklich in die Empfehlung einfliessen - // (User-Vorgabe 2026-08-06). `used === false` = dauerhaft entmachtet, weil - // gemessen ohne Inhalt (Elliott: 1.384 Episoden flach bei 25,9 % Einfluss; - // Orderbuch: 2.688 Episoden beidhaelftig negativ). Sie bleiben im Snapshot - // und in `verdict_votes` — nur der Chip entfaellt. - // Module mit `weight === 0` bleiben SICHTBAR: die schweigen nur situativ - // (Welle bei WARTEN, Squeeze ohne Ausbruch) und sprechen wieder. - $("vd-modules").innerHTML = (v.votes || []).filter(m => m.used !== false).map(mod => { - const [c, a] = vote(mod.vote); - // Gewicht sichtbar machen: Module OHNE Aussage (w=0) grau, sonst das Gewicht - // anzeigen — so ist erkennbar, dass z. B. Orderbuch/Liq-Trend (0,5, ungemessen) - // schwächer zählen als die Welle (3,0) oder der Squeeze (2,0). - const w = Number(mod.weight || 0); - const cls = w > 0 ? c : "flat mute"; - const wtag = w > 0 ? `${w.toFixed(1).replace(".0", "")}` : ""; - return `