实盘量化最怕什么?不是策略回撤,而是数据断流。我维护的港股多因子调仓系统,曾在开盘 15 分钟内连续出现 3 次实时行情超时,导致算法误判错过买卖窗口。痛定思痛,我从数据链路的角度做了一次外科手术式改造,今天把方案拆开讲讲。
一、量化为什么对超时零容忍
量化策略的 tick 级数据是信号触发的原材料。假如你依赖港股 Level 1 快照,每笔 tick 延迟或丢失,可能让均值回归信号变成过时判断。对于投顾类的高端客户,盘中报警延迟更是直接与信任挂钩。所以,我的目标不是“降低超时率”,而是构筑一条确定性的数据管道。
二、排查:行情源端与本地消费速率
先用日志把每次请求的全链路耗时抓出来,发现两个现象:
- 服务端在 9:30~9:45 之间的 p99 延迟会放大到平日的 4 倍以上,且偶发 429 限流。
- 本地回调中同步计算复权因子并逐行写 ClickHouse,单条处理耗时虽然只有 2ms,但瞬间积压 500 条就会造成队列阻塞,引发 socket 读超时。 看清楚这两点,方向就清晰了。
三、用 WebSocket 替代 RESTful 拉取
这是最重要的架构变更。WebSocket 的全双工特性让服务端主动推送 tick,无需客户端不断轮询。我目前在用的行情源里,AllTick 的港股 WebSocket 实时推送比较稳定,一次订阅后可以拿到连续 tick 流,程序直接接进内部事件总线。下面这段是简化后的订阅逻辑:
import websocket
import json
def on_message(ws, message):
data = json.loads(message)
print(data) # 收到的实时行情直接打印,也可以放到队列或数据库
def on_open(ws):
subscribe_data = {
"action": "subscribe",
"symbols": ["00700.HK", "09988.HK"] # 订阅股票列表
}
ws.send(json.dumps(subscribe_data))
ws = websocket.WebSocketApp(
"wss://api.alltick.co/ws/stock",
on_message=on_message,
on_open=on_open
)
ws.run_forever()
替换为推送模式后,超时重连的次数下降了 90% 以上。
四、批量订阅降低握手开销
即使使用 WebSocket,订阅消息本身也需克制。我会把几百只股票切成每批 20 只,批间延迟 50 毫秒。这样保证网关不会因瞬间订阅洪水而丢包,同时本地接收队列也不会在开局就爆炸。
五、重连机制要兼顾快速恢复和防雪崩
我实现了一个状态机:正常情况下每 30 秒做一次 ping 心跳,连续两次无 pong 则触发重连。重连间隔采用指数退避,1s→2s→4s→8s,上限 30s。关键是,重连成功后只重新订阅那些之前有成交但未被确认的标的,而非全量重订,把恢复时间压缩到最短。
六、异步处理与流计算结合
on_message 里面我只做 schema 校验和入队。后端由一个多进程的流计算单元消费队列,做复权、计算中间指标,再批量写入时序库。这样就彻底隔离了网络 I/O 和计算 I/O,行情再密集也不会互相拖累。数据库写入也从逐条 insert 改成批量 insert,减少磁盘争抢。
七、成果与建议
改造之后,即使在港股剧烈波动的交易日,系统端到端延迟也能稳定在亚秒级,超时告警几乎为零。对量化交易来说,数据链路的确定性就是策略的生命线。如果你正在被港股实时行情超时问题困扰,我建议你立刻审视自己的连接模式、订阅粒度和处理架构,优先拥抱 WebSocket 和异步解耦。


