3. 数据获取与处理:打好量化交易的地基
做CTA趋势跟踪,说白了就是跟数据打交道。我见过太多人一上来就写策略、跑回测,结果数据都没整明白——那能赚钱才怪呢。今天咱们就聊聊数据这块儿,怎么拿、怎么洗、怎么存。
核心观点:数据质量决定了策略的天花板。数据脏,策略再牛也白搭。
3.1 期货历史数据从哪来?
我个人习惯用以下几个渠道,各有优劣,你根据情况选:
| 数据源 | 优点 | 缺点 |
|---|---|---|
| Wind/聚宽/米筐 | 数据全、接口规范 | 要付费,个人用户门槛高 |
| Tushare/AkShare | 免费、社区活跃 | 稳定性一般,有调用限制 |
| 交易所官网 | 最权威、免费 | 需要自己爬,格式不统一 |
| 期货公司API | 实时数据、延迟低 | 历史数据有限,接口复杂 |
我在项目中遇到过用免费接口跑回测,结果某天数据突然断档,策略信号全乱了。嗯,从那以后我都是至少两个数据源交叉验证。
3.2 数据清洗与对齐——最磨人的一步
拿到原始数据后,别急着用。先看看有没有这些问题:
- 缺失值:某天没交易?或者数据没更新?
- 异常值:价格突然跳空几百点?多半是数据错误。
- 时间戳不对齐:不同合约的交易日历不一样,得统一。
我的小技巧:用pandas的isna()和describe()先扫一遍,心里有个底。我曾经因为没检查,用了一组有10%缺失值的数据跑了三个月回测,结果策略表现好得离谱——后来发现是数据填充方式搞错了。
数据对齐这块,说白了就是让不同合约的数据在同一个时间轴上。举个例子:
import pandas as pd
# 假设你有两个合约的数据
df_1 = pd.read_csv('rb2401.csv', index_col='date', parse_dates=True)
df_5 = pd.read_csv('rb2405.csv', index_col='date', parse_dates=True)
# 对齐到共同的交易日
common_dates = df_1.index.intersection(df_5.index)
df_1_aligned = df_1.loc[common_dates]
df_5_aligned = df_5.loc[common_dates]
# 或者用reindex,自动填充缺失值
all_dates = pd.date_range(start='2023-01-01', end='2023-12-31', freq='B')
df_1_aligned = df_1.reindex(all_dates, method='ffill')
为什么会这样?因为不同合约的上市日期、退市日期都不一样,直接拼在一起肯定出问题。
3.3 复权处理——别让分红影响你的判断
期货虽然没有股票那种现金分红,但有换月和交割的问题。你想想看,主力合约每个月都在变,价格本身就不连续。如果不做复权,回测结果会失真。
常用的复权方法有两种:
- 前复权:调整历史价格,让它们和当前价格可比。适合看趋势。
- 后复权:调整当前价格,让它们和历史价格可比。适合算收益。
注意:期货复权不是简单的加减,要考虑持仓成本、移仓损耗。我见过有人直接用股票复权方法做期货,结果回测收益虚高20%。
一个简单的复权示例:
def adjust_price(df, method='forward'):
"""
简单的期货复权处理
method: 'forward' 前复权, 'backward' 后复权
"""
df = df.copy()
if method == 'forward':
# 以最新合约为基准,调整历史价格
ratio = df['close'].iloc[-1] / df['close'].iloc[0]
df['adj_close'] = df['close'] * ratio
else:
# 以最早合约为基准
ratio = df['close'].iloc[0] / df['close'].iloc[-1]
df['adj_close'] = df['close'] * ratio
return df
当然,实际项目中复权逻辑要复杂得多。我个人习惯用连续合约,比如用持仓量最大的合约拼接成一条连续价格曲线,再配合复权处理。
3.4 数据存储——选CSV还是HDF5?
这个问题我纠结过很久。直接说结论:
| 存储方式 | 适用场景 | 我的建议 |
|---|---|---|
| CSV | 小数据量、临时分析、跨平台分享 | 适合做原型验证,简单粗暴 |
| HDF5 | 大数据量、频繁读写、需要压缩 | 适合生产环境,性能好 |
| Parquet | 列式存储、大数据分析 | 如果你用Spark或Dask,可以考虑 |
举个例子,我一般这样存:
# CSV方式——简单直接
df.to_csv('data/rb_continuous.csv', index=False)
# HDF5方式——高效压缩
df.to_hdf('data/futures.h5', key='rb', mode='a', complevel=9)
# 读取也很方便
df_from_h5 = pd.read_hdf('data/futures.h5', key='rb')
避坑指南:我曾经用CSV存了十年的分钟线数据,结果一个文件就几个G,读取慢得要命。后来换成HDF5,读取速度快了10倍,文件大小还缩小了60%。所以,数据量大的话,别犹豫,上HDF5。
3.5 本章知识体系总览
下面这张图,是我自己梳理的数据处理流程。你照着这个走,基本不会出大问题:
嗯,数据这块儿就聊到这儿。记住一句话:数据干净了,策略才能跑得稳。下一章咱们开始真正写策略代码,到时候你就知道今天这些功夫没白花。