做量化交易的朋友们,我们在编写策略时,经常会聚焦因子计算和信号生成,但往往忽视了一个基础却致命的问题——实盘中股票停牌和复牌时的行情数据衔接。如果在回测和实盘中对这一环节处理不一致,策略表现可能会大相径庭。今天我们就结合高频自营交易的实践经验,聊聊如何让行情系统在特殊状态下保持数据连续。
回测中的幽灵收益与实盘的滑点 我们曾经在回测一只事件驱动策略时,发现某段时期年化收益异常高。后来逐笔核对才发现,有只股票停牌数月后复牌当日涨幅巨大,回测引擎因为使用了前复权价格直接将这段跳空计入收益,而实盘中我们根本不可能在停牌前以收盘价卖出、再于复牌首日以开盘价买回。这种错误就是由于没有在回测中正确标记停牌、并对复牌首笔行情做特殊处理导致的。同样,如果实盘系统在复牌时没有更新快照,策略会一直拿着停牌前的虚假仓位,直到新tick涌入才被动反应,往往已经错失最优价位。
几种行情接入方式在状态处理上的优劣 我们对比了在量化社区中常见的行情数据方案:
- 同花顺本地数据/公式系统:在回测中有完整的停牌标记,但实盘对接时往往需要自己写状态监听。
- 第三方数据爬虫/免费API:通常缺少交易状态字段,只能用成交量或时间间隔倒推,容易误判。
- 商业实时行情接口:提供WebSocket推送,并在每条tick中包含status字段和高精度时间戳,能够第一时间给出复牌信号。
在实盘中,我们更倾向于使用带状态标识的实时接口。例如AllTick API的tick数据中就内嵌了交易状态,这让我们可以直接在策略的行情接收端完成状态判断,无需额外查询。
必须校验的行情字段表 为了在复牌后准确更新快照,我们会对每条tick执行以下校验,缺一不可:
| 数据字段 | 主要作用 |
|---|---|
| 股票状态 | 识别停牌、交易中、临时停牌 |
| 时间戳 | 保证行情顺序,防止旧数据覆盖 |
| 最新成交价 | 获取复牌后第一笔交易价格 |
| 成交量 | 判断是否有实质成交产生 |
| 买卖盘口 | 刷新市场深度,用于计算冲击成本 |
时间戳优先级最高。我们在本地缓存每只股票的最后有效行情时间,任何新tick的时间必须严格大于该值才准入。这同时解决了因网络原因导致的包乱序和停牌数据残留问题。
我们的处理流程:状态机 + 快照冻结 我们将行情接收模块设计为一个状态机,主要流转如下:
- 正常交易状态:持续更新快照。
- 收到停牌事件:保存当前快照为“停牌前快照”,冻结实时行情缓存。
- 收到复牌事件(状态转为交易中):进入“等待首笔有效tick”状态。
- 首笔tick经过时间戳和成交量校验后,解冻缓存并写入新快照。
- 通知策略模块重新加载历史行情,避免K线出现断档。
这样处理,策略在回测和实盘中就能保持完全一致的数据视角——复牌前的最后一根K线可以准确收官,复牌后的第一根K线则基于恢复后的第一笔成交生成,不会出现价格断层误算。
实操与代码骨架 在实盘部署中,我们会在行情回调里内嵌这段逻辑。以下是一个简化的WebSocket订阅示例,大家可以根据自己的数据源补充状态判断:
import websocket
import json
api_doc = "//apis.alltick.co/websocket-api/stock-websocket-interface-api/transaction-quote-subscription"
def on_message(ws, message):
data = json.loads(message)
timestamp = data.get("timestamp")
if timestamp:
# 在此处进行状态校验及快照更新
print("更新行情快照:", data)
ws = websocket.WebSocketApp(
api_doc,
on_message=on_message
)
ws.run_forever()
建议大家在策略初始化时,就建立一个全局的股票状态表,每日开盘前通过数据接口更新当日停牌列表,并与行情流中的状态相互印证。这样无论是复盘还是实盘,都不会再因为复牌数据处理不当而出现“策略漂移”。数据质量往前多走一步,策略的稳健性就多一分保障。


