API 参考 / 实时数据
按 topic 订阅的 WebSocket 流。公开 topic 无需鉴权;私有 topic 在订阅帧里带会话 token。流只推增量,权威状态从 REST 获取。
| 地址 | ws://127.0.0.1:8090/api/v1/stream |
|---|---|
| 鉴权 | 公开 topic 无需鉴权;私有 topic 在 subscribe 帧的 token 字段带会话 Bearer token(只读校验,不消费会话)。 |
| 握手令牌 | 交易线 WS 另有短时握手令牌:POST /api/v1/ws/handshake-token |
| 浏览器 | 同源代理只转发 REST,WebSocket 需直连流地址或由部署层反代升级。 |
{ "op": "subscribe", "topic": "px.crypto.BTC" }
{ "op": "subscribe", "topic": "private.0x3f1a…9c2e.px", "token": "<session bearer token>" }
{ "op": "unsubscribe", "topic": "px.crypto.BTC" }{ "op": "subscribed", "topic": "px.crypto.BTC", "needsSnapshot": true, "currentSequence": 184 }
{ "op": "message", "topic": "px.crypto.BTC", "sequence": 185, "serverTime": 1758240000000,
"type": "tick", "data": { "symbol": "BTC", "priceText": "118425.50", "tsMs": 1758240000000 } }
{ "op": "error", "topic": "private.0x…px", "message": "token address does not match topic" }| topic | 鉴权 | data | 说明 |
|---|---|---|---|
px.crypto.{coin} | 公开 | { "symbol": "BTC", "priceText": "118425.50", "tsMs": 1758240000000 } | 现货价(Pyth 行情)。全字符串,priceText 口径与 REST 历史端点一致。 |
px.updown.{series} | 公开 | { "series": "btc-5m", "marketId": "px-btc-5m-…", "roundStartMs": …, "roundEndMs": …, "resolution": null } | 轮次边界事件,type = rotate | resolved。收到即重新拉 GET /px/updown;payload 不含行情。 |
private.{address}.px | 订阅帧带 Bearer 会话 token | { "kind": "orders" | "positions" | "fills" } | 账户变更通知。token 归属地址必须与 topic 一致,不一致只拒这条订阅、不断连接;明细一律走 REST 重取。 |
sequence 在单个连接内单调连续,跨连接可能归零。用它检测同一连接内的丢帧,不用于跨连接对账。subscribed 帧带 needsSnapshot: true 与 currentSequence。收到后通过 REST 拉取快照,之后从 currentSequence + 1 开始消费增量。const ws = new WebSocket("ws://127.0.0.1:8090/api/v1/stream");
let expected: number | undefined;
ws.onopen = () => {
ws.send(JSON.stringify({ op: "subscribe", topic: "px.crypto.BTC" }));
};
ws.onmessage = async (event) => {
const frame = JSON.parse(event.data);
if (frame.op === "subscribed") {
expected = frame.currentSequence + 1;
await loadSnapshotOverRest(); // 先拉快照,再消费增量
return;
}
if (frame.op === "message") {
if (expected !== undefined && frame.sequence !== expected) {
// 丢帧:单连接内 sequence 必须连续。重建快照,不要试图补洞。
await loadSnapshotOverRest();
}
expected = frame.sequence + 1;
apply(frame.type, frame.data);
}
};