Kontext in die Meldungen (v=145)
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 <noreply@anthropic.com>
189 lines
8.2 KiB
Python
189 lines
8.2 KiB
Python
#!/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())
|