4. 平稳性检验与配对交易
任务目标
本案例选取两只具有潜在协整关系的股票(以浦发银行与兴业银行日收盘价为例),计算其价差序列,并对该价差进行ADF(Augmented Dickey-Fuller)平稳性检验。根据检验结果判断价差是否平稳,若平稳则进一步构建基于价差均值回归的配对交易信号(开仓与平仓阈值)。
数据准备与依赖
本案例使用Python实现,所需依赖库:numpy、pandas、statsmodels、yfinance(用于获取股票数据)。若无法访问网络获取实时数据,可使用本地CSV文件替代,代码中已提供两种数据加载方式。
核心实现
import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller
import yfinance as yf
# ========== 1. 数据获取 ==========
def load_data():
"""获取浦发银行(600000.SS)与兴业银行(601166.SS)近1年日收盘价"""
try:
df = yf.download(['600000.SS', '601166.SS'], period='1y', interval='1d')['Close']
df.columns = ['pf', 'xy']
df = df.dropna()
return df
except Exception as e:
print(f"网络数据获取失败,请使用本地CSV: {e}")
# 本地CSV需包含 date, pf, xy 三列
df = pd.read_csv('pairs_data.csv', parse_dates=['date'], index_col='date')
return df
# ========== 2. 价差计算与ADF检验 ==========
def calculate_spread(df):
"""计算对数价差: log(P_pf) - beta * log(P_xy)"""
# 使用OLS估计beta(协整回归)
X = sm.add_constant(np.log(df['xy']))
model = sm.OLS(np.log(df['pf']), X).fit()
beta = model.params['xy']
spread = np.log(df['pf']) - beta * np.log(df['xy'])
return spread, beta
def adf_test(spread):
"""ADF平稳性检验,返回检验统计量与p值"""
result = adfuller(spread, autolag='AIC')
return {'statistic': result[0], 'p_value': result[1],
'critical_values': result[4]}
# ========== 3. 交易信号生成 ==========
def generate_signals(spread, entry_z=2.0, exit_z=0.5):
"""基于价差Z-Score生成配对交易信号
返回: DataFrame包含价差、Z-Score、持仓方向(1多价差/-1空价差/0平仓)
"""
zscore = (spread - spread.mean()) / spread.std()
signals = pd.DataFrame(index=spread.index)
signals['spread'] = spread
signals['zscore'] = zscore
signals['position'] = 0 # 0:空仓, 1:做多价差, -1:做空价差
position = 0
for i in range(1, len(signals)):
z = zscore.iloc[i]
# 开仓条件
if position == 0:
if z > entry_z: # 价差过高,做空价差
position = -1
elif z < -entry_z: # 价差过低,做多价差
position = 1
# 平仓条件
elif position == -1 and z < exit_z:
position = 0
elif position == 1 and z > -exit_z:
position = 0
signals.iloc[i, signals.columns.get_loc('position')] = position
return signals
# ========== 4. 主流程 ==========
if __name__ == "__main__":
# 加载数据
df = load_data()
print(f"数据范围: {df.index[0].date()} 至 {df.index[-1].date()}, 共{len(df)}个交易日")
# 计算价差
spread, beta = calculate_spread(df)
print(f"协整系数 beta = {beta:.4f}")
# ADF检验
adf_result = adf_test(spread)
print("\n===== ADF平稳性检验结果 =====")
print(f"ADF统计量: {adf_result['statistic']:.4f}")
print(f"p值: {adf_result['p_value']:.4f}")
print("临界值:")
for key, val in adf_result['critical_values'].items():
print(f" {key}: {val:.4f}")
# 判断平稳性
is_stationary = adf_result['p_value'] < 0.05
print(f"\n结论: 价差序列{'平稳' if is_stationary else '不平稳'} (p值{'<' if is_stationary else '>'} 0.05)")
# 若平稳则生成交易信号
if is_stationary:
signals = generate_signals(spread)
# 统计信号分布
pos_counts = signals['position'].value_counts()
print("\n===== 交易信号统计 =====")
print(f"做多价差天数: {pos_counts.get(1, 0)}")
print(f"做空价差天数: {pos_counts.get(-1, 0)}")
print(f"空仓天数: {pos_counts.get(0, 0)}")
print("\n最近5日信号:")
print(signals.tail())
else:
print("\n价差非平稳,不适用配对交易策略,建议尝试其他股票对或差分处理。")
运行与验证
运行方式:直接执行上述Python脚本。若使用本地数据,请确保CSV文件包含date(日期)、pf(浦发收盘价)、xy(兴业收盘价)三列。
验证方法:
- 观察ADF检验输出:若p值小于0.05,则拒绝“存在单位根”的原假设,判定价差序列平稳,符合配对交易前提。
- 对比ADF统计量与1%、5%、10%临界值:统计量小于临界值则对应置信水平下平稳。
- 若价差平稳,检查生成的交易信号:当Z-Score超过+2时产生做空信号(position=-1),低于-2时产生做多信号(position=1),回归至±0.5以内时平仓(position=0)。
预期输出示例(基于历史数据,实际数值因数据区间不同而异):
数据范围: 2024-01-02 至 2025-01-02, 共242个交易日
协整系数 beta = 0.9821
===== ADF平稳性检验结果 =====
ADF统计量: -3.4521
p值: 0.0087
临界值:
1%: -3.458
5%: -2.873
10%: -2.573
结论: 价差序列平稳 (p值< 0.05)
关键说明
本案例中ADF检验的滞后阶数由AIC准则自动确定。若检验结果不平稳,可尝试对价差进行一阶差分后再检验,或更换股票对重新估计协整系数。交易信号的阈值(入场2倍标准差、出场0.5倍标准差)为经验默认值,实际应用中可根据历史回测优化。