问题描述
解决方案
在量化交易中,ADF(Augmented Dickey-Fuller)检验是判断时间序列(如股票价格、价差、残差等)是否具有**平稳性(Stationarity)**的常用统计方法。
平稳性是许多统计套利策略(如配对交易、均值回归)的前提。如果一个序列是平稳的,它的均值和方差不随时间变化,价格偏离均值后有很大通过率会回归。
ADF 检验原理简述
- 原假设 ($H_0$):序列存在单位根(Unit Root),即序列是不平稳的。
- 备择假设 ($H_1$):序列不存在单位根,即序列是平稳的。
- 判断标准:
- P-value(P值):如果 P值 < 显著性水平(通常为 0.05 或 0.01),则拒绝原假设,认为序列是平稳的。
- Test Statistic(t统计量):如果 t统计量小于临界值(1%, 5%, 10% Critical Values),则拒绝原假设,认为序列是平稳的。
PTrade 实现代码
在 PTrade 中,我们可以使用 Python 标准的第三方库 statsmodels 来进行 ADF 检验。
以下是一个完整的策略示例,该策略在每天盘前获取某只股票过去 100 天的收盘价,并对其进行 ADF 检验。
# 导入 statsmodels 库中的 adfuller 函数
from statsmodels.tsa.stattools import adfuller
import numpy as np
def initialize(context):
"""
初始化函数
"""
# 设置要检验的标的,这里以 600519.SS (贵州茅台) 为例
g.security = '600519.SS'
set_universe(g.security)
def before_trading_start(context, data):
"""
盘前处理函数,执行 ADF 检验
"""
# 1. 获取历史数据
# 获取过去 100 天的收盘价数据
# 注意:为了统计检验的有效性,建议数据长度至少在 30 个以上
history_data = get_history(100, frequency='1d', field='close', security_list=g.security)
# 提取收盘价序列 (numpy array)
close_prices = history_data['close'].values
# 2. 进行 ADF 检验
# adfuller 返回值是一个元组,包含统计量、P值等信息
adf_result = adfuller(close_prices)
# 3. 解析结果
adf_statistic = adf_result[0] # t统计量
p_value = adf_result[1] # P值
usedlag = adf_result[2] # 使用的滞后阶数
nobs = adf_result[3] # 样本数量
critical_values = adf_result[4] # 临界值字典 (1%, 5%, 10%)
# 4. 打印详细日志
log.info("========== ADF 平稳性检验报告 ==========")
log.info("标的: %s" % g.security)
log.info("T-Statistic (t统计量): %f" % adf_statistic)
log.info("P-Value (P值): %f" % p_value)
log.info("Critical Values (临界值):")
for key, value in critical_values.items():
log.info(" %s: %f" % (key, value))
# 5. 判断结论 (使用 5% 作为显著性水平)
if p_value < 0.05:
log.info("结论: P值 < 0.05,拒绝原假设,序列是【平稳】的。")
else:
log.info("结论: P值 >= 0.05,无法拒绝原假设,序列是【不平稳】的。")
# 额外演示:通常价格本身不平稳,但收益率可能是平稳的
# 计算一阶差分(即收益率近似值)
diff_prices = np.diff(close_prices)
# 剔除 NaN (如果有)
diff_prices = diff_prices[~np.isnan(diff_prices)]
adf_result_diff = adfuller(diff_prices)
p_value_diff = adf_result_diff[1]
log.info("--- 尝试对一阶差分(收益率)进行检验 ---")
log.info("一阶差分 P-Value: %f" % p_value_diff)
if p_value_diff < 0.05:
log.info("结论: 一阶差分序列是【平稳】的。")
log.info("========================================")
def handle_data(context, data):
"""
盘中运行函数
"""
pass
代码详解
- 数据获取 (
get_history):- 使用
get_history获取指定长度(如 100 天)的收盘价数据。ADF 检验对数据量有一定要求,数据太少会导致检验效力不足。
- 使用
- 调用
adfuller:from statsmodels.tsa.stattools import adfuller是核心导入语句。adfuller(series)接收一个一维数组或 Series。
- 返回值解析:
result[0]: Test Statistic。负得越多越好。result[1]: p-value。这是最直观的判断依据。result[4]: Critical Values。这是一个字典,展示了在 1%、5%、10% 置信度下的临界值。如果 Test Statistic 小于这些临界值,也可以判定为平稳。
- 差分处理:
- 在实际金融数据中,原始的价格序列(Price)通常是不平稳的(Random Walk)。
- 通常我们需要对价格进行一阶差分(即 $P_t - P_{t-1}$,近似于收益率),差分后的序列通常是平稳的。代码中包含了一个简单的差分检验逻辑作为演示。
注意事项
- Python 版本兼容性: 代码使用了
%格式化字符串,而不是 f-string,以确保在 PTrade 可能使用的旧版本 Python(如 3.5)环境中也能正常运行。 - 应用场景: 单独对一只股票的价格做 ADF 检验通常意义不大(因为大概率不平稳)。ADF 检验更多用于**配对交易(Pairs Trading)中,检验两只股票价格的价差(Spread)**或者是回归残差是否平稳。如果价差平稳,则可以进行均值回归交易。
Q&A
Q1: 为什么股票价格通常通不过 ADF 检验?
A1: 股票价格通常遵循“随机游走”模型,意味着当前的变动与过去无关,均值和方差随时间发散,因此是不平稳的。通常收益率(价格的变化量)才是平稳的。
Q2: 如果 P 值很小(例如 0.001),代表什么?
A2: P 值越小,拒绝原假设(不平稳)的证据越强。0.001 意味着有 99.9% 的信心认为该序列是平稳的,非常适合做均值回归策略。
Q3: 在 PTrade 中做配对交易,如何使用 ADF?
A3: 你需要获取两只股票的价格序列 $A$ 和 $B$,计算价差 $Spread = A - \beta \times B$($\beta$ 可通过 OLS 回归计算),然后对 $Spread$ 序列调用 adfuller。如果 $Spread$ 平稳,则两者存在协整关系。