最近在维护一套港股量化策略时,我遇到一个比较隐蔽的数据质量问题:通过港股实时api接收的逐笔成交数据,偶尔会出现序列号不连续的情况。这个问题对低频策略可能影响不大,但对依赖逐笔成交分布的高频因子,累积起来会直接改变计算结果。这篇文章记录我的排查和解决过程,供同做港股量化的朋友参考。
场景:从一次成交统计偏差说起
我们的策略需要实时接收港股逐笔成交数据,用来计算盘口活跃度、成交分布和短期动量因子。实盘运行一段时间后,我发现某个因子的数值与交易所公布的汇总数据存在小幅偏差。一开始我怀疑是价格解析或者成交量字段处理有误,于是逐步排查了数据格式、时间戳对齐,甚至重新检查了策略逻辑,都没有找到明显问题。后来我把逐笔消息的序列号打印出来,才发现中间缺了一条记录。就是这一次断档,导致后续的累计成交量和因子值出现了偏差。
数据痛点:逐笔序列号断档意味着什么
港股实时api推送的逐笔行情中,通常会带一个递增的序列号字段,用来标识消息的先后顺序。理想情况下,本地收到的每一条消息,序列号都应该比上一条大1。但实际环境中,网络波动、程序处理延迟或者断线重连,都可能导致消息丢失。下面是我在日志中观察到的典型情况:
| 序列号 | 状态 |
|---|---|
| 60001 | 正常接收 |
| 60002 | 正常接收 |
| 60003 | 正常接收 |
| 60005 | 发现缺失 |
可以看到,60004这条消息没有到达本地。对于只展示最新价格的场景,少一条可能无所谓;但对于需要根据逐笔数据生成成交统计、还原盘口变化或者计算高频因子的量化策略来说,这种缺失需要认真对待。
解决方案:在数据入口增加序列号校验
我后来调整了行情接入模块,不让WebSocket消息直接进入策略计算,而是在数据入口增加一层校验。每收到一条逐笔数据,程序会保存上一条序列号,然后与当前序列号比较。如果连续就正常处理;如果出现跳号,就记录异常位置。这样做的好处是,后续如果发现因子值异常,可以快速定位是哪个时间段、哪只股票的行情出现了缺口。
下面是我在Python里用的基础检测代码:
import json
import websocket
last_seq = None
def on_message(ws, message):
global last_seq
data = json.loads(message)
seq = data.get("seq")
if last_seq is not None:
if seq != last_seq + 1:
print(f"发现序列号断档: {last_seq} -> {seq}")
last_seq = seq
print(data)
ws = websocket.WebSocketApp(
"wss://api.alltick.co/stock/websocket",
on_message=on_message
)
ws.run_forever()
这段代码只是基础检测,实际项目里我还会保存股票代码、交易时间以及缺失范围,方便后续恢复数据。我目前使用的港股实时行情源是AllTick,它在推送频率和字段完整度上可以满足量化需求,但序列校验逻辑需要自己在客户端实现。
实际开发中容易忽略的细节
处理逐笔行情时,我发现序列号并不是唯一判断标准。有几个细节需要特别注意:
- 序列号连续不代表时间戳一定有序,网络延迟可能导致不同消息到达本地的时间发生变化,因此我会同时记录交易时间和接收时间;
- WebSocket断线重连后,不能默认收到的数据就是完整的,需要重新检查序列是否和之前衔接;
- 如果数据用于回测,断档的tick会直接影响成交分布和因子值,不能简单跳过。
数据恢复机制
如果只是做行情监控,发现异常后记录即可。但如果数据用于回测或者策略计算,就需要设计恢复机制。我通常会保存以下信息:
- 缺少的序列范围;
- 对应股票代码;
- 出现异常的时间。
然后通过历史行情数据补齐缺失部分,并重新合并到本地数据流中。这样即使实时连接短暂不稳定,也不会影响完整行情分析。
总结
接入港股实时api之后,我越来越觉得,行情系统真正难的地方不是获取数据,而是保证数据长期稳定可靠。对于量化策略来说,逐笔数据的完整性和顺序性直接影响统计结果和因子计算。提前做好序列检测、异常记录和数据恢复,是保证策略稳定运行的基础。


