第四章:数据获取——期货数据的清洗与对齐
做跨品种对冲,数据是地基。地基没打好,策略再漂亮也是空中楼阁。
我个人习惯把数据获取分成三步:拿到原始数据、清洗对齐、处理异常。每一步都有坑,我踩过不少,今天一并告诉你。
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 本章知识体系
下面这张图,是我做数据获取时的完整流程。你可以照着这个框架来搭建自己的数据管道。
嗯,数据这块就讲到这里。记住一句话:数据质量决定策略上限。你花80%的时间在数据上,都不为过。