一、核心概念解析#
1.1 策略本质理解#
配对交易通过捕捉两个相关资产的价格偏差获利。如同用弹簧连接的两个小球:
-
当价差拉伸时(弹簧被拉长)建仓
-
当价差恢复时(弹簧回弹)平仓
-
核心假设:价差具有均值回归特性
这个类比只用于帮助理解:真实交易中没有物理弹簧,价差恢复必须由协整检验、样本外表现和交易成本共同验证。
1.2 协整性数学表达#
协整关系可表示为:
其中残差项 满足:
需通过 ADF 检验验证 的平稳性(p<0.05)
1.3 动态对冲演进#
code
# 传统静态对冲(2010年前主流)
beta = 固定值
# 滚动窗口对冲(2010-2015常用)
rolling_beta = 30日滚动回归系数
# 状态空间模型(现代方法)
from statsmodels.tsa.statespace.sarimax import SARIMAX
model = SARIMAX(Y, exog=X, order=(1,0,0)).fit()二、完整策略实现#
2.1 数据获取与预处理#
code
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
# 获取数据(以麦当劳与百胜集团为例)
symbols = ['MCD', 'YUM']
data = yf.download(
symbols, start='2018-01-01', end='2023-12-31',
auto_adjust=True, group_by='column', multi_level_index=True,
)['Close']
# 数据清洗
data = data.dropna()
log_prices = np.log(data) # 对数变换便于解释相对变化,但不会自动使序列平稳
# 可视化验证
data.plot(subplots=True, figsize=(12,6), title='Price Series')2.2 协整检验进阶#
code
from statsmodels.tsa.stattools import coint
def enhanced_coint_test(s1, s2):
# Engle-Granger 检验有回归方向,先按经济关系预先指定因变量
statistic, pvalue, critical_values = coint(s1, s2)
return statistic, pvalue, critical_values
statistic, pvalue, critical_values = enhanced_coint_test(
log_prices['YUM'], log_prices['MCD']
)
print(f"协整检验 p 值: {pvalue:.4f}")对已经平稳的一阶差分序列再次调用 coint 不能作为“第三重协整验证”。更可靠的做法是预先固定回归方向和趋势项,并在独立样本期检验价差稳定性。
2.3 动态价差构建#
code
# 滚动120天计算对冲比率
rolling_period = 120
hedge_ratios = []
for i in range(rolling_period, len(log_prices)):
X = sm.add_constant(log_prices['MCD'].iloc[i-rolling_period:i])
model = sm.OLS(log_prices['YUM'].iloc[i-rolling_period:i], X).fit()
hedge_ratios.append(model.params['MCD'])
# 创建对冲比率序列
hedge_ratio_series = pd.Series(
index=log_prices.index[rolling_period:],
data=hedge_ratios
)
# 计算动态价差
spread = log_prices['YUM'] - hedge_ratio_series * log_prices['MCD']2.4 信号生成优化#
code
# 自适应布林通道
def adaptive_bollinger(series, window=30):
ma = series.rolling(window).mean()
std = series.rolling(window).std()
upper = ma + std*(1 + 0.1*(window//30)) # 波动率放大
lower = ma - std*(1 + 0.1*(window//30))
return (series - ma)/std, upper, lower
zscore, upper, lower = adaptive_bollinger(spread.dropna())
# 信号规则
signals = pd.DataFrame(index=zscore.index)
signals['entry_short'] = zscore > 1.5
signals['entry_long'] = zscore < -1.5
signals['exit'] = abs(zscore) < 0.52.5 回测增强模块#
code
# 用状态机让仓位从入场持续到退出。state=1 表示做多价差:
# 做多 YUM、按滚动对冲比率做空 MCD。
positions = pd.DataFrame(0.0, index=signals.index, columns=['MCD', 'YUM'])
state = 0
for date in signals.index:
if signals.at[date, 'exit']:
state = 0
elif signals.at[date, 'entry_short']:
state = -1
elif signals.at[date, 'entry_long']:
state = 1
positions.at[date, 'YUM'] = state
positions.at[date, 'MCD'] = -state * hedge_ratio_series.at[date]
# t 日收盘后生成的信号最早在 t+1 日持有
positions = positions.shift(1).fillna(0)
# 收益计算
returns = data.pct_change().reindex(positions.index).fillna(0)
strategy_returns = (positions['MCD'] * returns['MCD'] +
positions['YUM'] * returns['YUM'])
# 可视化
(1 + strategy_returns).cumprod().plot(title='Enhanced Strategy Performance')三、关键问题诊断#
3.1 协整关系破裂场景#
code
# 断点检测(以2020年疫情为例)
import ruptures as rpt
clean_spread = spread.dropna()
model = rpt.Pelt(model="rbf").fit(clean_spread.values.reshape(-1, 1))
change_points = model.predict(pen=10)
# 标记结构变化点
plt.plot(spread)
for cp in change_points:
if cp < len(clean_spread): # ruptures 会把序列终点也作为分段边界返回
plt.axvline(x=clean_spread.index[cp], color='r', linestyle='--')3.2 交易成本影响分析#
code
# 交易成本模型
def apply_transaction_cost(returns, positions, cost=0.0005):
one_way_turnover = positions.diff().abs().sum(axis=1).fillna(0)
return returns - one_way_turnover * cost
net_returns = apply_transaction_cost(strategy_returns, positions)四、实战练习题#
题目1:动态对冲验证#
获取CVX(雪佛龙)和XOM(埃克森美孚)2015-2020年数据:
-
比较固定对冲比率与滚动对冲比率的表现差异
-
计算滚动窗口为60日与120日的夏普比率对比
题目2:市场状态过滤#
在原有策略中加入:
code
# 使用SPY判断市场趋势
spy = yf.download('SPY', start='2018-01-01')['Close']
market_up = spy.pct_change(30) > 0.03
# 只在市场震荡期交易
signals['valid'] = ~market_up五、风险控制方案#
5.1 止损机制#
code
# 动态跟踪止损
max_drawdown = 0.05 # 5%最大回撤
peak = (1 + strategy_returns).cumprod().cummax()
drawdown = (peak - (1 + strategy_returns).cumprod())/peak
signals['stop_loss'] = drawdown > max_drawdown5.2 头寸规模优化#
code
# 波动率倒数加权
volatility = returns.rolling(30).std()
position_size = 1 / volatility
position_size = position_size.div(position_size.sum(axis=1), axis=0)本案例给出配对交易的实现要点与优化方向。进一步研究时,应在独立样本中复核协整关系、信号延迟、交易成本和容量约束,并参考《Pairs Trading: Quantitative Methods and Analysis》深化理论理解。