Hyperliquid fill 接口响应体详解
官方文档地址
这是一个 Hyperliquid fill(成交记录) 的响应体示例(常见于 userFills 接口、WebSocket userFills 推送或类似地方),下面逐字段解释:
{
"coin": "xyz:AMD", // 资产符号。对于 HIP-3(多 dex)资产会带前缀,比如 xyz:、 purp: 等;普通 perp 就是单纯币种如 BTC、ETH
"px": "213.47", // **成交价格**(成交时的执行价格)
"sz": "0.912", // **本次成交数量**(这次 fill 实际成交的张数/币数)
"side": "A", // 成交方向: "B" = Buy(买入),"A" = Sell(卖出 / Ask)
"time": 1770215435819, // **成交时间戳**(毫秒级 Unix timestamp)
"startPosition": "0.912", // **本次成交前的持仓大小**(正数=多头,负数=空头)。用于帮助计算/校验持仓变化
"dir": "Close Long", // **本次成交的持仓影响方向**(前端显示用),常见值:
// Open Long → 开多
// Open Short → 开空
// Close Long → 平多
// Close Short → 平空
// Buy / Sell → 现货或不影响持仓的情况
"closedPnl": "-11.688192", // **本次成交实现的已实现盈亏**(只有平仓方向才有非零值,单位通常是 USDC)
"hash": "0x23eb7bda8746a46f25650434aa96720206b200c02249c341c7b4272d464a7e59",
// **L1 交易哈希**(上链的交易哈希,可在浏览器查看)
"oid": 311561736864, // **订单 ID**(order id),本次 fill 所属的 maker/taker 订单的 oid
"crossed": true, // 是否**吃单(taker)**:true = 吃掉了订单簿上的流动性(taker),false = 挂单成交(maker)
"fee": "0.01682", // **本次成交手续费**(金额)
"tid": 833777413644058, // **成交 ID**(全局唯一的 trade/fill ID,通常比 oid 更唯一)
"liquidation": { // **如果是强平成交**,这里会有内容;否则为 null 或不存在
"liquidatedUser": "0x324f74880ccee9a05282614d3f80c09831a36774", // 被强平的用户地址
"markPx": "214.04", // 强平时的标记价格
"method": "market" // 强平方式(market = 市价强平)
},
"feeToken": "USDC", // 手续费扣除的代币(目前几乎都是 USDC)
"twapId": null // 如果是 TWAP 订单的一部分,这里会有 TWAP 的 id;否则 null
}
字段总结对比表(最常用字段)
| 字段 | 中文含义 | 是否必有 | 典型值示例 | 备注 |
|---|---|---|---|---|
| coin | 资产 | 是 | BTC, SOL, xyz:AMD | HIP-3 资产带前缀 |
| px | 成交价格 | 是 | "213.47" | 字符串,注意精度 |
| sz | 本次成交量 | 是 | "0.912" | 字符串 |
| side | 买卖方向 | 是 | "B" / "A" | B=买 A=卖 |
| time | 时间戳 | 是 | 毫秒级 | — |
| dir | 持仓方向(显示用) | 是 | "Close Long" | 最直观判断是开/平仓 |
| closedPnl | 实现盈亏 | 是 | "-11.688192" / "0.0" | 平仓才有非零值 |
| startPosition | 成交前持仓 | 是 | "0.912" / "-5.0" | 判断持仓变化非常有用 |
| crossed | 是否 taker | 是 | true/false | true=吃单,付 taker 费 |
| fee | 手续费金额 | 是 | "0.01682" | — |
| oid | 订单ID | 是 | 311561736864 | 对应你下的订单 |
| tid | 成交ID | 是 | 833777413644058 | 全局唯一 |
| hash | 上链交易哈希 | 是 | 0x23eb... | 可查区块链浏览器 |
| liquidation | 强平信息 | 否 | object / null | 只在强平 fill 出现 |
| feeToken | 手续费币种 | 是 | "USDC" | 目前基本固定 |
| twapId | TWAP ID | 否 | null / number | TWAP 分拆成交才有值 |
这份结构基本覆盖了目前(2026年初)Hyperliquid perp 和 HIP-3 资产的 fill 返回格式。如果你在写风控、pnl 计算、对账系统,强烈建议重点关注 startPosition → sz → dir → closedPnl 这几个字段的组合,它们能完整重现持仓和盈亏变化路径。
注意 liquidation 这个字段一般不存在,存在就意味着是强平的!
强平检测脚本
#!/usr/bin/env python3
"""
爆仓检测测试脚本
直接从 API 获取数据,验证 liquidation 字段
"""
from hyperliquid.info import Info
from datetime import datetime
import sys
def test_liquidation(address: str):
"""检测指定地址的爆仓记录"""
print("=" * 80)
print("🔍 爆仓检测测试")
print("=" * 80)
print(f"\n地址: {address}")
print("-" * 80)
info = Info(skip_ws=True)
# 1. 直接从 API 获取 fills 数据
print("\n【步骤1】从 API 获取交易记录...")
fills = info.user_fills_by_time(address, start_time=0)
print(f" 获取 {len(fills)} 条记录")
# 2. 检测爆仓记录
print("\n【步骤2】检测爆仓记录...")
liquidations = []
for fill in fills:
liq_info = fill.get('liquidation')
if liq_info:
liquidations.append({
'time': fill.get('time'),
'coin': fill.get('coin'),
'side': fill.get('side'),
'price': fill.get('px'),
'size': fill.get('sz'),
'closedPnl': fill.get('closedPnl'),
'dir': fill.get('dir'),
'liquidation': liq_info,
'hash': fill.get('hash')
})
if liquidations:
print(f"\n ⚠️ 发现 {len(liquidations)} 笔爆仓记录:")
print("-" * 80)
total_loss = 0.0
for i, liq in enumerate(liquidations, 1):
time_str = datetime.fromtimestamp(liq['time'] / 1000).strftime('%Y-%m-%d %H:%M:%S')
pnl = float(liq['closedPnl'])
total_loss += pnl
print(f"\n [{i}] {time_str}")
print(f" 币种: {liq['coin']}")
print(f" 方向: {liq['dir']} ({liq['side']})")
print(f" 价格: {liq['price']}")
print(f" 数量: {liq['size']}")
print(f" 已实现盈亏: ${pnl:,.2f}")
print(f" 清算详情:")
print(f" - 被清算用户: {liq['liquidation'].get('liquidatedUser', 'N/A')}")
print(f" - 标记价格: {liq['liquidation'].get('markPx', 'N/A')}")
print(f" - 清算方式: {liq['liquidation'].get('method', 'N/A')}")
print(f" 交易哈希: {liq['hash']}")
print("\n" + "=" * 80)
print(f"📊 爆仓统计")
print("=" * 80)
print(f" 总爆仓次数: {len(liquidations)}")
print(f" 总爆仓损失: ${total_loss:,.2f}")
# 按币种统计
coin_stats = {}
for liq in liquidations:
coin = liq['coin']
if coin not in coin_stats:
coin_stats[coin] = {'count': 0, 'loss': 0.0}
coin_stats[coin]['count'] += 1
coin_stats[coin]['loss'] += float(liq['closedPnl'])
print(f"\n 按币种统计:")
for coin, stats in sorted(coin_stats.items(), key=lambda x: x[1]['loss']):
print(f" {coin}: {stats['count']} 次, ${stats['loss']:,.2f}")
else:
print("\n ✅ 未发现爆仓记录")
# 3. 对比数据库数据(可选)
print("\n" + "=" * 80)
print("📋 数据结构对比")
print("=" * 80)
if fills:
last_fill = fills[-1]
print("\n API 返回的完整字段:")
for key in sorted(last_fill.keys()):
value = last_fill[key]
if key == 'liquidation' and value:
print(f" ✅ {key}: {value}")
else:
print(f" {key}: {type(value).__name__}")
print("\n 数据库存储的字段:")
db_fields = ['address', 'time', 'coin', 'side', 'price', 'size', 'closed_pnl', 'fee', 'hash']
for field in db_fields:
print(f" {field}")
print(f" ❌ liquidation (未存储)")
return liquidations
def main():
# 默认测试地址
default_address = "0x324f74880ccee9a05282614d3f80c09831a36774"
if len(sys.argv) > 1:
address = sys.argv[1]
else:
address = input(f"请输入地址 (默认={default_address}): ").strip()
if not address:
address = default_address
test_liquidation(address)
if __name__ == "__main__":
main()