第三章 数据获取与处理:打好策略的地基

做CTA策略,我最深的体会是什么?

数据不行,策略白搭。

你想想看,再牛逼的模型,喂进去一堆垃圾数据,出来的只能是垃圾信号。这一章,我们就来搞定期货数据的获取和清洗。说白了,就是给策略准备一顿干净、可口的“食材”。

3.1 数据源选择:tushare vs akshare

国内做期货量化,常用的免费数据源就两个:tushare和akshare。我个人习惯两个都备着,一个挂了换另一个。

对比项 tushare akshare
数据稳定性 较好,有积分限制 一般,依赖网络爬虫
期货品种覆盖 全品种,含指数合约 全品种,但部分数据需额外参数
调用方式 API token认证 直接函数调用
我的推荐 主力合约连续数据 实时行情、历史Tick
小提示: 我个人建议,回测用tushare的复权数据,实盘监控用akshare的实时接口。两者互补,效果最好。

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()}")
核心观点: 数据清洗不是越多越好。过度填充会引入噪声,过度删除会丢失信息。我的经验是:先看缺失比例,低于1%直接删,高于1%再考虑填充。

3.4.2 异常值检测

异常值,说白了就是那些“离谱”的数据。比如螺纹钢突然出现一个0元的价格,或者成交量突然放大100倍。

我常用的方法有两种:

  1. 3σ原则: 超过均值±3倍标准差的数据,标记为异常
  2. 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σ原则处理股指期货数据,结果没考虑到2015年股灾期间波动率本身就大,把很多正常的大波动给误杀了。后来我改用滚动窗口的IQR方法,效果好了很多。

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 本章知识体系

下面这张图,是我自己总结的数据处理流程。每次做新策略,我都按这个步骤走一遍。

期货数据获取与处理流程 数据获取 tushare / akshare 数据清洗 缺失值 / 异常值 重采样 1min → 5min 输出数据 DataFrame / CSV 清洗细节 • 缺失值:ffill / interpolate • 异常值:3σ / IQR 核心原则:先清洗,再重采样,最后输出 数据质量决定策略上限,别在第一步偷懒
我的习惯: 每次获取新数据,我都会先画一张这样的流程图。不是为了好看,而是为了理清每一步做什么。你想想看,如果连数据流程都没想清楚,策略怎么可能稳定?

好了,数据获取与处理这部分就讲到这里。记住一句话:数据质量决定策略上限。别在第一步偷懒,后面会省很多事。