Files
AH-Oil-Trader/analyze_hl_funding.py
Axel HocksandClaude Opus 5 2cdd1ba0e8 Hyperliquid geprueft: Orderbuch-Stimme raus, Funding gemessen (traegt nicht),
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>
2026-08-06 23:38:01 +02:00

189 lines
8.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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,21,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** (JanApr / AprAug)
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 h1 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())