4. 数据获取与预处理:用Python搞定期货与现货数据
做基差交易,数据就是你的弹药。弹药不行,枪法再好也白搭。
我见过太多人,策略逻辑写得漂漂亮亮,结果一跑回测全是坑——数据缺失、时间戳对不齐、交割日没处理。嗯,今天我们就来把这几个硬骨头啃掉。
4.1 数据源的选择与获取
先说说数据从哪来。我个人习惯用以下几个渠道:
- Tushare Pro:国内用得最多的免费数据源,期货和现货都有。需要注册拿token。
- Wind / 聚宽:机构级数据,质量高但收费。我早期在私募时主要用Wind。
- 交易所官网:上期所、大商所、郑商所、中金所,每天收盘后发布结算数据。
这里我直接给一段能跑的代码,获取螺纹钢期货和现货数据:
import tushare as ts
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
# 初始化Tushare
ts.set_token('你的token')
pro = ts.pro_api()
# 获取螺纹钢期货连续合约数据
# 这里用RB9999代表螺纹钢指数
df_futures = pro.fut_daily(
ts_code='RB9999.SHF',
start_date='20230101',
end_date='20231231',
fields='trade_date, close, volume, open_interest'
)
# 获取螺纹钢现货价格(上海地区)
df_spot = pro.spot_daily(
ts_code='RB.SH',
start_date='20230101',
end_date='20231231',
fields='trade_date, price'
)
print(f"期货数据量: {len(df_futures)}")
print(f"现货数据量: {len(df_spot)}")
⚠️ 注意: 连续合约的代码规则各交易所不同。上期所是"RB9999",大商所是"i9999"。别搞混了,我吃过这个亏。
4.2 数据清洗——脏数据是最大的敌人
数据拿到手,别急着用。先看看有没有脏东西。
我遇到过最离谱的一次:某天螺纹钢期货收盘价突然跳涨5%,查了半天发现是数据源把除权日搞错了。你想想看,这种异常点要是没处理,回测结果能信吗?
4.2.1 缺失值处理
期货和现货的交易日并不完全一致。期货有夜盘,现货没有。这就导致时间戳对不齐。
# 合并期货和现货数据
df = pd.merge(df_futures, df_spot, on='trade_date', how='outer')
# 检查缺失值
print("缺失情况:")
print(df.isnull().sum())
# 处理缺失:用前向填充
df['close'] = df['close'].ffill()
df['price'] = df['price'].ffill()
# 或者用插值法
df['close'] = df['close'].interpolate(method='linear')
df['price'] = df['price'].interpolate(method='linear')
💡 我的习惯: 对于期货数据,我一般用前向填充。因为期货价格是连续的,缺失值大概率是节假日。现货则用线性插值,毕竟现货价格波动更平滑。
4.2.2 异常值检测
说白了,就是找出那些明显不合理的数据点。比如价格突然变成0,或者涨跌幅超过10%。
# 计算日收益率
df['futures_ret'] = df['close'].pct_change()
df['spot_ret'] = df['price'].pct_change()
# 标记异常:收益率超过3个标准差
mean_ret = df['futures_ret'].mean()
std_ret = df['futures_ret'].std()
df['outlier'] = np.abs(df['futures_ret'] - mean_ret) > 3 * std_ret
# 查看异常点
outliers = df[df['outlier'] == True]
print(f"发现 {len(outliers)} 个异常点")
print(outliers[['trade_date', 'close', 'futures_ret']])
⚠️ 避坑指南: 我曾经用3倍标准差去过滤,结果把2015年股灾那几天的数据全删了。后来改成用5倍标准差,再结合人工复核。记住:异常不一定是错误,可能是极端行情。
4.3 时间戳对齐——基差计算的前提
基差 = 现货价格 - 期货价格。但问题是,期货和现货的交易时间不一样。
期货有夜盘(晚上9点到凌晨2点),现货只有白天。你想想看,如果用夜盘的期货价格去对白天的现货价格,基差能准吗?
4.3.1 对齐规则
我一般用这个规则:
- 日频数据:用期货的日盘收盘价(15:00)对应当天的现货价格
- 高频数据:只取两者重叠的交易时段(9:00-15:00)
- 夜盘数据:单独处理,不混入日盘
# 假设我们有分钟级数据
# 只保留9:00-15:00的重叠时段
df_minute = df_minute[
(df_minute['time'] >= '09:00:00') &
(df_minute['time'] <= '15:00:00')
]
# 按分钟对齐
df_aligned = df_minute.set_index('datetime').resample('1T').last()
# 计算基差
df_aligned['basis'] = df_aligned['spot_price'] - df_aligned['futures_price']
4.3.2 交割日处理
期货有到期日,临近交割时价格会向现货收敛。这个特性既是基差交易的核心,也是坑。
# 获取交割日信息
df_delivery = pro.fut_delivery(
ts_code='RB2401.SHF',
fields='delivery_date'
)
delivery_date = df_delivery['delivery_date'].iloc[0]
print(f"交割日: {delivery_date}")
# 剔除交割日前5天的数据(避免流动性问题)
cutoff = pd.to_datetime(delivery_date) - timedelta(days=5)
df = df[df['trade_date'] < cutoff.strftime('%Y%m%d')]
💡 经验之谈: 我个人习惯在交割月前两周就切换到下一个主力合约。因为临近交割,持仓量下降,流动性变差,基差波动会变得很不稳定。
4.4 知识体系总览
下面这张图,是我做基差数据预处理的核心流程。你看一遍,心里就有数了。
4.5 实战中的坑与对策
最后,分享几个我踩过的坑:
| 常见问题 | 表现 | 我的对策 |
|---|---|---|
| 数据源不一致 | 期货和现货日期对不上 | 统一用交易日历,剔除节假日 |
| 主力合约切换 | 基差突然跳变 | 用持仓量加权构建连续合约 |
| 夜盘数据干扰 | 基差波动异常大 | 只取日盘重叠时段 |
| 交割日效应 | 基差快速收敛到0 | 提前切换合约,避免参与交割 |
核心要点:
- 数据获取:选对数据源,注意合约代码规则
- 数据清洗:缺失值用前向填充,异常值用5倍标准差
- 时间戳对齐:只取重叠时段,处理交割日
- 最终目标:得到干净、对齐的基差序列
数据预处理这一步,看起来琐碎,但决定了你后面所有工作的质量。我见过太多人在这上面翻车。记住:花80%的时间在数据上,20%的时间在策略上,这才是正确的比例。