第四章:数据获取与清洗

做量化交易,尤其是农产品套利,最怕什么?

不是策略失效,不是回测过拟合。而是——数据是脏的。

我见过太多人,策略写得漂漂亮亮,回测曲线完美得像艺术品。结果一上实盘,亏得亲妈都不认识。为什么?数据源有问题,复权没做对,或者某个异常值把整个模型带偏了。

这一章,我们就来聊聊数据获取与清洗。说白了,就是怎么把原材料搞到手,再把它洗干净。

4.1 免费数据源怎么选?

我个人习惯,把数据源分成三类:

  • 专业金融终端:比如Wind、Choice。数据全,质量高,但贵。一年几万块,个人玩家扛不住。
  • 开源Python库:比如Tushare、AkShare。免费或低价,适合研究和回测。
  • 国际数据源:比如Yahoo Finance、Quandl。做跨品种套利时用得上。

你想想看,做农产品套利,国内数据用Tushare就够了。但如果你要研究美豆和连豆的价差,那Yahoo Finance就绕不开。

我的建议:初期用Tushare + AkShare组合。一个偏股票期货,一个偏宏观经济和农产品现货。两个互补,基本覆盖90%的需求。

4.2 用Python获取数据

先看Tushare怎么用。嗯,这里要注意,Tushare pro需要token,你得先去官网注册申请。

import tushare as ts

# 设置token
ts.set_token('你的token')

# 初始化接口
pro = ts.pro_api()

# 获取豆粕期货日线数据
df = pro.fut_daily(
    ts_code='M2409.DCE',
    start_date='20230101',
    end_date='20240601',
    fields='trade_date,open,high,low,close,vol'
)

print(df.head())

我在项目中遇到过一个问题:Tushare的期货数据,有时候会缺掉某些合约的最后交易日。尤其是临近交割月,数据更新不及时。所以,我一般会多拉几个合约,做交叉验证。

再看Yahoo Finance。做国际品种时,yfinance库很顺手。

import yfinance as yf

# 下载美豆连续合约
soybean = yf.download('ZS=F', start='2023-01-01', end='2024-06-01')

print(soybean.head())

这里有个坑——Yahoo的数据是未复权的。做套利分析时,必须自己处理复权。不然价差序列会失真。

4.3 数据清洗实战

数据拿到手,别急着用。先洗一遍。我总结了三步走:

4.3.1 缺失值处理

农产品期货有夜盘,但节假日又不同步。比如国庆节,国内休市,外盘还在交易。这就导致数据里出现空值。

处理方法,我一般分情况:

  • 连续缺失不超过3天:用前向填充(ffill)。说白了,就是用上一个交易日的数据补上。
  • 缺失超过3天:用插值法。比如线性插值,或者用同品种其他合约的数据来补。
  • 关键日期缺失:比如交割日、报告发布日。直接删除,别补。补了也是假的。
# 前向填充
df['close'].fillna(method='ffill', inplace=True)

# 线性插值
df['close'].interpolate(method='linear', inplace=True)

小技巧:做套利时,两个品种的日期必须对齐。我习惯先合并两个DataFrame,再统一处理缺失值。这样能保证时间轴一致。

4.3.2 异常值检测

什么叫异常值?比如豆粕价格突然跳涨5%,但当天没有任何消息。这大概率是数据错误。

我常用的方法有两种:

  • 标准差法:超过均值±3倍标准差,标记为异常。
  • IQR法:超过四分位距的1.5倍,标记为异常。
# 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

outliers = df[(df['close'] < lower_bound) | (df['close'] > upper_bound)]
print(f'发现 {len(outliers)} 个异常值')

我曾经在回测豆棕价差时,发现一个异常点导致整个策略信号全乱了。排查了半天,原来是某一天的数据小数点错位了。嗯,从那以后,我每次清洗都会画个箱线图看一眼。

注意:不要机械地删除异常值。有些异常是真实的,比如突发事件导致的跳空。要结合基本面判断。我一般会先标记,再人工复核。

4.3.3 复权处理

复权是期货数据清洗里最容易被忽视的一环。

期货合约有到期日,价格会跳空。比如豆粕M2409合约,临近交割时,价格会向现货回归。如果你直接用连续合约的价格做回测,价差序列里会有一堆假跳空。

我常用的复权方法:

  • 前复权:调整历史价格,让价格连续。适合做技术分析。
  • 后复权:调整当前价格,保持历史真实。适合做套利分析。
# 简单的后复权处理
def adjust_price(df, factor_col='adj_factor'):
    """
    df: 包含复权因子的DataFrame
    """
    df['adj_close'] = df['close'] * df[factor_col]
    df['adj_open'] = df['open'] * df[factor_col]
    return df

说实话,很多免费数据源不提供复权因子。这时候,我一般自己算。用换月日的价差,反推出调整系数。虽然麻烦,但数据质量有保障。

4.4 知识体系总览

下面这张图,是我做数据清洗时的完整流程。你可以照着这个框架来。

数据获取与清洗流程 数据源选择 Wind / Tushare / Yahoo 数据获取 API调用 / 批量下载 数据清洗 三大步骤 缺失值处理 前向填充 / 插值 / 删除 连续缺失≤3天用ffill 异常值检测 标准差法 / IQR法 结合基本面人工复核 复权处理 前复权 / 后复权 套利分析用后复权 清洗后的干净数据 可用于回测和策略开发

4.5 实战中的避坑指南

最后,分享几个我踩过的坑:

  • 数据对齐问题:不同数据源的时间戳格式可能不一样。Tushare用'20230101',Yahoo用'2023-01-01'。合并前一定要统一格式。
  • 合约换月跳空:做连续合约时,换月日的价差跳空必须处理。我一般用持仓量最大的合约作为主力合约,换月时做平滑处理。
  • 节假日差异:国内农产品期货有夜盘,但外盘没有。做跨品种套利时,日期对齐要特别小心。我习惯用交易日历库来统一。

我的习惯:每次清洗完数据,我都会保存一份原始数据的哈希值。这样万一后面发现数据有问题,可以快速定位是清洗过程出的错,还是原始数据就有问题。

数据清洗这件事,说白了就是「磨刀不误砍柴工」。你花80%的时间在数据上,策略开发反而会顺风顺水。反过来,数据没弄干净,后面全是坑。

嗯,这一章就到这里。记住:干净的数据,是量化套利的基石。