4. 数据清洗与对齐:去重与异常值过滤、时间戳对齐、复权数据处理

数据清洗与对齐,说白了就是给原始数据“洗澡”。

我见过太多人,策略写得漂亮,回测结果也漂亮,一上实盘就崩。为什么?数据没洗干净。你想想看,拿脏数据做回测,跟用过期地图导航有什么区别?

4.1 去重与异常值过滤

先聊去重。交易所的数据源,偶尔会重复推送同一笔行情。比如网络抖动,同一个tick来了两次。如果你不处理,回测里就会出现两笔一模一样的成交,资金曲线直接失真。

我个人习惯的做法是:按“时间戳+价格+成交量”三元组去重。三个字段完全一致,就判定为重复,只保留第一条。

核心原则:去重不是简单删数据,而是保留最原始的那一笔。

异常值过滤,这个坑我踩过。有一次做股指期货的Tick级回测,发现某天下午突然出现一笔成交价是前一笔的10倍。我一开始以为是行情异动,后来查了日志才发现是数据源字段错位了。

常用的过滤规则就三条:

  • 价格范围校验:当日涨跌停板内,超出直接剔除
  • 价格突变检测:相邻两笔价格变化超过阈值(比如5%),标记为可疑
  • 成交量异常:单笔成交量超过日均量的10倍,大概率是错误数据

注意:不要一刀切。我曾经把某只股票的真实大单成交当异常给过滤了,结果回测少赚了20%。建议异常值先标记,人工复核后再决定是否剔除。

4.2 时间戳对齐:Tick级 / 秒级 / 分钟级

时间戳对齐,是回测系统里最磨人的环节。为什么?因为不同数据源的时间精度不一样。有的精确到毫秒,有的只到秒,还有的干脆是字符串格式。

我建议你记住一个原则:统一时间基准,向下对齐

举个例子:

  • Tick级对齐:所有数据精确到毫秒。如果某条数据只有秒级时间戳,就补上“.000”。对齐后按时间排序,相邻tick之间不能有空洞
  • 秒级对齐:把毫秒部分截断。注意,这里有个坑——截断后可能出现多条数据时间戳相同,需要按“最后一笔”或“加权平均”合并
  • 分钟级对齐:按分钟K线生成。我习惯用“OHLC”结构,即开盘价、最高价、最低价、收盘价。注意分钟线的第一笔和最后一笔时间戳要严格对齐到整分钟

小技巧:写一个统一的时间戳转换函数。输入任意格式的时间字符串,输出统一的Unix毫秒时间戳。这样后续所有对齐逻辑都基于这个函数,改一处就行。

代码示例(伪代码,核心逻辑):

def align_timestamp(raw_ts, level='tick'):
    # 统一转为毫秒时间戳
    ms_ts = parse_to_milliseconds(raw_ts)
    
    if level == 'tick':
        return ms_ts  # 精确到毫秒
    elif level == 'second':
        return ms_ts // 1000 * 1000  # 截断毫秒
    elif level == 'minute':
        return ms_ts // 60000 * 60000  # 截断到整分钟
    else:
        raise ValueError('不支持的时间级别')

4.3 复权数据处理:前复权 vs 后复权

复权,是回测里最容易出幻觉的地方。不处理复权,你的策略可能看起来赚钱,实际上亏钱。

先讲清楚概念:

  • 前复权:调整历史价格,让价格连续。当前价格不变,历史价格按除权因子下调。说白了,就是“现在的价格是真实的,过去的都按比例缩一下”
  • 后复权:调整当前价格,让历史价格不变。历史价格是真实的,当前价格按除权因子上调。也就是“过去的价格不变,现在的价格放大”

我个人习惯用前复权做回测。为什么?因为前复权后的价格序列是连续的,不会出现除权日的大跳空。你想想看,如果某只股票10送10,除权日价格直接腰斩,你的均线系统会瞬间发出错误信号。

重要提醒:前复权数据不能用于计算真实收益率。因为历史价格被调整过,算出来的收益率是“复权收益率”,不是真实收益率。真实收益率要用后复权或原始价格算。

我曾经犯过一个低级错误:用前复权数据算夏普比率,结果算出来高得离谱。后来才发现,前复权把历史波动率也压缩了,导致夏普比率虚高。嗯,从那以后我养成了习惯——回测用前复权,绩效评估用后复权

复权因子计算逻辑:

def calc_adjust_factor(dividend, split_ratio):
    """
    dividend: 每股分红
    split_ratio: 送转比例(如10送10,则split_ratio=2)
    """
    # 除权因子 = (前收盘价 - 分红) / 前收盘价 / 送转比例
    # 简化版:
    factor = (1 - dividend / prev_close) / split_ratio
    return factor

避坑指南:我曾经遇到过一个数据源,复权因子算错了小数点。结果回测里某只股票的历史价格变成了负数。所以,拿到复权数据后,先手动验证几个除权日的价格变化。比如某股票10送10,除权日价格应该正好是前一天的50%。如果不对,赶紧换数据源。

4.4 本章知识体系总览

下面这张图,是我自己画的数据清洗与对齐流程。你可以把它当作一个检查清单:

数据清洗与对齐流程 原始行情数据 去重(三元组校验) 异常值过滤(价格/量) 时间戳对齐(Tick/秒/分) 复权处理(前/后复权) 输入:原始Tick/分钟数据 去重:时间+价格+量 过滤:涨跌停+突变+异常量 对齐:统一时间基准 复权:回测用前复权

数据清洗这件事,没有捷径。你花多少时间在数据上,回测就给你多少回报。我见过最夸张的一个项目,光清洗数据就占了整个开发周期的40%。但结果呢?上线后几乎没出过数据问题。

所以,别嫌麻烦。把数据洗干净,你的策略才能跑得稳。


专注资料整理