halo 的技术博客

返回

日内波动率的 U 型季节性:开盘收盘高、午间低

把一个交易日切成几十个 5 分钟的小格子,逐格算收益的绝对值(一个粗糙的波动代理),再把很多天叠在一起平均——你会得到一条几乎每天都重演的曲线:开盘那一柱冲得老高,之后一路衰减,午间趴到谷底,临近收盘又抬起来。 画出来就是一个 U(A 股午休把它切成两个小 U,但形状同源)。

这条 U 型不是随机噪声,是日内季节性(intraday seasonality)——市场微观结构的一个稳定事实。开盘高,因为隔夜信息在开盘集中释放、做市商重新校准报价;午间低,因为信息流枯竭、机构午休;收盘高,因为指数基金收盘建仓、日内头寸了结、大量算法在 VWAP 尾段冲刺。

问题来了:如果你不把这条 U 型剥掉,几乎所有日内量化工作都会被它系统性带偏。 你会以为开盘的高波动是”异常”,其实那是每天都有的常态;你会用一条全天固定的阈值去检测跳跃,结果开盘漏报一堆假信号、午间漏掉真正的跳跃。要正确处理日内数据,第一步永远是:先估计季节性,再去季节化。

Andersen 和 Bollerslev(1997, 1998)给出的答案,就是本文的主角——柔性傅里叶形式(Flexible Fourier Form, FFF)

一、为什么日内波动必须先去季节化#

先说清楚不处理季节性会踩哪些坑。

跳跃检测被污染。 Lee-Mykland、BNS 这类跳跃检验,本质是拿单个 bin 的收益去和局部波动水平比,超过某个阈值就判为跳跃。但如果局部波动水平本身在开盘天然偏高、午间天然偏低,用一条固定阈值就会:在开盘把正常的高波动误判成跳跃(假阳性),在午间把真正的小跳跃埋没在阈值之下(假阴性)。跳跃检验必须在去季节化之后的收益上做。

已实现波动率被扭曲。 用日内数据估日度已实现波动率(RV)时,如果采样落点和季节性形状交互(比如恰好多采了开盘段),估计会有偏。日内波动预测模型(HAR 的日内版、日内 GARCH)如果不先扣季节性,模型会把每天重演的 U 型当成”可预测的动态”,其实那只是日历效应。

执行算法被误导。 VWAP/TWAP 拆单需要预测日内成交量与波动的分布。成交量也有极强的 U 型季节性(和波动同源)。不建模季节性,你的拆单曲线会和市场的天然节奏错位——在流动性最差的午间下太多单,在开盘尾盘的高流动性窗口反而下太少。

一句话:日内季节性是一个已知的、稳定的、可建模的成分。把它留在数据里当噪声处理,是在自找麻烦。

二、FFF:用几个参数画出一条 U 型#

怎么建模这条 U 型?最笨的办法是给每个 bin 估一个自己的均值(bin-by-bin 平均),比如 48 个 bin 就 48 个参数。这能拟合任何形状,但参数太多、噪声大、无法外推,而且忽略了”相邻 bin 的季节性应该平滑过渡”这个先验。

FFF 的思路优雅得多:日内季节性是时间的一个平滑周期函数,那就用傅里叶基去逼近它。 把归一化的日内时刻记为 τ[0,1]\tau \in [0, 1](0=开盘,1=收盘),FFF 把对数季节性因子写成:

lns(τ)=β0+β1τ+β2τ2+p=1P[γpsin(2πpτ)+δpcos(2πpτ)]\ln s(\tau) = \beta_0 + \beta_1 \tau + \beta_2 \tau^2 + \sum_{p=1}^{P}\left[\gamma_p \sin(2\pi p\tau) + \delta_p \cos(2\pi p\tau)\right]

三部分各司其职:

  • 低阶多项式 β0+β1τ+β2τ2\beta_0 + \beta_1\tau + \beta_2\tau^2:捕捉整体的趋势与弯曲(U 型的大轮廓)。
  • 傅里叶谐波 sin/cos\sin/\cos 项:捕捉周期性的精细结构(开盘尖峰、午间凹陷这些偏离光滑 U 型的形状)。
  • 阶数 PP:控制模型的柔性。PP 越大,能拟合越尖锐的局部特征,参数也越多(每加一阶多两个参数)。

写成对数是为了让拟合出来的季节因子恒正(波动不能为负),也让乘法季节性(rt=s(τt)εtr_t = s(\tau_t)\cdot\varepsilon_t)变成加法回归。设计矩阵和拟合就是一个普通最小二乘:

import numpy as np

def fff_design(tau, P=4):
    """柔性傅里叶设计矩阵:常数 + 一次 + 二次 + P 阶正弦余弦。"""
    cols = [np.ones_like(tau), tau, tau**2]
    for p in range(1, P + 1):
        cols.append(np.sin(2*np.pi*p*tau))
        cols.append(np.cos(2*np.pi*p*tau))
    return np.column_stack(cols)

# y = 对数波动代理(如 log|收益| 的跨日平均)
X = fff_design(tau, P=4)
beta = np.linalg.lstsq(X, y, rcond=None)[0]
season = np.exp(X @ beta)          # 估计的日内季节因子
season = season / season.mean()    # 归一化,均值=1
python

十几行,一个 OLS,就把一条 U 型曲线用十来个参数刻画出来了。文首图里的红色虚线(真实形状)和蓝色实线(跨日平均)几乎重合,说明这个平滑的季节结构确实稳定存在。

三、阶数 P:柔性与过拟合的老矛盾#

FFF 的唯一关键旋钮是阶数 PP。它直接控制模型能画出多尖锐的形状。

柔性傅里叶阶数 P 越高,越贴合开盘尖峰

四条拟合线讲了一个清晰的权衡:

  • P=1(绿线,5 参数):只能画出最粗的 U 型轮廓,开盘那个尖峰完全抓不住——它把尖峰”抹平”了。
  • P=2(橙线,7 参数):开始有开盘和收盘的抬升,但尖峰还是钝的。
  • P=4(蓝线,11 参数):贴合得相当好,开盘尖峰、午间凹陷、收盘抬升都还原了。
  • P=8(紫线,19 参数):几乎完美贴合每个观测点——但这已经进入危险区。

这就是老掉牙但永远绕不开的偏差-方差权衡。PP 太小欠拟合(漏掉尖峰这种真实结构),PP 太大过拟合(把采样噪声也当成季节性刻进去,在样本外失效)。Andersen-Bollerslev 原文用的阶数不高(通常 P=4P=4 到 6 量级就够刻画日内 U 型),因为季节性形状本身是光滑的,不需要很高的阶数。如果你发现必须 P=15P=15 才能拟合,那多半是你把噪声当成了信号。

实践中选 PP 的方法就是标准套路:用信息准则(AIC/BIC)在样本内选、或用样本外验证。但一个更朴素的判据往往更管用——季节性应该稳定。把样本分成前后两半分别估 FFF,如果两半估出来的季节曲线高度一致,说明阶数合理;如果后半段的曲线和前半段差很多,说明你在拟合噪声。

四、去季节化:把 U 型拉直#

估出季节因子 s(τ)s(\tau) 之后,去季节化就是一个除法:

r~t=rts(τt)\tilde{r}_t = \frac{r_t}{s(\tau_t)}

原始收益除以它所在时刻的季节因子,得到的 r~t\tilde{r}_t 就剥离了日内节律。

去季节化:把 U 型从波动里剥掉,还原真实异常

上图是某一天的原始 |收益|(蓝柱)叠加估计的季节因子(红线);下图是去季节化之后的 |收益|(绿柱)。对比很直观:

  • 去季节化前:开盘和收盘天然偏高,这是每天都有的常态。你无法判断某个开盘的高波动到底是”正常的开盘高”还是”真的有大事”。
  • 去季节化后:曲线被拉平到 1 附近波动。此时任何显著偏离 1 的 bin,才是真正的异常。 午间那个平时被季节性压低的时段,如果出现一个跳跃,去季节化后它会突出来——而在原始数据里它可能低于全天平均,被完全忽略。

这一步是所有后续日内分析的地基。去季节化之后的收益才近似同分布,才能拿去做跳跃检验、估 RV、喂进日内波动模型。

五、季节性校正如何改变跳跃检测#

把去季节化的价值落到一个具体决策上——跳跃检测。这是 FFF 最实打实的应用场景之一。

季节性校正改变跳跃检测:固定阈值会漏报午间跳跃

图里我在午间(低波动区)人为注入了一个真实跳跃(橙色星标),然后用两种阈值去检测:

  • 固定阈值(红虚线):全天一条水平线(均值 + 2.5 倍标准差)。它被开盘收盘的高波动抬高了,结果——午间那个真实跳跃低于这条线,被彻底漏报。 与此同时,开盘段那些正常的高波动柱却贴着甚至超过这条线,随时会造成假阳性。
  • FFF 校正阈值(绿线):先去季节化、在拉平的数据上定一个统一阈值,再乘回季节因子。于是阈值本身随季节起伏——开盘高、午间低。午间的阈值被压低到刚好能让那个真实跳跃冒头,同时开盘的阈值抬高,避免把正常的开盘高波动误判。

结论很硬:用固定阈值在有季节性的日内数据上做跳跃检测,等于同时犯两个错——开盘假阳性、午间假阴性。 正确的做法要么在去季节化后的收益上用固定阈值,要么等价地用随季节起伏的阈值。这不是锦上添花,是日内跳跃检验能不能用的前提。

六、诚实的部分#

第一,本文全部基于合成数据。 U 型形状、开盘尖峰的高度、午间凹陷的深度、注入的跳跃,都是按微观结构的定性规律构造出来的演示,用来把 FFF 的机制讲透。真实市场的日内季节性会随品种、时段、市场状态漂移,形状远没有这么干净,估计出来的曲线会更毛糙。

第二,季节性不是完全静止的。 FFF 通常假设季节形状在估计窗口内稳定,但真实的日内节律会缓慢演化——比如收盘竞价机制改革、算法交易占比上升,都会改变尾盘那个抬升的形状。稳健做法是滚动重估 FFF,或允许季节参数缓慢时变,而不是一次估计用到天荒地老。

第三,午休和分段交易需要特殊处理。 本文用一个连续的 τ[0,1]\tau \in [0,1] 演示,但 A 股有午休,上午和下午是两段。正确做法是要么分段各估一条季节曲线,要么用能容纳断点的设计(比如对上下午各设一组基函数)。直接把午休两端接起来当连续时间处理,会在午休断点附近产生虚假的形状。

第四,波动代理的选择会影响结果。 本文用 |收益| 作粗糙代理,实务中更常用 5 分钟已实现方差、双幂变差(对跳跃稳健)或 Garman-Klass 类日内极差估计。不同代理对跳跃的敏感度不同,估出来的季节曲线也略有差异——尤其在跳跃频繁的品种上,用不抗跳跃的代理会让季节曲线被少数大跳跃带偏。

第五,FFF 是预处理,不是策略。 它的产出是”干净的、去季节化的日内收益”,这是地基,不是楼。真正的 alpha 在地基之上——去季节化后的动量、跳跃后的漂移、日内均值回归。把 FFF 当成能直接赚钱的东西是误解,它的价值在于让你不被季节性骗,从而让上层策略建立在正确的信号上。

七、总结#

日内波动率有一条每天重演的 U 型季节曲线:开盘高、午间低、收盘抬。这条曲线是稳定的微观结构事实,不是噪声。不把它剥掉,日内的跳跃检测、波动预测、执行拆单都会被系统性带偏——最直接的后果就是固定阈值的跳跃检测在开盘假阳性、在午间假阴性。

FFF 用低阶多项式加少数傅里叶谐波,十来个参数就能平滑地刻画这条曲线,比 bin-by-bin 平均更省参数、更抗噪、更能外推。它的唯一关键旋钮是阶数 PP——够大以抓住开盘尖峰,别大到把噪声也刻进去,判据是”样本内外季节曲线要一致”。

它的正确定位是日内分析的预处理层:先估季节、再去季节、再在拉平的数据上做真正的分析。先把每天都一样的东西减掉,剩下的才是今天真正发生的事——这个顺序,是所有高频量化工作的第一课。


参考文献:

  • Andersen, T. G., & Bollerslev, T. (1997). Intraday Periodicity and Volatility Persistence in Financial Markets. Journal of Empirical Finance, 4(2-3), 115-158.
  • Andersen, T. G., & Bollerslev, T. (1998). Deutsche Mark-Dollar Volatility: Intraday Activity Patterns, Macroeconomic Announcements, and Longer Run Dependencies. Journal of Finance, 53(1), 219-265.
  • Boudt, K., Croux, C., & Laurent, S. (2011). Robust estimation of intraweek periodicity in volatility and jump detection. Journal of Empirical Finance, 18(2), 353-367.
日内波动率季节性 FFF:用柔性傅里叶形式剖开 U 型日内波动曲线
https://blog.halo26812.eu.org/blog/intraday-seasonality-fff
Author halo
Published at 2026年7月26日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨