主题切换
5.3 统计套利
概念详解
统计套利(Statistical Arbitrage,简称Stat Arb)是一类基于统计模型和均值回归假设的交易策略。与传统的无风险套利不同,统计套利不保证每次交易都盈利,而是在大量交易中依赖概率优势获利。
统计套利的三大支柱:
- 协整关系:两个或多个资产价格之间存在长期均衡关系
- 均值回归:价差偏离均衡水平后会回归
- 统计显著性:偏离达到一定阈值才触发交易
名词解释:配对交易
寻找两只历史价格高度相关的股票,当价差偏离均值时做多弱势股、做空强势股,押注价差回归。这是统计套利最简单的形式:找到一对"长相相似"的股票,计算它们的价差 = Price_A - hedge_ratio × Price_B,当价差偏离其历史均值超过阈值时开仓,回归均值时平仓。
名词解释:协整
两个或多个非平稳时间序列的线性组合是平稳的,则它们之间存在协整关系。协整是配对交易的数学基础——单纯的价格相关性高不等于可配对交易(可能只是伪回归),只有协整关系才能确保价差具有均值回归特性。
名词解释:对冲比率
做多1单位股票A的同时做空h单位的股票B,使得组合价值对B的波动免疫。对冲比率通常通过回归
统计套利的演化
| 阶段 | 类型 | 描述 |
|---|---|---|
| 第一代 | 配对交易 | 两只股票的价差回归 |
| 第二代 | 篮子交易 | 一篮子股票 vs 另一篮子或指数 |
| 第三代 | 统计因子套利 | 对因子残差做均值回归 |
| 第四代 | 高频统计套利 | 毫秒级的微观结构套利 |
数学原理
协整检验(Engle-Granger两步法)
Step 1: 用OLS估计长期均衡关系:
Step 2: 检验残差
价差信号生成
对冲比率
Z-score标准化:
交易信号:当
半衰期估计
价差均值回归的半衰期衡量偏离后回到一半距离所需的时间:
半衰期越短,回归速度越快,套利机会越频繁。如果半衰期过长(如超过一个季度),价差的回归可能更多是统计假象。
Python实战
📌 案例:价差信号
此处有展示代码展开 ▼
python
spread = df['stock1'] - hedge_ratio * df['stock2']
zscore = (spread - spread.mean()) / spread.std()
df['position'] = -zscore # 均值回归点击展开可浏览运行结果
对冲比率: 0.905 最新价差 z-score: 1.18 信号: 不交易(价差在正常范围)
📌 案例:完整的配对交易框架
此处有展示代码展开 ▼
python
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import adfuller, coint
class PairsTrader:
def __init__(self, lookback=60, entry_z=2.0, exit_z=0.5, min_half_life=1, max_half_life=30):
"""
Parameters:
lookback: 用于估计对冲比率和价差统计的回看窗口
entry_z: 入场Z-score阈值
exit_z: 出场Z-score阈值
min_half_life: 可接受的最短半衰期(天)
max_half_life: 可接受的最长半衰期(天)
"""
self.lookback = lookback
self.entry_z = entry_z
self.exit_z = exit_z
self.min_half_life = min_half_life
self.max_half_life = max_half_life
def estimate_hedge_ratio(self, price_a, price_b):
"""用滚动窗口回归估计对冲比率"""
y = price_a.values
X = np.column_stack([np.ones(len(price_b)), price_b.values])
beta = np.linalg.lstsq(X, y, rcond=None)[0]
return beta[0], beta[1] # intercept, hedge_ratio
def compute_half_life(self, spread):
"""估计价差的均值回归半衰期"""
spread_lag = spread.shift(1)
spread_diff = spread - spread_lag
spread_lag = spread_lag.dropna()
spread_diff = spread_diff.dropna()
# OLS: spread_t - spread_{t-1} = gamma * spread_{t-1} + const
X = np.column_stack([np.ones(len(spread_lag)), spread_lag.values])
y = spread_diff.values
if len(X) < 10:
return np.inf
beta = np.linalg.lstsq(X, y, rcond=None)[0]
theta = beta[1] + 1 # AR(1)系数
if theta <= 0:
return np.inf
half_life = -np.log(2) / np.log(theta)
return min(half_life, 1e6) # 防止无穷大
def generate_signals(self, price_a, price_b):
"""生成配对交易信号"""
# 滚动估计
alpha, hedge_ratio = self.estimate_hedge_ratio(
price_a.iloc[:self.lookback], price_b.iloc[:self.lookback]
)
# 计算全期价差
spread = price_a - hedge_ratio * price_b
# 检查协整性
_, p_value, _ = coint(price_a, price_b)
if p_value > 0.05:
return pd.Series(0, index=price_a.index), {'coint_pvalue': p_value}
# 检查半衰期
spread_hist = spread.iloc[:self.lookback]
half_life = self.compute_half_life(spread_hist)
if half_life < self.min_half_life or half_life > self.max_half_life:
return pd.Series(0, index=price_a.index), {'half_life': half_life}
# 计算滚动Z-score
rolling_mean = spread.rolling(self.lookback).mean()
rolling_std = spread.rolling(self.lookback).std()
zscore = (spread - rolling_mean) / rolling_std
# 生成信号:均值回归方向
signals = pd.Series(0, index=price_a.index)
signals[zscore < -self.entry_z] = 1 # 价差过低,做多spread
signals[zscore > self.entry_z] = -1 # 价差过高,做空spread
# 出场信号:Z-score回到阈值内
signals[abs(zscore) < self.exit_z] = 0
return signals, {'coint_pvalue': p_value, 'half_life': half_life}点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:类 `PairsTrader`。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。
📌 案例:多配对筛选
此处有展示代码展开 ▼
python
def pairs_screening(price_df, n_top=10):
"""
从股票池中筛选最优的交易对
步骤:
1. 计算所有配对的相关性和协整p值
2. 筛选协整显著且半衰期合理的配对
3. 按夏普比率排序,选择前N对
"""
stocks = price_df.columns
pairs_results = []
for i, stock_a in enumerate(stocks):
for stock_b in stocks[i+1:]:
pa = price_df[stock_a].dropna()
pb = price_df[stock_b].dropna()
# 对齐时间
common_idx = pa.index.intersection(pb.index)
if len(common_idx) < 252: # 至少一年数据
continue
pa = pa[common_idx]
pb = pb[common_idx]
# 相关性
corr = pa.pct_change().corr(pb.pct_change())
# 协整检验
_, p_value, _ = coint(pa, pb)
# 如果通过初筛,做完整的回测
if p_value < 0.1 and corr > 0.6:
trader = PairsTrader()
signals, info = trader.generate_signals(pa, pb)
if info['half_life'] < 60: # 半衰期在合理范围
rets_a = pa.pct_change()
rets_b = pb.pct_change()
spread_rets = signals.shift(1) * (rets_a - info.get('hedge_ratio', 1) * rets_b)
sharpe = spread_rets.mean() / spread_rets.std() * np.sqrt(252)
pairs_results.append({
'stock_a': stock_a, 'stock_b': stock_b,
'correlation': corr,
'coint_pvalue': p_value,
'half_life': info['half_life'],
'sharpe': sharpe
})
return pd.DataFrame(pairs_results).sort_values('sharpe', ascending=False).head(n_top)点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:函数 `pairs_screening`(从股票池中筛选最优的交易对)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。
常见误区
高相关性不等于协整:两只股票可能有0.95的高相关性(都跟随大盘),但它们的价差可能一直在扩大(没有均值回归)。协整才是配对交易的数学基础。
参数稳定性问题:对冲比率、价差的均值和标准差都可能随时间变化。用历史数据估计的参数在样本外可能已经失效。需要定期(如每月)重新估计。
忽略结构变化:并购、分拆、商业模式变化等结构性事件可能永久性地改变配对关系。以往高度协整的配对在事件后可能不再均值回归——这意味着策略可能变得单向亏损。
半衰期过长:价差回归半衰期如果超过30天,期间的持仓成本和心理压力会极大。而且半衰期越长,它越可能只是一个统计假象而非真正的经济联系。
忽略做空限制:配对交易通常需要做空一只股票。在A股市场,融券做空成本高、券源有限。如果不能做空,只能做"配对轮动"(超配低估的、低配高估的),而非纯多空配对。
实战练习
选择同一行业的两只股票(如贵州茅台和五粮液),执行完整的配对交易流程:
- 进行协整检验
- 估计对冲比率和半衰期
- 实现Z-score交易策略
- 回测过去3年的表现
滚动参数 vs 固定参数:分别用固定回看窗口(全样本估计一次参数)和滚动窗口(每季度重新估计参数)回测,对比两种方法的收益和稳定性差异。
研究半衰期的时变性:用滚动2年窗口每季度估计一次半衰期,绘制半衰期的时间序列。观察半衰期是否稳定,讨论不稳定的原因。
延伸阅读
- Vidyamurthy, G. (2004). Pairs Trading: Quantitative Methods and Analysis. Wiley. 配对交易最全面的专著。
- Alexander, C. (2001). Market Models. Wiley. 深入讨论协整及其在金融中的应用。
- Engle, R. F. & Granger, C. W. J. (1987). "Co-Integration and Error Correction." Econometrica. 协整理论的奠基之作。
- Avellaneda, M. & Lee, J. H. (2010). "Statistical Arbitrage in the US Equities Market." 现代统计套利框架的经典论文。
本章要点
- 统计套利基于协整关系和均值回归假设,通过做多低估资产、做空高估资产来获取价差回归的收益。
- 协整是配对交易的数学基础——两个非平稳价格序列的线性组合是平稳的,意味着价差具有均值回归性质。
- 对冲比率通过回归
估计,它决定了多空仓位的比例,确保组合对标的资产价格方向免疫。 - 半衰期衡量价差回归的速度,半衰期过长(>30天)意味着回归可能不可靠,过短(<1天)几乎不可交易。
- 统计套利的风险:结构变化导致协整关系破裂、参数不稳定、做空限制、流动性风险。
- 交易信号的核心是Z-score:入场阈通常设在±1.5到±2.5之间,出场设在±0.5或均值附近。