AllTick
-
实时市场数据股票api金融数据 api

实时行情接口实战:用 WebSocket 搭建实时行情监控面板

介绍如何使用 WebSocket 实时行情接口和 Node.js 搭建股票行情监控面板,讲解多股票订阅、逐笔行情更新、数据转发及断线重连,解决行情不同步和停止更新等问题。

AllTick阅读 5 分钟

股票行情监控面板已经接入 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 依赖:

text
npm install ws

将 API Token 配置在服务端环境变量 ALLTICK_API_TOKEN 中,再建立连接:

text
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 维护最新行情:

text
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() 负责更新对应股票的页面区域,而不是重新渲染整个监控面板。

假设页面中有一个展示腾讯股票价格的区域,可以通过股票代码找到对应元素:

text
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 元素:

text
<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 连接再次发送订阅请求时,新列表会覆盖之前的订阅列表。

正确做法是重新提交完整列表:

text
const symbols = [
  { code: "700.HK" },
  { code: "1288.HK" },
  { code: "AAPL.US" }
];

这样可以避免新增股票后,原有股票意外停止推送。

订阅数量需要与套餐匹配。

AllTick 不同套餐对最新成交价的同时订阅品种数量有不同限制:

套餐最大同时订阅品种数
Free5
Basic100
Premium200
Professional3000

以上为逐笔成交订阅数量限制,不代表每个套餐都能无限增加 WebSocket 连接。实际部署还需要同时考虑连接数和账号权限。

订阅请求不能无限频繁发送。

AllTick 的 WebSocket 限制说明中,对请求间隔也有明确要求:

  • 同一个 WebSocket 连接内,不同接口请求之间至少间隔 1 秒。
  • 多个 WebSocket 连接之间,发起订阅请求至少间隔 3 秒。
  • 成功订阅后,不需要反复发送订阅请求,应按照接口要求维持心跳。
    因此,如果监控面板支持动态增加股票,建议先更新本地订阅列表,再统一发送完整订阅请求,而不是每次用户点击股票时立即发送多个请求。

行情停止更新后,应该如何排查?

监控面板最容易被忽略的问题,是连接断开后页面仍然显示最后一次收到的价格。

用户看到价格还在页面上,不代表行情仍然正常更新。建议在监控面板中同时展示连接状态、最后更新时间和最新成交价。

可以按照以下顺序排查:

现象优先检查
所有股票都停止更新WebSocket 连接状态、心跳和网络
只有部分股票停止更新股票代码、订阅范围及交易状态
新增股票后旧股票不再更新是否重新提交了完整订阅列表
服务端收到数据但页面不变化缓存更新和前端消息转发
重连后行情仍然没有恢复是否重新发送订阅请求

对于自动重连,还需要考虑连接恢复后的数据状态。重新建立连接并重新订阅,只能恢复后续行情推送,不能自动补齐断线期间遗漏的逐笔数据。

如果业务需要完整的历史记录或连续回测数据,应另外通过支持的历史数据接口补充查询,不能把 WebSocket 重连当成历史数据恢复机制。
实时行情接口应该如何搭配使用?
对于一个需要展示股票最新价格和历史走势的监控面板,比较合理的设计是将 REST API 与 WebSocket API 分工使用。

REST API 负责初始化页面和查询历史 K 线,WebSocket API 负责持续接收最新成交价。两者配合后,开发者不必通过频繁轮询来维持最新价格,也不需要用逐笔推送代替历史行情查询。

AllTick 提供股票实时成交价和盘口订阅能力,开发者可以根据监控面板的实际需求选择对应接口。需要注意,最新成交价订阅不等于盘口订阅,WebSocket 股票接口也不能直接替代历史 K 线查询。

如果你的项目正在开发股票行情看板,可以先从少量股票开始,验证订阅响应、逐笔推送、数据更新和断线恢复,再根据套餐限制扩展监控范围。

可以进一步查看:

立即开始接入市场数据

几秒内生成免费 API 密钥,通过一个端点连接所有市场。