2. 均值回归策略
任务目标
本案例将基于两只高度相关资产的价差序列,构建布林带与Z-Score均值回归策略。核心任务是:计算价差的Z-Score,设定入场与出场阈值,生成交易信号,并在历史数据上完成回测,输出策略的收益与风险指标。
实现思路
采用统计套利中经典的配对交易框架。首先计算两只资产价格的对数价差,然后通过滚动窗口计算价差的均值与标准差,进而得到标准化后的Z-Score序列。交易规则为:当Z-Score上穿入场阈值(如+1.5)时做空价差(卖高买低),下穿出场阈值(如+0.5)时平仓;对称地,当Z-Score下穿入场阈值(如-1.5)时做多价差,上穿出场阈值(如-0.5)时平仓。回测过程将逐日模拟持仓状态,并计算策略净值曲线及年化收益、夏普比率、最大回撤等核心风险指标。
完整代码
import numpy as np
import pandas as pd
from typing import Tuple
# ---------- 1. 生成模拟价差数据(用于演示,实际可替换为真实行情) ----------
def generate_spread_data(n: int = 500, seed: int = 42) -> pd.Series:
"""生成具有均值回归特性的价差序列(模拟数据)"""
np.random.seed(seed)
# 使用AR(1)过程模拟均值回归价差
phi = 0.85 # 回归系数,越小回归越快
noise_std = 0.5
spread = np.zeros(n)
for t in range(1, n):
spread[t] = phi * spread[t-1] + np.random.normal(0, noise_std)
return pd.Series(spread, index=pd.date_range('2022-01-01', periods=n, freq='D'))
# ---------- 2. 计算Z-Score ----------
def calculate_zscore(spread: pd.Series, window: int = 20) -> pd.Series:
"""计算滚动Z-Score: (当前值 - 滚动均值) / 滚动标准差 """
rolling_mean = spread.rolling(window=window).mean()
rolling_std = spread.rolling(window=window).std()
zscore = (spread - rolling_mean) / rolling_std
return zscore
# ---------- 3. 生成交易信号 ----------
def generate_signals(zscore: pd.Series, entry: float = 1.5, exit: float = 0.5) -> pd.Series:
"""
生成持仓信号(1=做多价差,-1=做空价差,0=空仓)
做多条件:Z-Score 下穿 -entry(价差过低,预期回升)
做空条件:Z-Score 上穿 +entry(价差过高,预期回落)
平仓条件:Z-Score 回归至 ±exit 以内
"""
signals = pd.Series(0, index=zscore.index, dtype=int)
position = 0 # 当前持仓状态
for i in range(1, len(zscore)):
z = zscore.iloc[i]
z_prev = zscore.iloc[i-1]
if position == 0: # 空仓,寻找入场机会
if z_prev < entry <= z: # 上穿 +entry → 做空
position = -1
elif z_prev > -entry >= z: # 下穿 -entry → 做多
position = 1
elif position == -1: # 持空仓,寻找平仓或反转机会
if z <= exit: # 回归至 +exit 以下 → 平空仓
position = 0
elif z <= -entry: # 直接下穿 -entry → 反手做多
position = 1
elif position == 1: # 持多仓
if z >= -exit: # 回归至 -exit 以上 → 平多仓
position = 0
elif z >= entry: # 直接上穿 +entry → 反手做空
position = -1
signals.iloc[i] = position
return signals
# ---------- 4. 回测引擎 ----------
def backtest(spread: pd.Series, signals: pd.Series) -> Tuple[pd.Series, dict]:
"""
回测策略。假设每次交易1单位价差,不考虑交易成本。
返回:策略净值序列 + 绩效指标字典
"""
# 价差日收益率(价差本身可交易,收益率为差分)
spread_ret = spread.diff().fillna(0)
# 策略日收益 = 持仓信号 * 价差日变动(做多赚价差上涨,做空赚价差下跌)
strategy_ret = signals.shift(1).fillna(0) * spread_ret
# 净值曲线
equity_curve = (1 + strategy_ret).cumprod()
# ---------- 绩效指标 ----------
total_return = equity_curve.iloc[-1] - 1
years = len(spread) / 252
annual_return = (1 + total_return) ** (1 / years) - 1 if years > 0 else 0
daily_std = strategy_ret.std()
sharpe = (strategy_ret.mean() / daily_std * np.sqrt(252)) if daily_std > 0 else 0
# 最大回撤
rolling_max = equity_curve.cummax()
drawdown = equity_curve / rolling_max - 1
max_drawdown = drawdown.min()
metrics = {
'总收益率': f'{total_return:.2%}',
'年化收益率': f'{annual_return:.2%}',
'夏普比率': f'{sharpe:.2f}',
'最大回撤': f'{max_drawdown:.2%}',
'交易次数': int((signals.diff() != 0).sum())
}
return equity_curve, metrics
# ---------- 5. 主程序:执行回测 ----------
if __name__ == '__main__':
# 参数设置
WINDOW = 20 # Z-Score 计算窗口
ENTRY = 1.5 # 入场阈值
EXIT = 0.5 # 出场阈值
# 生成数据并计算
spread = generate_spread_data()
zscore = calculate_zscore(spread, WINDOW)
signals = generate_signals(zscore, ENTRY, EXIT)
# 执行回测
equity_curve, metrics = backtest(spread, signals)
# 输出结果
print('=== 均值回归策略回测结果 ===')
for key, value in metrics.items():
print(f'{key}: {value}')
# 展示最近10日信号与净值
result_df = pd.DataFrame({
'价差': spread,
'Z-Score': zscore,
'持仓信号': signals,
'净值': equity_curve
})
print('\n=== 最近10日明细 ===')
print(result_df.tail(10).round(4))
运行方式
- 环境依赖:需要
numpy和pandas库,可通过pip install numpy pandas安装。 - 执行:将代码保存为
mean_reversion.py,在终端运行python mean_reversion.py。
验证方法与通过标准
程序运行后应输出回测绩效指标及最近10日的交易明细。通过标准如下:
- 信号逻辑正确:检查明细表中持仓信号列,确认当Z-Score上穿+1.5时信号变为-1,下穿-1.5时变为+1,回归至±0.5以内时归零。
- 净值曲线合理:由于模拟数据具有均值回归特性,策略净值应呈现平稳上升趋势,总收益率为正。
- 指标完整性:输出应包含总收益率、年化收益率、夏普比率、最大回撤和交易次数五项指标,且数值格式正确。
说明:本案例使用模拟数据演示完整策略流程。实际应用中,将
generate_spread_data() 替换为真实的两只股票价格序列,并计算对数价差(log(P1/P2))即可直接套用后续逻辑。当前实现未考虑交易成本与滑点,实盘应用时需在 backtest() 中扣除相应费用。