上周五凌晨三点,我的交易系统突然报警——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 是「自由竞争」模式——交易所内部撮合完成后立即推送。这意味着:
- Binance 的数据是「准时」的,适合回测时精确还原
- Hyperliquid 的数据是「即时」的,更适合实时交易但回溯时需额外处理
七、为什么选择 HolySheep 获取加密货币数据
如果你正在开发量化交易系统或需要历史 Order Book 数据进行回测,强烈建议使用 HolySheep AI 的 Tardis.dev 加密货币数据中转。
HolySheep 提供的核心优势:
- 支持 Binance / Bybit / OKX / Deribit 等主流交易所的逐笔成交数据
- Order Book 历史快照(Level 2),支持自定义深度
- 资金费率、强平数据等合约特有指标
- 国内直连延迟 < 50ms,无需翻墙
- ¥7.3 = $1 无损汇率,比官方节省 85%+
对于需要同时接入 Hyperliquid 实时数据和 Binance 历史数据的开发者,HolySheep 可以提供统一的接口层,极大简化多数据源整合的复杂度。
总结
Hyperliquid 与 Binance 的 Order Book 差异本质上是「对象式结构」与「数组式结构」的设计哲学不同。建议开发者在数据层做一层统一抽象,将两者的差异封装成统一的 OrderBookEntry 类,后续无论接入哪个交易所都能保持一致的解析逻辑。
对于高频交易场景,强烈建议使用代理服务或专业数据中转(如 HolySheep),将网络延迟从 200-500ms 降低到 < 50ms,这在高波动行情中可能是盈利与亏损的分界线。
👉 免费注册 HolySheep AI,获取首月赠额度