4、领先滞后关系检测:Granger因果检验、交叉相关函数(CCF)、领先滞后指标构建
正股与联动标的之间,到底谁先动?谁后动?
这个问题,我做了这么多年量化,几乎每个项目都会遇到。你想想看,如果连谁领涨、谁跟涨都搞不清楚,那所谓的「联动策略」就是空中楼阁。
这一章,我们就来彻底解决这个问题。我会用三种方法,帮你把领先滞后关系「扒个精光」。
4.1 Granger因果检验:谁是谁的「因」?
Granger因果检验,名字听着挺唬人。说白了,就是问一个问题:用A的过去值,能不能更好地预测B的未来值?
如果能,我们就说「A是B的Granger原因」。注意,这不是哲学上的因果关系,而是统计学上的「预测关系」。我在项目中遇到过很多次,有人把Granger因果当成真正的因果,结果策略一上线就亏钱。嗯,这里要注意。
核心思想:
如果加入X的滞后项后,对Y的预测模型有显著改善,则X是Y的Granger原因。
具体怎么做?我们来看代码。我个人习惯用Python的statsmodels库,非常方便。
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import grangercausalitytests
# 假设df包含两列:'stock'(正股)和'bond'(联动标的)
# 我们想检验:正股是否是债券的Granger原因?
def granger_test(df, max_lag=5):
"""
执行Granger因果检验
df: DataFrame,包含两列
max_lag: 最大滞后阶数
"""
# 提取数据
data = df[['stock', 'bond']].dropna()
# 执行检验
test_result = grangercausalitytests(data, maxlag=max_lag, verbose=False)
# 整理结果
results = []
for lag in range(1, max_lag+1):
# 提取p值
p_value = test_result[lag][0]['ssr_ftest'][1]
results.append({
'lag': lag,
'p_value': p_value,
'significant': p_value < 0.05
})
return pd.DataFrame(results)
# 使用示例
result_df = granger_test(df, max_lag=5)
print(result_df)
我的经验:
滞后阶数的选择很关键。我一般会从1到10都试一遍,看哪个滞后阶数下p值最显著。另外,数据必须是平稳的,否则检验结果不可靠。我曾经因为忘了做平稳性检验,白白浪费了一周时间。
4.2 交叉相关函数(CCF):直观的「时间差」探测器
Granger因果检验告诉你「有没有关系」,但CCF告诉你「关系在哪个时间偏移上最强」。
CCF的原理很简单:把两个时间序列,一个固定,另一个左右平移,然后计算每个平移位置上的相关系数。相关系数最大的那个平移量,就是最可能的领先滞后阶数。
from scipy import signal
import matplotlib.pyplot as plt
def ccf_analysis(series1, series2, max_lag=20):
"""
计算交叉相关函数
series1, series2: 两个时间序列
max_lag: 最大滞后阶数
"""
# 标准化
s1 = (series1 - series1.mean()) / series1.std()
s2 = (series2 - series2.mean()) / series2.std()
# 计算CCF
ccf = signal.correlate(s1, s2, mode='full')
lags = signal.correlation_lags(len(s1), len(s2), mode='full')
# 只保留我们关心的滞后范围
mask = (lags >= -max_lag) & (lags <= max_lag)
ccf = ccf[mask]
lags = lags[mask]
# 归一化
ccf = ccf / len(s1)
return lags, ccf
# 使用示例
lags, ccf_values = ccf_analysis(df['stock'], df['bond'], max_lag=10)
# 找到最大相关系数对应的滞后
max_idx = np.argmax(np.abs(ccf_values))
best_lag = lags[max_idx]
print(f"最优滞后阶数: {best_lag}")
注意:
CCF对异常值非常敏感。我建议在使用前先做一次异常值处理。另外,如果两个序列都有很强的自相关性,CCF可能会产生「伪相关」。这时候,可以先对序列做「预白化」处理,也就是用AR模型过滤掉自相关部分。
4.3 领先滞后指标构建:把理论变成可交易的信号
好了,现在我们知道谁领先、谁滞后了。下一步,就是把这个关系变成一个可以用的指标。
我个人习惯构建一个「领先滞后强度指标」,它综合了Granger检验的显著性和CCF的滞后阶数信息。
def build_lead_lag_indicator(df, stock_col='stock', bond_col='bond',
granger_lag=3, ccf_max_lag=10):
"""
构建领先滞后指标
"""
# 1. 计算滚动Granger检验的p值
# 这里用滚动窗口,比如过去60天
window = 60
p_values = []
for i in range(window, len(df)):
window_data = df.iloc[i-window:i][[stock_col, bond_col]]
test_result = grangercausalitytests(window_data, maxlag=granger_lag, verbose=False)
p_val = test_result[granger_lag][0]['ssr_ftest'][1]
p_values.append(p_val)
# 2. 计算滚动CCF的最优滞后
best_lags = []
for i in range(window, len(df)):
window_data = df.iloc[i-window:i]
lags, ccf = ccf_analysis(window_data[stock_col], window_data[bond_col],
max_lag=ccf_max_lag)
max_idx = np.argmax(np.abs(ccf))
best_lags.append(lags[max_idx])
# 3. 合成指标
indicator = pd.DataFrame({
'date': df.index[window:],
'granger_p': p_values,
'best_lag': best_lags
})
# 当Granger检验显著(p<0.05)且滞后阶数>0时,认为存在领先关系
indicator['lead_lag_signal'] = 0
indicator.loc[(indicator['granger_p'] < 0.05) & (indicator['best_lag'] > 0),
'lead_lag_signal'] = 1
indicator.loc[(indicator['granger_p'] < 0.05) & (indicator['best_lag'] < 0),
'lead_lag_signal'] = -1
return indicator
# 使用示例
indicator = build_lead_lag_indicator(df)
print(indicator.tail())
实战建议:
这个指标不是一成不变的。市场环境变了,领先滞后关系也会变。我一般会每周重新计算一次,动态调整。另外,当指标显示「无显著关系」时,我建议暂停该策略,不要强行交易。
4.4 本章知识体系
下面这张图,是我自己总结的领先滞后检测的完整流程。你可以把它当作一个检查清单。
这张图把整个流程串起来了。从数据准备开始,到Granger检验判断关系是否存在,再到CCF确定具体的时间偏移,最后构建成可交易的指标。每一步都环环相扣。
核心要点回顾:
- Granger因果检验回答「有没有关系」,CCF回答「关系在哪个时间偏移」
- 两个方法要配合使用,缺一不可
- 指标需要动态更新,市场环境变了,关系也会变
- 当没有显著关系时,不要强行交易
好了,这一章的内容就到这里。领先滞后检测是正股联动策略的「眼睛」,没有它,你就是在黑暗中交易。下一章,我们会把这些指标组合起来,构建一个完整的交易策略。
公众号:蓝海资料掘金营,微信deep3321