From 2cdd1ba0e89eae81d132641ad638c3fe24407b61 Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Thu, 6 Aug 2026 23:38:01 +0200 Subject: [PATCH] Hyperliquid geprueft: Orderbuch-Stimme raus, Funding gemessen (traegt nicht), Kontext in die Meldungen (v=145) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Direkt gegen die HL-API geprueft statt aus dem Code geschlossen. ANGEBOT: 4 Oel-Maerkte (2 liquide: xyz:CL OI 3,07 Mio, xyz:BRENTOIL 2,25 Mio), je Markt mark/oracle/funding/open_interest/day_volume + l2Book (20 Level je Seite). Historie: fundingHistory 5.095 Stundenwerte ueber 7 Monate inkl. premium. Open Interest hat KEINE Historie -> waere sammelpflichtig. Netz-Frage geklaert: config sagt testnet (leeres Buch, 0 Bids/0 Asks), aber es sind zwei Schalter — [hyperliquid] network steuert das Handeln (aus), [sim] price_network die Daten (mainnet, live bestaetigt). (1) Mehr Orderbuch-Daten: NEIN. Zweimal unabhaengig gemessen — bookflow_report 0,2-1,0 bp gegen eine ~3-bp-Schwelle und KIPPT; Lead-Lag: Pepperstone fuehrt, HL trifft nach 6 s zu 51 %. (2) Modul "Orderbuch" Gewicht 0,5 -> 0. Ueber 2.688 Episoden beidhaelftig negativ (-0,054/-0,066, 49 % Treffer). Chip bleibt. (3) analyze_hl_funding.py: Funding/Premium gegen den CFD, 2 Haelften. ⚠ Der erste Lauf meldete mehrere "robuste" Buckets und war FALSCH — ueberlappende Forward-Fenster (aus n=506 werden ~21 unabhaengige Faelle) und global gebildete Quintile (Bucket mit der Zeit konfundiert). Dass Funding- und Premium-Tabelle fast identisch waren, war der dritte Hinweis: HL rechnet das Funding aus dem Premium. Entueberlappt haelt kein Bucket. (4) hl_ctx in den Meldungen (#hl-note) als reine Anzeige. ⚠ Zwei Bau-Fallen behoben: der Aufruf erbte den 2-s-Timeout der Waende und lief still ins Leere (braucht real 7,2 s), und er haette den Trend-Loop blockiert -> Daemon-Thread wie beim Wirtschaftskalender. Live verifiziert: hl_ctx befuellt, Orderbuch weight=0 bei erhaltenem Chip, v=145 ausgeliefert, eine Instanz auf Port 8000. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 55 +++++++++++- analyze_hl_funding.py | 188 ++++++++++++++++++++++++++++++++++++++++++ core/engine.py | 41 ++++++++- core/hl_walls.py | 58 +++++++++++++ web/app.js | 23 ++++++ web/index.html | 9 +- web/style.css | 5 ++ 7 files changed, 375 insertions(+), 4 deletions(-) create mode 100644 analyze_hl_funding.py diff --git a/CLAUDE.md b/CLAUDE.md index 6761aa3..4d5a922 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3661,7 +3661,60 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: 49 % Treffer) bei Gewicht 0,5 — der einzige konsistent falsche Beitrag. **Muster** (+1,27/+0,67, 58 %, n=88) und **Liq-Trend** (+0,20/+0,32, n=132) sehen gut aus, sind aber zu dünn. Alle übrigen kippen zwischen den Hälften. -- Asset-Version aktuell **v=144** (in `web/index.html` hochzählen, siehe Workflows). +- **⚠⚠ HYPERLIQUID: WAS ES LIEFERT UND WAS DAVON TRÄGT (2026-08-06, User-Frage + „können wir mehr HL-Daten nutzen?").** Direkt gegen die API geprüft, nicht aus + dem Code geschlossen. + **Angebot:** vier Öl-Märkte, zwei mit echter Liquidität — `xyz:CL` (unser Feed, + OI 3,07 Mio, Tagesvolumen 316 Mio) und `xyz:BRENTOIL` (OI 2,25 Mio); `flx:OIL` + und `cash:WTI` sind tot (OI 0). Je Markt `mark_px` · **`oracle_px`** · + **`funding`** · **`open_interest`** · `day_volume`, dazu `l2Book` mit 20 Levels + je Seite. **Historie:** `fundingHistory` gibt **5.095 Stundenwerte über 7 Monate** + (inkl. `premium`), `candleSnapshot` H1-Kerzen, `predictedFundings` venue-übergreifend. + ⚠ **Open Interest hat KEINE Historie** — nur der Momentanwert; OI wäre + sammelpflichtig und bleibt offen. + ✅ **Netz-Frage geklärt (sah zuerst nach einem Defekt aus):** `config.ini` sagt + `network = testnet`, und **Testnet hat ein LEERES Buch** (0 Bids/0 Asks, OI 2,55 + gegen 3,07 Mio). Es sind aber **zwei** Schalter: `[hyperliquid] network` steuert + das **Handeln** (bewusst aus), `[sim] price_network` die **Daten** und steht per + Default auf mainnet — live bestätigt (`/api/market` meldet `network: mainnet`). + ⚠ Bleibt eine Falle: ein Wort in der falschen Sektion, und der Bot zeichnet + stillschweigend Wände aus einem leeren Buch. + **(1) MEHR ORDERBUCH-DATEN = NEIN, zweimal unabhängig gemessen.** Das HL-Projekt + selbst (`bookflow_report.txt`): Bid/Ask-Druck auf den CFD −0,61/−0,52 bp und + **KIPPT**; Wände halten in 47–50 %; die eigene Schwelle dort lautet **„< ~3 bp + ist von den Kosten gefressen"** — gemessen sind es 0,2–1,0 bp. Dazu der + Lead-Lag-Befund: **Pepperstone führt** (Peak k=−1 ≈ 6 s), HL-Richtung trifft nach + 6 s zu **51 %** (n=545). Alles Preis-Abgeleitete von HL läuft hinterher. + **(2) MODUL „ORDERBUCH" ENTMACHTET — Gewicht 0,5 → 0.** Unabhängige Bestätigung + aus `analyze_verdict_modules.py`: über **2.688 Episoden beidhälftig NEGATIV** + (−0,054/−0,066 ×ATR, 49 % Treffer) — der einzige konsistent falsche Beitrag im + Feld. Chip bleibt, Stimme entfällt. Zurück: `ow = 0.5`. + **(3) FUNDING/PREMIUM GEMESSEN = TRÄGT NICHT (`analyze_hl_funding.py`).** + Positionierung statt Preis, also nicht vom Lead-Lag-Einwand betroffen — und dank + `fundingHistory` sofort prüfbar. 5.095 Stundenwerte, 3.626 mit passender H1-Bar, + gegen den **Pepperstone-CFD**, 2 Hälften, gegen die Drift. + ⚠⚠ **Der erste Lauf meldete mehrere „robuste" Buckets — und war falsch.** Zwei + Fehler in meinem eigenen Skript: **(a) überlappende Forward-Fenster** (stündliche + Beobachtungen, 4/12/24-h-Horizont → aufeinanderfolgende Zeilen teilen fast ihr + ganzes Fenster; aus n=506 werden ~21 unabhängige Fälle) und **(b) global + gebildete Quintile**, wodurch die Bucket-Zugehörigkeit mit der Zeit konfundiert + war (Q5 hatte 506 Fälle in H1, aber 219 in H2 = Regimewechsel, nicht Merkmal). + ⚠ Dritter Hinweis, der stutzig machte: **Funding- und Premium-Tabelle waren + nahezu identisch** — HL rechnet das Funding AUS dem Premium; es war eine + Messung, doppelt berichtet. + ✅ **Entüberlappt und mit Quintilen je Hälfte hält KEIN Bucket** die Regel; die + verbleibenden ✅ sitzen bei n=15–30 und sind Rauschen. **Kein Verdict-Gewicht.** + **(4) HL-KONTEXT IN DEN MELDUNGEN (reine Anzeige, v=145).** `hl_walls.market()` + (lokales Dashboard, keine neue externe Abhängigkeit) → Snapshot **`hl_ctx`** → + Zeile **`#hl-note`**: „Perp −0,03 $ zum Oracle · Funding −0,0083 %/h · OI + 3,07 Mio". Kein Gewicht, kein Trigger; der Tooltip nennt die Messung. + ⚠ **Zwei Bau-Fallen, beide behoben:** der Aufruf lief zunächst immer ins Leere, + weil er den **2-s-Timeout der Wände** erbte — `/api/market` fragt HL live ab und + braucht real **7,2 s** (eigener `_MARKET_TIMEOUT_S=12`); und der stille `except` + machte daraus ein wortloses `None`. Danach hätte er den **Trend-Loop 7 s + blockiert** → läuft jetzt im **Daemon-Thread**, wie der Wirtschaftskalender und + aus demselben Grund. +- Asset-Version aktuell **v=145** (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` — diff --git a/analyze_hl_funding.py b/analyze_hl_funding.py new file mode 100644 index 0000000..409db73 --- /dev/null +++ b/analyze_hl_funding.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +"""Sagt die Hyperliquid-POSITIONIERUNG (Funding / Premium) den CFD voraus? + +ANLASS (2026-08-06, User: „koennen wir mehr HL-Daten nutzen?"). Die Order-Flow- +Klasse ist zweimal unabhaengig gemessen und traegt nicht (`bookflow_report.txt`: +0,2–1,0 bp gegen eine ~3-bp-Kostenschwelle, KIPPT; `analyze_verdict_modules.py`: +Modul „Orderbuch" ueber 2.688 Episoden beidhaelftig negativ). Ungetestet ist eine +ANDERE Klasse: **Positionierung statt Preis.** + +⚠ WARUM DAS NICHT AM LEAD-LAG SCHEITERT. Gemessen fuehrt **Pepperstone** (Peak bei +k=−1 ≈ 6 s), HL-Richtung trifft nach 6 s zu 51 %. Damit ist alles PREIS-Abgeleitete +von HL erledigt. Funding und Premium sind aber keine Preisprognose, sondern der +Preis dafuer, eine Position zu HALTEN — eine Bestandsgroesse. Der Lead-Lag-Einwand +greift dort nicht. + +✅ UND ES IST SOFORT MESSBAR: `fundingHistory` liefert fuer `xyz:CL` **5.095 +Stundenwerte ueber 7 Monate** (inkl. `premium`) — kein wochenlanges Sammeln noetig, +anders als beim Bookflow-Recorder. +⚠ **Open Interest ist NICHT dabei**: die HL-Info-API hat keinen OI-History- +Endpunkt (`metaAndAssetCtxs` liefert nur den Momentanwert). OI waere also +sammelpflichtig und bleibt hier offen. + +GEMESSEN WIRD gegen den **Pepperstone-CFD** (das ist, was gehandelt wird), nicht +gegen den HL-Preis: + · Forward-Return ueber 4 / 12 / 24 h, ATR-normiert + · **gegen die DRIFT gelesen** — sonst faerbt das Regime alle Buckets ein + · Buckets nach Funding bzw. Premium (Quintile) + · **2 Haelften** (Jan–Apr / Apr–Aug) + +VORAB FIXIERTE ENTSCHEIDUNGSREGEL: + Tragfaehig nur, wenn ein Bucket in BEIDEN Haelften denselben Vorzeichen- + Ueberschuss zeigt UND der Betrag > 0,1×ATR liegt (B3-Huerde). Ein Effekt, der + zwischen den Haelften kippt, ist Rauschen — daran sind 23 Signal-Ideen + gescheitert. + +Aufruf: python analyze_hl_funding.py +""" +import bisect +import sys +import time +from datetime import datetime, timedelta, timezone + +import MetaTrader5 as mt5 + +sys.path.insert(0, r"c:/Users/ah/Downloads/HyperLiquid-WTI Trader") +from core.hl_info import post, url_for # noqa: E402 + +_COIN = "xyz:CL" +_BROKER_OFF = 3 * 3600 # MT5-Bar-Zeit ist Broker-Wallclock (UTC+3) +_HORIZONTE = (4, 12, 24) # Stunden +_HUERDE = 0.10 # B3 + + +def hole_funding() -> list[dict]: + """Blaettert `fundingHistory` zurueck (500 je Antwort), dedupliziert.""" + u = url_for("mainnet") + jetzt = int(time.time() * 1000) + start = jetzt - 400 * 86400000 + aus: dict[int, dict] = {} + for _ in range(15): + r = post(u, {"type": "fundingHistory", "coin": _COIN, + "startTime": start}, timeout=20) + if not r: + break + for x in r: + aus[int(x["time"])] = x + if len(r) < 500: + break + start = int(r[-1]["time"]) + 1 + return [aus[k] for k in sorted(aus)] + + +def _atr(H, L, C, n=14): + tr = [0.0] + [max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1])) + for i in range(1, len(C))] + out, s = [0.0] * len(C), 0.0 + for i in range(1, len(C)): + s += tr[i] + if i > n: + s -= tr[i - n] + out[i] = s / min(i, n) + return out + + +def quintile(werte: list[float]) -> list[float]: + s = sorted(werte) + return [s[int(q * len(s))] for q in (0.2, 0.4, 0.6, 0.8)] + + +def main(): + fh = hole_funding() + if len(fh) < 500: + print(f"zu wenig Funding-Historie ({len(fh)})"); return 1 + + if not mt5.initialize(): + print("MT5 nicht erreichbar"); return 1 + sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD") + if mt5.symbol_info(c)), None) + bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_H1, 0, 20000) + mt5.shutdown() + H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars] + C = [float(b["close"]) for b in bars] + # Bar-Zeit → ECHTE UTC-Epoch (die Rohwerte sind Broker-Wallclock) + EP = [int(b["time"]) - _BROKER_OFF for b in bars] + A = _atr(H, L, C) + + # ── Beobachtungen bauen ────────────────────────────────────────────── + beob = [] + for x in fh: + ts = int(x["time"]) // 1000 + j = bisect.bisect_right(EP, ts) - 1 + if j < 20 or j + max(_HORIZONTE) >= len(C) or A[j] <= 0: + continue + if abs(EP[j] - ts) > 2 * 3600: # keine Bar in der Naehe (Wochenende) + continue + beob.append({"ts": ts, "j": j, + "fund": float(x["fundingRate"]), + "prem": float(x["premium"]), + "atr": A[j]}) + if len(beob) < 400: + print(f"zu wenige verwertbare Beobachtungen ({len(beob)})"); return 1 + + f = lambda s: datetime.fromtimestamp(s, timezone.utc).strftime("%d.%m.%Y") + print("=" * 100) + print(f" HL-POSITIONIERUNG gegen den Pepperstone-CFD ({sym})") + print("=" * 100) + print(f" {len(fh)} Funding-Stundenwerte · {len(beob)} mit passender H1-Bar") + print(f" {f(beob[0]['ts'])} bis {f(beob[-1]['ts'])} · 2 Hälften · " + f"gegen die Drift gelesen · B3-Hürde {_HUERDE:.2f}×ATR") + mitte = len(beob) // 2 + + for feld, lbl in (("fund", "FUNDING-RATE"), ("prem", "PREMIUM (mark − oracle)")): + print() + print("=" * 100) + print(f" {lbl}") + print("=" * 100) + namen = ["Q1 (am negativsten)", "Q2", "Q3 (neutral)", "Q4", + "Q5 (am positivsten)"] + for h in _HORIZONTE: + # ⚠⚠ ENTÜBERLAPPEN. Die Beobachtungen kommen STÜNDLICH, das Fenster ist + # h Stunden lang — aufeinanderfolgende Zeilen teilen sich h−1 Stunden + # ihres Forward-Returns. Ungefiltert ist das kein n=506, sondern ~n/h + # unabhängige Fälle, und jede „Robustheit" daraus ist Autokorrelation. + # Deshalb nur jede h-te Beobachtung. + print(f" ── Horizont {h} h (entüberlappt: jede {h}. Beobachtung) " + + "─" * 22) + print(f" {'Bucket':<22}{'H1 Übersch.':>14}{'H2 Übersch.':>14}" + f"{'n H1/H2':>12} Urteil") + for q in range(5): + zeile = [] + for lo, hi in ((0, mitte), (mitte, len(beob))): + # ⚠ Quintile JE HÄLFTE. Global gebildet wandern die Grenzen mit + # dem Funding-Regime, und die Bucket-Zugehörigkeit wäre mit der + # Zeit konfundiert (real: Q5 hatte 506 Fälle in H1, aber nur 219 + # in H2 — das misst den Regimewechsel, nicht das Merkmal). + alle = beob[lo:hi:h] + if len(alle) < 50: + zeile.append((None, 0)); continue + gr = quintile([b[feld] for b in alle]) + bucket = lambda v: sum(1 for g in gr if v > g) + grp = [b for b in alle if bucket(b[feld]) == q] + if len(grp) < 15: + zeile.append((None, len(grp))); continue + r = lambda b: (C[b["j"] + h] - C[b["j"]]) / b["atr"] + m = sum(r(b) for b in grp) / len(grp) + drift = sum(r(b) for b in alle) / len(alle) + zeile.append((m - drift, len(grp))) + (u1, n1), (u2, n2) = zeile + if u1 is None or u2 is None: + print(f" {namen[q]:<22}{'—':>14}{'—':>14}{f'{n1}/{n2}':>12}") + continue + robust = (u1 > 0) == (u2 > 0) and min(abs(u1), abs(u2)) > _HUERDE + print(f" {namen[q]:<22}{u1:>+14.3f}{u2:>+14.3f}" + f"{f'{n1}/{n2}':>12} {'✅ robust' if robust else ''}") + print() + print("=" * 100) + print(" LESART: Überschuss = Kursbewegung MINUS der allgemeinen Drift der") + print(" jeweiligen Hälfte. ✅ nur, wenn das Vorzeichen in BEIDEN Hälften gleich") + print(" ist UND der Betrag über der Kostenhürde liegt.") + print(" ⚠ Open Interest fehlt: die HL-Info-API hat dafür keine Historie.") + print(" ⚠ `xyz:CL` ist mit ~3 Mio OI ein winziger Markt gegen NYMEX-CL — dass") + print(" seine Positionierung einen globalen Benchmark vorhersagt, ist a priori") + print(" unwahrscheinlich. Genau deshalb wird gemessen statt vermutet.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/core/engine.py b/core/engine.py index 223965e..8b86c69 100644 --- a/core/engine.py +++ b/core/engine.py @@ -694,6 +694,20 @@ class TradingEngine: except Exception as re_: log.warning(f"rec_outcomes: {re_}") # Wirtschaftskalender (self-throttled ~1 h, außerhalb des mt5_lock # unkritisch — reiner HTTP-Fetch mit Timeout) + # HL-Kontext (Funding/OI/Premium) — reine Anzeige. + # ⚠⚠ IM DAEMON-THREAD, nicht inline: der Aufruf fragt die + # HL-API live nach allen Öl-Märkten und braucht real ~7 s. + # Inline würde er den Trend-Loop bei jedem Fälligwerden + # (~5 min) so lange anhalten — dieselbe Begründung, aus der + # der Wirtschaftskalender im Thread läuft. `market()` ist + # selbst gedrosselt und thread-sicher, ein zweiter Aufruf + # während der erste läuft liefert einfach den Cache. + try: + if not getattr(self, "_hl_ctx_busy", False): + self._hl_ctx_busy = True + threading.Thread(target=self._hl_ctx_refresh, + daemon=True).start() + except Exception as he: log.debug(f"hl market: {he}") try: self.calendar.maybe_refresh() except Exception as ke: log.warning(f"calendar.refresh: {ke}") self._run_analysis() @@ -2974,7 +2988,20 @@ class TradingEngine: 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 + # ⚠⚠ GEWICHT AUF 0 (2026-08-06). Das Orderbuch-Modul ist aus + # ZWEI unabhängigen Richtungen als nicht tragfähig gemessen: + # · `analyze_verdict_modules.py` (15.544 Zeilen): über **2.688 + # Episoden beidhälftig NEGATIV** (−0,054/−0,066 ×ATR, 49 % + # Treffer) — der einzige konsistent falsche Beitrag im Feld. + # · Das HL-Projekt selbst (`bookflow_report.txt`): Bid/Ask-Druck + # auf den CFD übertragen −0,61/−0,52 bp und KIPPT; die eigene + # Schwelle dort lautet „< ~3 bp ist von den Kosten gefressen". + # Dazu der Lead-Lag-Befund: **Pepperstone führt** (Peak k=−1 ≈ 6 s), + # HL-Richtung trifft nach 6 s zu 51 % (n=545) — alles + # Preis-Abgeleitete von HL läuft hinterher. + # Der CHIP bleibt (Imbalance ist als Kontext lesbar), nur die + # Stimme entfällt. Zurück: `ow = 0.5`. + ow = 0.0 odet = f"Imbalance {imb:+.2f}" elif isinstance(imb, (int, float)): odet = f"Imbalance {imb:+.2f} (zu schwach)" @@ -3090,6 +3117,17 @@ class TradingEngine: self._align_ts = now return getattr(self, "_align_cache", None) + def _hl_ctx_refresh(self): + """Holt den HL-Kontext im Hintergrund (s. Aufrufstelle im Trend-Loop).""" + try: + d = self.hl_walls.market() + if d: + self._hl_ctx = d + except Exception as e: + log.debug(f"hl market: {e}") + finally: + self._hl_ctx_busy = False + def _block_mix_cached(self) -> dict | None: """WELCHE Gates blocken heute, und wie oft? (2026-08-06) @@ -3726,6 +3764,7 @@ class TradingEngine: (self.news.sentiment or {}).get("score")), "alignment": self._alignment_cached(), "block_mix": self._block_mix_cached(), + "hl_ctx": getattr(self, "_hl_ctx", None), "rec_track": self._rec_track_cached(), # Kennzahlen der MANUELLEN Trades für die gleichnamige Karte # (2026-08-04). Rein deskriptiv; ~120 s gecacht, damit der heiße diff --git a/core/hl_walls.py b/core/hl_walls.py index 29045fb..8d09ddb 100644 --- a/core/hl_walls.py +++ b/core/hl_walls.py @@ -33,6 +33,12 @@ log = get_logger("hlwalls") _TIMEOUT_S = 2.0 # der MT5-Loop darf nicht am HTTP hängen _TTL_S = 10.0 # Cache — die Wände wandern langsamer als der Export-Takt (~5 s) +_MARKET_TTL_S = 300.0 # Funding/OI aendern sich stuendlich — 5 min genuegt +# ⚠ EIGENER, laengerer Timeout: `/api/market` fragt die HL-API live nach ALLEN +# Oel-Maerkten (4 Stueck) und braucht deutlich laenger als der Waende-Endpunkt, +# der aus einer lokalen DB liest. Mit dem kurzen Waende-Timeout lief der Aufruf +# immer ins Leere — und der stille `except` machte daraus ein wortloses None. +_MARKET_TIMEOUT_S = 12.0 _MAX_AGE_S = 60.0 # älter = HL-Feed steht → nicht mehr zeichnen _STALE_GRACE_S = 120.0 # nach einem Fehlschlag den letzten guten Stand so lange # weiterreichen (Blink-Fix — s. Kommentar in `walls()`) @@ -47,6 +53,58 @@ class HLWalls: self._cache: dict | None = None self._cache_ts = 0.0 self._fail_logged = False + self._mkt_cache: dict | None = None + self._mkt_ts = 0.0 + + def market(self) -> dict | None: + """HL-KONTEXT je Oel-Markt: Funding, Open Interest, Oracle-Premium. + + ⚠⚠ REINE ANZEIGE — kein Verdict-Gewicht, kein Trade-Trigger. Gemessen + (`analyze_hl_funding.py`, 2026-08-06, 5.095 Funding-Stundenwerte ueber + 7 Monate gegen den Pepperstone-CFD): **entueberlappt haelt KEIN Bucket** + die 2-Stichproben-Regel. Der erste Lauf sah nach mehreren robusten + Effekten aus — das war Autokorrelation (stuendliche Beobachtungen, + 4/12/24-h-Fenster teilen sich fast ihre ganze Laenge). + Deshalb steht das hier als Kontext in den Meldungen und NICHT in der + Empfehlung. + + Quelle ist das LOKALE Dashboard (Port 8001), nicht die HL-API direkt — + keine neue externe Abhaengigkeit im Trading-Bot. Eigene, laengere + Drosselung (5 min): Funding aendert sich stuendlich, das braucht nicht + den 5-s-Takt der Waende. Fail-safe: bei Fehlern None. + """ + if not self.enabled: + return None + now = time.time() + with self._lock: + if (self._mkt_cache is not None + and now - self._mkt_ts < _MARKET_TTL_S): + return self._mkt_cache + aus = None + try: + url = self.url.replace("/api/walls", "/api/market") + with urllib.request.urlopen(url, timeout=_MARKET_TIMEOUT_S) as r: + d = json.loads(r.read().decode()) + if d.get("ok"): + for m in (d.get("markets") or []): + # Nur der Markt, aus dem auch die Waende kommen. + if m.get("market") != "xyz:CL": + continue + mark, orac = m.get("mark_px"), m.get("oracle_px") + aus = {"funding": m.get("funding"), + "open_interest": m.get("open_interest"), + "mark": mark, "oracle": orac, + "premium": (float(mark) - float(orac)) + if (mark and orac) else None, + "day_volume": m.get("day_volume"), + "network": d.get("network")} + break + except Exception: + aus = None # still — Kontext darf nie stoeren + with self._lock: + if aus is not None: + self._mkt_cache, self._mkt_ts = aus, now + return self._mkt_cache def walls(self) -> dict | None: """{'bid','bid_sz','ask','ask_sz','basis','imb5'} auf MT5-Preisniveau — diff --git a/web/app.js b/web/app.js index aa007e6..4a007e7 100644 --- a/web/app.js +++ b/web/app.js @@ -202,6 +202,29 @@ function renderVerdict(v, snap) { } } + // ── HL-Kontext (Funding / OI / Premium) — reine Anzeige ─────────────── + // ⚠ Gemessen ohne Prognosewert (analyze_hl_funding.py): entüberlappt hält + // kein Bucket die 2-Stichproben-Regel. Steht deshalb neutral in den + // Meldungen und NICHT in der Empfehlung. Null-sicher (Cache-Regel). + const hlEl = $("hl-note"); + if (hlEl) { + const h = snap && snap.hl_ctx; + if (h && h.premium != null) { + const fr = (h.funding != null) ? (h.funding * 100).toFixed(4) + " %" : "—"; + const oi = (h.open_interest != null) + ? (h.open_interest / 1e6).toFixed(2) + " Mio" : "—"; + hlEl.classList.remove("hidden"); + hlEl.textContent = `⛓ Hyperliquid: Perp ${h.premium >= 0 ? "+" : ""}` + + `${fmt(h.premium, 3)} $ zum Oracle · Funding ${fr}/h · OI ${oi}`; + hlEl.title = "Kontext vom Hyperliquid-Öl-Perp (xyz:CL). Positives Premium = " + + "der Perp notiert über dem Index, Longs zahlen. ⚠ Gemessen über 5.095 " + + "Funding-Stundenwerte (7 Monate) gegen den CFD: kein belastbarer " + + "Prognosewert — deshalb ohne Stimme in der Empfehlung."; + } else { + hlEl.classList.add("hidden"); + } + } + // ── Warum WARTEN? Verteilung der blockierenden Gates (heute) ─────────── const blkEl = $("vd-blocks"); if (blkEl) { diff --git a/web/index.html b/web/index.html index 6dc8de0..dbbcefb 100644 --- a/web/index.html +++ b/web/index.html @@ -6,7 +6,7 @@ Oil · MT5 - + @@ -141,6 +141,11 @@ + +