第1章:数据获取与清洗——期货统计套利的基石

做统计套利,说白了就是找两个品种之间的「价差规律」。但有个前提——你的数据得干净。我见过太多人,模型跑得飞起,结果一查,数据里藏着几个空值,策略直接崩了。今天咱们就把数据获取与清洗这关彻底打通。

1.1 期货行情数据API调用

我个人习惯用 tushareakshare 这两个库。前者稳定,后者免费数据源多。你想想看,如果连数据都拿不到,后面全是空谈。

核心要点: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%,这明显是数据错误。怎么检测?

我常用两种方法:

  1. 3σ原则:超过均值±3倍标准差的数据,视为异常。
  2. 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小时级别。但要注意,期货夜盘数据也要包含进去,别漏了。

知识体系总览

下面这张图,是我做数据清洗时的完整流程。你可以照着这个顺序来,基本不会出错。

数据获取与清洗流程 1. API数据获取 2. 多合约对齐 3. 缺失值处理 4. 异常值检测 5. 数据重采样 关键注意事项 • 限流重连机制 • 内连接对齐 • 禁止未来数据填充 • 可视化辅助判断 • 夜盘数据包含 • 换月跳空处理 • 收益率计算检测 • 3σ与IQR方法 • 重采样聚合函数 • 多周期适配

数据清洗这件事,看起来枯燥,但它是整个套利策略的根基。我见过太多人在这上面翻车。记住一句话:垃圾进,垃圾出。数据不干净,模型再牛也没用。

核心总结:

  • API调用要稳定可靠,做好异常处理
  • 多合约对齐用内连接,避免NaN泛滥
  • 缺失值优先向前填充,慎用未来数据
  • 异常值先可视化,再决定处理方式
  • 重采样根据策略周期灵活选择

专注资料整理