← 返回《初阶量化交易:策略编写及系统搭建》
📑 查看全课大纲(第 19 / 38 节)

修复行情数据中的问题

约 13 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

修复行情数据中的问题:停牌与缺失值治理

小象实战讲义 · 初阶量化交易:策略编写及系统搭建 本讲义仅用于编程与量化方法教学,不构成任何投资建议。

在量化投资的回测与实盘工程体系中,数据质量治理(Data Cleaning & Fixing) 直接决定了回测结果的信度。A 股市场中,上市公司因重大事项重组、财报披露延迟等原因频繁出现停牌(Suspension)。原始财经数据接口(如 Tushare)在股票停牌期间直接不生成当天的记录,导致时间序列发生物理断裂。

若未经过严格的数据修复处理,回测引擎在遍历到停牌日时将无法读取持仓股票的最新价格,导致当日持仓市值被错误归零或触发异常报错,引发资产净值曲线的灾难性失真。本讲系统讲解基于标准交易日历的停牌断点对齐算法、价格前向填充(Forward Fill)规范与 is_trading 状态标记工程


💡 核心导读

  • 停牌数据缺失在回测中的致命危害
    • 净值断崖失真:若组合持有某重仓股,该股停牌一个月,回测引擎若缺失数据将无法评估持仓市值,导致账户净值虚假暴跌,复牌后又虚假暴涨;
    • 指标序列截断:滑动窗口函数(如 rolling(20))若遇到日期跳空,会把相隔数月的两根 K 线当成连续交易日计算均线,彻底破坏技术指标的数理意义。
  • 标准停牌数据修复规范(Daily Fixing 规则)
    1. 日历对齐(Calendar Reindex):以大盘指数的标准交易日历为轴,全量补齐个股缺失的日期;
    2. 价格前向填充(Forward Fill):将停牌日的高、开、低、收(OHLC)四个价格统一设置为停牌前最后一个有效交易日的收盘价(last_close
    3. 成交量与状态标识:停牌日成交量显式设为 volume = 0,新增布尔字段 is_trading = False(正常交易日为 True);
    4. 预热前收价(pre_close:自动计算上一交易日有效收盘价,方便后续秒级计算当日收益率。
  • 双集合同步治理原则
    • 停牌修复必须在不复权集合(daily)与后复权集合(daily_hfq)中同时执行,确保回测与信号计算两端基准一致。
┌─────────────────────────────────────────────────────────────┐
│                    行情数据停牌断点清洗与修复流程               │
└──────────────────────────────┬──────────────────────────────┘

 ┌─────────────────────────────┼─────────────────────────────┐
 ▼                             ▼                             ▼
┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐
│ 1. 交易日历对齐   │   │ 2. 价格前向填充   │   │ 3. 状态与成交置零 │
├──────────────────┤   ├──────────────────┤   ├──────────────────┤
│ • 以指数日历为轴 │   │ • OHLC 统一设为  │   │ • volume = 0     │
│ • 扫描个股缺失日 │   │   停牌前末日收盘 │   │ • is_trading=False│
│ • 补齐空缺文档   │   │ • 保持持仓市值平滑│  │ • 锁定不可交易状态│
└──────────────────┘   └──────────────────┘   └──────────────────┘

一、停牌断点对量化系统的冲击机制

在真实交易中,股票停牌期间投资者无法买入或卖出,但持仓市值依然存在,并以停牌前收盘价计算资产总值。

Position Valuet=Shares×Closelast\text{Position Value}t = \text{Shares} \times Close{last}

若未做数据补齐,查询结果为 None,若回测系统处理不当直接赋予 0,将产生不可接受的逻辑回撤。


二、Daily Fixing 修复算法与实现逻辑

标准修复脚本 daily_fixing.py 核心执行四步转换:

  1. 获取全集交易日:从 daily 集合中查询 index=True 的全量有序日期列表;
  2. 扫描个股缺失记录:比对个股实际记录日期与标准日历集合;
  3. 构造补齐文档:对每个缺失日,继承前一日收盘价,成交量填 0,is_trading 设为 False
  4. 批量更新入库:使用 bulk_write 执行批量 Upsert。

📝 动手练一练

下面我们用 Python 编写一段 标准行情停牌数据清洗与断点修复脚本,演示如何基于标准交易日历自动补齐停牌断点并平滑持仓市值:

import pandas as pd
import numpy as np

def fix_suspension_and_missing_data(stock_df, calendar_dates):
    """
    行情数据修复管线 (Daily Fixing):
    1. 基于标准交易日历对齐补齐停牌断点
    2. 停牌日高开低收以前一交易日收盘价 (Forward Fill) 填充
    3. 成交量置 0, 标记 is_trading = False
    4. 填充 pre_close 字段
    """
    stock_df = stock_df.copy()
    code = stock_df['code'].iloc[0]
    
    # 1. 重新索引至全量交易日历
    fixed_df = stock_df.reindex(calendar_dates)
    fixed_df['code'] = code
    
    # 2. 识别真实交易日与停牌日
    fixed_df['is_trading'] = ~fixed_df['close'].isna()
    
    # 3. 前向填充收盘价 (停牌期间保持停牌前持仓价值不变)
    fixed_df['close'] = fixed_df['close'].ffill()
    fixed_df['open'] = fixed_df['open'].fillna(fixed_df['close'])
    fixed_df['high'] = fixed_df['high'].fillna(fixed_df['close'])
    fixed_df['low'] = fixed_df['low'].fillna(fixed_df['close'])
    fixed_df['volume'] = fixed_df['volume'].fillna(0.0)
    
    # 4. 填充上一日有效收盘价 pre_close
    fixed_df['pre_close'] = fixed_df['close'].shift(1)
    
    return fixed_df

def test_fixing_run():
    # 构造全市场标准 5 天交易日历 (周一至周五)
    calendar_dates = pd.date_range("2023-06-01", periods=5, freq="B")
    
    # 模拟某股票在第 3、4 天停牌 (原始数据中直接缺失 6月5日与6月6日)
    raw_dates = pd.to_datetime(["2023-06-01", "2023-06-02", "2023-06-07"])
    raw_df = pd.DataFrame({
        'code': '600000',
        'open': [10.0, 10.2, 10.8],
        'high': [10.5, 10.6, 11.0],
        'low': [9.8, 10.1, 10.5],
        'close': [10.2, 10.5, 10.9],
        'volume': [10000, 15000, 12000]
    }, index=raw_dates)
    
    fixed_res = fix_suspension_and_missing_data(raw_df, calendar_dates)
    
    print("================ 行情数据停牌断点清洗与修复 ================")
    print(fixed_res[['code', 'open', 'close', 'volume', 'is_trading', 'pre_close']])
    print("----------------------------------------------------------")
    suspended_count = (~fixed_res['is_trading']).sum()
    print(f"成功识别并补齐停牌天数: {suspended_count} 天 (成交量置 0, 价格平滑 ffill)")
    print("==========================================================")

if __name__ == "__main__":
    test_fixing_run()

运行结果输出:

================ 行情数据停牌断点清洗与修复 ================
              code  open  close   volume  is_trading  pre_close
2023-06-01  600000  10.0   10.2  10000.0        True        NaN
2023-06-02  600000  10.2   10.5  15000.0        True       10.2
2023-06-05  600000  10.5   10.5      0.0       False       10.5
2023-06-06  600000  10.5   10.5      0.0       False       10.5
2023-06-07  600000  10.8   10.9  12000.0        True       10.5
----------------------------------------------------------
成功识别并补齐停牌天数: 2 天 (成交量置 0, 价格平滑 ffill)
==========================================================

本章小结

  1. 防范停牌净值失真:停牌数据缺失是导致回测市值断裂的核心诱因,必须以全量交易日历为标尺进行前向填充。
  2. 规范字段标准:停牌日价格继承前收盘、成交量设为 0、is_trading 明确置为 False
  3. 确保双端一致性:对 dailydaily_hfq 集合统一执行清洗,为后续选股与撮合回测奠定无瑕数据基础。

📋 行动清单 (Action Checklist)

  • 运行本讲停牌修复代码,观察 6 月 5 日与 6 月 6 日被自动补齐并标记为 is_trading=False
  • 在本地 MongoDB 中验证修复逻辑,确保清洗后无断点跳空;
  • 预习下一讲,学习如何抓取东方财富财务报表数据并实现定时增量更新。

— 小象教研组

配套学习资源与课件
  • 第3章课件:上手搭建最简单的交易系统(PDF · 3.8MB)
    下载
  • 开发环境准备:软件安装指导(PDF · 0.9MB · 建议第3章动手实践前先通读)
    下载
  • 量化入门推荐书单(PDF · 318KB)
    下载
🎁 免费学习资源

领取《小象 11GB VIP 课件资料包与大厂真题手册》

包含全套实战 Jupyter 源码、清洗后数据集、大厂高频面试真题与专属学员答疑交流群。

  • 完整 Python / 数据分析 Jupyter 实战源码
  • 大厂真实业务数据集与练习题
  • 微信扫码添加课程顾问,免费获取网盘下载链接
微信二维码:扫码添加课程顾问微信扫码添加顾问