跳到主要内容

WebSocket:统一账户频道

私有 WebSocket 频道,向单个用户推送实时 uniMMR、风险预警、reduce_only 转换与清算步骤。

连接地址

wss://api.prex.world/ws

鉴权使用 JWT(与 positions / orders / balance 频道一致)。可在 subscribe 消息内附带 token,或先发送 auth_token 消息完成鉴权后再订阅。

订阅

{ "type": "subscribe", "channel": "unified_account", "token": "<JWT>" }

服务器响应:

{ "type": "subscribed", "channel": "unified_account" }

推送频率

用户处于 unified 模式时,风控 Worker 每个 2 秒 tick 恰好发送一个事件(即使状态无变化), 前端可据此渲染实时 uniMMR 而无需轮询。平静的 tick 发送的是基线 update 事件。 发生状态转换的 tick,事件类型会被替换:你会收到单个 margin_call / reduce_only / liquidating / status_change 事件来取代基线 update, 而不是在其之外额外推送。同理,某个 tick 内清算步骤成功时,该 tick 的事件是 liquidation_step — 它取代同一 tick 内本应发出的 liquidating 转换事件。

事件信封

{
"channel": "unified_account",
"event": "<event_name>",
"data": {
"user_address": "0x...",
"event": "<event_name>",
"uni_mmr": "1.85",
"total_equity": "52000.00",
"available_balance": "2000.00",
"account_status": "warning_1",
"reason": "uniMMR=1.85 status: normal → warning_1",
"timestamp": 1712678400000
}
}

reasonorders_cancelled 在没有值时会从 JSON 中整体省略 — 它们永远不会以 null 序列化。上面的示例是一次 margin_call 转换, 因此 reason 存在而 orders_cancelled 缺席。

事件类型

event触发时机附加语义
update每个无转换、无清算步骤的 tick(约 2 秒)基线心跳;reason 被省略
margin_call进入 warning_1warning_2(取代该 tick 的 updatereason 说明旧 → 新状态
reduce_only进入 reduce_only(取代该 tick 的 updateorders_cancelled = 本次自动撤单数量
liquidating进入 liquidating(取代该 tick 的 update若第一个清算步骤在同一 tick 内成功,则被 liquidation_step 取代
liquidation_step该 tick 强平了一个仓位(取代该 tick 的 update / liquidatingreason 形如 "liquidated {symbol} {side} size_usd={...} pnl={...}"
status_change其他状态转换(如恢复 normal

字段说明

  • uni_mmr 为字符串形式的 Decimal 以保留精度;无仓位时为 null
  • orders_cancelled 仅在 reduce_only / liquidating 转换时填充;其他事件中该字段被整体省略(永远不会以 null 发送),请使用 data.get("orders_cancelled") 这类访问方式
  • 服务器只将 data.user_address 等于连接鉴权地址的事件转发给该会话,客户端无需自行过滤

示例(Python)

import asyncio, json, websockets
from eth_account import Account
from eth_account.messages import encode_typed_data
import requests

# 1. 用钱包私钥登录拿到 JWT
acct = Account.from_key("<PRIVATE_KEY>")
addr = acct.address.lower()
typed = requests.get(f"https://api.prex.world/api/v1/auth/nonce/{addr}").json()["typed_data"]
signable = encode_typed_data(full_message=typed)
sig = "0x" + acct.sign_message(signable).signature.hex()
ts = int(typed["message"]["timestamp"])
jwt = requests.post(
"https://api.prex.world/api/v1/auth/login",
json={"address": addr, "signature": sig, "timestamp": ts},
).json()["token"]

# 2. 订阅
async def run():
async with websockets.connect("wss://api.prex.world/ws") as ws:
await ws.send(json.dumps({"type": "auth_token", "token": jwt}))
await ws.send(json.dumps({
"type": "subscribe",
"channel": "unified_account",
"token": jwt,
}))
async for msg in ws:
m = json.loads(msg)
if m.get("channel") == "unified_account":
d = m["data"]
print(f"[{m['event']}] uniMMR={d['uni_mmr']} "
f"status={d['account_status']} reason={d.get('reason')}")

asyncio.run(run())