4. 失效诊断框架:基于时间序列的因子衰减检测
做量化这几年,我见过太多因子从“明星”变成“流星”。
一开始回测曲线漂亮得让人心动,实盘跑着跑着就不行了。你想想看,这背后到底是什么在作祟?说白了,就是因子在衰减。今天我就跟你聊聊,怎么用时间序列的方法,给因子做个“体检”。
4.1 为什么要做因子衰减检测?
我个人习惯,每季度都会对实盘因子做一次全面诊断。为什么?因为市场在变,因子也会老。
我记得2020年有个规模因子特别火,小市值策略跑得飞起。结果到了2021年,风格突然切换,很多同行被打了个措手不及。如果当时做了衰减检测,至少能提前减仓。
因子衰减检测的核心目的有三个:
- 提前预警:在因子彻底失效前,发现异常信号
- 量化评估:用数据说话,而不是凭感觉判断
- 辅助决策:决定是调整权重、暂停使用,还是直接废弃
核心观点:因子衰减不是突然发生的,它一定有个过程。我们的任务就是在这个过程早期抓住它。
4.2 时间序列检测的三种主流方法
我在项目中用过不少方法,最后沉淀下来三种最实用的。嗯,这里要注意,不是越复杂越好,关键是能落地。
4.2.1 滚动窗口IC衰减法
这个方法最直观。把因子收益率按时间窗口滚动计算IC值,然后看趋势。
import pandas as pd
import numpy as np
def rolling_ic_decay(factor_returns, forward_returns, window=60):
"""
滚动窗口IC衰减检测
factor_returns: 因子收益率序列
forward_returns: 未来N日收益率
window: 滚动窗口大小(月数)
"""
ic_series = []
dates = factor_returns.index
for i in range(window, len(dates)):
# 取窗口内的数据
factor_window = factor_returns.iloc[i-window:i]
return_window = forward_returns.iloc[i-window:i]
# 计算Spearman相关系数
ic = factor_window.corr(return_window, method='spearman')
ic_series.append(ic)
# 检测衰减:最近12期IC均值 vs 前12期IC均值
recent_ic = np.mean(ic_series[-12:])
prior_ic = np.mean(ic_series[-24:-12])
decay_ratio = recent_ic / prior_ic if prior_ic != 0 else 0
return ic_series, decay_ratio
我曾经用这个方法抓到一个动量因子的衰减信号。当时decay_ratio跌到了0.6以下,我果断减仓,后来那个因子果然崩了。
实战技巧:我个人习惯把decay_ratio的阈值设在0.7。低于这个值,就要开始警惕了。
4.2.2 分段回归斜率检测
这个方法更精细。把因子收益序列分成若干段,每段做线性回归,看斜率的变化。
from scipy import stats
def segment_slope_detection(factor_returns, n_segments=4):
"""
分段回归斜率检测
factor_returns: 因子收益率序列
n_segments: 分段数量
"""
segment_len = len(factor_returns) // n_segments
slopes = []
for i in range(n_segments):
start = i * segment_len
end = (i + 1) * segment_len if i < n_segments - 1 else len(factor_returns)
segment_data = factor_returns.iloc[start:end]
x = np.arange(len(segment_data))
y = segment_data.values
# 线性回归
slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
slopes.append(slope)
# 检测斜率是否持续下降
slope_trend = np.polyfit(range(n_segments), slopes, 1)[0]
return slopes, slope_trend
你想想看,如果斜率从正变负,或者持续下降,说明因子的赚钱效应在减弱。我一般会结合p_value来看,如果斜率下降的同时p_value还显著,那基本可以确认衰减了。
4.2.3 累计收益曲线拐点检测
这个方法适合做可视化。把因子累计收益画出来,找拐点。
说白了,就是看累计收益曲线什么时候开始走平或者掉头。我习惯用滚动夏普比来辅助判断:
def cumulative_return_inflection(factor_returns, rolling_window=20):
"""
累计收益曲线拐点检测
"""
# 计算累计收益
cum_returns = (1 + factor_returns).cumprod()
# 滚动夏普比
rolling_sharpe = factor_returns.rolling(rolling_window).mean() / \
factor_returns.rolling(rolling_window).std()
# 检测拐点:滚动夏普比连续N期低于阈值
threshold = 0.5 # 年化夏普比阈值
consecutive_below = 0
inflection_points = []
for i, sharpe in enumerate(rolling_sharpe):
if sharpe < threshold:
consecutive_below += 1
else:
consecutive_below = 0
if consecutive_below >= 5: # 连续5期低于阈值
inflection_points.append(rolling_sharpe.index[i])
return cum_returns, rolling_sharpe, inflection_points
避坑指南:我曾经犯过一个错误——只看累计收益曲线,忽略了波动率的变化。后来发现,有些因子收益没变,但波动率翻倍了,这其实也是衰减的信号。
4.3 衰减检测的完整框架
上面三种方法各有侧重,我建议组合使用。下面是我在项目中用的框架:
这个框架的核心逻辑是:三种方法互相验证。如果两个以上方法都发出信号,那基本可以确认衰减了。
4.4 实战中的注意事项
做了这么多年,我总结了几条经验:
- 窗口大小要动态调整:市场波动大的时候,窗口要短一些;平稳的时候可以长一些。我一般用60个月做基准,然后根据市场波动率调整。
- 不要只看一个指标:IC、夏普比、最大回撤,这些指标要结合起来看。单一指标容易误判。
- 注意数据频率:日频数据噪声大,周频数据更稳定。我习惯先用周频做初步判断,再用日频做精细分析。
记住:因子衰减检测不是一次性的工作,而是持续的过程。我每个月底都会跑一遍这个框架,花不了多少时间,但能避免大亏。
好了,关于因子衰减检测就聊这么多。这套框架我用了好几年,帮我在几次风格切换中保住了收益。你可以在自己的策略上试试,看看哪些因子已经“老了”。
小建议:刚开始做的时候,可以先从最简单的滚动IC衰减法入手。等熟悉了,再逐步加入分段回归和拐点检测。别想一口吃成胖子。
公众号:蓝海数据掘金营,微信deep3321