3、价差数据获取:使用 Tushare/AkShare 获取期货行情数据、数据清洗与对齐
做跨期套利,最怕什么?
不是策略失效,而是数据打架。
你想想看,主力合约换月了,你还在用旧数据算价差,那结果能对吗?我早年就吃过这个亏,回测曲线漂亮得像艺术品,实盘一跑直接崩了。后来一查,原来是数据对齐出了问题。
所以这一章,咱们就把数据获取和清洗这件事,彻底讲透。
3.1 数据源的选择:Tushare vs AkShare
国内做期货量化,主流的数据源就两个:Tushare 和 AkShare。我个人习惯是两者搭配着用。
- Tushare:老牌数据平台,数据质量高,但部分接口需要积分。适合做深度回测。
- AkShare:开源免费,更新快,接口丰富。适合做实时数据监控。
说白了,Tushare 是正规军,AkShare 是游击队。正规军稳,游击队快。
3.2 安装与初始化
先装包,再初始化。这一步没什么难度,但容易踩坑。
# 安装 Tushare
pip install tushare
# 安装 AkShare
pip install akshare
# Tushare 需要 token,去官网注册获取
import tushare as ts
ts.set_token('你的token')
pro = ts.pro_api()
# AkShare 直接调用即可
import akshare as ak
3.3 获取期货行情数据
以螺纹钢为例,咱们获取主力连续合约的数据。
3.3.1 使用 Tushare 获取
# 获取螺纹钢主力连续合约日线数据
df_ts = pro.fut_daily(
ts_code='RB9999.XSGE', # 9999 表示主力连续
start_date='20230101',
end_date='20231231',
fields='trade_date,open,high,low,close,vol'
)
print(df_ts.head())
这里有个坑:RB9999 是主力连续合约,但它是拼接出来的。换月的时候会有跳空。我一般会同时获取具体合约的数据,自己手动拼接,这样更可控。
3.3.2 使用 AkShare 获取
# 获取螺纹钢期货实时行情
df_ak = ak.futures_zh_minute_sina(
symbol='RB2401', # 具体合约代码
period='1' # 1分钟线
)
print(df_ak.head())
AkShare 的接口返回的是 DataFrame,字段名是中文。嗯,这里要注意,中文列名在后续处理中可能会带来麻烦,建议统一重命名。
3.4 数据清洗:别让脏数据毁了你的策略
数据拿到手,第一件事不是算价差,而是清洗。
我曾经遇到过某天的数据突然多了一个零,收盘价从 3800 变成了 38000。你想想看,如果我没清洗,直接拿去算价差,那天的信号得有多离谱。
3.4.1 缺失值处理
# 检查缺失值
print(df.isnull().sum())
# 处理方式:向前填充
df.fillna(method='ffill', inplace=True)
期货数据偶尔会有缺失,尤其是节假日前后。我个人习惯用向前填充,因为期货价格是连续的,用前一天的收盘价填充当天的开盘价,逻辑上说得通。
3.4.2 异常值检测
# 用 Z-score 检测异常值
from scipy import stats
z_scores = np.abs(stats.zscore(df['close']))
df_clean = df[(z_scores < 3)]
Z-score 大于 3 的,基本可以判定为异常。但要注意,极端行情下(比如涨停跌停),Z-score 也会很大。这时候需要结合业务逻辑判断。
3.5 数据对齐:跨期套利的关键
跨期套利,本质上是两个合约的价差交易。所以两个合约的数据必须严格对齐。
什么叫对齐?就是同一天、同一分钟的数据,必须一一对应。
3.5.1 时间对齐
# 假设我们有近月合约和远月合约的数据
df_near = pd.DataFrame({
'date': ['2023-01-01', '2023-01-02', '2023-01-03'],
'close_near': [3800, 3810, 3820]
})
df_far = pd.DataFrame({
'date': ['2023-01-01', '2023-01-02', '2023-01-04'],
'close_far': [3850, 3860, 3870]
})
# 合并对齐
df_aligned = pd.merge(df_near, df_far, on='date', how='inner')
print(df_aligned)
这里用 inner 连接,只保留两个合约都有数据的日期。为什么不用 outer?因为如果某一天只有一个合约有数据,价差算出来也是错的。
3.5.2 换月处理
主力合约会换月。比如螺纹钢,每个月都会换一次主力。换月的时候,价差会出现跳变。
我的做法是:
- 提前一周开始监控持仓量和成交量。
- 当新合约的成交量超过旧合约时,切换数据源。
- 切换时,用旧合约的最后一天数据作为基准,调整新合约的价格。
# 换月调整示例
# 假设旧合约最后一天收盘价 3800,新合约当天收盘价 3850
# 价差调整量 = 3850 - 3800 = 50
# 新合约所有数据减去 50,实现无缝拼接
adjustment = 50
df_new_contract['close_adjusted'] = df_new_contract['close'] - adjustment
3.6 知识体系图:数据获取与清洗全流程
下面这张图,是我自己总结的数据处理流程。每次做新策略,我都会照着这个流程走一遍。
3.7 实战代码:完整的数据获取与清洗流程
最后,给出一段完整的代码。你可以直接拿去用,改一下合约代码和日期就行。
import pandas as pd
import numpy as np
import tushare as ts
from scipy import stats
# 初始化
ts.set_token('你的token')
pro = ts.pro_api()
def get_and_clean_data(ts_code, start_date, end_date):
"""
获取并清洗期货数据
"""
# 获取数据
df = pro.fut_daily(
ts_code=ts_code,
start_date=start_date,
end_date=end_date,
fields='trade_date,open,high,low,close,vol'
)
# 排序
df.sort_values('trade_date', inplace=True)
# 清洗缺失值
df.fillna(method='ffill', inplace=True)
# 清洗异常值
z_scores = np.abs(stats.zscore(df['close']))
df = df[z_scores < 3]
return df
# 获取近月和远月合约数据
df_near = get_and_clean_data('RB2401.SHFE', '20230101', '20231231')
df_far = get_and_clean_data('RB2405.SHFE', '20230101', '20231231')
# 数据对齐
df_aligned = pd.merge(
df_near[['trade_date', 'close']].rename(columns={'close': 'close_near'}),
df_far[['trade_date', 'close']].rename(columns={'close': 'close_far'}),
on='trade_date',
how='inner'
)
# 计算价差
df_aligned['spread'] = df_aligned['close_far'] - df_aligned['close_near']
print(df_aligned.head())
数据获取和清洗,是跨期套利的基础。基础不牢,地动山摇。别嫌麻烦,把这一步做扎实了,后面的策略开发才能事半功倍。