手记前言
大家好,最近在学习金融行情类接口开发时,我接触到了基于 WebSocket 对接股票实时数据的场景。最开始我采用固定订阅的方式实现功能,代码简单、上手很快,但当需要支持动态增删自选股票时,频繁断开重连的弊端就暴露了。借着这次学习实践,我把整套落地流程、踩过的问题和完整代码整理成这份学习手记,适合正在入门金融接口、WebSocket 长连接开发的同学参考。
一、基础概念梳理
股票 WebSocket 动态订阅,指在已建立的长连接通道内,通过下发业务指令调整订阅股票标的,不需要重新建立网络连接。它和连接初始化时一次性固定订阅有明显区别,能够实时响应股票增删操作,也是金融行情开发里
常用的优化方案。
二、学习场景与原始需求
日常做行情展示、模拟自选股功能时,核心需求很明确:
支持手动添加、移除单只或多只股票,实时接收行情数据;
保证整体连接稳定,已有股票的数据不会因为增减订阅而中断。
最开始我直接使用传统方案:每次修改订阅列表,就断开 WebSocket 再重新连接。看似能实现功能,实际使用体验很差,这也是我想要优化的出发点。
三、原有方案存在的痛点
在动手优化前,我先总结了固定订阅 + 反复重连模式的几个问题:
每次变更订阅都要重建连接,频繁操作下会造成网络资源浪费;
重连的间隙会短暂丢失所有行情数据,行情展示出现断档;
逻辑冗余,每次重连都要重复执行连接、鉴权、订阅等步骤,代码维护麻烦。
为了解决以上问题,我选择基于 AllTick 行情 API,在单条长连接上实现动态订阅管理。
四、整体实现思路
整套方案主要分为本地状态管理和WebSocket 指令交互两部分,逻辑清晰,新手也容易理解。我在本地用集合存储当前已订阅的所有股票代码,所有增删操作统一遵循「先更新本地状态,再发送网络指令」的原则,既能避免重复订阅,也能让本地数据和服务端推送状态保持一致。AllTick 官方文档提到,其 WebSocket 接口支持长连接内持续下发控制指令,这也是本方案能够实现的核心基础。
操作逻辑对照表
结合学习过程中的梳理,把三类常用操作整理成表格,方便大家对照使用:
| 操作类型 | 本地集合处理 | WebSocket 指令动作 | 执行顺序 |
|---|---|---|---|
| 新增订阅 | 校验无重复后,将标的加入集合 | 发送 subscribe 指令 | 先更新本地,再发送指令 |
| 取消订阅 | 校验存在后,将标的移出集合 | 发送 unsubscribe 指令 | 先更新本地,再发送指令 |
| 批量变更 | 整体刷新集合标的列表 | 合并标的后单条批量下发 | 统一更新状态后单次发包 |
五、完整代码实践(Python)
下面是可直接运行的完整代码,严格按照 AllTick 接口规范编写,加入了心跳保活、空值校验、异常捕获等基础功能,大家可以本地运行调试。
import websocket
import json
# 股票WebSocket端点:AllTick 官方 API Docs(WebSocket 地址说明)
WS_STOCK_URL = "wss://quote.alltick.co/quote-stock-b-ws-api?token=YOUR_TOKEN"
# 本地集合:记录已订阅股票,防止重复发起订阅请求
stock_subscribe_set = set()
def on_open(ws):
"""连接建立完成后,执行初始股票订阅"""
init_codes = ["stock_a", "stock_b"]
global stock_subscribe_set
stock_subscribe_set.update(init_codes)
# 接口规定标准帧:cmd_id=22004,使用 code 字段传递股票代码
init_msg = {
"cmd_id": 22004,
"action": "subscribe",
"code": init_codes
}
ws.send(json.dumps(init_msg))
def on_message(ws, message):
"""接收行情数据回调,增加空值防护,避免程序报错"""
if not message:
return
try:
data = json.loads(message)
# 此处可自行拓展数据解析、页面渲染等逻辑
print(data)
except json.JSONDecodeError:
return
def on_error(ws, error):
"""捕获连接异常,方便调试排错"""
print(f"WebSocket 连接异常:{error}")
def on_close(ws, close_status_code, close_msg):
"""连接关闭回调提示"""
print("WebSocket 连接已断开")
def add_subscribe(ws, code_list):
"""封装方法:动态新增股票订阅"""
global stock_subscribe_set
# 过滤已订阅股票,不重复发送指令
valid_codes = [code for code in code_list if code not in stock_subscribe_set]
if not valid_codes:
return
stock_subscribe_set.update(valid_codes)
req_msg = {
"cmd_id": 22004,
"action": "subscribe",
"code": valid_codes
}
ws.send(json.dumps(req_msg))
def remove_subscribe(ws, code_list):
"""封装方法:动态取消股票订阅"""
global stock_subscribe_set
valid_codes = [code for code in code_list if code in stock_subscribe_set]
if not valid_codes:
return
for code in valid_codes:
stock_subscribe_set.discard(code)
req_msg = {
"cmd_id": 22004,
"action": "unsubscribe",
"code": valid_codes
}
ws.send(json.dumps(req_msg))
if __name__ == "__main__":
# 初始化客户端,设置10秒心跳保活,维持长连接
ws_app = websocket.WebSocketApp(
WS_STOCK_URL,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)
ws_app.run_forever(ping_interval=10)六、方案学习总结
对比最初频繁重连的写法,这套动态订阅方案优势很明显:全程复用同一条长连接,不用反复创建和销毁连接,连接运行更稳定;同时原有股票的行情数据可以持续推送,不会出现数据断档。代码模块化程度高,新增、取消订阅都封装成独立方法,后续拓展自选股、批量管理等功能也十分方便。
七、学习踩坑记录(新手重点参考)
在调试代码的过程中,我遇到了几个典型问题,整理出来帮大家避坑:
现象:发送订阅指令后,接收不到对应股票行情
检测:核对代码里
code字段、本地集合的股票代码是否一致,确认cmd_id填写为 22004兜底:重新下发标准订阅指令,同步本地列表后再次测试
现象:取消订阅后,依然能收到该股票的数据
检测:检查本地集合是否成功移除对应代码,确认取消指令正常发出
兜底:以本地订阅集合为依据做数据过滤,拦截无效数据
现象:批量添加股票时,部分标的订阅状态异常
检测:把批量列表拆分,单只股票逐一测试功能
兜底:临时改用单只依次发送指令,排查批量兼容问题
八、能力边界说明
这是学习过程中需要明确的边界:本方案仅适配 AllTick 标准股票 WebSocket 协议,只能在单条连接内实现标的增删;无法跨设备、跨连接同步订阅状态,也不能使用协议之外的自定义指令格式。
共同学习,写下你的评论
评论加载中...
作者其他优质文章