WebSocket 实时行情推送实战:实时报价与 K 线数据

用户头像sh_***312kg3
2026-11-10 发布

一、为什么实时行情更适合使用 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

**

评论