3步算出因子IC半衰期:用 gs-quant 拟合衰减曲线
2026/9/13 4:33:13 网站建设 项目流程

3步算出因子IC半衰期:用 gs-quant 拟合衰减曲线

【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant

某动量因子回测曲线漂亮,三个月后IC悄悄腰斩——这是量化团队最常遇到的"因子有效性"塌方。与其反复换因子,不如先用 gs-quant 把它的 IC 半衰期算出来,定位衰减到底快还是慢。

IC 是信息系数(Information Coefficient),衡量当期因子暴露与下期收益的相关强度。它不是静态数字,而是一条会随时间走低的时间序列。把这条序列拟合到指数上,就能得到一个有业务含义的参数:因子信号"半衰"到一半所需的时间,也就是 IC 半衰期。

为什么值得盯住IC衰减

把因子想成一管药。药效打进去后不会永远维持峰值,而是按时间一点点消退。IC 也是同理:刚计算出来的那一期信息系数最高,往后每一期都在衰减。这种衰减在多数因子上接近指数形式,可以写成 $IC(t)=IC_0,e^{-\lambda t}$,其中 $IC_0$ 是起始强度,$\lambda$ 是衰减速度。$\lambda$ 越大,曲线掉得越快。

把"半衰"这件事翻译成参数:令 $IC(t)=IC_0/2$,解出来就是 $t_{1/2}=\ln 2/\lambda$。这个 $t_{1/2}$ 直接把"因子还有多有用"变成了一个可比较的数字——它告诉你,信号在多久之后只剩下原来一半,也就决定了你多久必须重新调仓。

盯住IC衰减的价值在于:它把"这个因子还灵不灵"从主观判断变成了一条可回测的曲线。曲线掉得快,说明信号被市场消化得很快,拖得越久越接近噪声;曲线掉得慢,说明信息还在缓慢释放,低频调仓也不亏。

从取数到半衰期的三步落地

取数与IC序列

先取因子收益和资产收益两条时间序列,再用滚动相关算出逐期的IC。gs-quant 的 gs_quant/models/risk_model.py 里FactorRiskModel负责取数,gs_quant/timeseries/econometrics.py 的correlation负责滚动相关,窗口大小用参数w控制,缺值自动处理。

from datetime import date import numpy as np from gs_quant.models.risk_model import FactorRiskModel from gs_quant.timeseries import correlation model = FactorRiskModel.get("MODEL_ID") # 换成你的模型ID fret = model.get_factor_returns_by_name( # 因子收益序列 start_date=date(2023, 1, 1), end_date=date(2023, 12, 31), factors=["MOMENTUM"]) ar = model.get_daily_return( # 资产日收益 start_date=date(2023, 1, 1), end_date=date(2023, 12, 31)) # 取单只资产/组合的"下一期"收益,与当期因子收益做滚动相关 ic = correlation(fret, ar["GS US 005"].shift(-1), w=20).dropna()

跑完你会得到一条带日期索引的ic序列:横轴是时间,纵轴是每期的信息系数,这正是拟合衰减的原料。

指数拟合求λ

原始IC噪声大、且会在零附近来回跳,直接对整条曲线拟指数经常失败。稳妥做法是先滚动平滑、只取正半轴那段再拟合,$IC(t)=IC_0,e^{-\lambda t}$ 在这里用scipy.optimize.curve_fit求 $\lambda$。

from scipy.optimize import curve_fit import numpy as np ic_s = ic.rolling(10).mean().dropna() # 平滑噪声 ic_s = ic_s[ic_s > 0] # 只拟合衰减(正值)段 def decay(t, ic0, lam): return ic0 * np.exp(-lam * t) t = np.arange(len(ic_s)) popt, _ = curve_fit(decay, t, ic_s.values, p0=[abs(ic_s.iloc[0]), 0.05], maxfev=20000) half_life = np.log(2) / popt[1] # 半衰期 = ln2 / λ print(f"λ={popt[1]:.4f}, 半衰期≈{half_life:.1f} 期")

结果是一个具体的期数,比如"半衰期≈24期",含义是因子信号大约一个月后只剩一半。

画图验证

拟合完务必画图确认曲线形状确实向下弯、而不是拟合到了噪声上。下面把原始IC、平滑IC和拟合曲线叠在一起,一眼就能判断衰减是否成立。

import matplotlib.pyplot as plt plt.figure(figsize=(10, 6)) plt.plot(ic.index, ic, alpha=0.5, label="滚动 IC") plt.plot(ic_s.index, ic_s, label="平滑 IC") plt.plot(t, decay(t, *popt), "r--", label=f"拟合曲线 (半衰期≈{half_life:.1f})") plt.xlabel("日期"); plt.ylabel("IC 值") plt.legend(); plt.title("因子 IC 衰减与半衰期") plt.show()

如果拟合曲线明显贴着平滑IC的下降段,说明指数假设成立;如果拟合线飘到数据外面,多半是这段IC根本不在衰减,该重新切样本。

📉 半衰期数字怎么读

算出来的 $t_{1/2}$ 要落到调仓动作上才有意义。经验对应关系大致如下:

半衰期区间建议调仓节奏
< 5 期(约一周)高频,信号消耗快,交易成本敏感
5–20 期(一两周到一个月)月度或双周调仓
20–60 期(一个月到季度)季度或低频再平衡
> 60 期低频即可,重点看稳定性而非频率

两个容易踩的坑:一是IC窗口太短,噪声盖过真信号,半衰期会被随机拉得忽长忽短;建议先在 20–60 期之间对照着试。二是别只看单段,因子有效性会随行情切换,gs_quant/models/risk_model_utils.py 里的工具函数可以在拟合前后对因子序列做稳定性校验,确认衰减不是某段行情的偶发。

从单因子到组合的延伸

单因子算完 $t_{1/2}$ 只是起点。真正上线时,多因子之间的相关与半衰期差异,可以直接喂给 gs_quant/markets/ 里的组合优化模块,让每个因子的权重随其信号半衰期动态衰减,把调仓频率和成本一并纳入约束。

IC 半衰期把"因子还灵不灵"变成了一条可回测的曲线和一个可调仓的数字。拿你手里因子的近一年数据跑一遍,看看衰减是快是慢,再决定是加仓还是收手。

【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询