数据准备与清洗:数据源选择、对齐与复权、缺失值与异常值处理、存储方案

做量化回测,最怕什么?

不是策略亏钱,而是数据有问题。我见过太多人,策略回测曲线漂亮得像艺术品,一上实盘就崩。查到最后,十有八九是数据埋了雷。今天咱们就把这块硬骨头啃下来。

数据源选择:日线、分钟线还是Tick?

选什么粒度,取决于你的策略频率。别一上来就搞Tick数据,那玩意儿存储成本高,处理也慢。

  • 日线:适合中长线策略,持仓几天到几周。数据量小,处理快。我个人的习惯是,做因子测试先用日线,跑通了再降级验证。
  • 分钟线:适合日内策略或中高频交易。1分钟、5分钟最常见。注意,不同交易所的分钟线生成规则不一样,有的用最后成交价,有的用加权平均。
  • Tick数据:逐笔成交或逐笔挂单。适合高频策略。但坑也多——比如交易所的Tick流偶尔会断,或者出现重复数据。
我的建议:如果你刚开始做回测,从日线入手。等策略稳定了,再考虑用分钟线或Tick做精细化验证。别一上来就追求高频,容易迷失在数据噪音里。

数据对齐与复权处理

数据对齐,说白了就是让不同时间序列的数据站在同一起跑线上。比如你有沪深300的日线,又有中证500的日线,它们的交易日可能不完全重合。这时候就需要对齐。

我常用的对齐方法是:以主力合约或基准指数的交易日为锚点,其他数据按时间戳左连接。缺失的交易日直接填充前值或删除。

复权处理,这是个老生常谈但总有人踩坑的问题。

  • 前复权:调整历史价格,让价格连续。适合技术分析,因为K线图看起来平滑。
  • 后复权:调整当前价格,保持历史真实价格。适合计算真实收益率。

我个人强烈建议:回测时用后复权,实盘时用前复权。为什么?回测要算真实盈亏,后复权能保留分红送股的影响。实盘看盘时,前复权的K线更直观。

注意:千万不要在回测中混合使用不同复权方式。我曾经见过一个策略,回测用前复权,实盘用后复权,结果收益率差了20%以上。嗯,那哥们后来改行了。

缺失值处理

数据缺失是常态。原因很多:停牌、节假日、数据源漏了。处理方式取决于缺失比例和策略需求。

缺失比例 推荐处理方式 适用场景
< 5% 前向填充(ffill)或插值 大多数策略
5% - 20% 删除该时间段或使用模型填充 对数据完整性要求高的策略
> 20% 直接剔除该标的 避免引入偏差

我个人的经验是:先用pandas的isnull().sum()快速扫描,如果缺失集中在某几天,大概率是数据源问题,直接补全或重拉数据。如果缺失分散,用前向填充最稳妥。

# 快速处理缺失值示例
import pandas as pd

df = pd.read_hdf('data.h5')
# 检查缺失
print(df.isnull().sum())
# 前向填充
df.fillna(method='ffill', inplace=True)
# 如果还有缺失,用中位数填充
df.fillna(df.median(), inplace=True)

异常值检测

异常值比缺失值更隐蔽。比如某只股票突然涨了100倍,你以为是发现了妖股,其实是数据录入错误。

常用的检测方法:

  • 3σ原则:超过均值±3倍标准差的值视为异常。适合正态分布的数据。
  • IQR方法:四分位距法。超过Q1-1.5IQR或Q3+1.5IQR的值视为异常。更稳健,不受极端值影响。
  • 业务规则:比如涨跌幅超过20%的日线数据,大概率是异常。这个最实用。
避坑指南:我曾经在回测中没处理异常值,结果策略在某个“涨停”日全仓买入,第二天“跌停”卖出,回测收益率高得离谱。后来发现是数据源把复权价格搞错了。从那以后,我每次回测前都会跑一遍异常值检测。
# IQR方法检测异常值
Q1 = df['close'].quantile(0.25)
Q3 = df['close'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['close'] < lower_bound) | (df['close'] > upper_bound)]
print(f'发现 {len(outliers)} 个异常值')

数据存储方案:HDF5 vs Parquet

数据存哪里?怎么存?直接影响回测速度和磁盘占用。

特性 HDF5 Parquet
读取速度 快(列式存储) 更快(压缩率高)
压缩率 中等 高(通常节省50%以上空间)
兼容性 Python生态好 跨语言支持好
适合场景 单机回测、小规模数据 分布式、大规模数据

我个人偏好:日常回测用HDF5,因为pandas直接支持,读写方便。如果数据量超过10GB,或者需要跟其他语言交互,我会转成Parquet。

# HDF5存储示例
df.to_hdf('data.h5', key='daily', mode='w')

# Parquet存储示例
df.to_parquet('data.parquet', compression='snappy')
小技巧:用Parquet时,记得指定compression='snappy',速度和压缩比最均衡。别用gzip,解压太慢。

知识体系总览

下面这张图,概括了数据准备与清洗的核心流程。你想想看,从数据源到最终可用的回测数据,每一步都不能省。

数据准备与清洗核心流程 数据源选择 日线 / 分钟线 / Tick 对齐与复权 前复权 / 后复权 缺失值处理 ffill / 插值 / 删除 异常值检测 3σ / IQR / 业务规则 数据存储 HDF5 / Parquet 关键原则 • 数据源粒度匹配策略频率 • 复权方式保持一致,回测用后复权 • 缺失值处理要谨慎,避免引入偏差 • 异常值检测结合统计与业务规则 • 存储方案根据数据规模和场景选择

说白了,数据准备就是给策略打好地基。地基不稳,楼盖得再高也得塌。我每次开始一个新策略,都会花至少30%的时间在数据清洗上。别嫌麻烦,这步省了,后面全是坑。

专注资料整理