第四章:数据获取——期货数据的清洗与对齐

做跨品种对冲,数据是地基。地基没打好,策略再漂亮也是空中楼阁。

我个人习惯把数据获取分成三步:拿到原始数据、清洗对齐、处理异常。每一步都有坑,我踩过不少,今天一并告诉你。

4.1 用Tushare和AkShare拿数据

国内期货数据源,我主要用两个:Tushare和AkShare。Tushare老牌,数据全但需要积分;AkShare免费,更新快,适合快速验证。

核心原则:两个源都拿一份,交叉验证。我遇到过Tushare某天数据缺失,AkShare却正常的情况。

先看Tushare怎么拿期货日线数据:

import tushare as ts
import pandas as pd

# 设置token(你需要在tushare官网注册获取)
ts.set_token('你的token')
pro = ts.pro_api()

# 获取螺纹钢主力连续合约
df_ts = pro.fut_daily(
    ts_code='RB9999.XSGE',  # 9999代表主力连续
    start_date='20200101',
    end_date='20231231',
    fields='trade_date,open,high,low,close,vol'
)
df_ts['trade_date'] = pd.to_datetime(df_ts['trade_date'])
df_ts.set_index('trade_date', inplace=True)
df_ts.sort_index(inplace=True)
print(df_ts.head())

再看AkShare的写法,更简洁:

import akshare as ak

# 获取螺纹钢主力连续
df_ak = ak.futures_main_sina(
    symbol='RB0',  # 0代表主力
    start_date='20200101',
    end_date='20231231'
)
df_ak.rename(columns={'日期': 'trade_date', '开盘': 'open', 
                      '最高': 'high', '最低': 'low', 
                      '收盘': 'close', '成交量': 'vol'}, inplace=True)
df_ak['trade_date'] = pd.to_datetime(df_ak['trade_date'])
df_ak.set_index('trade_date', inplace=True)
df_ak.sort_index(inplace=True)
print(df_ak.head())

小技巧:主力连续合约会有换月跳空。我一般用复权处理,或者干脆用指数合约(比如RB8888)。指数合约没有跳空,但无法直接交易。

4.2 数据清洗与对齐——最磨人的一步

数据拿回来,你以为就能用了?太天真了。

不同品种的交易时间不一样。螺纹钢有夜盘,豆粕也有夜盘,但股指期货没有。你想想看,如果拿螺纹钢和沪深300做对冲,时间戳对不上怎么办?

我的做法是:统一用自然日对齐,缺失的日期用NaN填充。

# 假设df_rb是螺纹钢,df_if是沪深300
# 先合并,再填充
combined = pd.DataFrame({
    'RB_close': df_rb['close'],
    'IF_close': df_if['close']
})

# 按日期对齐,缺失值用NaN
combined = combined.reindex(
    pd.date_range(start='2020-01-01', end='2023-12-31', freq='D')
)

# 检查缺失情况
print(combined.isnull().sum())

注意:千万别直接dropna()!有些品种节假日不同,比如国内休市但国外不休。直接删数据会破坏时间序列的连续性。

4.3 处理缺失值与异常值

缺失值怎么补?我分三种情况:

  • 连续缺失不超过3天:用前向填充(ffill)或线性插值
  • 连续缺失超过3天:用同品种其他合约的数据补,或者干脆跳过这段时间
  • 节假日缺失:保持NaN,不填充
# 前向填充(适合短期缺失)
combined['RB_close'].fillna(method='ffill', inplace=True)

# 线性插值(适合价格缓慢变化的情况)
combined['IF_close'].interpolate(method='linear', inplace=True)

# 检查异常值(3倍标准差法)
def detect_outliers(series, n_std=3):
    mean = series.mean()
    std = series.std()
    outliers = (series < mean - n_std * std) | (series > mean + n_std * std)
    return outliers

rb_outliers = detect_outliers(combined['RB_close'])
print(f"螺纹钢异常值数量:{rb_outliers.sum()}")

异常值处理,我曾经吃过亏。有一次螺纹钢数据里出现了一个涨停价,我没处理,结果协整检验直接报错。后来我养成了习惯:异常值先标记,再手动确认,不盲目删除。

4.4 数据频率选择——日线还是分钟线?

这个问题没有标准答案。我个人的经验是:

策略类型 推荐频率 原因
统计套利(均值回归) 日线 信号稳定,交易成本低
趋势跟踪 1小时或4小时 捕捉中期趋势,避免噪音
高频做市 1分钟或tick 需要快速反应价差变化

刚开始做跨品种对冲时,我总想用高频数据,觉得信号多。结果呢?手续费吃掉大部分利润,还经常被假突破骗。后来我回归日线,反而稳定了。

避坑指南:如果你用分钟线,一定要检查数据是否连续。有些数据源在夜盘收盘后会有空档,直接拼接会导致时间戳错乱。我曾经因为这个原因,回测结果看起来很好,实盘一跑就亏。

4.5 本章知识体系

下面这张图,是我做数据获取时的完整流程。你可以照着这个框架来搭建自己的数据管道。

数据获取与清洗流程 数据源 Tushare(积分制) AkShare(免费) 交叉验证 & 合并 数据清洗:对齐、填充、去异常 输出:干净的对齐数据

嗯,数据这块就讲到这里。记住一句话:数据质量决定策略上限。你花80%的时间在数据上,都不为过。

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