查看全课大纲(第 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。

⚠️ 数据修复即回测假设:停牌日”前向填充价格、成交量置 0”是一种建模约定而非事实——它隐含”停牌期间按停牌前价格估值、复牌后才能成交”的假设,而真实复牌常伴随大幅跳空,回测必须配合不可成交约束(见 3.7)。本讲演示为内置合成样例;真实停牌与除权数据可通过 Tushare 等接口获取(需注册凭证,规则以官方文档为准)。

📌 合规自查(回测/策略类内容的通用前提):本讲数值来自内置合成数据或人为设定的假设输入,仅用于演示方法,不代表真实交易结果;幸存者偏差(回测样本必须包含当时在市、日后可能退市的全部标的,只在幸存股票上计算会虚增表现);前视偏差(未来函数)(当日收盘后得到的信息只能用于其后的交易决策,财报须按实际披露日对齐);参数过拟合(周期、阈值等参数贴合某段历史不代表样本外有效,须用滚动样本外验证,见 5.4)。


📝 动手练一练

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

import pandas as pd
# 保证宽表在任意终端宽度下都完整打印(不折叠中间列),使输出确定、可复现
pd.set_option("display.width", 250)
pd.set_option("display.max_columns", None)
pd.set_option("display.max_rows", None)
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("==========================================================")
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. 确保双端一致性:对 daily 与 daily_hfq 集合统一执行清洗,为后续选股与撮合回测奠定无瑕数据基础。

📋 行动清单 (Action Checklist)

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

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

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