diff --git a/core/engine.py b/core/engine.py index 4f76aee..c673d5c 100644 --- a/core/engine.py +++ b/core/engine.py @@ -66,6 +66,7 @@ _MIN_CONF_DISPLAY = 70 # Konfidenz-Schwelle für die "Order ohne Sign # bis 2026-07-23 auf dem entfernten Dry-Run-Auto- # Trader beheimatet, hier als eigener Wert erhalten) _BERLIN = ZoneInfo("Europe/Berlin") +_OB_IMB_MIN = 0.30 # Orderbuch-Imbalance darunter = zu schwach fuer eine Stimme _SQUEEZE_NIGHT = (0, 1, 2, 3, 4, 5, 6, 7) # Auto-Squeeze-Nacht-Sperre (Kostenfalle) _SQUEEZE_SR_COOLDOWN_S = 180.0 # nach S/R-Close kein Auto-Squeeze in GLEICHER Richtung # (sonst Close-„Level hält" vs. Entry-„Level bricht"- @@ -1305,7 +1306,8 @@ class TradingEngine: # py, 2026-07-24): Copilot nicht robust prädiktiv, n aber klein → sammeln. try: vd = self._verdict(s, rec, self.wave.snapshot(), self.tu.snapshot(), - self.agent.snapshot(), self.elliott.snapshot()) + self.agent.snapshot(), self.elliott.snapshot(), + self.patterns.snapshot()) vv = {v["name"]: v for v in vd.get("votes") or []} def _v(name, key="vote"): return (vv.get(name) or {}).get(key, 0) @@ -1315,7 +1317,16 @@ class TradingEngine: tf=vd.get("tf") or "", wave=_v("Welle"), m30=_v("M30"), h1=_v("H1"), ki=_v("KI-Copilot"), ki_w=float(_v("KI-Copilot", "weight") or 0.0), elliott=_v("Elliott"), elliott_w=float(_v("Elliott", "weight") or 0.0), - squeeze=_v("Squeeze"), news_score=_news) + squeeze=_v("Squeeze"), news_score=_news, + # Die drei ab 2026-07-30 ergänzten Stimmen MITLOGGEN — nur so ist in + # ein paar Wochen datenbasiert entscheidbar, ob sie bleiben (Muster: + # Kontrolltest zeigt kleinen Zusatz) bzw. ob die UNGEMESSENEN + # Orderbuch-/Liq-Trend-Stimmen überhaupt Prädiktivität haben. + pattern=_v("Muster"), pattern_w=float(_v("Muster", "weight") or 0.0), + orderbook=_v("Orderbuch"), + orderbook_w=float(_v("Orderbuch", "weight") or 0.0), + liqtrend=_v("Liq-Trend"), + liqtrend_w=float(_v("Liq-Trend", "weight") or 0.0)) except Exception as e: log.debug(f"log_verdict_votes: {e}") @@ -2094,7 +2105,8 @@ class TradingEngine: except Exception as e: log.debug(f"write_levels: {e}") - def _verdict(self, market, wave_signal, wave_snap, tu, agent, elliott) -> dict: + def _verdict(self, market, wave_signal, wave_snap, tu, agent, elliott, + snap_parts_patterns=None) -> dict: """Gesamtempfehlung aus den Modulen für die Widget-Grafik. Headline = die **validierte Welle** (einziges Modul mit belegtem Edge); @@ -2161,6 +2173,63 @@ class TradingEngine: add("Squeeze", sqv, 2.0 if sq_active else 0.0, f"{sq.get('state') or '—'}" + (f" {sq.get('dir')}" if sq.get("dir") else "")) + # 8) Chartmuster — User-Vorgabe 2026-07-30 („berücksichtige die Chartmuster + # in der Gesamtempfehlung, sobald eins erkannt wird"). Gewicht bewusst KLEIN: + # der fällige KONTROLLTEST (2026-07-30) zeigt, dass die Muster einen zwar in + # BEIDEN Hälften positiven, aber kleinen Zusatz über einen GENERISCHEN + # Breakout hinaus liefern — Muster ØR +0,206/+0,328 vs. beliebiger Swing-Bruch + # +0,184/+0,220 (Delta nur +0,022 in H1, +0,108 in H2). Der Löwenanteil des + # Ertrags kommt also vom Breakout+Trailing, nicht vom Muster. Deshalb 1,0 + # (wie Elliott/KI) statt 2,0 (Squeeze) — und `forming` zählt nur halb, weil + # das Muster dort noch nicht bestätigt ist. Ziel-Trefferquote bleibt niedrig + # (23–26 %), die Stimme sagt also „Richtungstendenz", nicht „Kursziel". + pv, pdet, pw = 0, "—", 0.0 + try: + _pats = (snap_parts_patterns or {}).get("patterns") or [] + _p0 = next((m for m in _pats if m.get("dir") in ("bull", "bear")), None) + if _p0: + pv = 1 if _p0["dir"] == "bull" else -1 + _conf = _p0.get("status") == "confirmed" + pw = 1.0 if _conf else 0.5 + pdet = f"{_p0.get('name', '—')} · {'bestätigt' if _conf else 'bildet sich'}" + except Exception: + pass + add("Muster", pv, pw, pdet) + + # 9/10) ORDERBUCH-Imbalance + LIQUIDITÄTS-TREND (User-Vorgabe 2026-07-30). + # ⚠ BEIDE sind UNGEMESSEN — anders als alle anderen Stimmen. Die Übertragbar- + # keit von Hyperliquid-Orderbuchdaten auf den Pepperstone-CFD wird gerade + # erst erhoben (`analyze_bookflow.py`, ab ~50k Snapshots ≈ 3 Tage erster Blick). + # ⚠ Der Lead-Lag-Test (2026-07-30, 72 h) zeigt sogar, dass **Pepperstone + # FÜHRT** (Peak bei k=−1, r=0,525) und HL folgt — HL ist ein Oracle-basierter + # HIP-3-Perp, also strukturell Follower. Das dämpft die Erwartung erheblich. + # Deshalb bewusst das KLEINSTE Gewicht im ganzen Verdict (0,5 je Stimme; + # zum Vergleich: Welle 3,0 · Squeeze 2,0 · Muster/Elliott/KI 1,0). + # Beide werden in `verdict_votes` mitgeloggt → in ein paar Wochen ist + # datenbasiert entscheidbar, ob sie bleiben, mehr Gewicht bekommen oder + # rausfliegen (wie TU 2026-07-06). + ov, odet, ow = 0, "—", 0.0 + lv, ldet, lw = 0, "—", 0.0 + try: + w = self.hl_walls.walls() + if w: + imb = w.get("imb5") + if isinstance(imb, (int, float)) and abs(imb) >= _OB_IMB_MIN: + ov = 1 if imb > 0 else -1 + ow = 0.5 + odet = f"Imbalance {imb:+.2f}" + elif isinstance(imb, (int, float)): + odet = f"Imbalance {imb:+.2f} (zu schwach)" + tr = w.get("trend") or {} + if tr.get("dir") in ("up", "down"): + lv = 1 if tr["dir"] == "up" else -1 + lw = 0.5 + ldet = f"{tr['dir']} {tr.get('slope_per_h', 0):+.2f}$/h" + except Exception: + pass + add("Orderbuch", ov, ow, odet) + add("Liq-Trend", lv, lw, ldet) + # Bias nur über stimmberechtigte Module (weight>0); agree/total = wie viele # der ANDEREN stimmberechtigten Module die Wellen-Richtung teilen (die Welle # zählte sich vorher selbst mit — „3/6 für LONG" war geschönt). @@ -2548,6 +2617,8 @@ class TradingEngine: } sr_close_hint = self._sr_close_hint(market, position, wave_snap) stop_approach = self._stop_approach_hint(market, position, wave_snap) + # einmal holen — Verdict-Stimme UND Snapshot-Feld nutzen dasselbe Objekt + _pat_snap = self.patterns.snapshot() snap = { "ts": time.time(), "connected": self._connected, @@ -2580,11 +2651,11 @@ class TradingEngine: "tu": tu, "agent": agent, "verdict": self._verdict(market, wave_signal, wave_snap, - tu, agent, elliott), + tu, agent, elliott, _pat_snap), "daily_levels": self.daily.snapshot(), "gaps": self.gaps.snapshot(), "structure": self.structure.snapshot(), - "patterns": self.patterns.snapshot(), + "patterns": _pat_snap, "stats_compact": {"week": self._compact_stats("week"), "all": self._compact_stats("all")}, "close_alert_count": self._close_alert_count, diff --git a/core/history.py b/core/history.py index 2054891..ea24e0d 100644 --- a/core/history.py +++ b/core/history.py @@ -154,7 +154,14 @@ SCHEMA = [ elliott INTEGER, elliott_w REAL, squeeze INTEGER, - news_score REAL -- News-Sentiment zum Zeitpunkt (Kontext) + news_score REAL, -- News-Sentiment zum Zeitpunkt (Kontext) + -- ab 2026-07-30 (User-Vorgabe, Stimmen mit KLEINEM Gewicht): + pattern INTEGER, -- Chartmuster-Vote (Kontrolltest: kleiner Zusatz) + pattern_w REAL, -- 1,0 bestaetigt / 0,5 bildet sich / 0 keins + orderbook INTEGER, -- HL-Orderbuch-Imbalance (UNGEMESSEN) + orderbook_w REAL, + liqtrend INTEGER, -- Liquiditaets-Trend (UNGEMESSEN) + liqtrend_w REAL ) """, ] @@ -417,7 +424,9 @@ class HistoryLogger: def log_verdict_votes(self, *, headline: str, conf: int, bias: float, tf: str, wave: int, m30: int, h1: int, ki: int, ki_w: float, elliott: int, elliott_w: float, squeeze: int, - news_score=None) -> None: + news_score=None, pattern: int = 0, pattern_w: float = 0.0, + orderbook: int = 0, orderbook_w: float = 0.0, + liqtrend: int = 0, liqtrend_w: float = 0.0) -> None: """Verdict-Modul-Stimmen für die spätere Kalibrier-Auswertung loggen (Copilot/Elliott sind unbelegte Stimmen — nach ein paar Wochen kann `analyze_verdict_calibration.py`-artig entschieden werden, ob sie @@ -430,10 +439,12 @@ class HistoryLogger: conn.execute(""" INSERT INTO verdict_votes (ts, headline, conf, bias, tf, wave, m30, h1, ki, ki_w, - elliott, elliott_w, squeeze, news_score) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) + elliott, elliott_w, squeeze, news_score, + pattern, pattern_w, orderbook, orderbook_w, liqtrend, liqtrend_w) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) """, (int(now), headline, conf, bias, tf, wave, m30, h1, ki, ki_w, - elliott, elliott_w, squeeze, news_score)) + elliott, elliott_w, squeeze, news_score, + pattern, pattern_w, orderbook, orderbook_w, liqtrend, liqtrend_w)) conn.commit() def alignment_stats(self, last_n: int = 40) -> dict: diff --git a/web/app.js b/web/app.js index be000fd..87e04be 100644 --- a/web/app.js +++ b/web/app.js @@ -131,8 +131,14 @@ function renderVerdict(v, snap) { const vote = (x) => x > 0 ? ["long", "▲"] : x < 0 ? ["short", "▼"] : ["flat", "—"]; $("vd-modules").innerHTML = (v.votes || []).map(mod => { const [c, a] = vote(mod.vote); - return `