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(兴业收盘价)三列。

验证方法:

  1. 观察ADF检验输出:若p值小于0.05,则拒绝“存在单位根”的原假设,判定价差序列平稳,符合配对交易前提。
  2. 对比ADF统计量与1%、5%、10%临界值:统计量小于临界值则对应置信水平下平稳。
  3. 若价差平稳,检查生成的交易信号:当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倍标准差)为经验默认值,实际应用中可根据历史回测优化。