Skip to content

5.3 统计套利

概念详解

统计套利(Statistical Arbitrage,简称Stat Arb)是一类基于统计模型和均值回归假设的交易策略。与传统的无风险套利不同,统计套利不保证每次交易都盈利,而是在大量交易中依赖概率优势获利。

统计套利的三大支柱:

  1. 协整关系:两个或多个资产价格之间存在长期均衡关系
  2. 均值回归:价差偏离均衡水平后会回归
  3. 统计显著性:偏离达到一定阈值才触发交易
名词解释:配对交易

寻找两只历史价格高度相关的股票,当价差偏离均值时做多弱势股、做空强势股,押注价差回归。这是统计套利最简单的形式:找到一对"长相相似"的股票,计算它们的价差 = Price_A - hedge_ratio × Price_B,当价差偏离其历史均值超过阈值时开仓,回归均值时平仓。

名词解释:协整

两个或多个非平稳时间序列的线性组合是平稳的,则它们之间存在协整关系。协整是配对交易的数学基础——单纯的价格相关性高不等于可配对交易(可能只是伪回归),只有协整关系才能确保价差具有均值回归特性。

名词解释:对冲比率

做多1单位股票A的同时做空h单位的股票B,使得组合价值对B的波动免疫。对冲比率通常通过回归 PA=α+hPB+ϵ 来估计,其中 h 即为对冲比率。

统计套利的演化

阶段类型描述
第一代配对交易两只股票的价差回归
第二代篮子交易一篮子股票 vs 另一篮子或指数
第三代统计因子套利对因子残差做均值回归
第四代高频统计套利毫秒级的微观结构套利

数学原理

协整检验(Engle-Granger两步法)

Step 1: 用OLS估计长期均衡关系:yt=α+βxt+ϵt

Step 2: 检验残差 ϵ^t 是否平稳(ADF检验):

Δϵ^t=γϵ^t1+i=1pδiΔϵ^ti+utH0:γ=0 (无协整关系)

价差信号生成

对冲比率 β 通过回归 PA=α+βPB+ϵ 估计。价差定义为:

spreadt=PA,tβ^PB,t

Z-score标准化:

zt=spreadtμspreadσspread

交易信号:当 |zt|>threshold 时入场,当 |zt|<exit_thresholdsign(zt) 反转时平仓。

半衰期估计

价差均值回归的半衰期衡量偏离后回到一半距离所需的时间:

spreadtμ=θ(spreadt1μ)+ηtHalfLife=ln(2)ln(θ)

半衰期越短,回归速度越快,套利机会越频繁。如果半衰期过长(如超过一个季度),价差的回归可能更多是统计假象。

Python实战

📌 案例:价差信号

PYTHON3 行 · 141 B
📄此处有展示代码3 行 · 141 B展开 ▼
python
spread = df['stock1'] - hedge_ratio * df['stock2']
zscore = (spread - spread.mean()) / spread.std()
df['position'] = -zscore  # 均值回归
点击展开可浏览运行结果
对冲比率: 0.905
最新价差 z-score: 1.18
信号: 不交易(价差在正常范围)

📌 案例:完整的配对交易框架

PYTHON85 行 · 3.1 KB
📄此处有展示代码85 行 · 3.1 KB展开 ▼
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`。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。

📌 案例:多配对筛选

PYTHON51 行 · 1.8 KB
📄此处有展示代码51 行 · 1.8 KB展开 ▼
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`(从股票池中筛选最优的交易对)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。

常见误区

  1. 高相关性不等于协整:两只股票可能有0.95的高相关性(都跟随大盘),但它们的价差可能一直在扩大(没有均值回归)。协整才是配对交易的数学基础。

  2. 参数稳定性问题:对冲比率、价差的均值和标准差都可能随时间变化。用历史数据估计的参数在样本外可能已经失效。需要定期(如每月)重新估计。

  3. 忽略结构变化:并购、分拆、商业模式变化等结构性事件可能永久性地改变配对关系。以往高度协整的配对在事件后可能不再均值回归——这意味着策略可能变得单向亏损。

  4. 半衰期过长:价差回归半衰期如果超过30天,期间的持仓成本和心理压力会极大。而且半衰期越长,它越可能只是一个统计假象而非真正的经济联系。

  5. 忽略做空限制:配对交易通常需要做空一只股票。在A股市场,融券做空成本高、券源有限。如果不能做空,只能做"配对轮动"(超配低估的、低配高估的),而非纯多空配对。

实战练习

  1. 选择同一行业的两只股票(如贵州茅台和五粮液),执行完整的配对交易流程:

    • 进行协整检验
    • 估计对冲比率和半衰期
    • 实现Z-score交易策略
    • 回测过去3年的表现
  2. 滚动参数 vs 固定参数:分别用固定回看窗口(全样本估计一次参数)和滚动窗口(每季度重新估计参数)回测,对比两种方法的收益和稳定性差异。

  3. 研究半衰期的时变性:用滚动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." 现代统计套利框架的经典论文。

本章要点

  • 统计套利基于协整关系均值回归假设,通过做多低估资产、做空高估资产来获取价差回归的收益。
  • 协整是配对交易的数学基础——两个非平稳价格序列的线性组合是平稳的,意味着价差具有均值回归性质。
  • 对冲比率通过回归 PA=α+βPB+ϵ 估计,它决定了多空仓位的比例,确保组合对标的资产价格方向免疫。
  • 半衰期衡量价差回归的速度,半衰期过长(>30天)意味着回归可能不可靠,过短(<1天)几乎不可交易。
  • 统计套利的风险:结构变化导致协整关系破裂、参数不稳定、做空限制、流动性风险。
  • 交易信号的核心是Z-score:入场阈通常设在±1.5到±2.5之间,出场设在±0.5或均值附近。