数据获取与清洗实战:用tushare/akshare搞定期货日线数据
做量化交易,数据就是你的弹药库。弹药不行,再好的策略也白搭。今天咱们就聊聊怎么把期货日线数据搞到手,再把它收拾得干干净净。
我个人习惯把数据获取和清洗看作一个整体流程。你想想看,从API拿到的数据就像刚从菜市场买回来的菜——带着泥、带着水,甚至还有烂叶子。不处理干净,你敢下锅吗?
核心流程:数据获取 → 缺失值处理 → 异常值检测 → 数据对齐 → 重采样 → 持久化存储
2.1 用tushare/akshare获取期货日线数据
先说tushare。这个库我用了好几年,数据质量在国内算第一梯队。不过要注意,tushare pro版本需要token,你得去官网注册一下。
import tushare as ts
import pandas as pd
# 初始化tushare
ts.set_token('你的token')
pro = ts.pro_api()
# 获取螺纹钢主力连续合约日线数据
df_rb = pro.fut_daily(
ts_code='RB9999.XSGE', # 螺纹钢主力连续
start_date='20200101',
end_date='20231231',
fields='trade_date,open,high,low,close,vol,amount'
)
print(df_rb.head())
再说akshare。这个库更接地气,完全免费,接口也丰富。我最近的项目里经常用它做快速验证。
import akshare as ak
# 获取沪铜主力合约日线数据
df_cu = ak.futures_main_sina(
symbol='CU0', # 沪铜主力连续
start_date='20200101',
end_date='20231231'
)
print(df_cu.head())
这里有个坑——主力连续合约存在换月跳空。我记得第一次做回测时,没处理这个跳空,结果策略收益曲线看着漂亮,实盘一跑就崩。后来才明白,换月那天价格会跳,必须做复权处理。
我的经验:建议同时获取主力连续合约和具体合约月份的数据。主力连续用于回测,具体合约用于实盘信号生成。
2.2 处理缺失值与异常值
数据拿到手,第一件事就是检查缺失值。期货市场有节假日,非交易日自然没数据,这不算缺失。但交易日里出现NaN,那就得小心了。
# 检查缺失值
print(df_rb.isnull().sum())
# 处理缺失值:前向填充
df_rb.fillna(method='ffill', inplace=True)
# 或者用插值法
df_rb.interpolate(method='linear', inplace=True)
异常值更隐蔽。我曾在一次回测中发现某天螺纹钢涨幅超过10%,当时还以为是行情爆发,后来一查——数据录入时多打了个0。这种错误会严重扭曲你的模型。
# 用Z-score检测异常值
from scipy import stats
z_scores = stats.zscore(df_rb['close'])
abs_z_scores = np.abs(z_scores)
filtered_entries = (abs_z_scores < 3) # 3倍标准差
# 标记异常值
df_rb['is_outlier'] = ~filtered_entries
# 处理异常值:用前后均值替换
df_rb.loc[df_rb['is_outlier'], 'close'] = (
df_rb['close'].shift(1) + df_rb['close'].shift(-1)
) / 2
注意:不要机械地删除异常值。有些异常是真实的极端行情(比如2020年原油暴跌),删除它们会丢失重要信息。建议先标记,再根据业务逻辑判断。
2.3 数据对齐与重采样
做多品种策略时,数据对齐是个大问题。不同合约的交易时间、节假日安排都不一样。你想想看,螺纹钢和沪铜的交易日能完全重合吗?不能。
# 多品种数据对齐
df_rb = df_rb.set_index('trade_date')
df_cu = df_cu.set_index('trade_date')
# 合并并前向填充
df_combined = pd.concat([df_rb['close'], df_cu['close']], axis=1, keys=['RB', 'CU'])
df_combined = df_combined.fillna(method='ffill').dropna()
print(df_combined.head())
重采样也很常用。比如你想把日线数据转成周线,或者把5分钟数据转成15分钟。我习惯用resample方法,简单高效。
# 日线转周线
df_weekly = df_rb.resample('W').agg({
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'vol': 'sum'
})
print(df_weekly.head())
这里有个细节——期货的夜盘交易。国内期货有夜盘,交易时间跨天。如果你按自然日对齐,夜盘数据会被归到第二天,这会导致数据错位。我曾经在这个问题上栽过跟头,后来改用自定义交易日历才解决。
2.4 数据持久化存储
数据清洗完了,得存起来。我一般用两种格式:CSV和HDF5。
CSV简单直观,适合小规模数据。但如果你有几十个品种、几年数据,CSV的读写速度会让你抓狂。
# 存储为CSV
df_rb.to_csv('rb_daily.csv', index=False)
# 读取CSV
df_rb = pd.read_csv('rb_daily.csv', parse_dates=['trade_date'])
HDF5是我个人比较推荐的格式。它支持压缩、支持按列读取、速度还快。特别是做机器学习时,特征工程后的数据量很大,HDF5能省不少空间和时间。
# 存储为HDF5
df_rb.to_hdf('futures_data.h5', key='rb_daily', mode='w', complevel=9)
# 读取HDF5
df_rb = pd.read_hdf('futures_data.h5', key='rb_daily')
# 多品种存储
with pd.HDFStore('futures_data.h5') as store:
store.put('rb_daily', df_rb)
store.put('cu_daily', df_cu)
store.put('combined', df_combined)
我的建议:开发阶段用CSV,方便查看和调试。生产环境用HDF5,性能和存储都更优。
2.5 本章知识体系
下面这张图概括了数据获取与清洗的完整流程,我画了很久才理清楚各环节的关系。
这张图展示了从数据获取到最终存储的完整链路。你会发现,这不是一条直线走到底的过程——中间需要反复迭代。比如处理完缺失值后,你可能发现异常值检测的结果不理想,得回头重新调整参数。
好了,数据获取和清洗这块就聊到这儿。记住一句话:数据质量决定了模型的上限。花80%的时间在数据上,剩下的20%做模型,这个比例我试过,效果最好。