第三章:数据获取与清洗——基差交易的第一道坎
说实话,很多做量化的人,都栽在数据上。模型再漂亮,策略再精巧,数据是脏的,结果就是垃圾。我见过太多人花三个月写策略,结果数据对齐没做好,回测曲线漂亮得像假的一样——嗯,其实就是假的。
这一章,咱们就聊聊怎么拿到靠谱的期货和现货数据,怎么把它们对齐,怎么处理那些烦人的缺失值。说白了,就是给基差交易模型打好地基。
3.1 数据源的选择:tushare vs akshare
我个人习惯用两个库:tushare 和 akshare。它们各有千秋,我一般搭配着用。
| 特性 | tushare | akshare |
|---|---|---|
| 数据覆盖 | 期货、股票、基金等 | 期货、现货、宏观数据等 |
| 数据质量 | 较高,需积分 | 中等,免费 |
| 接口稳定性 | 稳定 | 偶尔变动 |
| 适用场景 | 主力合约、历史数据 | 现货指数、实时行情 |
3.2 获取期货行情数据
先拿期货数据。我一般用 tushare 的 fut_daily 接口,它能拿到所有合约的日线数据。但这里有个坑——主力合约怎么处理?
你想想看,基差交易的核心是近月合约和远月合约的价差。如果直接用所有合约,数据量太大,而且流动性差的合约会引入噪音。我的做法是:只取主力合约和次主力合约。
import tushare as ts
import pandas as pd
# 设置token
ts.set_token('your_token_here')
pro = ts.pro_api()
# 获取螺纹钢期货所有合约
df = pro.fut_daily(
ts_code='RB.SHF', # 螺纹钢
start_date='20230101',
end_date='20231231',
fields='ts_code,trade_date,close,volume,oi'
)
# 筛选主力合约(按持仓量排序)
df['rank'] = df.groupby('trade_date')['oi'].rank(ascending=False)
main_contract = df[df['rank'] == 1] # 主力合约
sub_contract = df[df['rank'] == 2] # 次主力合约
print(f"主力合约数量: {len(main_contract)}")
print(f"次主力合约数量: {len(sub_contract)}")
3.3 获取现货行情数据
现货数据,我推荐用 akshare。它有个好处——能直接拿到商品指数的现货价格,比如南华商品指数、螺纹钢现货价格等。
import akshare as ak
# 获取螺纹钢现货价格(上海地区)
spot_df = ak.spot_price(
symbol="螺纹钢",
region="上海",
start_date="20230101",
end_date="20231231"
)
# 获取南华商品指数
index_df = ak.index_daily(
symbol="NHCI", # 南华商品指数
start_date="20230101",
end_date="20231231"
)
print(spot_df.head())
print(index_df.head())
这里有个细节:现货数据通常是日频,而且节假日不更新。但期货市场有夜盘,所以日期上会有差异。嗯,这就是数据对齐的难点所在。
3.4 数据对齐:时间戳的精确匹配
数据对齐,说白了就是让期货和现货的数据在同一个时间点上。我遇到过最头疼的情况:期货有夜盘,现货没有。结果就是,期货的日期比现货多一天。
举个例子:螺纹钢期货夜盘是晚上9点到11点,交易的是第二天的日期。但现货价格还是当天的。如果你直接按日期合并,就会错位。
我的解决方案是:统一使用自然日对齐,把夜盘数据归到当天的日期上。
# 假设期货数据有夜盘,日期需要调整
futures_df['trade_date'] = pd.to_datetime(futures_df['trade_date'])
spot_df['date'] = pd.to_datetime(spot_df['date'])
# 对齐:只取两个数据集都有的日期
common_dates = set(futures_df['trade_date']).intersection(set(spot_df['date']))
futures_aligned = futures_df[futures_df['trade_date'].isin(common_dates)]
spot_aligned = spot_df[spot_df['date'].isin(common_dates)]
# 按日期排序
futures_aligned = futures_aligned.sort_values('trade_date')
spot_aligned = spot_aligned.sort_values('date')
# 合并
merged_df = pd.merge(
futures_aligned,
spot_aligned,
left_on='trade_date',
right_on='date',
how='inner'
)
print(f"对齐后数据量: {len(merged_df)}")
3.5 缺失值处理:别轻易填充
缺失值,是数据清洗里最烦人的部分。我见过有人直接用前值填充,结果回测时基差突然变成0,策略直接爆仓。
我的处理原则是:
- 连续缺失不超过3天:用线性插值
- 连续缺失超过3天:直接删除该时间段
- 节假日缺失:保留NaN,不填充
# 检查缺失值
print(merged_df.isnull().sum())
# 线性插值(连续缺失不超过3天)
merged_df['close_futures'] = merged_df['close_futures'].interpolate(
method='linear',
limit=3
)
# 删除连续缺失超过3天的行
merged_df = merged_df.dropna(subset=['close_futures', 'close_spot'])
print(f"处理后数据量: {len(merged_df)}")
3.6 知识体系总览
这一章的内容,我画了个图,方便你理解整个流程:
3.7 实战中的避坑指南
最后,分享几个我实战中总结的经验:
- 数据源不要只用一家:我习惯用 tushare 拿期货,akshare 拿现货,交叉验证。如果两个数据源对不上,那肯定有一个有问题。
- 节假日处理要小心:期货有夜盘,现货没有。节假日前后,数据容易错位。我一般会手动检查春节、国庆前后的数据。
- 保存原始数据:清洗后的数据不要覆盖原始数据。我习惯在文件名上加
_raw和_cleaned后缀,方便回溯。
好了,数据获取与清洗就聊到这儿。下一章,咱们会深入基差的计算与特征工程。记住:数据是量化交易的血液,血液干净了,策略才能跑得稳。