第三章 数据获取与处理:打好策略的地基
做CTA策略,我最深的体会是什么?
数据不行,策略白搭。
你想想看,再牛逼的模型,喂进去一堆垃圾数据,出来的只能是垃圾信号。这一章,我们就来搞定期货数据的获取和清洗。说白了,就是给策略准备一顿干净、可口的“食材”。
3.1 数据源选择:tushare vs akshare
国内做期货量化,常用的免费数据源就两个:tushare和akshare。我个人习惯两个都备着,一个挂了换另一个。
| 对比项 | tushare | akshare |
|---|---|---|
| 数据稳定性 | 较好,有积分限制 | 一般,依赖网络爬虫 |
| 期货品种覆盖 | 全品种,含指数合约 | 全品种,但部分数据需额外参数 |
| 调用方式 | API token认证 | 直接函数调用 |
| 我的推荐 | 主力合约连续数据 | 实时行情、历史Tick |
3.2 用tushare获取期货日线数据
先来个最基础的——获取螺纹钢主力合约的日线数据。代码不长,但坑不少。
import tushare as ts
import pandas as pd
# 初始化(记得换成你自己的token)
pro = ts.pro_api('你的token')
# 获取螺纹钢主力连续合约
df = pro.fut_daily(
ts_code='RB9999.XSGE', # 9999代表主力连续
start_date='20200101',
end_date='20231231',
fields='trade_date,open,high,low,close,vol'
)
# 看一眼数据
print(df.head())
嗯,这里要注意:RB9999.XSGE这个代码,9999不是随便写的。它代表交易所自己计算的主力合约连续。我曾经遇到过直接用具体合约代码(比如RB2301)做回测,结果换月那天跳空缺口把我策略搞崩了。
3.3 用akshare获取分钟数据
做CTA策略,分钟数据是家常便饭。akshare获取1分钟数据很方便,但有个小坑——它返回的是字符串格式的时间。
import akshare as ak
import pandas as pd
# 获取铁矿石1分钟数据
df_min = ak.futures_zh_minute_sina(
symbol='i2401', # 具体合约
period='1' # 1分钟
)
# 时间处理(这一步很多人会忘)
df_min['datetime'] = pd.to_datetime(df_min['datetime'])
df_min = df_min.set_index('datetime')
print(df_min.head())
为什么我要强调时间处理?因为akshare返回的datetime列是字符串,你直接拿去做时间序列分析,pandas会报错。我刚开始用的时候就被这个坑过,排查了半天。
3.4 数据清洗:处理缺失值和异常值
数据拿到手,别急着用。先看看有没有“脏数据”。
3.4.1 缺失值处理
期货数据缺失,常见原因有:节假日、涨跌停板、数据源本身缺失。我的处理原则是:
- 日线缺失: 直接向前填充(ffill),因为非交易日价格不变
- 分钟线缺失: 如果是盘中短暂缺失,用前后均值填充
- 连续缺失超过5根: 直接删除,别犹豫
# 日线缺失处理
df['close'] = df['close'].fillna(method='ffill')
# 分钟线缺失处理(最多填充3根)
df['close'] = df['close'].interpolate(limit=3)
# 检查还有多少缺失
print(f"剩余缺失值: {df['close'].isna().sum()}")
3.4.2 异常值检测
异常值,说白了就是那些“离谱”的数据。比如螺纹钢突然出现一个0元的价格,或者成交量突然放大100倍。
我常用的方法有两种:
- 3σ原则: 超过均值±3倍标准差的数据,标记为异常
- IQR方法: 超过Q1-1.5IQR或Q3+1.5IQR的数据,标记为异常
# IQR方法检测异常值
Q1 = df['close'].quantile(0.25)
Q3 = df['close'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 找出异常值
anomalies = df[(df['close'] < lower_bound) | (df['close'] > upper_bound)]
print(f"发现 {len(anomalies)} 个异常值")
# 处理:用前一个正常值替换
df.loc[anomalies.index, 'close'] = df['close'].shift(1).loc[anomalies.index]
3.5 数据重采样:1分钟转5分钟
做CTA策略,5分钟、15分钟、30分钟是常用周期。但数据源往往只提供1分钟数据。这时候就需要重采样。
重采样的核心逻辑:
- 开盘价: 取第一个值
- 收盘价: 取最后一个值
- 最高价: 取最大值
- 最低价: 取最小值
- 成交量: 求和
# 1分钟转5分钟
df_5min = df_min.resample('5min').agg({
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'sum'
})
# 删除重采样产生的NaN
df_5min = df_5min.dropna()
print(df_5min.head())
这里有个细节:resample默认会包含非交易时间。比如夜盘收盘到第二天白盘开盘之间,会产生空数据。我建议重采样后检查一下时间索引,把那些成交量全为0的K线删掉。
3.6 本章知识体系
下面这张图,是我自己总结的数据处理流程。每次做新策略,我都按这个步骤走一遍。
好了,数据获取与处理这部分就讲到这里。记住一句话:数据质量决定策略上限。别在第一步偷懒,后面会省很多事。