1. 基于协整的配对交易入门
任务目标
选取两只具有长期均衡关系的股票,通过协整检验确认配对有效性,构建基于价差回归的配对交易策略,并在历史数据上完成回测,输出策略累计收益曲线与关键绩效指标。
环境与依赖
本案例使用 Python 3.9+,所需库:numpy、pandas、statsmodels(协整检验)、yfinance(数据获取)、matplotlib(可视化)。若无法访问 yfinance,可替换为本地 CSV 数据,代码中数据加载部分需相应调整。
实现思路与关键代码
核心流程:① 获取两只股票近两年的日收盘价;② 用 Engle-Granger 两步法检验协整关系(对价格序列做 OLS 回归,对残差做 ADF 检验);③ 若协整成立,计算价差 spread = y - βx,以其均值为中枢、标准差为阈值构建交易信号;④ 回测时,价差上穿 +1 倍标准差时做空价差(卖 y 买 x),下穿 -1 倍标准差时做多价差(买 y 卖 x),回归均值时平仓;⑤ 计算策略净值与基准(等权持有)对比。
import numpy as np
import pandas as pd
import yfinance as yf
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller
import matplotlib.pyplot as plt
# ---------- 1. 数据获取 ----------
tickers = ['XOM', 'CVX'] # 埃克森美孚 vs 雪佛龙
data = yf.download(tickers, start='2022-01-01', end='2024-01-01')['Adj Close']
data = data.dropna()
x = data[tickers[0]] # 解释变量
y = data[tickers[1]] # 被解释变量
# ---------- 2. 协整检验 (Engle-Granger) ----------
X = sm.add_constant(x)
ols = sm.OLS(y, X).fit()
beta = ols.params[tickers[0]]
alpha = ols.params['const']
spread = y - beta * x - alpha # 价差序列
adf_stat, p_value, _, _, _, _ = adfuller(spread)
print(f'ADF统计量: {adf_stat:.4f}, p值: {p_value:.4f}')
if p_value >= 0.05:
raise SystemExit('两只股票不存在协整关系,请更换标的')
# ---------- 3. 交易信号生成 ----------
spread_mean = spread.mean()
spread_std = spread.std()
entry_threshold = 1.0 # 开仓阈值(倍标准差)
exit_threshold = 0.0 # 平仓阈值(回归均值)
signal = pd.Series(0, index=spread.index)
position = 0 # 1=多价差, -1=空价差
for i in range(1, len(spread)):
z = (spread.iloc[i] - spread_mean) / spread_std
if position == 0:
if z < -entry_threshold:
signal.iloc[i] = 1 # 做多价差
position = 1
elif z > entry_threshold:
signal.iloc[i] = -1 # 做空价差
position = -1
elif position == 1 and z >= exit_threshold:
signal.iloc[i] = 0 # 平多
position = 0
elif position == -1 and z <= exit_threshold:
signal.iloc[i] = 0 # 平空
position = 0
# ---------- 4. 回测 ----------
# 价差收益 = Δspread(做多时),做空时取负
spread_ret = spread.diff().shift(-1) # 当日收盘开仓,次日收盘平仓的收益
trade_ret = signal * spread_ret
trade_ret = trade_ret.fillna(0)
# 策略净值(假设初始资金1,不考虑交易成本)
equity = (1 + trade_ret).cumprod()
# 基准:等权持有两只股票
benchmark = (data.pct_change().mean(axis=1) + 1).cumprod().fillna(1)
# ---------- 5. 绩效指标 ----------
total_ret = equity.iloc[-1] - 1
sharpe = trade_ret.mean() / trade_ret.std() * np.sqrt(252) if trade_ret.std() != 0 else 0
print(f'策略总收益: {total_ret:.2%}, 夏普比率: {sharpe:.2f}')
# ---------- 6. 可视化 ----------
plt.figure(figsize=(12, 6))
plt.plot(equity.index, equity.values, label='配对交易策略')
plt.plot(benchmark.index, benchmark.values, label='等权持有基准')
plt.legend()
plt.title('配对交易策略回测净值')
plt.savefig('pairs_trading_result.png', dpi=150)
print('净值曲线已保存为 pairs_trading_result.png')
运行方式
将上述代码保存为 pairs_trading.py,在终端执行 python pairs_trading.py。首次运行需联网下载行情数据,若网络受限,可提前用 yf.download 下载并保存为 CSV 后修改数据读取部分。
验证方法与通过条件
- 协整检验通过:控制台输出的 ADF 检验 p 值小于 0.05,说明价差序列平稳,两只股票存在长期均衡关系。
- 策略有正收益:回测输出的总收益率大于 0,且净值曲线图(pairs_trading_result.png)中策略曲线在大部分时间位于基准上方或同步增长。
- 信号逻辑自洽:价差偏离均值超过 1 倍标准差时触发反向交易,回归均值时平仓——可通过打印 signal 中非零值对应的日期与当日价差进行抽查验证。
常见问题
- 数据长度不足:协整检验需要至少 1~2 年日线数据,若时间窗口过短,p 值可能不显著,可延长回测区间。
- 未来函数:本代码使用全样本均值与标准差计算 z-score,实盘中需用滚动窗口。若需更严谨的回测,应将
spread_mean和spread_std改为滚动计算(如 60 日窗口)。 - 交易成本:当前回测未计入手续费与滑点,实盘应用时需在
trade_ret中扣除双边成本。