4. 数据获取与清洗
跨品种与期限价差研究的第一道门槛,往往不是模型,而是数据。价差研究的本质是“同一逻辑链条上两个价格序列的相对关系”,因此对数据的时间一致性、合约一致性、口径一致性要求远高于单品种研究。本章围绕数据源选择、API接口调用、缺失值处理、异常值检测与数据对齐五个环节,给出可落地的工程化流程。
一、核心原理:价差研究对数据的三个硬约束
价差 = 价格A − 价格B(或比值)。这个减法看似简单,却隐含三个硬约束:
- 时间戳对齐:两个序列若存在分钟级错位,价差会凭空产生“伪波动”。例如A在14:59:30更新,B在15:00:00更新,跨节假日前会出现虚假跳空。
- 合约口径一致:跨期价差必须明确是“近月−远月”还是“主力−次主力”,且换月规则要统一,否则价差序列会在换月日出现非市场性断裂。
- 复权与单位一致:跨品种价差常涉及不同报价单位(元/吨、美分/蒲式耳、点),需统一到同一量纲,否则统计量(均值、方差、相关系数)全部失真。
二、常用数据源与API接口
实战中数据源通常分三类:交易所官方、商业数据终端、开源/社区接口。选择依据是频率、历史深度、字段完整度、调用限制。
| 数据源类型 | 典型代表 | 优势 | 局限 |
|---|---|---|---|
| 交易所官方 | 上期所、大商所、郑商所、CFFEX | 权威、免费、含结算价与持仓 | 接口分散、历史文件格式不统一 |
| 商业终端 | Wind、同花顺iFinD、Choice | 字段全、复权方便、支持批量 | 需授权、API有并发限制 |
| 开源/社区 | AkShare、Tushare、yfinance | 免费、Python友好、上手快 | 稳定性依赖社区、字段可能缺失 |
以AkShare获取期货主力连续合约为例,典型调用如下:
import akshare as ak
import pandas as pd
# 获取某品种主力连续日线
df = ak.futures_main_sina(symbol="RB0", start_date="20230101", end_date="20241231")
df = df.rename(columns={
"日期": "date", "开盘价": "open", "最高价": "high",
"最低价": "low", "收盘价": "close", "成交量": "volume"
})
df["date"] = pd.to_datetime(df["date"])
df = df.set_index("date").sort_index()
print(df.head())
time.sleep(0.5)避免触发限流;对返回结果先做dtypes检查,很多接口把价格返回为字符串,直接参与计算会报错或产生意外结果。
三、缺失值处理:先分类,再决策
缺失值不能一律删除或一律填充。按成因分为三类:
- 非交易时段缺失:夜盘、节假日、停牌。这类缺失是“真实无交易”,不应填充,应保留为NaN并在对齐时按交易日历裁剪。
- 单边缺失:A有数据、B无数据。常见于某合约流动性差。此时价差无法计算,应标记为无效而非插值。
- 随机缺失:接口丢包、字段为空。可用前值填充(ffill)或线性插值,但需限制连续填充长度。
# 限制连续填充长度,避免长段伪造数据
df["close"] = df["close"].ffill(limit=2)
# 标记单边缺失:两序列任一为空则价差无效
spread = (df_a["close"] - df_b["close"]).where(
df_a["close"].notna() & df_b["close"].notna()
)
四、异常值检测与数据对齐
异常值检测在价差研究中比单序列更敏感,因为价差本身波动小,一个错误价格就能主导统计量。推荐组合使用三种方法:
- 绝对阈值法:价差超过历史分位数(如99.5%)或涨跌停板幅度,标记为可疑。
- 滚动Z-Score:
z = (spread - rolling_mean) / rolling_std,|z|>4 触发告警。 - 跳变检测:相邻两期变化率超过阈值,且下一期回归,判定为瞬时错误。
# 滚动Z-Score异常检测
roll_mean = spread.rolling(60, min_periods=30).mean()
roll_std = spread.rolling(60, min_periods=30).std()
zscore = (spread - roll_mean) / roll_std
outliers = zscore.abs() > 4
# 跳变检测:单期变化超过5倍历史波动
jump = spread.diff().abs() > 5 * spread.diff().abs().rolling(60).median()
数据对齐是最后一步,也是最容易出错的一步。对齐的核心是统一时间轴:先取两序列时间戳的并集或交集,再按交易日历重采样。对于跨品种价差,建议使用交集对齐,只保留两品种都有交易的时点,避免单边缺失污染价差。
# 交集对齐:只保留两序列都有数据的时点
aligned = pd.concat([df_a["close"], df_b["close"]], axis=1, join="inner")
aligned.columns = ["close_a", "close_b"]
aligned["spread"] = aligned["close_a"] - aligned["close_b"]
# 按交易日历重采样到日频
aligned = aligned.resample("1D").last().dropna()
- 两序列时间戳是否同一时区、同一频率?
- 是否使用同一交易日历(尤其跨市场、跨时区)?
- 换月规则是否统一?主力合约切换日是否一致?
- 价格单位是否统一?是否使用同一价格类型(收盘价/结算价)?
- 对齐后价差序列是否存在异常跳空?
五、案例复盘:一次跨品种价差数据事故
某研究中使用螺纹钢与热卷主力连续合约构建价差。初版代码直接对两个主力连续序列做减法,结果价差在换月日出现约200点的跳空,协整检验p值从0.03变为0.21。复盘发现:螺纹钢主力在月初换月,热卷在月中换月,两个序列的换月日不同步,导致价差在换月窗口内混入了“新旧合约价差”而非真实品种价差。
修正方案:改用同一换月规则(如按持仓量最大合约),并在换月日对价差做拼接调整,即用新旧合约价差的变化量修正历史序列,而非直接拼接价格。修正后价差序列连续,协整检验恢复显著。
至此,数据获取与清洗的完整链路已经建立:从数据源选择与API调用,到缺失值分类处理、异常值组合检测,再到时间轴与合约口径的对齐。这条链路的稳健性,直接决定了后续价差统计研究结论的可信度。