先给结论:停牌日没有成交,交易所不生成K线,这是市场机制的真实表现。回测里真正的危险不是"数据少了几行",而是你用什么假设去填那个洞——填零、填充、还是留空,三种选择对应三条完全不同的回测命运。让策略在历史里"完美避开"所有暴跌日的,往往正是这个洞。
个股因重大事项停牌时,交易所停止该标的的竞价撮合。当天没有任何成交,就不会产生开盘价、收盘价,也就没有K线可言。行情软件上表现为K线直接断开、中间空白——这不是数据丢了,是那几天市场根本没有价格。
常见的停牌场景:
| 停牌类型 | 典型时长 | 触发原因 |
|---|---|---|
| 重大资产重组 | 数周至数月 | 并购、定增、换股吸收合并 |
| 异常波动核查 | 1-5 个交易日 | 股价异动监管核查 |
| 重要公告披露 | 1 个交易日 | 年报预披露、重大合同 |
| 盘中临时停牌 | 10 分钟 | 新股上市初期盘中涨跌幅达 30%/60% |
同一个停牌事件,不同数据源给你的数据长得完全不一样:
| 类型 | 数据表现 | 对回测的影响 |
|---|---|---|
| 舍弃型 | 停牌日整行消失,时间索引断裂 | 逐日遍历会把"复牌跳变"当成相邻两天计算,涨跌幅算出天量数字 |
| 空值型 | 日期存在,价格字段为 NaN,部分源附带停牌标识 | 指标计算批量产生空值;若下游把 NaN 当 0,直接制造假暴跌 |
| 填充型 | 日期与数值都连续,停牌段沿用前收盘价 | 序列看起来完美,但策略会"以为"价格没变,错过复牌风控窗口 |
# 数据源停牌行为检测:对比交易日序列与预期交易日
import pandas as pd
df = pd.DataFrame(api_data) # 接口返回的日线
df["date"] = pd.to_datetime(df["date"])
full_days = pd.date_range(df["date"].min(), df["date"].max(), freq="B") # 粗筛:工作日
missing = set(full_days) - set(df["date"]) # 日期缺口 → 舍弃型
nan_rows = df[df[["open","close","high","low"]].isna().any(axis=1)] # 空值行 → 空值型
flat = df[df["high"] == df["low"]] # 全天一字且无量 → 疑似填充型
| 方案 | 做法 | 适用场景 | 代价 |
|---|---|---|---|
| 保留缺失,不填充 | 时间轴保留停牌日,价格留空,指标分段计算 | 高精度仿真、需要严格区分可交易/不可交易日的合规回测 | 均线、ATR 等连续指标产生空值,预处理开发量大 |
| 前向填充 + 停牌标记 | 收盘价 ffill 补齐,同时加一列 suspended=True |
绝大多数自研回测框架的通用做法 | 策略在停牌段会误判"价格没变",必须用标记列禁用交易信号 |
无论选哪种,有一件事绝对不能做:把缺失值当 0 填。价格填 0 等于告诉回测引擎"这天股价归零了"——净值曲线会画出一个不存在的-100%,收益率统计从此全是废纸。量化圈流传多年的"回测夏普 3.2、实盘亏 40%"的故事,根源多半就在这一行代码。
长期停牌复牌首日,往往伴随大幅补跌或补涨(重组失败连续跌停、重组成功连续涨停)。三个口径要点:
Q1:停牌期间为什么没有K线?
停牌期间没有竞价撮合、没有成交,交易所不生成当日K线,行情软件直接断开。这是市场机制的真实表现,不是数据缺失。
Q2:数据接口对停牌日一般返回什么?
常见三类:整行舍弃(时间索引断裂)、日期在但价格字段为空、用前收盘自动填充。先用本文的断档检测代码判定你的数据源属于哪一类。
Q3:回测该用前向填充还是保留缺失?
短停牌通用做法是前向填充加停牌标记;严格仿真则保留缺失分段计算。共同底线是绝不填零。
Q4:为什么我的均线信号在停牌段会提前或滞后?
剔除停牌日后,"20日均线"实际是20根有效K线的均值,窗口跨越的真实时间更长,信号相对日历日期偏移。
Q5:长期停牌的股票在回测里怎么处理?
视为不可交易标的:持仓冻结、禁止申报,复牌首日按可成交性重新评估,警惕连续跌停下的排队卖出假设。
Q6:复牌第一天的涨跌停价按什么昨收计算?
按停牌前最后一个交易日的真实收盘价(不复权口径)计算,与正常交易日规则一致。详见涨停价计算专篇。
Q7:怎么检测我的数据源在停牌日的返回行为?
拉一段已知有停牌的历史,日期缺口为舍弃型、数值为空为空值型、连续无波动为填充型——本文第二节给出了现成代码。
以A股历史交易数据接口为例,标准流程是:拉取目标标的完整日线 → 跑第二节的断档检测 → 按判定结果选择填充方案 → 回测引擎读取停牌标记列。指数停牌场景少见但存在(成分股大面积停牌的极端时期),指数历史行情接口同样适用本流程。
suspended 列,回测撮合层读取它禁用停牌日申报