4、数据清洗:处理缺失值、异常值、时间戳对齐与重采样
数据清洗这事儿,说白了就是给策略喂饭前先挑挑沙子。我见过太多人,策略逻辑写得漂漂亮亮,结果一跑回测全是坑——为什么?数据没洗干净。今天咱们就把这块硬骨头啃下来。
4.1 缺失值处理:别让空值毁了你的策略
做套利最怕什么?两个品种的数据对不上。一个品种有报价,另一个缺了,你的价差瞬间变成NaN。我个人习惯,拿到数据第一件事就是检查缺失率。
核心原则:缺失率低于5%可以填充,高于20%建议重新获取数据源。
4.1.1 常见填充方法
- 向前填充(ffill):用上一个有效值填充。适合高频数据,比如1分钟K线,价格变化不大。
- 向后填充(bfill):用下一个有效值填充。适合收盘后补数据。
- 线性插值:用前后两个值的均值。适合缓慢变化的数据,比如指数成分股调整。
- 删除缺失行:如果缺失比例极小,直接删掉最省事。
import pandas as pd
import numpy as np
# 模拟数据
df = pd.DataFrame({
'time': pd.date_range('2024-01-01', periods=10, freq='1min'),
'price': [100, 101, np.nan, 103, 104, np.nan, 106, 107, 108, np.nan]
})
# 向前填充
df['price_ffill'] = df['price'].ffill()
# 线性插值
df['price_interp'] = df['price'].interpolate()
print(df)
我的经验:做期货套利时,我习惯用ffill。因为期货价格连续,跳空概率低。但做股票就不一样了——停牌导致的数据缺失,千万别填充,直接删掉那行。我曾经吃过这个亏,填充了停牌数据,结果策略在停牌期间疯狂开仓,回测曲线漂亮得不像话,实盘直接崩了。
4.2 异常值检测:把那些离谱的数据揪出来
异常值是什么?就是那些明显不符合市场规律的数据。比如某只股票突然涨了1000%,或者价格变成负数。嗯,这里要注意,异常值不一定是错误,也可能是真实事件(比如乌龙指)。
4.2.1 常用检测方法
| 方法 | 适用场景 | 阈值 |
|---|---|---|
| Z-score | 正态分布的数据 | |Z| > 3 |
| IQR(四分位距) | 偏态分布的数据 | Q1 - 1.5*IQR 或 Q3 + 1.5*IQR |
| 百分比截断 | 极端值明显的场景 | 上下1%或5% |
# 使用IQR检测异常值
Q1 = df['price'].quantile(0.25)
Q3 = df['price'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['price'] < lower_bound) | (df['price'] > upper_bound)]
print(f"发现 {len(outliers)} 个异常值")
避坑指南:我曾经在回测国债期货套利时,用Z-score检测异常值,结果把2016年11月的“债灾”数据全删了。那波行情恰恰是策略最该学习的极端情况。所以,异常值要分情况处理:如果是数据录入错误,直接删;如果是真实市场事件,保留并标记。
4.3 时间戳对齐:让两个品种的数据步调一致
做套利策略,最核心的就是时间对齐。你想想看,如果A品种的报价是10:00:01,B品种是10:00:03,这两笔数据能直接算价差吗?当然不能。时间戳不对齐,价差全是错的。
4.3.1 对齐策略
- 精确对齐:找到时间戳完全一致的数据。适合高频数据,但数据量会减少。
- 最近邻对齐:用最接近的时间戳匹配。适合不同频率的数据。
- 向前对齐:用上一个时间戳的数据匹配当前时间戳。适合处理延迟数据。
# 假设有两个品种的数据
df_a = pd.DataFrame({
'time': pd.to_datetime(['2024-01-01 10:00:01', '2024-01-01 10:00:03', '2024-01-01 10:00:05']),
'price_a': [100, 101, 102]
})
df_b = pd.DataFrame({
'time': pd.to_datetime(['2024-01-01 10:00:02', '2024-01-01 10:00:04', '2024-01-01 10:00:06']),
'price_b': [200, 201, 202]
})
# 使用merge_asof进行最近邻对齐
df_aligned = pd.merge_asof(df_a, df_b, on='time', direction='nearest')
print(df_aligned)
我的习惯:做跨期套利时,我一般用向前对齐。因为主力合约切换时,近月合约的流动性更好,报价更新更快。用近月合约的时间戳去对齐远月合约,能减少很多伪信号。
4.4 重采样:统一数据频率
数据频率不统一,是套利回测的常见问题。一个品种是1分钟数据,另一个是5分钟数据,怎么算?重采样就是干这个的。
4.4.1 重采样方法
| 频率转换 | 方法 | 示例 |
|---|---|---|
| 降采样(高频→低频) | 取均值、OHLC | 1分钟→5分钟 |
| 升采样(低频→高频) | 插值、填充 | 日线→小时线 |
# 将1分钟数据重采样为5分钟
df_resampled = df.set_index('time').resample('5min').agg({
'price': 'ohlc', # 生成开盘、最高、最低、收盘
'volume': 'sum'
})
print(df_resampled.head())
关键点:降采样时,价格用OHLC(开盘价、最高价、最低价、收盘价),成交量用求和。升采样时,价格用插值,成交量用0填充(因为低频到高频,成交量无法拆分)。
4.5 本章知识体系
下面这张图,把数据清洗的四个核心步骤串起来了。你可以把它当作操作手册,每次做回测前对照检查一遍。
数据清洗这件事,看起来琐碎,但直接影响策略的生死。我见过太多人,策略逻辑写得天花乱坠,结果数据没洗干净,回测曲线漂亮得不像真的。记住一句话:垃圾进,垃圾出。数据干净了,策略才有意义。