Python策略开发详解:均值回归与配对交易策略的回测及可视化实现
基于Python的策略开发与回测:均值回归 – 配对交易策略(附可视化代码)
1. 策略原理深度解析
核心逻辑:
寻找两个具有长期均衡关系的资产(如可口可乐与百事可乐股票),当价差偏离历史均值时:
数学基础:
- 协整检验:通过ADF检验判断价差序列的平稳性(p值<0.05)
- 对冲比例:通过OLS回归确定资产配比 ( y = βx + ε )
- 交易信号:价差Z-score突破±1.5标准差触发交易
2. 完整策略实现代码
pip install matplotlib statsmodels pandas numpy
步骤1:生成协整资产模拟数据
import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller
# 生成具有协整关系的价格序列
np.random.seed(42)
days = 1000
# 基础资产X
x = np.cumprod(1 + np.random.normal(0.0005, 0.02, days)) * 100
# 协整资产Y = 0.8X + 噪声
spread = np.random.normal(0, 2, days) # 平稳价差
y = 0.8 * x + spread
data = pd.DataFrame({'Asset_X': x, 'Asset_Y': y},
index=pd.date_range('2020-01-01', periods=days))
步骤2:协整检验与对冲比例计算
# 协整检验
def cointegration_test(series1, series2):
model = sm.OLS(series1, sm.add_constant(series2)).fit()
residuals = model.resid
adf_result = adfuller(residuals)
return adf_result[1] # 返回p值
p_value = cointegration_test(data['Asset_X'], data['Asset_Y'])
print(f'ADF检验p值: {p_value:.4f}') # 应小于0.05
# 计算对冲比例
model = sm.OLS(data['Asset_X'], sm.add_constant(data['Asset_Y'])).fit()
beta = model.params[1]
print(f'对冲比例: 1单位Y对应{beta:.2f}单位X')
输出结果:
ADF检验p值: 0.0000
对冲比例: 1单位Y对应1.24单位X
步骤3:价差分析与信号生成
# 计算价差序列
data['Spread'] = data['Asset_X'] - beta * data['Asset_Y']
# 计算Z-score
lookback = 60
data['Mean'] = data['Spread'].rolling(lookback).mean()
data['Std'] = data['Spread'].rolling(lookback).std()
data['Zscore'] = (data['Spread'] - data['Mean']) / data['Std']
# 生成交易信号
data['Signal'] = 0
data.loc[data['Zscore'] > 1.5, 'Signal'] = -1 # 做空价差(卖X买Y)
data.loc[data['Zscore'] < -1.5, 'Signal'] = 1 # 做多价差(买X卖Y)
data.loc[abs(data['Zscore']) < 0.5, 'Signal'] = 0 # 平仓
3. 可视化代码与解析
可视化1:资产价格与价差关系
plt.figure(figsize=(14, 9))
# 左轴绘制资产价格
ax1 = plt.subplot(211)
ax1.plot(data['Asset_X'], label='Asset X', color='#1f77b4')
ax1.plot(data['Asset_Y'], label='Asset Y', color='#ff7f0e')
ax1.set_title('Cointegrated Assets Price Movement', pad=15)
ax1.legend(loc='upper left')
ax1.grid(alpha=0.3)
# 右轴绘制价差
ax2 = plt.subplot(212)
ax2.plot(data['Spread'], label='Spread (X - βY)', color='#2ca02c')
ax2.axhline(data['Spread'].mean(), color='#d62728', linestyle='--', label='Mean')
ax2.fill_between(data.index,
data['Mean'] + 1.5*data['Std'],
data['Mean'] - 1.5*data['Std'],
color='gray', alpha=0.2, label='±1.5σ')
ax2.set_title('Spread Series with Mean Reversion Bands', pad=15)
ax2.legend()
ax2.grid(alpha=0.3)
plt.tight_layout()
plt.show()
图表说明:
可视化2:Z-score信号触发点
plt.figure(figsize=(12, 5))
# 绘制Z-score
plt.plot(data['Zscore'], color='#9467bd', label='Z-score')
plt.axhline(1.5, color='red', linestyle='--', alpha=0.7, label='Upper Threshold')
plt.axhline(-1.5, color='green', linestyle='--', alpha=0.7, label='Lower Threshold')
# 标注交易信号
buy_signals = data[data['Signal'] == 1]
sell_signals = data[data['Signal'] == -1]
plt.scatter(buy_signals.index, buy_signals['Zscore'],
marker='^', color='lime', s=100, edgecolors='black', label='Long Spread')
plt.scatter(sell_signals.index, sell_signals['Zscore'],
marker='v', color='red', s=100, edgecolors='black', label='Short Spread')
# 图表装饰
plt.title('Z-score Signal Triggers', pad=15)
plt.ylabel('Z-score')
plt.legend(loc='upper left', ncol=2)
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
图表说明:
4. 关键风险控制
- 协整断裂监测:
# 滚动窗口协整检验 rolling_pvalues = [cointegration_test(data['Asset_X'].iloc[i-120:i], data['Asset_Y'].iloc[i-120:i]) for i in range(120, len(data))]
- 动态头寸调整:根据波动率调整仓位大小
- 交易成本模型:
commission = 0.001 # 单边千分之一手续费 slippage = 0.002 # 滑点成本
作者:灏瀚星空