跳到主要内容

配对交易 · 协整

:::info 学习目标 你将能区别相关与协整,使用滚动历史估计对冲比率,构造进出场状态机,并计入双腿成本、借券和结构断裂风险。 :::

直觉

配对交易是统计套利的经典:找两只「长期绑在一起」的股票(比如同一行业的龙头),它们的价差虽然时大时小,但总会回归均值。价差拉得太开就「做空强的、做多弱的」,赌它收敛。关键前提是两只股票协整(cointegrated)——各自乱走,但差值平稳。

协整与价差

两只股票价格 y,xy, x 各自是非平稳的,但若存在 β\beta 使

spreadt=lnytβlnxt\text{spread}_t = \ln y_t - \beta \ln x_t

平稳(均值与方差恒定),则称二者协整,β\beta 是对冲比率。

「人话」解释:协整 vs 相关,区别在哪?

相关是「同涨同跌」——两个都涨的序列相关性可能很高。 协整更强:要求「差值」稳定在某个水平附近,不会越走越远。 两条同时上涨的线可能高度相关、但差值发散(不协整);只有差值「像橡皮筋拉住」的,才能做配对交易——因为你可以确信「价差终会回来」。

交易规则(z-score 进出场)

用价差的滚动 z-score:

zt=spreadtspread60σ60(spread)z_t = \frac{\text{spread}_t - \overline{\text{spread}}_{60}}{\sigma_{60}(\text{spread})}
  • z<2z < -2 → 价差过低,做多价差(多 yy、空 βx\beta x);
  • z>+2z > +2 → 价差过高,做空价差
  • z<0.5|z| < 0.5 → 回归,平仓

可运行案例:AAPL / MSFT 配对(合成数据)

读取 aapl_msft_daily.csv,用过去 252 日滚动 OLS 估计 β\beta,构造价差与 z-score,按规则交易。滚动估计确保早期交易不会偷看后面的样本;价差组合收益还要扣除双腿换手成本。

:::caution 合成数据 本数据刻意协整(真值 β1.30\beta\approx1.30,价差 ADF t142.86t\approx-14\ll-2.86)。你估计出的 β\beta 会偏离真值——这正是「估计误差」的教学点,并非套利 bug。 :::

import quant
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('/data/aapl_msft_daily.csv', parse_dates=['date']).set_index('date')
pa, pm = df['aapl_close'], df['msft_close']
la, lm = np.log(pa), np.log(pm)

# 仅用过去 252 日做滚动 OLS: la ~ a + b*lm
window = 252
b = la.rolling(window).cov(lm) / lm.rolling(window).var()
a = la.rolling(window).mean() - b * lm.rolling(window).mean()
spread = la - (a + b*lm)
print(f"滚动 β 中位数 = {b.median():.3f}   (合成数据真值 ≈ 1.30)")

z = (spread - spread.rolling(60).mean()) / spread.rolling(60).std()

raw = pd.Series(np.nan, index=pa.index)
raw[z < -2.0] = 1.0
raw[z >  2.0] = -1.0
raw[z.abs() < 0.5] = 0.0
signal = raw.ffill().fillna(0.0)

# 昨日估计的 β 决定今日对冲;双腿换手都要付成本
ra = np.log(pa / pa.shift(1)); rm = np.log(pm / pm.shift(1))
spread_ret = (ra - b.shift(1)*rm).fillna(0.0)
position = signal.shift(1).fillna(0.0)        # 防前视(同引擎约定)
leg_y = position
leg_x = (-position * b.shift(1)).fillna(0.0)
turnover = leg_y.diff().abs().fillna(0) + leg_x.diff().abs().fillna(0)
strat_ret = (position * spread_ret - turnover * 2/1e4).fillna(0.0)
equity = (1 + strat_ret).cumprod()

print(quant.performance_summary(equity, strat_ret, freq=252).round(3))

fig, ax = plt.subplots(2, 1, figsize=(9, 5), sharex=True,
                     gridspec_kw={'height_ratios':[2,1]})
ax[0].plot(z, color='#2563eb', lw=0.9); ax[0].axhline(2, color='r', ls='--', lw=0.8)
ax[0].axhline(-2, color='g', ls='--', lw=0.8); ax[0].axhline(0, color='gray', lw=0.8)
ax[0].set_title('价差 z-score(红=做空价差, 绿=做多价差)')
ax[1].plot(equity, color='crimson'); ax[1].set_title('配对策略资金曲线')
plt.tight_layout(); plt.show()

动手改一改:拖动阈值即时看回测

拖动进场阈值 z|z| 与平仓阈值——阈值越小交易越频繁、成本越敏感;越大越挑剔、信号越少。

# ParamSlider(SSR 预览)
参数: ENTRY, EXIT

import quant
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('/data/aapl_msft_daily.csv', parse_dates=['date']).set_index('date')
pa, pm = df['aapl_close'], df['msft_close']
la, lm = np.log(pa), np.log(pm)
window = 252
b = la.rolling(window).cov(lm) / lm.rolling(window).var()
a = la.rolling(window).mean() - b * lm.rolling(window).mean()
spread = la - (a + b * lm)
z = (spread - spread.rolling(60).mean()) / spread.rolling(60).std()

raw = pd.Series(np.nan, index=pa.index)
raw[z < -ENTRY] = 1.0
raw[z >  ENTRY] = -1.0
raw[z.abs() < EXIT] = 0.0
signal = raw.ffill().fillna(0.0)

ra = np.log(pa / pa.shift(1)); rm = np.log(pm / pm.shift(1))
spread_ret = (ra - b.shift(1) * rm).fillna(0.0)
position = signal.shift(1).fillna(0.0)
leg_y = position
leg_x = (-position * b.shift(1)).fillna(0.0)
turnover = leg_y.diff().abs().fillna(0) + leg_x.diff().abs().fillna(0)
strat = (position * spread_ret - turnover * 2/1e4).fillna(0.0)
eq = (1 + strat).cumprod()

print(quant.performance_summary(eq, strat, freq=252).round(3))
plt.figure(figsize=(9, 3.4))
plt.plot(eq, color='crimson')
plt.title(f'配对策略  进场 |z|>{ENTRY}  平仓 |z|<{EXIT}'); plt.ylabel('净值')
plt.tight_layout(); plt.show()

进一步:把滚动窗口从 252 改成 126504。短窗口适应快但估计噪声大,长窗口稳定却可能跟不上结构变化;比较的不只是收益,还应看 β\beta 的稳定性和换手。

失效机制与研究检查

协整是统计关系,不是经济保证。公司基本面、指数纳入、并购或商业模式变化都可能让关系永久断裂。若先扫描大量配对再挑 p 值最小者,会有严重数据窥探;应保留发现期/验证期,并监控滚动 ADF、β\beta 和半衰期。仓位还应按两腿名义价值或风险统一归一化。

实践与延伸

  1. 加入滚动协整失效阈值,比较“继续持有”和“立即停机”。
  2. 分别按股数、美元和波动率中性构造两腿权重。

延伸阅读:Engle–Granger;Gatev、Goetzmann 与 Rouwenhorst 的配对交易研究。

小结

  • 配对交易赌的是协整价差的均值回归
  • β\beta 应使用当时可得历史滚动估计,价差 =lnyβlnx=\ln y-\beta\ln x,z-score 决定进出场;
  • 多空两条腿都产生换手、价差和借券成本,不能只看无成本价差收益;
  • 协整 \ne 相关——只有差值平稳的「橡皮筋」关系才能套利。

章末自测

检查核心概念

1. 关于本章主题,哪项说法更准确?