
实时行情接口实战:用 WebSocket 搭建实时行情监控面板
介绍如何使用 WebSocket 实时行情接口和 Node.js 搭建股票行情监控面板,讲解多股票订阅、逐笔行情更新、数据转发及断线重连,解决行情不同步和停止更新等问题。
股票行情监控面板已经接入 API,为什么有些股票价格能够持续更新,有些却停留在旧价格?当监控品种从两三只增加到几十只时,行情不同步、订阅被覆盖、网络断开后数据停止更新等问题,往往会逐渐暴露出来。
这类问题不一定是股票行情 API 本身出了故障,也可能与 WebSocket 订阅方式、数据处理逻辑和前端更新机制有关。
对于需要持续展示股票最新成交价的金融应用,使用 WebSocket 接收实时行情,比反复通过 HTTP 请求查询更适合。本文以 AllTick 实时行情接口为例,使用 Node.js 搭建行情接收服务,再将数据传递给前端监控面板,重点解决多股票订阅、行情更新和连接恢复问题。
为什么多只股票的行情容易出现更新不同步?
先看一个常见场景:一个股票监控页面同时展示 700.HK、1288.HK 和其他股票的最新价格。开发者通过定时请求获取行情,页面能够正常显示,但随着监控品种增加,请求数量也随之增加。
如果改用 WebSocket,客户端建立连接并订阅股票后,服务端可以持续推送新的成交数据,不必为每次价格变化重新发起 HTTP 请求。
两种接口的职责可以这样划分:
| 数据需求 | 适合的接口 | 主要用途 |
|---|---|---|
| 最新成交价 | WebSocket | 持续接收逐笔行情 |
| 买卖盘深度 | WebSocket | 展示盘口变化 |
| 历史 K 线 | REST API | 加载历史价格走势 |
| 初始行情查询 | REST API | 初始化页面数据 |
需要注意,WebSocket 并不意味着页面一定会自动更新。服务端收到行情后,还需要完成数据解析、缓存和前端转发。如果这些环节处理不当,即使 API 已经收到新价格,浏览器也可能继续显示旧数据。
使用 WebSocket API 订阅多只股票
AllTick 为股票市场提供独立的 WebSocket 接入地址。建立连接时,需要在地址中携带有效 Token。
最新成交价订阅使用协议号 22004,订阅响应使用 22005,服务端通过 22998 推送最新成交数据。
先安装 Node.js 的 WebSocket 依赖:
npm install ws将 API Token 配置在服务端环境变量 ALLTICK_API_TOKEN 中,再建立连接:
const WebSocket = require("ws");
const crypto = require("crypto");
const token = process.env.ALLTICK_API_TOKEN;
if (!token) {
throw new Error("Missing API Token");
}
const symbols = [
{ code: "700.HK" },
{ code: "1288.HK" }
];
let ws;
let reconnectTimer;
let heartbeatTimer;
let stopped = false;
function send(data) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
}
function subscribe() {
send({
cmd_id: 22004,
seq_id: 1,
trace: crypto.randomUUID(),
data: {
symbol_list: symbols
}
});
}
function connect() {
ws = new WebSocket(
`wss://quote.alltick.co/quote-stock-b-ws-api?token=${encodeURIComponent(token)}`
);
ws.on("open", () => {
console.log("WebSocket connected");
subscribe();
// 心跳消息应按照当前官方心跳协议发送
// 此处不将未经单独核验的心跳报文写成已验证实现
});
ws.on("message", (raw) => {
try {
const message = JSON.parse(raw.toString());
if (message.cmd_id === 22005) {
if (message.ret !== 200) {
console.error("Subscription failed", message.msg);
} else {
console.log("Subscription successful");
}
return;
}
if (message.cmd_id === 22998) {
updateQuote(message.data);
}
} catch (error) {
console.error("Message parsing failed", error);
}
});
ws.on("close", () => {
console.log("WebSocket disconnected");
clearInterval(heartbeatTimer);
if (!stopped && !reconnectTimer) {
reconnectTimer = setTimeout(() => {
reconnectTimer = null;
connect();
}, 10000);
}
});
ws.on("error", (error) => {
console.error("WebSocket error:", error.message);
});
}
function updateQuote(tick) {
const quote = {
code: tick.code,
price: Number(tick.price),
volume: Number(tick.volume),
tickTime: tick.tick_time
};
console.log("Latest quote:", quote);
// 在这里更新行情缓存,并转发给前端
}
connect();这段代码展示了连接、订阅、消息解析和断线后的重新连接逻辑。它是行情接收服务的核心示例,不是完整的生产环境项目。正式运行时,还需要按照官方心跳协议补充定时心跳,并实现服务端向浏览器转发行情的逻辑。
这里尤其要注意:订阅响应和行情推送不是同一类消息。22005 用来判断订阅请求是否成功,22998 才是实际的最新成交价推送。不能把收到订阅成功响应当成已经持续收到行情。
收到行情后如何更新监控面板?
行情数据进入 Node.js 服务后,建议先按照股票代码保存最新报价,再将数据转发到浏览器。
例如,前端可以通过一个 Map 维护最新行情:
const latestQuotes = new Map();
function updateQuote(tick) {
latestQuotes.set(tick.code, {
price: Number(tick.price),
volume: Number(tick.volume),
tickTime: tick.tick_time
});
renderQuote(tick.code, latestQuotes.get(tick.code));
}renderQuote() 负责更新对应股票的页面区域,而不是重新渲染整个监控面板。
假设页面中有一个展示腾讯股票价格的区域,可以通过股票代码找到对应元素:
function renderQuote(code, quote) {
const row = document.getElementById(`quote-${code}`);
if (!row) return;
row.querySelector(".price").textContent =
quote.price.toFixed(2);
row.querySelector(".volume").textContent =
quote.volume;
row.querySelector(".time").textContent =
quote.tickTime;
}前端需要预先建立对应的 HTML 元素:
<div id="quote-700.HK">
<span class="price">--</span>
<span class="volume">--</span>
<span class="time">--</span>
</div>这里的 tickTime 暂时直接展示原始值。官方字段说明与示例中的时间戳格式需要结合当前接口版本核对,实际项目中应确认单位后再进行日期转换。
还有一个重要的架构问题:浏览器不应直接保存服务端 API Token。更稳妥的方式是让 Node.js 服务端连接 AllTick,再通过自己的 WebSocket 服务把行情转发给前端。这样既能保护认证信息,也方便统一管理多个页面的订阅。
多股票订阅时,哪些问题最容易被忽略?
当监控面板需要展示更多股票时,订阅规则和连接管理会直接影响行情能否持续更新。
订阅列表不是自动追加的。
这是使用 AllTick 股票 WebSocket 时需要特别注意的地方。
例如,第一次订阅了 700.HK 和 1288.HK,之后想增加 AAPL.US,不能只发送新增的股票代码。因为同一个 WebSocket 连接再次发送订阅请求时,新列表会覆盖之前的订阅列表。
正确做法是重新提交完整列表:
const symbols = [
{ code: "700.HK" },
{ code: "1288.HK" },
{ code: "AAPL.US" }
];这样可以避免新增股票后,原有股票意外停止推送。
订阅数量需要与套餐匹配。
AllTick 不同套餐对最新成交价的同时订阅品种数量有不同限制:
| 套餐 | 最大同时订阅品种数 |
|---|---|
| Free | 5 |
| Basic | 100 |
| Premium | 200 |
| Professional | 3000 |
以上为逐笔成交订阅数量限制,不代表每个套餐都能无限增加 WebSocket 连接。实际部署还需要同时考虑连接数和账号权限。
订阅请求不能无限频繁发送。
AllTick 的 WebSocket 限制说明中,对请求间隔也有明确要求:
- 同一个 WebSocket 连接内,不同接口请求之间至少间隔 1 秒。
- 多个 WebSocket 连接之间,发起订阅请求至少间隔 3 秒。
- 成功订阅后,不需要反复发送订阅请求,应按照接口要求维持心跳。
因此,如果监控面板支持动态增加股票,建议先更新本地订阅列表,再统一发送完整订阅请求,而不是每次用户点击股票时立即发送多个请求。
行情停止更新后,应该如何排查?
监控面板最容易被忽略的问题,是连接断开后页面仍然显示最后一次收到的价格。
用户看到价格还在页面上,不代表行情仍然正常更新。建议在监控面板中同时展示连接状态、最后更新时间和最新成交价。
可以按照以下顺序排查:
| 现象 | 优先检查 |
|---|---|
| 所有股票都停止更新 | WebSocket 连接状态、心跳和网络 |
| 只有部分股票停止更新 | 股票代码、订阅范围及交易状态 |
| 新增股票后旧股票不再更新 | 是否重新提交了完整订阅列表 |
| 服务端收到数据但页面不变化 | 缓存更新和前端消息转发 |
| 重连后行情仍然没有恢复 | 是否重新发送订阅请求 |
对于自动重连,还需要考虑连接恢复后的数据状态。重新建立连接并重新订阅,只能恢复后续行情推送,不能自动补齐断线期间遗漏的逐笔数据。
如果业务需要完整的历史记录或连续回测数据,应另外通过支持的历史数据接口补充查询,不能把 WebSocket 重连当成历史数据恢复机制。
实时行情接口应该如何搭配使用?
对于一个需要展示股票最新价格和历史走势的监控面板,比较合理的设计是将 REST API 与 WebSocket API 分工使用。
REST API 负责初始化页面和查询历史 K 线,WebSocket API 负责持续接收最新成交价。两者配合后,开发者不必通过频繁轮询来维持最新价格,也不需要用逐笔推送代替历史行情查询。
AllTick 提供股票实时成交价和盘口订阅能力,开发者可以根据监控面板的实际需求选择对应接口。需要注意,最新成交价订阅不等于盘口订阅,WebSocket 股票接口也不能直接替代历史 K 线查询。
如果你的项目正在开发股票行情看板,可以先从少量股票开始,验证订阅响应、逐笔推送、数据更新和断线恢复,再根据套餐限制扩展监控范围。
可以进一步查看:
- AllTick 股票最新成交价 WebSocket 订阅文档:了解订阅协议、推送字段和完整请求示例。
- AllTick 股票盘口 WebSocket 订阅文档:了解买卖盘深度数据的订阅方式。
- AllTick WebSocket 接口限制说明:核对订阅数量、请求间隔和连接限制。