""" Hyperliquid API utilities. 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" def _post(api_url: str, payload: dict) -> Any: resp = requests.post(api_url, json=payload, timeout=10) resp.raise_for_status() return resp.json() def get_asset_contexts(testnet: bool = True) -> dict[str, dict]: """ Fetch asset contexts including current funding rates. 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"}) 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 a per-8-hour rate. Positive = longs pay shorts. Multiply by 3 * 365 for approximate annualized rate. """ ctxs = get_asset_contexts(testnet=testnet) 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) return {name: float(ctx["funding"]) for name, ctx in ctxs.items()} def get_predicted_funding(asset_name: str, testnet: bool = True) -> float | None: """ Get the predicted funding rate for the next interval. Uses the predictedFundings endpoint. """ api = TESTNET_API if testnet else MAINNET_API 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", "").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