上周五凌晨三点,我的交易系统突然报警——Hyperliquid 的订单簿数据完全无法解析,所有挂单信息都变成了 NaN。排查了整整两个小时,才发现根本原因:Hyperliquid 的 Order Book 采用的是 GXM(Global Matching)架构,数据结构与 Binance 的合并深度(Depth)接口完全不同。本文将详细对比两者的字段映射、时序特性和接入差异,并给出可复制的代码方案。

一、报错场景还原

我的 WebSocket 订阅代码最初是这样写的:

# 原始 Binance 风格的解析逻辑(错误示例)
import json
import asyncio
from hyperliquid_info import Info

async def on_message(msg):
    if "data" in msg and "depth" in msg["data"]:
        # ❌ 直接复用 Binance 逻辑
        bids = msg["data"]["depth"]["bids"]  # 期望列表
        asks = msg["data"]["depth"]["asks"]
        for price, qty in bids:
            print(f"买单 {price} @ {qty}")

async def main():
    info = Info()
    await info.subscribe("book", {"coin": "BTC"}, on_message)
    await asyncio.sleep(3600)

asyncio.run(main())

运行后抛出异常:

TypeError: cannot unpack non-iterable float object

The above error occurred during handling of the 'book' message:
{'type': 'book', 'data': {'coin': 'BTC', 'sz_decimals': 8, 
'levels': {'bids': [{'px': '66123.50', 'n': 12}], 'asks': [{'px': '66125.00', 'n': 8}]}}}

问题很清楚了——Hyperliquid 的订单簿不是 {"bids": [[price, qty], ...]},而是 {"bids": [{"px": price, "n": qty}, ...]}。这是一个对象数组而非二维数组

二、核心数据结构对比

维度 Hyperliquid Order Book Binance Depth (diff/100ms)
数据结构 对象数组 [{"px": str, "n": int}] 二维数组 [[price, qty], ...]
价格精度 字符串,指数形式如 "1.23e-5" 字符串或数字,固定小数位
数量单位 张数(contracts),需乘 sz_decimals 基础资产数量(币)
更新类型 全量快照 + 增量 delta 仅增量 diff,需维护本地簿
更新频率 ~200ms 自由竞争 固定 100ms / 1000ms
WebSocket 端点 wss://api.hyperliquid.xyz/ws wss://stream.binance.com:9443/ws
字段命名 px (price), n (num contracts) [0] (price), [1] (qty)

三、正确的数据解析代码

3.1 Hyperliquid Order Book 解析

import json
import asyncio
import websockets

async def hyperliquid_orderbook():
    """Hyperliquid 订单簿正确订阅方式"""
    uri = "wss://api.hyperliquid.xyz/ws"
    
    subscribe_msg = {
        "method": "subscribe",
        "subscription": {
            "type": "book",
            "coin": "BTC",
            "depth": 20  # 请求深度,可选 10/20/50
        },
        "id": 1
    }
    
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps(subscribe_msg))
        print("已订阅 Hyperliquid BTC 订单簿")
        
        async for raw_msg in ws:
            msg = json.loads(raw_msg)
            
            # 解析订单簿数据
            if msg.get("type") == "book":
                data = msg["data"]
                sz_decimals = data.get("sz_decimals", 8)  # 数量精度
                
                for bid in data["levels"]["bids"]:
                    # ⚠️ px 是字符串,可能为科学计数法
                    price = float(bid["px"])
                    contracts = bid["n"]
                    # 转换为实际币数量
                    base_qty = contracts / (10 ** sz_decimals)
                    
                    print(f"[BID] 价格: {price:.2f}, 张数: {contracts}, "
                          f"等效币量: {base_qty:.8f}")
                
                for ask in data["levels"]["asks"]:
                    price = float(ask["px"])
                    contracts = ask["n"]
                    base_qty = contracts / (10 ** sz_decimals)
                    
                    print(f"[ASK] 价格: {price:.2f}, 张数: {contracts}, "
                          f"等效币量: {base_qty:.8f}")

asyncio.run(hyperliquid_orderbook())

3.2 Binance Depth 解析(对比参考)

import json
import asyncio
import websockets

async def binance_orderbook():
    """Binance 订单簿订阅方式"""
    # 合并深度流:btcusdt@depth20@100ms
    uri = "wss://stream.binance.com:9443/ws/btcusdt@depth20@100ms"
    
    async with websockets.connect(uri) as ws:
        print("已订阅 Binance BTCUSDT 订单簿")
        
        async for raw_msg in ws:
            msg = json.loads(raw_msg)
            
            # Binance 直接是二维数组,无需精度转换
            # 注意:Binance 返回的是增量更新,需要维护本地订单簿
            for price, qty in msg.get("b", []):  # bids
                price = float(price)
                qty = float(qty)
                if qty == 0:
                    # 删除订单
                    print(f"[删除] BID {price}")
                else:
                    print(f"[BID] 价格: {price:.2f}, 数量: {qty:.6f}")
            
            for price, qty in msg.get("a", []):  # asks
                price = float(price)
                qty = float(qty)
                if qty == 0:
                    print(f"[删除] ASK {price}")
                else:
                    print(f"[ASK] 价格: {price:.2f}, 数量: {qty:.6f}")

asyncio.run(binance_orderbook())

四、常见报错排查

报错 1:ValueError: could not convert string to float

原因:Hyperliquid 的价格字段为科学计数法字符串,如 "1.2345e-5"

# ❌ 错误处理
price = float("1.2345e-5")  # 0.000012345,但字符串拼接可能报错

✅ 正确处理

price = float(bid["px"]) print(f"{price:.10f}") # 明确精度输出

报错 2:KeyError: 'bids' / 'asks'

原因:首次接收是快照(snapshot),后续才是增量(delta)。字段名不同。

def parse_book_update(msg):
    data = msg.get("data", {})
    
    # Hyperliquid 在首次连接时可能返回不同格式
    if "levels" in data:
        # 快照格式
        return data["levels"]["bids"], data["levels"]["asks"]
    elif "bids" in data:
        # 增量格式
        return data["bids"], data["asks"]
    else:
        print(f"未知格式: {msg}")
        return [], []

报错 3:连接超时 WebSocketTimeoutError

原因:Hyperliquid 端点对国内访问延迟高,WS 心跳间隔要求严格。

import asyncio
import websockets
from websockets.exceptions import WebSocketTimeoutError

async def connect_with_retry(uri, max_retries=3, timeout=10):
    for attempt in range(max_retries):
        try:
            # 设置超时和保活
            ws = await websockets.connect(
                uri, 
                ping_interval=20,  # Hyperliquid 要求
                ping_timeout=10,
                open_timeout=timeout
            )
            return ws
        except WebSocketTimeoutError as e:
            print(f"尝试 {attempt+1}/{max_retries} 失败: {e}")
            await asyncio.sleep(2 ** attempt)  # 指数退避
    
    # 建议切换到中转服务
    raise ConnectionError("直连 Hyperliquid 失败,建议使用代理服务")

代理端点示例(延迟 < 50ms)

PROXY_URI = "wss://your-proxy-domain.com/hyperliquid/ws" asyncio.run(connect_with_retry(PROXY_URI))

五、数据字段完整映射表

业务含义 Hyperliquid 字段 Binance 字段 注意事项
交易对 coin: "BTC" URL 中的 symbol HL 用币名,BN 用交易对如 BTCUSDT
价格 px: string [0]: string HL 需 float() 转换,可能为科学计数
数量 n: int [1]: string HL 是合约张数,需除以 sz_decimals
精度信息 sz_decimals: int 字段精度固定 HL 动态获取,订单簿外层
更新时间戳 time: int (纳秒) updateId: int HL 是纳秒级,需除以 1e9
订单 ID oid: int 无直接对应 HL 支持按订单粒度更新

六、实战经验总结

在我对接 Hyperliquid 历史数据时,最头疼的不是数据结构差异,而是精度丢失问题。Hyperliquid 的 sz_decimals 是动态的,每个币种不同,如果你硬编码除以 1e8(很多文档错误示例),就会导致 SHIB 这种高精度币种的数量全部归零。

另外,Binance 的 Depth 是固定 1000ms 或 100ms 更新,而 Hyperliquid 的 Order Book 是「自由竞争」模式——交易所内部撮合完成后立即推送。这意味着:

七、为什么选择 HolySheep 获取加密货币数据

如果你正在开发量化交易系统或需要历史 Order Book 数据进行回测,强烈建议使用 HolySheep AI 的 Tardis.dev 加密货币数据中转

HolySheep 提供的核心优势:

对于需要同时接入 Hyperliquid 实时数据和 Binance 历史数据的开发者,HolySheep 可以提供统一的接口层,极大简化多数据源整合的复杂度。

总结

Hyperliquid 与 Binance 的 Order Book 差异本质上是「对象式结构」与「数组式结构」的设计哲学不同。建议开发者在数据层做一层统一抽象,将两者的差异封装成统一的 OrderBookEntry 类,后续无论接入哪个交易所都能保持一致的解析逻辑。

对于高频交易场景,强烈建议使用代理服务或专业数据中转(如 HolySheep),将网络延迟从 200-500ms 降低到 < 50ms,这在高波动行情中可能是盈利与亏损的分界线。

👉 免费注册 HolySheep AI,获取首月赠额度