一、为什么实时行情更适合使用 WebSocket?``
股票、外汇、指数、贵金属和加密资产等行情具有持续变化的特点。对于实时盯盘、行情看板、量化监控和风险预警系统,客户端如果反复通过 HTTP 轮询获取数据,不仅会产生大量重复请求,也难以在请求间隔与服务端负载之间取得平衡。
WebSocket 建立长连接后,服务端可以在数据发生变化时主动推送,客户端也可以通过同一连接发送订阅请求。相比固定间隔轮询,这种模式更适合持续行情流。需要注意,WebSocket 只解决通信方式问题,端到端延迟仍受到数据源、网络距离、服务端处理、客户端消费速度等因素影响,不能仅凭使用 WebSocket 就承诺固定的毫秒级延迟。
1.1 典型应用场景
· 实时行情看板:订阅关注标的的实时报价,展示最新价格、涨跌幅和成交量,并与实时 K 线图表联动。
· 盘口监控:订阅买卖盘深度,观察挂单结构和盘口变化。
· 量化监控:将行情流接入策略监控、告警或内部消息总线。
· K 线图表:接收正在形成的 K 线,按标的和周期增量更新图表最后一根 K 线;报价数据用于同步展示最新成交/报价信息。
二、DeepTick WebSocket 接入流程
DeepTick WebSocket 接入地址为 wss://api.deeptick.cc/ws/v1。连接时在握手请求头中携带 deeptoken,连接建立后服务端会发送 welcome 消息。与原稿中使用的其他行情服务协议不同,DeepTick 使用 Topic 订阅模型:客户端发送 type=subscribe、topics 和 request_id 等字段;订阅请求应按同一种行情类型组织,不能将不同类型的 Topic 混在同一条订阅请求中,否则整批订阅可能失败。
2.1 接入前准备
· 在 DeepTick 平台申请 API Token,并通过环境变量保存,不要将密钥写入前端代码或提交到代码仓库。
· 准备 Node.js 运行环境,本文示例使用 ws 库作为 WebSocket 客户端及服务端。
· 确认要订阅的市场、标的代码和数据类型,并按 DeepTick 文档中的 Topic 规则生成订阅主题。
npm init -y
npm install ws
2.2 连接与鉴权
下面示例展示 Node.js 如何在 WebSocket 握手阶段传递 deeptoken,并等待服务端首帧 welcome。示例没有再发送旧协议中的 auth 指令;认证信息随连接请求头提交。
const WebSocket = require("ws");
const WS_URL = "wss://api.deeptick.cc/ws/v1";
const TOKEN = process.env.DEEPTICK_API_TOKEN;
if (!TOKEN) {
throw new Error("请先设置环境变量 DEEPTICK_API_TOKEN");
}
function connectDeepTick() {
const ws = new WebSocket(WS_URL, {
headers: { deeptoken: TOKEN }
});
ws.on("open", () => {
console.log("WebSocket transport connected");
});
ws.on("message", (buffer) => {
const raw = buffer.toString();
let message;
try {
message = JSON.parse(raw);
} catch {
console.warn("收到非 JSON 消息:", raw);
return;
}
// DeepTick 建连后会发送 welcome 首帧。
if (message.type === "welcome") {
console.log("DeepTick welcome:", message);
// 在这里启动订阅流程。
return;
}
handleDeepTickMessage(message);
});
ws.on("error", (err) => {
console.error("DeepTick WebSocket error:", err.message);
});
ws.on("close", (code, reason) => {
console.warn("DeepTick connection closed:", code, reason.toString());
});
return ws;
}
function handleDeepTickMessage(message) {
if (message.type === "ack") {
console.log("订阅回执:", {
accepted: message.accepted || [],
rejected: message.rejected || []
});
return;
}
// 行情推送通常包含 topic 与 data。
if (message.topic) {
console.log("行情推送:", message.topic, message.data);
return;
}
console.log("其他服务端消息:", message);
}
const ws = connectDeepTick();
复制
三、订阅行情:使用 Topic 模型
DeepTick 的订阅请求采用 JSON 消息,核心字段包括 type、topics 和 request_id。下面以港股腾讯控股为例,实时报价 Topic 使用 quote.HK.00700;实时 K 线 Topic 则应按官方实时 K 线文档中的市场、标的和周期规则配置。报价与 K 线属于不同的行情类型,必须分成两条订阅请求发送。
function sendSubscribe(ws, topic) {
if (ws.readyState !== WebSocket.OPEN) {
throw new Error("WebSocket 尚未处于 OPEN 状态");
}
const request = {
type: "subscribe",
topics: [topic],
request_id: `sub-Date.now()-{Math.random().toString(16).slice(2)}`
};
ws.send(JSON.stringify(request));
}
// welcome 到达后调用:不同数据类型分开发送。
function subscribeHongKongExample(ws) {
sendSubscribe(ws, "quote.HK.00700");
sendSubscribe(ws, "depth.HK.00700");
}
复制
生产代码建议将 Topic 列表按行情类型分组后逐组发送,并为每次请求生成唯一 request_id。收到 ack 后检查 accepted 与 rejected,不能只根据 send() 没有抛错就认定订阅成功。
3.1 订阅关系管理建议
· 在业务服务中维护“客户端 ID → Topic 集合”的映射,客户端断开时清理对应关系。
· 对相同 Topic 做去重,避免重复订阅造成无效请求。
· 区分客户端订阅与上游 DeepTick 订阅:多个前端订阅同一 Topic 时,上游可共享一份订阅,再由业务服务向多个客户端分发。
· 订阅失败时记录 request_id、Topic、错误回执和时间,便于定位参数错误或套餐权限问题。
四、心跳、断线重连与订阅恢复
长连接可能因为网络抖动、代理超时、服务端维护或客户端网络切换而断开。工程上需要将心跳检测、连接状态管理、重连退避和订阅恢复组合起来。DeepTick 文档提供心跳与短时间中断后的续传能力,实际项目应按官方心跳文档规定的消息格式、间隔和续传参数实现,不应继续沿用其他服务商的 ac=ping 或 pong 消息结构。
下面的 Node.js 片段使用 ws 库的 WebSocket 控制帧 ping/pong 做本地连接存活检测示例。它属于客户端链路健康检查示范,不代表替代 DeepTick 文档规定的应用层心跳或续传协议;如果官方文档要求特定 JSON 心跳消息,应以官方定义为准。
function attachHeartbeat(ws, intervalMs = 30000, timeoutMs = 10000) {
let pongReceived = true;
ws.on("pong", () => {
pongReceived = true;
});
const timer = setInterval(() => {
if (ws.readyState !== WebSocket.OPEN) return;
if (!pongReceived) {
console.warn("心跳超时,主动关闭连接");
ws.terminate();
return;
}
pongReceived = false;
ws.ping();
}, intervalMs);
ws.on("close", () => clearInterval(timer));
ws.on("error", () => clearInterval(timer));
}
复制
4.1 自动重连与重新订阅
重连成功后,客户端需要重新发送仍然有效的 Topic 订阅。重连建议使用指数退避并增加随机抖动,避免服务端或网络故障恢复时大量客户端同时冲击连接入口。对于 DeepTick 文档支持的短时续传能力,应按文档约定保存续传所需的状态或标识,并在重连后执行补续流程;不要自行假设服务端会自动补齐所有离线期间的数据。
const desiredTopicsByType = {
quote: ["quote.HK.00700"],
depth: ["depth.HK.00700"]
};
function resubscribe(ws) {
for (const topics of Object.values(desiredTopicsByType)) {
for (const topic of topics) {
sendSubscribe(ws, topic);
}
}
}
function reconnectWithBackoff(start, attempt = 0) {
const base = 1000;
const max = 30000;
const delay = Math.min(max, base * (2 ** attempt));
const jitter = Math.floor(Math.random() * 500);
setTimeout(() => {
const ws = start();
ws.once("open", () => {
// 生产环境应等待 DeepTick welcome 后再恢复订阅。
// welcome 到达后调用 resubscribe(ws)。
});
ws.once("close", () => {
reconnectWithBackoff(start, attempt + 1);
});
}, delay + jitter);
}
复制
五、实时 K 线订阅与处理
实时 K 线与普通报价是不同的数据类型。DeepTick 的实时 K 线能力用于订阅正在形成的 K 线,客户端收到更新后通常应更新图表的最后一根 Bar,而不是每次都把全部历史数据重新绘制。
K 线 Topic 的具体组成、周期参数、消息字段以及是否支持某些周期,必须以 DeepTick 实时 K 线文档为准。下面将 K 线 Topic 作为配置项传入,避免将其他行情服务商的 kline@1、ac=subscribe 等旧格式误用到 DeepTick。请将示例中的 CANDLE_TOPIC 替换为文档中与你的市场、标的和周期对应的完整 Topic。
// 请按 DeepTick「实时 K 线」文档填写准确的 Topic。
const CANDLE_TOPIC = process.env.DEEPTICK_CANDLE_TOPIC;
function subscribeCandle(ws) {
if (!CANDLE_TOPIC) {
throw new Error("请配置 DEEPTICK_CANDLE_TOPIC");
}
sendSubscribe(ws, CANDLE_TOPIC);
}
function handleCandleMessage(message) {
if (!message.topic || !message.data) return;
// 先保留原始字段,避免在未核对数据字典前误判字段含义。
const candle = message.data;
console.log("实时 K 线更新:", {
topic: message.topic,
data: candle
});
// 接入图表时,根据 DeepTick 文档字段映射为:
// { time, open, high, low, close, volume }
// 时间戳单位、成交量口径和周期边界均应按接口文档确认。
}
复制
5.1 K 线图表更新要点
· 历史 K 线可先通过 REST 接口加载,WebSocket 负责更新当前正在形成的 K 线。
· 同一根 K 线可能多次更新,应按标的、周期和 K 线时间定位并覆盖更新,而不是简单追加重复 Bar。
· 确认时间戳单位是秒还是毫秒,并统一转换到图表库要求的时间格式。
· 断线恢复后,结合官方续传能力和 REST 历史 K 线查询补齐缺口,再继续处理实时推送。
六、实时报价与实时 K 线联动
实时报价与实时 K 线面向不同的展示需求:报价流用于刷新最新价格、涨跌幅等即时行情摘要;K 线流用于更新图表中正在形成的 Bar。实际页面可以同时展示报价卡片和 K 线图,但上游订阅时应将两种行情类型拆成独立请求。
6.1 实时报价订阅
复制
DeepTick 实时报价 WebSocket 用于订阅后持续接收标的的最新报价。以下以港股腾讯控股为例,使用 quote.HK.00700 作为报价 Topic。收到消息后先保留服务端原始 data,并依据实时报价文档的数据字典映射字段,不要在未确认字段定义前假设价格、成交量或时间戳字段名称。
const QUOTE_TOPIC = "quote.HK.00700";
function subscribeQuote(ws) {\n sendSubscribe(ws, QUOTE_TOPIC);\n}
复制
6.2 报价与 K 线双订阅
下面将实时报价与实时 K 线放在同一条 WebSocket 连接中管理。两次 sendSubscribe() 会分别发送订阅请求,符合“同一条订阅请求只订阅同一种行情类型”的要求。请通过环境变量配置与当前市场、标的和周期匹配的 K 线 Topic;其具体格式以 DeepTick 实时 K 线文档为准。
const QUOTE_TOPIC = "quote.HK.00700";\nconst CANDLE_TOPIC = process.env.DEEPTICK_CANDLE_TOPIC;\n\nfunction subscribeQuoteAndCandle(ws) {\n if (ws.readyState !== WebSocket.OPEN) {\n throw new Error("WebSocket 尚未处于 OPEN 状态");\n }\n if (!CANDLE_TOPIC) {\n throw new Error("请配置 DEEPTICK_CANDLE_TOPIC");\n }\n\n // 报价和 K 线分开发送,不要合并成一条混合类型订阅。\n sendSubscribe(ws, QUOTE_TOPIC);\n sendSubscribe(ws, CANDLE_TOPIC);\n}
复制
6.3 统一处理两类行情消息
收到推送后,可根据服务端返回的 topic 将消息分流到报价处理器或 K 线处理器。下面的示例只负责消息路由并保留原始数据;字段映射、时间戳单位和 K 线周期边界应严格依据对应官方文档确认。
function handleMarketMessage(message) {\n if (!message || !message.topic || message.data == null) return;\n\n if (message.topic === QUOTE_TOPIC) {\n handleQuoteMessage(message);\n return;\n }\n\n if (message.topic === CANDLE_TOPIC) {\n handleCandleMessage(message);\n return;\n }\n\n console.log("收到其他行情推送:", message.topic, message.data);\n}\n\nfunction handleQuoteMessage(message) {\n // 按实时报价文档映射最新报价字段,再更新行情卡片。\n console.log("实时报价更新:", message.data);\n}\n\nfunction handleCandleMessage(message) {\n // 按实时 K 线文档映射 OHLCV,再更新图表最后一根 Bar。\n console.log("实时 K 线更新:", message.data);\n}
复制
6.4 前端展示与数据一致性
报价卡片与 K 线图应使用同一标的标识,并分别维护各自的数据状态。报价到达时只更新报价区域;K 线到达时按标的、周期和 K 线时间定位并更新当前 Bar。不要将每一条报价消息直接当作一根 K 线,也不要将重复到达的同一根 K 线简单追加到图表末尾。
页面初始化时可先通过历史行情接口加载图表所需的历史 K 线,再启动 WebSocket 实时报价和实时 K 线订阅。收到订阅确认后再将数据流状态标记为正常;重连后重新订阅,并结合官方续传能力或历史接口处理可能存在的数据缺口。
七、从示例走向生产环境:架构与优化
7.1 推荐架构
一个常见的行情接入架构可以拆分为四层:
· 数据接入层:维护与 DeepTick 的 WebSocket 连接,负责鉴权、订阅、心跳、重连和消息解析。
· 消息处理层:完成字段标准化、时间戳校验、重复消息处理和必要的业务计算。
· 缓存与消息分发层:将最新行情写入 Redis,并通过 Kafka、Redis Pub/Sub 或内部消息总线分发给下游。
· 客户端接入层:通过自有 WebSocket 网关向浏览器、移动端或内部服务推送数据,避免将长期 API Token 暴露给终端用户。
7.2 关键工程实践
· 连接复用:不要为每个前端用户单独建立一条上游行情连接;根据订阅规模和服务商连接限制设计共享连接池。
· 背压处理:当下游消费速度低于上游推送速度时,设置有界队列、丢弃策略或按业务重要性降采样,避免内存持续增长。
· 订阅配额:在业务侧限制单用户订阅数、Topic 数和订阅变更频率,并监控服务商套餐约束。
· 消息幂等:对行情消息按标的、类型、时间戳或服务端提供的序列信息进行去重;具体唯一键以数据协议为准。
· 可观测性:记录连接状态、订阅确认率、重连次数、消息吞吐、端到端处理耗时、队列积压和异常消息比例。
· 安全:Token 只保存在服务端环境变量或密钥管理服务中;对外部客户端使用自有身份认证和权限校验。
八、常见问题排查
问题:连接后没有收到行情
处理建议:先确认是否收到 welcome;检查 deeptoken 是否有效、握手请求头是否正确,以及订阅是否在连接建立后发送。
问题:订阅请求被拒绝
处理建议:检查 Topic 拼写、市场和标的编码;确认单条请求只包含同一种行情类型;读取 ack 中的 accepted 与 rejected。
问题:报价有数据但 K 线没有数据
处理建议:确认是否使用实时 K 线文档规定的 Topic 和周期参数,并检查账号权限、市场交易状态及该标的是否支持对应周期。
问题:连接频繁断开
处理建议:检查代理或负载均衡的空闲超时,按官方心跳文档实现保活,并加入指数退避重连。
问题:重连后行情出现缺口
处理建议:按官方心跳与续传文档使用短时续传能力;超出续传窗口时,再通过历史行情接口补齐。
问题:前端收到重复行情
处理建议:检查上游是否重复订阅同一 Topic,以及业务层是否对多个客户端重复转发;按业务键做幂等处理。
九、总结
使用 DeepTick WebSocket 接入实时行情,关键不只是建立一条长连接,而是正确处理连接鉴权、Topic 订阅、订阅回执、心跳、断线恢复和数据消费。本文同时覆盖实时报价与实时 K 线:报价流用于更新最新行情摘要,K 线流用于更新正在形成的图表 Bar;两种行情类型应分开发送订阅请求,并依据各自的数据文档解析消息。
在生产环境中,建议将上游行情接入与终端用户连接解耦,配合缓存、消息队列、限流、监控和断线补偿,逐步构建可扩展、可观测的行情分发服务。 免责声明:本文代码用于技术接入演示,不构成投资建议。行情数据的使用、展示、存储和再分发应遵守 DeepTick 服务条款及相关市场的数据授权要求。**
GitHub - deeptick-cc/deeptick-realtime-market-data-api: DeepTick · 实时金融行情数据 API · GitHub
**

