halo 的技术博客

返回

很多日内策略研究员都有过这种体验:回测里因子「看起来很能打」,实盘却一上就软。问题往往出在两个被忽视的维度——因子的预测力在一天之内是衰减的,以及你用来证明它显著的统计检验,本身就被多重比较污染了

本文用一套可复现的模拟,把这两件事讲清楚。

一、日内因子为什么会「过期」#

一个典型的高频 / 日内因子(如开盘前 30 分钟的订单流不平衡、隔夜跳空反转)预测的是随后一段时间的收益。它的信息来源有三道衰减机制:

  1. 信息被快速套利:一旦信号被足够多的参与者看到并执行,超额收益就被抹平。
  2. 微观结构噪声累积:越靠近收盘,买卖价差、收盘集合竞价的扰动越大,信号信噪比下降。
  3. 样本截面异质性漂移:不同时段参与交易的资金性质不同(开盘机构、午间散户、尾盘调仓),因子生效的子群体在变。

合起来的结果:因子在开盘前后 IC 最高,午后显著下滑。我们用模拟把这条曲线画出来。

二、模拟一个日内衰减因子#

设定 12 个半小时时段(09:30 → 16:00),真实 IC 随时段指数衰减:IC(b) = IC0 · exp(−b/τ),其中 IC0=0.075τ=4.5。每个 (交易日, 时段) 取 600 只股票的横截面,由信号与噪声合成收益,再用 250 个交易日平均得到稳健的时段 IC 估计。

跑出来的平均 IC 从开盘的 7.5% 一路滑到午后的 0.5%~0.6%——衰减不是线性的,而是指数式的:

高频因子日内衰减:开盘 IC 最强,午后显著下滑

误差棒(±1.96 SE)显示:早盘几个时段的 IC 远高于 0,而午后已基本落入噪声区间。

三、拟合半衰期:信号能「撑」几个时段#

把衰减曲线用指数拟合,半衰期 τ·ln2 直接告诉你因子的「有效期」:

def exp_decay(b, a, t):
    return a * np.exp(-b / t)

popt, _ = curve_fit(exp_decay, np.arange(B), mean_ic, p0=[IC0, tau], maxfev=20000)
half_life = popt[1] * np.log(2)
python

日内 IC 衰减的指数拟合:半衰期约为 3 个时段

拟合得到半衰期约 3 个时段(约 1.5 小时)。这意味着:如果你在开盘算出因子、打算持有到尾盘,实际吃到的是一段被严重稀释的暴露——因子该在衰减掉一半之前就平仓或再平衡。这是高频因子和日频因子在持仓周期设计上最根本的差异。

四、统计显著性:t 统计量而非「看起来大于 0」#

「IC 是正的」不等于「显著大于 0」。正确的判据是 t 统计量 t = 均值IC / 标准误,在正态近似下与 0 的偏离是否超过临界值。单检验 95% 临界值 |t| ≈ 1.96;但由于你看了 12 个时段,应当用 Bonferroni 校正|t| > Φ⁻¹(1 − 0.05/2B) ≈ 2.87

t95    = stats.norm.ppf(0.975)            # 1.96
t_bonf = stats.norm.ppf(1 - 0.05 / (2*B)) # 2.87
python

各时段 t 统计量:午后时段 IC 不再显著(仅早段通过 Bonferroni)

图上绿柱是通过 Bonferroni 的时段——基本集中在开盘前 4 个时段。午后那些「IC 还是正的」时段,t 统计量早已跌破阈值,纯粹是噪声。一个只报告「因子 IC 为正」的研究,等于把噪声当信号。

实务提示:横截面 IC 在有限样本下并非严格正态,更稳健的做法是用 Newey-West 调整标准误(处理时序相关)或 bootstrap(对交易日重采样)得到经验 p 值,结论方向不变但更抗造。

五、多重检验陷阱:挑「最好时段」报告必然翻车#

最隐蔽的坑在这里:研究员往往扫完 12 个时段,挑 IC 最高的那个写进报告。但即使因子完全无效(真实 IC=0),扫 12 遍也大概率能撞出一个「显著的」时段。我们模拟 200 个纯噪声零因子,看它们「最优时段」的 |t| 分布:

K = 200
best_t = np.zeros(K)
for k in range(K):
    z = rng.normal(0, 1, (D_days, N))
    r = rng.normal(0, 1, (D_days, N))          # 纯噪声,真实 IC = 0
    ic_k = np.array([np.corrcoef(z[d], r[d])[0, 1] for d in range(D_days)])
    se = ic_k.std() / np.sqrt(D_days)
    best_t[k] = abs(ic_k.mean() / se)           # 报告「最强时段」
python

多重检验陷阱:大量零因子误过单检验,仅少数过 Bonferroni

结果:200 个零因子里 16 个(8%)越过了单检验 95% 线,但只有 2 个(1%)越过 Bonferroni 线。换句话说,如果你不校正,每 12 个因子就会「凭空」冒出 1 个假显著——这正是文献里「因子动物园」泛滥的根源。落地时至少要做三选一:

  • Bonferroni / Holm 步进法:保守但简单;
  • 去膨胀夏普比率(Deflated Sharpe Ratio):用最大回撤与检验次数同时校正;
  • 样本外 / Walk-forward:把「最好时段」放到没参与选型的样本里再验一次。

边界声明#

  • 模拟不等于实盘:本文用合成数据演示机制,真实日内因子的衰减形状取决于该因子的信息源(订单流、舆情、隔夜跳空各异),务必用你自己的数据重画这张图。
  • 时段边界有前视风险:用「收盘收益」做某时段的标签时,必须确保因子只用该时刻之前的信息,否则午后 IC 会被偷偷高估。
  • 截面幸存者偏差:模拟里股票数固定,实盘中退市的票会让早段 IC 偏高,需做幸存者中性处理。
  • 半衰期是点估计:用一个指数拟合得到的 τ 有估计误差,实战应给半衰期一个置信区间,而不是当成精确刻度。
  • 显著性 ≠ 经济显著:即使午后 IC 在统计上「显著」,其量级可能小到被交易成本吃掉,仍需结合换手与手续费做净收益判断。

把「日内衰减曲线 + 半衰期 + 多重检验校正」三件套钉进你的因子评审流程,你筛掉的将不是「不显著的因子」,而是「看起来显著其实只是被多次比较挑中的噪声」。

高频因子的日内衰减与统计显著性:开盘最强、午后失灵,多重检验会坑你
https://blog.halo26812.eu.org/blog/intraday-factor-decay
Author halo
Published at 2026年7月11日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨