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 知识体系总览

下面这张图,是我做基差数据预处理的核心流程。你看一遍,心里就有数了。

基差交易数据预处理流程 数据获取 Tushare / Wind / 交易所 数据清洗 缺失值 / 异常值 / 重复值 时间戳对齐 日频 / 分钟频 / 交割日 数据源 期货:连续合约 + 主力合约 现货:地区报价 / 指数 清洗要点 前向填充 vs 线性插值 3σ vs 5σ 异常检测 对齐策略 重叠时段提取 交割日前5天剔除 输出:对齐后的基差数据 注:实际项目中需根据品种特性调整参数 关键参数建议: • 异常值阈值:5倍标准差(极端行情保留) • 交割日剔除:提前5-10个交易日 • 主力合约切换:持仓量最大 + 成交量最大

4.5 实战中的坑与对策

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

常见问题 表现 我的对策
数据源不一致 期货和现货日期对不上 统一用交易日历,剔除节假日
主力合约切换 基差突然跳变 用持仓量加权构建连续合约
夜盘数据干扰 基差波动异常大 只取日盘重叠时段
交割日效应 基差快速收敛到0 提前切换合约,避免参与交割

核心要点:

  • 数据获取:选对数据源,注意合约代码规则
  • 数据清洗:缺失值用前向填充,异常值用5倍标准差
  • 时间戳对齐:只取重叠时段,处理交割日
  • 最终目标:得到干净、对齐的基差序列

数据预处理这一步,看起来琐碎,但决定了你后面所有工作的质量。我见过太多人在这上面翻车。记住:花80%的时间在数据上,20%的时间在策略上,这才是正确的比例。

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