第3章:数据获取与预处理

做统计套利,数据就是你的命根子。这话一点都不夸张。

我见过太多人,模型建得花里胡哨,结果数据源就有问题。最后亏钱了,还怪策略不行。其实说白了,源头就歪了。

这一章,咱们就聊聊怎么把期货数据搞到手,再把它收拾得干干净净。我个人习惯,花在数据预处理上的时间,至少占整个项目时间的40%。别嫌多,这钱花得值。

3.1 数据获取:选对工具,事半功倍

目前国内做量化,常用的免费数据源就两个:TushareAkshare。我两个都用过,说说我的感受。

  • Tushare:老牌数据平台,数据质量高,但新用户有积分门槛。有些接口需要付费积分才能用。适合有一定预算的团队。
  • Akshare:完全开源免费,接口丰富,更新也快。我个人现在更偏爱它,尤其是做快速原型验证的时候。缺点就是偶尔会因为源网站改版而报错,需要及时更新库。

嗯,这里要注意:千万别在生产环境里裸用免费接口。我建议你搭建一个本地数据库,每天定时抓取并存储。这样即使源站挂了,你的策略还能跑。

核心原则: 数据获取的稳定性 > 数据获取的速度。宁可慢一点,也要保证每天都能拿到完整、准确的数据。

3.2 实战:用Akshare拉取期货主力连续数据

咱们直接上代码。以螺纹钢(RB)为例,拉取最近一年的日线数据。

import akshare as ak
import pandas as pd
from datetime import datetime, timedelta

# 设定时间范围
end_date = datetime.now().strftime('%Y%m%d')
start_date = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d')

# 获取螺纹钢主力连续数据
# 注意:symbol参数要用 'RB' 而不是 'rb'
try:
    df = ak.futures_main_sina(symbol="RB", 
                              start_date=start_date, 
                              end_date=end_date)
    print("数据获取成功!")
    print(df.head())
except Exception as e:
    print(f"数据获取失败:{e}")
    # 我曾经遇到过网络超时,加个重试机制会稳妥很多

跑完这段代码,你会得到一个DataFrame。里面包含了开盘价、最高价、最低价、收盘价、成交量、持仓量等字段。这就是我们做套利分析的基础素材。

小技巧: 如果你需要多个品种的数据,建议写一个循环函数,每次请求之间加个 time.sleep(1)。别把人家服务器搞崩了,这是基本的礼貌。

3.3 数据清洗:脏数据是策略的隐形杀手

数据拿到手,别急着用。先看看有没有坑。我总结了三类常见问题:

3.3.1 缺失值处理

期货市场有夜盘,节假日也不一样。所以经常会出现某一天某个品种没有数据。怎么办?

  • 直接删除:如果缺失比例很小(比如<1%),直接dropna()就行。
  • 向前填充:用上一个交易日的价格填充。我个人习惯用这个方法处理非活跃合约。
  • 插值法:用前后两个值取平均。适合处理盘中短暂的数据中断。
# 向前填充缺失值
df.fillna(method='ffill', inplace=True)

# 或者用线性插值
df.interpolate(method='linear', inplace=True)

3.3.2 异常值检测

什么叫异常值?比如螺纹钢一天涨了10%,这在期货市场几乎不可能(有涨跌停板限制)。

我常用的方法是3σ原则:计算收益率序列的均值和标准差,超过均值±3倍标准差的,就标记为异常。

# 计算日收益率
df['return'] = df['close'].pct_change()

# 3σ异常检测
mean = df['return'].mean()
std = df['return'].std()
df['is_outlier'] = (df['return'] > mean + 3*std) | (df['return'] < mean - 3*std)

# 查看异常值
outliers = df[df['is_outlier'] == True]
print(f"发现 {len(outliers)} 个异常值")

嗯,这里要注意:别一棍子打死所有异常值。有些异常可能是真实的极端行情(比如2020年原油暴跌)。我建议你人工复核一下,再决定是删除还是保留。

3.4 数据对齐与重采样

做跨品种套利,最头疼的问题就是时间对齐。不同品种的交易时间不一样:

  • 股指期货:9:30-15:00
  • 商品期货:9:00-15:00,外加夜盘
  • 有些品种夜盘到23:00,有些到凌晨2:30

你想想看,如果两个品种的时间戳对不上,价差就没法算。怎么办?

3.4.1 统一时间索引

我的做法是:以交易所的官方时间戳为准,把所有数据重采样到同一频率

# 假设 df_rb 和 df_hc 分别是螺纹钢和热卷的数据
# 先确保索引是datetime类型
df_rb.index = pd.to_datetime(df_rb.index)
df_hc.index = pd.to_datetime(df_hc.index)

# 重采样到日频,取收盘价
rb_daily = df_rb['close'].resample('D').last()
hc_daily = df_hc['close'].resample('D').last()

# 合并成一个DataFrame
combined = pd.concat([rb_daily, hc_daily], axis=1, keys=['RB', 'HC'])
# 删除任何一方缺失的行
combined.dropna(inplace=True)
避坑指南: 我曾经犯过一个错误——直接用 pd.merge 按日期合并,结果发现两个品种的日期索引差了1天。后来才意识到,是因为夜盘交易导致日期归属不同。所以,一定要检查日期对齐后的数据量,确保没有系统性偏差。

3.5 知识体系总览

下面这张图,是我自己总结的数据预处理流程。你可以把它当作一个检查清单。

数据预处理核心流程 1. 数据获取 2. 缺失值处理 向前填充 线性插值 3. 异常值检测 3σ原则 IQR方法 4. 对齐与重采样 时间戳统一 频率转换 5. 干净数据输出

这张图把整个流程串起来了。你每次做数据预处理,都可以对照着走一遍。别跳步,每一步都有它的道理。

3.6 本章小结

数据预处理,说白了就是把原材料变成半成品。你花在预处理上的每一分钟,都会在后续建模阶段加倍回报给你。

我个人习惯,每次拿到新数据,都会先跑一遍完整的清洗流程,然后保存一份“干净版”。这样后面做策略回测时,直接读这份数据就行,不用每次都重新清洗。

嗯,今天就先聊到这儿。数据准备好了,下一章咱们就可以开始真正的统计套利建模了。


蓝海数据掘金营,专注资料整理