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'

对齐的标准做法:

  1. 统一时间基准:全部转为UTC时间戳,或者统一用交易所时间
  2. 按最小粒度对齐:如果是日线,按交易日对齐;如果是分钟线,按分钟对齐
  3. 缺失值处理:对齐后出现的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 本章知识体系

下面这张图,是我自己总结的数据获取与清洗的完整流程。每次做新策略,我都会按这个流程走一遍。

数据获取与清洗流程 多源数据接口 Tushare / JoinQuant 数据对齐 时间戳统一 / 粒度对齐 异常值处理 3σ / IQR / 滚动窗口 复权与合约换月 前复权 / 后复权 / 自定义换月 清洗后的数据 可用于策略回测 数据质量反馈循环 每个环节都需要人工校验,不要完全依赖自动化 数据质量 = 策略生命线

数据清洗这件事,说白了就是「慢工出细活」。你花在数据上的时间越多,后面策略出问题的概率就越小。我个人的经验是,数据清洗至少占整个策略开发时间的40%。别嫌多,值得。

最后一句:数据是策略的基石。基石不稳,楼盖得再高也是危楼。

专注资料整理