3. 数据获取与清洗:多合约数据源、主力合约拼接、异常值处理
做跨品种价差分析,最头疼的其实不是策略本身。
是数据。
我见过太多人,策略逻辑写得漂漂亮亮,一回测就崩。为什么?数据源没对齐,主力合约跳空,或者某个交易日突然冒出一个天价成交。说白了,数据不干净,后面全是白搭。
这一章,我就把数据获取和清洗的坑,一个一个给你指出来。
3.1 多合约数据源:你从哪里拿数据?
国内期货市场,数据源就那么几个。我个人习惯用这三类:
- 交易所官方数据:最权威,但获取麻烦。适合做深度研究。
- 第三方数据商:比如万得、聚宽、米筐。接口稳定,字段齐全。我团队主力用这个。
- 开源数据包:比如 tushare、akshare。免费,但偶尔断更。适合个人研究。
你可能会问:「哪个最好?」
其实没有标准答案。我建议你至少准备两个数据源。一个做主数据,一个做校验。我在项目中遇到过,某数据商把螺纹钢的夜盘数据漏了整整一周。要不是我交叉验证,回测结果能骗死你。
3.2 主力合约拼接:别让换月吃掉你的利润
期货合约有到期日。你不能一直拿着同一个合约做回测。所以需要「主力合约拼接」。
什么叫主力合约?就是当前持仓量最大、成交量最活跃的那个合约。
拼接方式,我常用两种:
- 连续主力合约:每天取持仓量最大的合约。换月时直接跳转。
- 加权主力合约:按持仓量加权,平滑过渡。适合做价差分析。
嗯,这里要注意。直接跳转会有「跳空缺口」。比如螺纹钢从 RB2401 换到 RB2405,两个合约价格可能差几十点。如果你不做处理,回测里就会出现莫名其妙的盈亏。
我曾经犯过这个错。回测一个螺纹钢和热卷的价差策略,年化收益高得离谱。后来发现,全是主力合约跳空带来的假信号。白高兴一场。
怎么处理?我一般用「后复权」方法:
# 主力合约拼接 + 后复权示例
import pandas as pd
def splice_main_contract(df, method='volume'):
"""
df: 包含所有合约的日线数据,字段:date, symbol, close, volume
method: 'volume' 按持仓量选主力,'oi' 按持仓量
"""
# 选出每日主力合约
main_idx = df.groupby('date')[method].idxmax()
main_df = df.loc[main_idx].copy()
# 后复权处理:以第一个主力合约为基准
main_df['adj_close'] = main_df['close']
cum_shift = 0
for i in range(1, len(main_df)):
if main_df.iloc[i]['symbol'] != main_df.iloc[i-1]['symbol']:
# 换月日,计算价差
shift = main_df.iloc[i-1]['close'] - main_df.iloc[i]['close']
cum_shift += shift
main_df.iloc[i, main_df.columns.get_loc('adj_close')] += cum_shift
return main_df[['date', 'adj_close']]
3.3 异常值处理:那些让你亏钱的「黑天鹅」
数据里总有一些「怪物」。比如某天螺纹钢价格突然跌停,但成交量只有几手。这种数据,你信还是不信?
我的经验是:先怀疑,再验证。
常见的异常值分三类:
| 异常类型 | 典型表现 | 处理方法 |
|---|---|---|
| 价格异常 | 单日涨跌幅超过 10% | 用前一日价格填充,或剔除 |
| 成交量异常 | 成交量突然放大 100 倍 | 检查是否为交割日或数据错误 |
| 缺失值 | 某天没有数据 | 向前填充,或插值 |
你想想看,如果价差数据里混了一个异常点,你的协整检验结果可能完全相反。我见过有人因为一个异常值,把两个本来没有关系的品种硬生生做成了「伪套利对」。
我的清洗流程是这样的:
- 基础过滤:剔除涨跌停日、交割日数据。
- 统计过滤:用 3 倍标准差或 IQR 方法,标记极端值。
- 人工复核:标记出来的异常点,手动查一下当天新闻。如果是真实事件(比如政策突变),保留;如果是数据错误,剔除。
3.4 知识体系总览
下面这张图,是我做数据清洗时的完整流程。你可以把它贴在工位上。
这张图的核心逻辑很简单:数据源决定下限,清洗决定上限。你拿到的原始数据再好,清洗不到位,策略照样翻车。
好了,数据清洗这块,我就讲这么多。记住一句话:花 80% 的时间处理数据,花 20% 的时间写策略。这个比例,是我亏出来的经验。