量化策略里,停牌股票的价格推送到底怎么过滤?

用户头像sh_****559rtx
2026-06-11 发布

各位 Quant 朋友,今天聊一个细节但致命的问题:回测和实盘中,你是怎么处理停牌股票的行情的? 是不是经常发现某些停牌票的“最新价”还留在时间序列里,拉偏了整个板块的收益率?我最近重构一个高频选股策略时就重点优化了这一块,从客户需求、投顾痛点一路到具体实现,给大家做个分享。

客户与策略的需求:干净的行情输入

我们做量化,最怕输入数据有“脏东西”。停牌票的价格如果不剔除,会直接污染因子计算和组合表现。例如在市值因子、动量因子中混入一个停牌不变的数值,信号就会失真。对投顾型策略输出而言,客户也要求推荐池子里不能有不可交易的标的,否则合规和体验都成问题。

投顾/策略开发者的真实痛点

传统的行情数据,有些源并不提供明确的停牌标识。我就碰到过一个接口,停牌后成交量的确不动了,但价格还在每秒推送,甚至买卖盘口遗留了停牌前的挂单。回测时没注意,选股池里混进一只停牌股,结果用市价单模拟无法成交,信号回测偏差大到离谱。而实盘监控里,如果订阅几千只股票,停牌票持续消耗 WebSocket 流量,CPU 占用也会无谓升高。

数据支撑:停牌股票的接口特征

我梳理过多个数据源在停牌期间的行为,基本覆盖以下几种:

接口特征 判断方式 量化策略里的应用
最新价冻结 价格序列方差为零 不能单独作为停牌依据
成交量持续为零 连续N分钟无成交量 可作为辅助过滤
状态字段suspend 布尔值直接判断 可靠性最高,建议优先使用
时间戳连续但无交易 序列有更新时间 直接忽略时间戳

显然,有状态字段的接口对量化最友好

我的过滤升级方案

我现在实盘和回测都在用的三层架构,效果很好。

数据清洗层:在接收数据后,立即对每条 tick 检查状态字段;若无,则用“成交量 == 0 且 last_price 连续 N 秒不变”作为备用规则,打上停牌标签。

因子计算层:所有计算因子的函数,都先执行 df = df[df['suspend'] == False],保证输入干净。

订阅管理层:在 WebSocket 回调里,一旦识别出停牌,就动态取消对该标的的订阅,释放带宽。我目前使用的实时数据源(如 AllTick)直接提供 suspend 字段,让这步操作十分轻量。

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    for tick in data.get("ticks", []):
        if tick.get("suspend"):  # 判断是否停牌
            print(f"{tick['symbol']} 停牌中")
        else:
            print(f"{tick['symbol']} 最新价: {tick['last_price']}")

ws = websocket.WebSocketApp("wss://api.alltick.co/stock", on_message=on_message)
ws.run_forever()

接口差异与适配

不同行情 API 差异挺大,有些停牌就断流,有些价格照推但盘口为空。我习惯在接入新数据源时,先跑一天观察,把股票分三类:活跃、停牌、新上市/退市,用不同管道处理。停牌处理本质上是数据清洗的第一关,把这关做好了,策略的鲁棒性会显著提升。

fc5093a58d1b7c13070a369cce4a435a.jpg

评论