3. 数据准备与预处理:数据源选择、数据清洗、对齐时间戳、计算价差序列
做量化交易,尤其是价差回归策略,有一句老话我特别认同:「垃圾进,垃圾出」。你策略再牛,模型再花哨,数据源选错了,或者数据没洗干净,后面全是白忙活。我自己就吃过这个亏——有一回跑回测,收益曲线漂亮得不像话,结果发现是数据里混了前复权因子没处理干净,虚胖。
所以这一章,咱们就踏踏实实把数据准备工作捋清楚。说白了,就是四步走:选数据源 → 清洗脏数据 → 对齐时间戳 → 算出价差序列。每一步都有坑,我尽量把踩过的坑都给你指出来。
3.1 数据源选择:别在源头上翻车
我个人习惯,数据源至少要满足三个条件:
- 覆盖全:你要做的品种,历史K线得够长,至少3-5年吧?
- 精度够:分钟级数据最好,日线做价差有点糙。
- 接口稳:别动不动断流,或者数据突然少一段。
常用的数据源我列一下,供你参考:
| 数据源 | 优点 | 缺点 | 我常用的场景 |
|---|---|---|---|
| Wind / 聚宽 | 国内股票期货覆盖全,质量高 | 收费,接口有时限 | 股票配对交易 |
| Binance / OKX API | 免费,实时性好,历史数据全 | 只限加密货币 | 数字货币价差套利 |
| Yahoo Finance | 免费,美股ETF方便 | 数据偶尔有缺失,前复权处理需注意 | 美股ETF价差回测 |
| 本地CSV/Parquet | 完全可控,离线可用 | 需要自己维护更新 | 生产环境回测 |
3.2 数据清洗:把脏东西筛出去
拿到原始数据后,别急着算价差。先做三件事:
3.2.1 处理缺失值
金融数据里,缺失值很常见——节假日、停牌、网络波动。我的处理原则是:
- 日内分钟数据:如果连续缺失超过5根K线,直接丢弃当天数据。
- 日线数据:用前一个交易日填充,或者用插值法。
- 千万别用0填充!那会把你的价差序列搞出巨大的假信号。
3.2.2 剔除异常值
什么叫异常值?比如某根K线的价格突然跳了10%,明显是数据录入错误。我一般用3倍标准差法或者百分位截断法(比如去掉上下0.5%的极端值)。
import pandas as pd
import numpy as np
def clean_outliers(df, column='close', method='zscore', threshold=3):
"""
剔除异常值,返回布尔索引
"""
if method == 'zscore':
z = np.abs((df[column] - df[column].mean()) / df[column].std())
return df[z < threshold]
elif method == 'percentile':
lower = df[column].quantile(0.005)
upper = df[column].quantile(0.995)
return df[(df[column] >= lower) & (df[column] <= upper)]
3.2.3 复权处理
做股票价差,复权是绕不开的坎。我的建议是:统一用后复权。前复权会改变历史价格,导致价差序列出现「假突破」。后复权虽然当前价格看起来不对,但价差关系是真实的。
3.3 对齐时间戳:让两个品种「同步」
价差策略的核心,是同一时刻两个品种的价格差。如果时间戳没对齐,你算出来的价差就是「关公战秦琼」。我遇到过最坑的一次,是A股和港股配对,A股中午休市,港股不休,结果价差序列在中午那段完全乱套。
对齐时间戳,我一般分三步:
- 统一时区:全部转成UTC+0,避免夏令时问题。
- 统一频率:比如都转成1分钟K线,用前一根K线的收盘价填充缺失。
- 剔除不重叠时段:比如A股和港股,只保留两者都交易的时间段。
def align_timestamps(df1, df2, freq='1min'):
"""
对齐两个DataFrame的时间戳,按指定频率重采样
"""
# 统一索引为datetime类型
df1.index = pd.to_datetime(df1.index)
df2.index = pd.to_datetime(df2.index)
# 重采样到统一频率
df1_resampled = df1.resample(freq).last().ffill()
df2_resampled = df2.resample(freq).last().ffill()
# 取交集时间
common_index = df1_resampled.index.intersection(df2_resampled.index)
return df1_resampled.loc[common_index], df2_resampled.loc[common_index]
3.4 计算价差序列:从价格到信号
数据洗干净、时间也对齐了,终于可以算价差了。价差的计算方式,取决于你的策略假设:
- 简单价差:P1 - P2,适用于同品种不同到期日,或者同板块股票。
- 对数价差:ln(P1) - ln(P2),适用于价格差异较大的品种,能消除量纲影响。
- 回归残差:用OLS回归算出协整系数,然后取残差。这是最严谨的做法,但计算量也最大。
我个人习惯,先用简单价差快速验证,如果效果不错,再上回归残差。别一上来就搞复杂的,容易过拟合。
def calculate_spread(price1, price2, method='simple'):
"""
计算价差序列
"""
if method == 'simple':
spread = price1 - price2
elif method == 'log':
spread = np.log(price1) - np.log(price2)
elif method == 'ols':
# 用OLS回归计算残差
import statsmodels.api as sm
X = sm.add_constant(price2)
model = sm.OLS(price1, X).fit()
spread = model.resid
else:
raise ValueError("Unknown method")
return spread
3.5 本章知识体系总览
下面这张图,是我自己画的数据准备流程。你跟着走一遍,基本不会出大问题。
好了,数据准备这块就这些。你按这个流程走一遍,基本不会出大问题。下一章咱们就开始正式做参数优化了——不过在那之前,先把数据搞干净,这是最笨但最有效的办法。