From 3520056313be5af09af66dfb470182f5ffcf86dc Mon Sep 17 00:00:00 2001 From: ramseshk Date: Mon, 3 Aug 2026 11:39:11 +0000 Subject: [PATCH] Fix Hyperliquid API: asset names come from universe array, not context dicts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- common/hyperliquid_api.py | 73 ++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/common/hyperliquid_api.py b/common/hyperliquid_api.py index 50895b7..be594be 100644 --- a/common/hyperliquid_api.py +++ b/common/hyperliquid_api.py @@ -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