2. 数据源验证:历史行情数据完整性检查、Tick级数据对齐、除权除息数据校验

做回测系统这么多年,我踩过最大的坑,往往不是策略逻辑写错了,而是数据本身有问题。你想想看,如果喂给策略的数据都是错的,那回测结果再漂亮,也不过是垃圾进垃圾出。这一章,我们就来聊聊数据源验证这件事。

说白了,数据验证就是给你的回测系统装上一道安检门。我个人的习惯是,宁可花30%的开发时间在数据校验上,也不愿意花300%的时间去排查一个由数据错误导致的诡异回测结果。

核心观点:数据验证不是可有可无的步骤,而是回测系统的生命线。没有经过严格验证的数据,回测结果毫无意义。

2.1 历史行情数据完整性检查

完整性检查,说白了就是看看你的数据有没有「缺斤少两」。我在项目中遇到过好几次,回测跑得好好的,突然某一天收益率曲线出现一个诡异的跳变,查了半天才发现是某只股票缺了一天的数据。

完整性检查通常包括以下几个方面:

  • 时间连续性检查:交易日是否连续?有没有缺失的日期?
  • 数据字段完整性:每条记录的开盘价、最高价、最低价、收盘价、成交量是否齐全?
  • 时间戳合法性:时间戳是否在合理范围内?有没有未来的数据?
  • 重复数据检查:同一时间点是否存在多条记录?

这里我分享一个我常用的检查脚本片段:

def check_data_integrity(df, symbol):
    """
    检查历史行情数据的完整性
    """
    issues = []
    
    # 1. 检查时间连续性
    df['date'] = pd.to_datetime(df['date'])
    date_range = pd.date_range(start=df['date'].min(), 
                               end=df['date'].max(), 
                               freq='B')  # 仅交易日
    missing_dates = date_range.difference(df['date'])
    if len(missing_dates) > 0:
        issues.append(f"缺失交易日: {len(missing_dates)} 天")
    
    # 2. 检查字段完整性
    required_fields = ['open', 'high', 'low', 'close', 'volume']
    for field in required_fields:
        null_count = df[field].isnull().sum()
        if null_count > 0:
            issues.append(f"{field} 字段存在 {null_count} 个空值")
    
    # 3. 检查价格合理性
    if (df['high'] < df['low']).any():
        issues.append("存在最高价低于最低价的异常记录")
    
    return issues

小技巧:对于A股市场,我建议使用「交易日历」而非简单的「工作日」来判断连续性。因为A股有调休、春节等特殊安排,直接用工作日会误报很多「缺失」。我一般会从交易所官网下载官方的交易日历。

2.2 Tick级数据对齐

Tick级数据,说白了就是逐笔成交数据。这东西精度高,但坑也多。我记得有一次做高频策略回测,发现策略在某个时间点突然大量开仓,查了半天才发现是Tick数据的时间戳没对齐。

为什么会这样?因为不同交易所、不同数据源的时间戳精度可能不一样。有的精确到毫秒,有的精确到微秒,甚至有的只精确到秒。你想想看,如果两个数据源的时间戳精度不一致,那对齐的时候就会出现偏差。

Tick级数据对齐的核心步骤:

  1. 时间戳标准化:将所有数据源的时间戳统一到相同的精度(建议使用微秒级)
  2. 时钟漂移校正:检查并校正不同数据源之间的时钟偏差
  3. 数据交叉验证:用多个数据源互相验证,找出不一致的记录
  4. 异常值过滤:剔除明显不合理的数据点(比如价格超出当日涨跌停板)

我曾经遇到过一个经典案例:某数据源的Tick数据在每天开盘前5分钟会「提前」出现一些成交记录。后来发现是数据源的时间戳没有考虑集合竞价阶段。嗯,这里要注意,不同交易所的集合竞价规则不一样,处理方式也不同。

def align_tick_data(tick_df, exchange='SH'):
    """
    Tick级数据对齐
    """
    # 1. 标准化时间戳到微秒
    tick_df['timestamp'] = pd.to_datetime(tick_df['timestamp'], 
                                          unit='us')
    
    # 2. 根据交易所规则过滤
    if exchange == 'SH':
        # 上交所:9:15-9:25 集合竞价,9:30-11:30, 13:00-15:00 连续竞价
        market_hours = tick_df['timestamp'].dt.time.between(
            pd.Timestamp('09:15:00').time(),
            pd.Timestamp('15:00:00').time()
        )
        tick_df = tick_df[market_hours]
    
    # 3. 检查时间戳单调性
    if not tick_df['timestamp'].is_monotonic_increasing:
        tick_df = tick_df.sort_values('timestamp')
    
    return tick_df

警告:千万不要直接对Tick数据进行「线性插值」来填补缺失值!Tick数据是离散事件,不是连续信号。我曾经见过有人用插值法补Tick数据,结果回测出来的夏普比率高得离谱,一上实盘就崩了。

2.3 除权除息数据校验

除权除息,这是回测系统里最容易出问题的地方之一。我刚开始做回测的时候,就吃过这个亏。策略在分红除息日前后表现异常,查了三天才发现是复权数据没处理好。

除权除息数据校验的核心要点:

  • 复权方式一致性:前复权、后复权、不复权,三种方式必须统一
  • 除权除息日准确性:核对交易所公告,确保除权除息日没有偏差
  • 复权因子计算:验证复权因子的计算是否正确
  • 分红送转数据:检查每股分红、送股比例等数据是否准确

我个人习惯的做法是,维护一个独立的「除权除息事件表」,而不是依赖数据源提供的复权数据。这样我可以自己控制复权逻辑,出了问题也能快速定位。

def validate_corporate_actions(corp_df, price_df):
    """
    校验除权除息数据
    """
    errors = []
    
    for idx, row in corp_df.iterrows():
        ex_date = row['ex_date']
        symbol = row['symbol']
        
        # 获取除权除息日前后价格
        before_price = price_df.loc[
            (price_df['symbol'] == symbol) & 
            (price_df['date'] < ex_date), 
            'close'
        ].iloc[-1]
        
        after_price = price_df.loc[
            (price_df['symbol'] == symbol) & 
            (price_df['date'] >= ex_date), 
            'close'
        ].iloc[0]
        
        # 计算理论除权价
        dividend = row['dividend']
        bonus_ratio = row['bonus_ratio']
        theoretical_price = (before_price - dividend) / (1 + bonus_ratio)
        
        # 检查实际价格与理论价格的偏差
        deviation = abs(after_price - theoretical_price) / theoretical_price
        if deviation > 0.01:  # 允许1%的偏差
            errors.append(f"{symbol} 在 {ex_date} 除权价偏差 {deviation:.2%}")
    
    return errors

避坑指南:我曾经遇到过数据源把「除权除息日」和「股权登记日」搞混的情况。这两个日期差一天,但影响巨大。我的建议是,一定要从交易所官网或权威数据源获取除权除息日历,不要完全依赖第三方数据源。

2.4 数据验证框架图

下面这张图是我在实际项目中使用的数据验证框架,你可以参考一下:

数据源验证框架 原始数据源 完整性检查 时间连续性 字段完整性 Tick级对齐 时间戳标准化 时钟漂移校正 除权除息校验 复权一致性 事件准确性 数据清洗与标准化 异常值过滤 | 格式统一 | 缺失值处理 验证通过的数据 数据验证框架:从原始数据到验证通过数据的完整流程

这张图展示了我常用的数据验证流程。从原始数据源开始,经过三个并行验证模块(完整性检查、Tick级对齐、除权除息校验),然后进入数据清洗与标准化层,最终输出验证通过的数据。每个模块之间都有明确的输入输出接口,方便独立测试和调试。

2.5 实战经验总结

做了这么多年回测系统,我总结了几条数据验证的「铁律」:

  • 永远不要相信任何数据源:不管数据源多权威,都要做交叉验证
  • 验证要自动化:每次数据更新后自动运行验证脚本,不要手动检查
  • 保留原始数据:清洗后的数据可以覆盖,但原始数据一定要备份
  • 记录验证日志:每次验证的结果都要记录下来,方便追溯问题

最后提醒一句:数据验证不是一次性工作,而是持续的过程。数据源可能会更新、交易所规则可能会变化、新的数据问题可能会出现。我建议每周至少运行一次完整的数据验证流程,确保数据质量始终在线。


专注资料整理