第1章:数据获取与清洗——期货统计套利的基石
做统计套利,说白了就是找两个品种之间的「价差规律」。但有个前提——你的数据得干净。我见过太多人,模型跑得飞起,结果一查,数据里藏着几个空值,策略直接崩了。今天咱们就把数据获取与清洗这关彻底打通。
1.1 期货行情数据API调用
我个人习惯用 tushare 和 akshare 这两个库。前者稳定,后者免费数据源多。你想想看,如果连数据都拿不到,后面全是空谈。
核心要点:API调用要处理限流、重连、超时。我曾经在实盘前夜,因为没加重试机制,数据断了一小时,差点出事故。
import tushare as ts
import pandas as pd
from datetime import datetime
# 设置token(建议用环境变量)
ts.set_token('your_token_here')
pro = ts.pro_api()
# 获取螺纹钢连续合约日线数据
df = pro.fut_daily(
ts_code='RB9999.XSGE',
start_date='20200101',
end_date='20231231',
fields='trade_date,open,high,low,close,vol,amount'
)
print(df.head())
小技巧:连续合约代码通常带「9999」后缀,代表主力连续。但要注意换月跳空,后面我们会处理。
1.2 多合约数据对齐
做套利至少需要两个品种。比如螺纹钢和热卷,它们的交易日期可能不完全一致。怎么办?对齐!
我记得有一次,螺纹钢和铁矿石的数据差了三天,我愣是没发现,回测曲线漂亮得不行,实盘直接打脸。从那以后,我每次都对数据做严格对齐。
# 假设df_rb是螺纹钢,df_hc是热卷
df_rb = df_rb.set_index('trade_date')
df_hc = df_hc.set_index('trade_date')
# 内连接对齐(只保留共同交易日)
df_aligned = pd.merge(
df_rb[['close']],
df_hc[['close']],
left_index=True,
right_index=True,
how='inner',
suffixes=('_rb', '_hc')
)
print(f"对齐后数据量:{len(df_aligned)} 行")
注意:千万别用「outer」连接!否则会出现大量NaN,后面处理起来很麻烦。除非你明确知道自己在做什么。
1.3 缺失值处理
数据拿到手,第一件事就是检查缺失值。为什么?因为很多API在节假日、极端行情时会返回空值。
我常用的方法有三种:
- 向前填充(ffill):用上一个交易日的数据填充。适合非连续缺失。
- 线性插值:适合少量连续缺失,比如缺1-2天。
- 直接删除:如果缺失超过5%,我建议直接删掉这段数据。
# 检查缺失值
print(df_aligned.isnull().sum())
# 向前填充
df_aligned.fillna(method='ffill', inplace=True)
# 或者线性插值
df_aligned.interpolate(method='linear', inplace=True)
# 删除剩余缺失
df_aligned.dropna(inplace=True)
避坑指南:我曾经用「bfill」向后填充,结果把未来数据填到了过去,回测严重过拟合。记住:永远不要用未来数据填充过去!
1.4 异常值检测与处理
异常值,说白了就是那些「离谱」的数据。比如螺纹钢一天涨了20%,这明显是数据错误。怎么检测?
我常用两种方法:
- 3σ原则:超过均值±3倍标准差的数据,视为异常。
- IQR方法:超过Q1-1.5*IQR或Q3+1.5*IQR的数据,视为异常。
# 计算收益率
df_aligned['return_rb'] = df_aligned['close_rb'].pct_change()
# 3σ检测
mean = df_aligned['return_rb'].mean()
std = df_aligned['return_rb'].std()
outliers = df_aligned[
(df_aligned['return_rb'] > mean + 3*std) |
(df_aligned['return_rb'] < mean - 3*std)
]
print(f"检测到异常值:{len(outliers)} 个")
# 处理:用前后均值替换
df_aligned.loc[outliers.index, 'close_rb'] = (
df_aligned['close_rb'].shift(1) + df_aligned['close_rb'].shift(-1)
) / 2
警告:不要直接删除异常值!尤其是连续合约,换月时的跳空是正常现象,不是异常。我建议先可视化看看,再决定怎么处理。
1.5 数据重采样
有时候我们需要把日线数据转成周线或月线。比如做长周期套利,日线噪音太大。重采样就是干这个的。
嗯,这里要注意:重采样时,开盘价取第一个,收盘价取最后一个,最高价取最大值,最低价取最小值,成交量求和。
# 日线转周线
df_weekly = df_aligned.resample('W').agg({
'close_rb': 'last',
'close_hc': 'last',
'vol_rb': 'sum',
'vol_hc': 'sum'
})
# 日线转月线
df_monthly = df_aligned.resample('M').agg({
'close_rb': 'last',
'close_hc': 'last',
'vol_rb': 'sum',
'vol_hc': 'sum'
})
print(df_weekly.head())
小技巧:如果你做的是高频套利,可以重采样到15分钟或1小时级别。但要注意,期货夜盘数据也要包含进去,别漏了。
知识体系总览
下面这张图,是我做数据清洗时的完整流程。你可以照着这个顺序来,基本不会出错。
数据清洗这件事,看起来枯燥,但它是整个套利策略的根基。我见过太多人在这上面翻车。记住一句话:垃圾进,垃圾出。数据不干净,模型再牛也没用。
核心总结:
- API调用要稳定可靠,做好异常处理
- 多合约对齐用内连接,避免NaN泛滥
- 缺失值优先向前填充,慎用未来数据
- 异常值先可视化,再决定处理方式
- 重采样根据策略周期灵活选择