3、数据获取与清洗:多源数据接口、数据对齐、异常值处理、复权与合约换月
做跨品种套利,最怕什么?
不是策略亏钱,而是数据错了,你还不知道。
我见过太多人,策略回测曲线漂亮得像艺术品,一上实盘就崩。查到最后,发现是数据对齐差了1个tick,或者复权方式没统一。嗯,这种坑,我踩过不止一次。
3.1 多源数据接口:Tushare vs JoinQuant
数据源的选择,说白了就是「稳定性」和「丰富度」的权衡。
我个人习惯用Tushare做历史数据回测,用JoinQuant做实时数据验证。为什么?
- Tushare:数据全,期货、期权、ETF都有。但免费版有频率限制,每分钟最多200次请求。我曾在一次批量下载中,被限流了整整2小时。
- JoinQuant:实时性好,接口稳定。但历史数据深度不如Tushare,尤其是跨品种的价差数据,需要自己算。
我的建议:不要只依赖一个数据源。用Tushare拉历史,用JoinQuant做实时校验。两个源的数据交叉验证,能发现很多隐藏问题。
代码示例:Tushare获取期货日线数据
import tushare as ts
import pandas as pd
# 初始化接口
pro = ts.pro_api('your_token_here')
# 获取螺纹钢主力连续合约数据
df = pro.fut_daily(
ts_code='RB.SHF',
start_date='20230101',
end_date='20231231',
fields='trade_date,open,high,low,close,vol'
)
print(df.head())
3.2 数据对齐:时间戳的魔鬼细节
跨品种套利,最核心的一步是什么?
数据对齐。
你想想看,螺纹钢和热卷,虽然都是黑色系,但它们的交易时间、最小变动价位、甚至节假日都可能不同。如果不做对齐,你算出来的价差就是错的。
我在项目中遇到过最典型的问题:
- 两个品种的日线数据,一个用自然日,一个用交易日
- 一个品种有夜盘数据,另一个没有
- 时间戳格式不统一,一个用'2023-01-01',另一个用'20230101'
对齐的标准做法:
- 统一时间基准:全部转为UTC时间戳,或者统一用交易所时间
- 按最小粒度对齐:如果是日线,按交易日对齐;如果是分钟线,按分钟对齐
- 缺失值处理:对齐后出现的NaN,用前向填充(ffill)或插值
核心原则:对齐后的数据,必须保证每个时间点都有值。哪怕是用前一天的收盘价填充,也比空着强。
代码示例:两个品种的日线对齐
# 假设df_rb是螺纹钢数据,df_hc是热卷数据
# 按交易日对齐
df_aligned = pd.merge(
df_rb[['trade_date', 'close']],
df_hc[['trade_date', 'close']],
on='trade_date',
how='outer',
suffixes=('_rb', '_hc')
)
# 前向填充缺失值
df_aligned = df_aligned.fillna(method='ffill')
# 计算价差
df_aligned['spread'] = df_aligned['close_rb'] - df_aligned['close_hc']
3.3 异常值处理:别让一个错误数据毁了整个策略
数据清洗里,异常值是最头疼的。
我曾经在回测一个螺纹钢-热卷套利策略时,发现价差突然跳了200个点。一开始以为是行情异动,后来查了半天,发现是某一天的数据里,螺纹钢的收盘价少了一个小数点。
嗯,这种错误,你回测时根本看不出来,但实盘会直接爆仓。
异常值检测的常用方法:
| 方法 | 适用场景 | 缺点 |
|---|---|---|
| 3σ原则 | 正态分布的数据 | 对极端值敏感 |
| IQR(四分位距) | 非正态分布 | 可能漏掉部分异常 |
| 滚动窗口法 | 时间序列数据 | 窗口大小需要调参 |
注意:不要直接删除异常值。先标记,再人工确认。有些异常值其实是真实的极端行情,比如2020年原油暴跌。
代码示例:用IQR检测异常值
def detect_outliers_iqr(series):
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
return (series < lower_bound) | (series > upper_bound)
# 检测价差中的异常值
df_aligned['outlier'] = detect_outliers_iqr(df_aligned['spread'])
3.4 复权与合约换月:连续合约的陷阱
做期货套利,最绕不开的就是合约换月。
你想想看,主力合约每个月都会变。如果你直接用不同月份的合约数据拼接,价差里会夹杂着换月带来的跳空。这种跳空,不是市场行为,是数据拼接的假象。
我建议的做法:
- 使用主力连续合约:Tushare和JoinQuant都提供主力连续合约数据,但要注意它们的换月规则
- 复权处理:用前复权或后复权,消除换月跳空
- 自定义换月规则:如果你对换月有特殊要求,比如只在交割月前第5个交易日换月,那就自己写逻辑
避坑指南:我曾经用Tushare的主力连续合约做回测,结果发现换月那天价差突然变了。后来查代码,发现Tushare的换月规则是「成交量最大原则」,而我自己的策略用的是「持仓量最大原则」。两个规则差了3天,这3天里价差完全不一样。
代码示例:自定义换月逻辑
def roll_contract(df, roll_day=5):
"""
自定义换月逻辑:在交割月前第5个交易日换月
"""
df = df.copy()
df['roll_date'] = df['delivery_date'] - pd.Timedelta(days=roll_day)
# 在换月日,用新合约的价格替换旧合约
df.loc[df['trade_date'] >= df['roll_date'], 'close'] = df['next_close']
return df
3.5 本章知识体系
下面这张图,是我自己总结的数据获取与清洗的完整流程。每次做新策略,我都会按这个流程走一遍。
数据清洗这件事,说白了就是「慢工出细活」。你花在数据上的时间越多,后面策略出问题的概率就越小。我个人的经验是,数据清洗至少占整个策略开发时间的40%。别嫌多,值得。
最后一句:数据是策略的基石。基石不稳,楼盖得再高也是危楼。