Fix Hyperliquid API: asset names come from universe array, not context dicts
The metaAndAssetCtxs endpoint returns asset names in a parallel universe array — asset contexts don't have a "name" field. Fixed the API module to index by the universe array properly. Also added get_mark_price() helper. Tested against testnet: BTC funding 0.00125% per 8h, mark $62,873.
This commit is contained in:
+46
-27
@@ -1,14 +1,19 @@
|
||||
"""
|
||||
Hyperliquid API utilities.
|
||||
|
||||
Direct REST calls to Hyperliquid info endpoint for data
|
||||
Direct REST calls to Hyperliquid's info endpoint for data
|
||||
not yet covered by the NautilusTrader adapter (funding rates,
|
||||
predicted fundings, asset contexts).
|
||||
|
||||
API structure:
|
||||
POST /info {"type": "metaAndAssetCtxs"}
|
||||
Returns [universe_meta, asset_contexts]
|
||||
- universe_meta["universe"] = list of {"name": "BTC", ...}
|
||||
- asset_contexts[i] corresponds to universe[i]
|
||||
"""
|
||||
import requests
|
||||
from typing import Any
|
||||
|
||||
|
||||
TESTNET_API = "https://api.hyperliquid-testnet.xyz/info"
|
||||
MAINNET_API = "https://api.hyperliquid.xyz/info"
|
||||
|
||||
@@ -19,44 +24,47 @@ def _post(api_url: str, payload: dict) -> Any:
|
||||
return resp.json()
|
||||
|
||||
|
||||
def get_asset_contexts(testnet: bool = True) -> list[dict]:
|
||||
def get_asset_contexts(testnet: bool = True) -> dict[str, dict]:
|
||||
"""
|
||||
Fetch asset contexts including current funding rates.
|
||||
Returns list of per-asset dicts with keys:
|
||||
funding, openInterest, markPx, oraclePx, premium, dayNtlVlm, etc.
|
||||
Returns dict keyed by asset name (BTC, ETH, SOL, etc.)
|
||||
with values containing funding, markPx, openInterest, etc.
|
||||
"""
|
||||
api = TESTNET_API if testnet else MAINNET_API
|
||||
data = _post(api, {"type": "metaAndAssetCtxs"})
|
||||
# data[0] = universe, data[1] = asset contexts
|
||||
if isinstance(data, list) and len(data) >= 2:
|
||||
return data[1]
|
||||
return []
|
||||
|
||||
if not isinstance(data, list) or len(data) < 2:
|
||||
return {}
|
||||
|
||||
universe = data[0].get("universe", [])
|
||||
ctxs = data[1]
|
||||
|
||||
result = {}
|
||||
for i, asset_info in enumerate(universe):
|
||||
if i < len(ctxs):
|
||||
name = asset_info.get("name", "")
|
||||
if name:
|
||||
result[name] = ctxs[i]
|
||||
return result
|
||||
|
||||
|
||||
def get_funding_rate(asset_name: str, testnet: bool = True) -> float | None:
|
||||
"""
|
||||
Get the current funding rate for a specific asset.
|
||||
Funding is paid every 8 hours. Positive = longs pay shorts.
|
||||
Funding is a per-8-hour rate. Positive = longs pay shorts.
|
||||
Multiply by 3 * 365 for approximate annualized rate.
|
||||
"""
|
||||
ctxs = get_asset_contexts(testnet=testnet)
|
||||
for ctx in ctxs:
|
||||
if isinstance(ctx, dict) and ctx.get("name") == asset_name.upper():
|
||||
funding_str = ctx.get("funding", "0")
|
||||
return float(funding_str)
|
||||
ctx = ctxs.get(asset_name.upper())
|
||||
if ctx:
|
||||
return float(ctx.get("funding", 0))
|
||||
return None
|
||||
|
||||
|
||||
def get_all_funding_rates(testnet: bool = True) -> dict[str, float]:
|
||||
"""Get funding rates for all assets on Hyperliquid."""
|
||||
ctxs = get_asset_contexts(testnet=testnet)
|
||||
rates = {}
|
||||
for ctx in ctxs:
|
||||
if isinstance(ctx, dict):
|
||||
name = ctx.get("name", "")
|
||||
funding_str = ctx.get("funding", "0")
|
||||
if name:
|
||||
rates[name] = float(funding_str)
|
||||
return rates
|
||||
return {name: float(ctx["funding"]) for name, ctx in ctxs.items()}
|
||||
|
||||
|
||||
def get_predicted_funding(asset_name: str, testnet: bool = True) -> float | None:
|
||||
@@ -65,11 +73,22 @@ def get_predicted_funding(asset_name: str, testnet: bool = True) -> float | None
|
||||
Uses the predictedFundings endpoint.
|
||||
"""
|
||||
api = TESTNET_API if testnet else MAINNET_API
|
||||
data = _post(api, {"type": "predictedFundings"})
|
||||
try:
|
||||
data = _post(api, {"type": "predictedFundings"})
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
if isinstance(data, list):
|
||||
for item in data:
|
||||
if isinstance(item, dict) and item.get("name") == asset_name.upper():
|
||||
# Return the Hyperliquid-specific prediction
|
||||
predicted = item.get("funding", "0")
|
||||
return float(predicted)
|
||||
if isinstance(item, dict) and item.get("name", "").upper() == asset_name.upper():
|
||||
return float(item.get("funding", 0))
|
||||
return None
|
||||
|
||||
|
||||
def get_mark_price(asset_name: str, testnet: bool = True) -> float | None:
|
||||
"""Get the current mark price for an asset."""
|
||||
ctxs = get_asset_contexts(testnet=testnet)
|
||||
ctx = ctxs.get(asset_name.upper())
|
||||
if ctx:
|
||||
return float(ctx.get("markPx", 0))
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user