第三章 数据获取与处理:打好套利分析的地基

做跨期套利,数据就是你的眼睛。眼睛要是花了,再好的策略也白搭。

这一章,咱们聊聊怎么把期货数据搞到手,怎么处理那些烦人的主力合约切换问题,以及如何把价差序列算得漂漂亮亮。我做了这么多年量化,见过太多人策略写得天花乱坠,结果数据源就有问题——嗯,这种坑我踩过不少。

3.1 数据源的选择:Tushare vs AKShare

国内做期货量化,主流的数据源就两个:Tushare 和 AKShare。我个人习惯两个都备着,一个挂了用另一个。

对比项 Tushare Pro AKShare
数据质量 高,经过校验 中高,部分数据来自新浪/东财
获取方式 需注册+token 免费,pip安装即用
期货数据 日线、分钟线、主力合约 日线、分钟线、实时行情
稳定性 稳定,有频率限制 偶尔接口变动
适合场景 正式策略、回测 快速验证、实时监控
我的建议: 回测用 Tushare,实盘监控用 AKShare。为什么?Tushare 数据经过清洗,回测结果更可信;AKShare 实时性好,适合做盘中的价差计算。

3.2 主力合约连续数据拼接

做跨期套利,最头疼的就是主力合约切换。你想想看,螺纹钢的主力合约每几个月就换一次,如果不做拼接,你的价差序列就是断的。

我常用的方法有两种:

  1. 持仓量法: 每天取持仓量最大的合约作为主力
  2. 成交量法: 每天取成交量最大的合约

实战中,我偏向用持仓量法。为什么?因为持仓量更能反映资金的实际流向。成交量有时候会被短线交易放大,但持仓量骗不了人。

核心逻辑: 主力合约切换时,需要做「价差调整」。比如 RB1901 切换到 RB1905,价差序列不能直接拼接,要计算两个合约之间的价差基准差。

下面是我常用的主力合约拼接代码:

import akshare as ak
import pandas as pd
import numpy as np

def get_main_contract(symbol, start_date, end_date):
    """
    获取主力合约连续数据
    使用持仓量法判断主力合约
    """
    # 获取所有合约列表
    all_contracts = ak.futures_contract_detail(symbol=symbol)
    
    # 获取日线数据
    df_list = []
    for contract in all_contracts['contract']:
        try:
            df = ak.futures_daily(symbol=contract, 
                                  start_date=start_date, 
                                  end_date=end_date)
            df['contract'] = contract
            df_list.append(df)
        except:
            continue
    
    # 合并所有合约数据
    all_data = pd.concat(df_list)
    
    # 按日期分组,取持仓量最大的合约
    main_idx = all_data.groupby('date')['hold'].idxmax()
    main_data = all_data.loc[main_idx]
    
    return main_data.sort_index()

# 使用示例
rb_main = get_main_contract('RB', '2023-01-01', '2023-12-31')
print(rb_main.head())
避坑指南: 我曾经遇到过一个问题——节假日前后,主力合约的持仓量会异常波动。比如春节前,很多资金会平仓,导致持仓量骤降。这时候用持仓量法可能会选错主力。我的处理方式是:加一个「连续3天持仓量最大」的过滤条件,避免单日异常。

3.3 价差序列计算

有了主力合约数据,接下来就是算价差。跨期套利的价差,说白了就是近月合约价格减去远月合约价格。

但这里有个细节:价差计算的时间对齐。近月和远月的交易时间不完全一致,比如夜盘时段,近月可能先开盘。我习惯的做法是:只取两个合约都有交易的时刻。

def calculate_spread(near_contract, far_contract):
    """
    计算跨期价差
    near_contract: 近月合约数据
    far_contract: 远月合约数据
    """
    # 合并两个合约的数据
    merged = pd.merge(near_contract, far_contract, 
                      on='date', suffixes=('_near', '_far'))
    
    # 计算价差
    merged['spread'] = merged['close_near'] - merged['close_far']
    
    # 计算价差的移动平均和标准差
    merged['spread_ma'] = merged['spread'].rolling(window=20).mean()
    merged['spread_std'] = merged['spread'].rolling(window=20).std()
    
    # 计算Z-score
    merged['z_score'] = (merged['spread'] - merged['spread_ma']) / merged['spread_std']
    
    return merged

# 使用示例
rb_near = get_main_contract('RB', '2023-01-01', '2023-06-30')
rb_far = get_main_contract('RB', '2023-07-01', '2023-12-31')
spread_data = calculate_spread(rb_near, rb_far)
print(spread_data[['date', 'spread', 'z_score']].head())

3.4 数据清洗与异常值处理

数据拿到手,别急着算策略。先做清洗。我见过有人直接用原始数据跑回测,结果收益率高得离谱——后来发现是数据里有几个涨停板的异常值。

常用的清洗方法:

  • 缺失值处理: 期货有节假日,缺失是正常的。用前向填充(ffill)或插值法
  • 异常值检测: 用3倍标准差法或IQR法
  • 跳空处理: 主力合约切换时会有跳空,需要做复权处理
我的经验: 异常值不一定是错误。比如2015年股灾期间,股指期货的价差出现了极端值,但那不是数据错误,是市场真实的恐慌情绪。所以清洗时,我会先标记异常值,再人工判断是否剔除。
def clean_spread_data(df, z_score_threshold=3):
    """
    清洗价差数据
    """
    # 复制数据,避免修改原数据
    cleaned = df.copy()
    
    # 1. 处理缺失值
    cleaned['spread'] = cleaned['spread'].fillna(method='ffill')
    
    # 2. 检测异常值(3倍标准差法)
    mean = cleaned['spread'].mean()
    std = cleaned['spread'].std()
    upper_bound = mean + z_score_threshold * std
    lower_bound = mean - z_score_threshold * std
    
    # 标记异常值
    cleaned['is_outlier'] = (cleaned['spread'] > upper_bound) | \
                            (cleaned['spread'] < lower_bound)
    
    # 3. 处理异常值(用中位数替换)
    median = cleaned['spread'].median()
    cleaned.loc[cleaned['is_outlier'], 'spread'] = median
    
    # 4. 主力合约切换跳空处理
    cleaned['spread_adjusted'] = cleaned['spread'] - \
                                 cleaned['spread'].shift(1)
    cleaned['spread_adjusted'] = cleaned['spread_adjusted'].fillna(0)
    
    return cleaned

# 使用示例
cleaned_data = clean_spread_data(spread_data)
print(f"原始数据量: {len(spread_data)}")
print(f"清洗后数据量: {len(cleaned_data)}")
print(f"异常值数量: {cleaned_data['is_outlier'].sum()}")

3.5 数据处理的完整流程

下面这张图,是我做数据处理时的标准流程。每次拿到新数据,我都会走一遍这个流程。

数据获取与处理流程图 数据获取 主力合约连续数据拼接 价差序列计算 数据清洗与异常值处理 输出清洗后数据 数据源:Tushare / AKShare 主力合约判断:持仓量法 / 成交量法 清洗方法:3σ / IQR / 前向填充

3.6 实战中的注意事项

小技巧: 数据下载后,先做一次「数据完整性检查」。比如检查日期是否连续,有没有缺失的交易日。我习惯用 pandas 的 date_range 生成完整的交易日历,然后和实际数据做对比。
重要提醒: 期货数据有「涨跌停板」限制。当价格触及涨跌停时,当天的价差计算会失真。我的处理方式是:如果近月或远月合约触及涨跌停,当天的价差数据标记为「不可用」,不参与策略计算。

好了,数据获取和处理这部分就聊到这儿。记住一句话:数据质量决定策略上限。花80%的时间在数据处理上,剩下20%的时间写策略,这才是量化交易的正确姿势。

下一章,咱们聊聊价差序列的统计特征分析——怎么判断价差是否具有均值回复性,这可是跨期套利策略的核心。


蓝海数据掘金营 | 专注资料整理