
股价的日历效应这件事我最早是在做量化策略回测时被它“教育”的。当时跑一个简单的均线策略回测曲线漂亮得不像话实盘一上就拉胯排查了很久才发现回测里那些超额收益很大一部分来自特定交易日——周一、月末、长假前后——而我的策略压根没考虑这些时间维度上的规律。后来我把ARMA-GARCH这套时序工具搬过来专门拆解股价的日历效应和节假日效应才算把这块补上。这篇就聊聊我是怎么用这套模型把“哪天涨、哪天跌”这种看似玄学的东西量化出来的从思路到代码尽量讲透。ARMA-GARCH模型组合的核心价值在于ARMA部分负责刻画收益率序列的自相关结构也就是均值方程GARCH部分负责刻画波动率的聚集性和时变性也就是方差方程。股价的日历效应本质上是收益率均值在特定时间节点上的系统性偏移节假日效应则更复杂既影响均值也可能影响波动率。用ARMA-GARCH来建模好处是能把“日历”作为外生变量塞进均值方程同时用GARCH吸收掉波动率聚集带来的干扰让日历效应的估计更干净。这套思路适合有一定时序分析基础、想深入理解金融数据规律的朋友也适合刚接触GARCH族模型、想找个真实场景练手的人。1. 为什么日历效应值得单独建模1.1 日历效应不是玄学是统计上可检验的均值偏移很多人一听“日历效应”就觉得是股评话术其实它有明确的统计定义在控制其他因素后收益率在特定时间维度星期几、月份、月初月末、节假日前后的交易日上存在显著不为零的均值。比如“周一效应”指的是周一平均收益率显著为负“月末效应”指的是月末几个交易日收益率系统性偏高或偏低。这些现象在不同市场、不同时期表现不一但检验方法是统一的——把时间虚拟变量放进回归方程看系数是否显著。我最初的做法是直接对收益率做分组均值检验比如把周一的数据单独拎出来做t检验。这个方法简单但有个致命问题它假设收益率独立同分布而股价收益率恰恰存在自相关和波动率聚集。一旦这两个假设不成立t检验的p值就不可信。ARMA-GARCH的价值就在这里ARMA吸收自相关GARCH吸收波动率聚集剩下的残差才接近白噪声这时候再看日历虚拟变量的系数才是干净的估计。1.2 节假日效应和普通日历效应是两套逻辑普通日历效应星期效应、月份效应是周期性的每个周期都会出现样本量充足估计相对稳定。节假日效应不一样它跟具体节日绑定每年出现的日期不固定而且节前节后的交易日数量有限样本稀疏。更麻烦的是节假日效应往往同时影响均值和波动率——节前资金避险可能导致波动率抬升节后情绪释放可能导致收益率跳变。所以我在建模时把这两类效应分开处理星期效应、月份效应、月初月末效应用标准的周期虚拟变量节假日效应用“节前第N个交易日”“节后第N个交易日”这种相对位置变量。这样既能捕捉节假日的非周期性又能避免把节前节后的效应和星期效应混在一起。这个拆分思路是我踩过坑之后才想明白的一开始我把所有虚拟变量一股脑塞进去结果节前效应和周五效应高度共线系数完全没法解释。1.3 ARMA-GARCH的分工均值方程管日历方差方程管波动这里要把模型结构说清楚不然后面的代码没法理解。ARMA-GARCH是一个两部分组成的模型均值方程$r_t \mu \sum_{i1}^p \phi_i r_{t-i} \sum_{j1}^q \theta_j \epsilon_{t-j} \sum_k \beta_k D_{k,t} \epsilon_t$方差方程$\epsilon_t \sigma_t z_t$$\sigma_t^2 \omega \sum_{i1}^m \alpha_i \epsilon_{t-i}^2 \sum_{j1}^s \gamma_j \sigma_{t-j}^2$其中$D_{k,t}$就是日历虚拟变量。均值方程里的ARMA项负责吸收收益率的自相关日历虚拟变量负责捕捉均值偏移方差方程负责刻画波动率的时变性。这个分工的关键在于如果不先把ARMA项放进去日历虚拟变量可能会“抢”到本该属于自相关的解释力导致系数有偏。我实测过去掉ARMA项直接做带日历虚拟变量的OLS周一效应的系数能虚高30%以上因为周一收益率往往承接了上周五的自相关结构。2. 数据准备与日历变量的构造细节2.1 收益率序列的预处理对数收益率、平稳性、异常值数据这块我吃过亏得重点说。首先用对数收益率而不是简单收益率原因是对数收益率有可加性跨期累加等于区间总收益而且分布更接近正态。计算方式是$r_t \ln(P_t / P_{t-1})$其中$P_t$是复权收盘价——一定要用复权价不然分红除权那天会出现假暴跌把日历效应的估计带偏。平稳性是ARMA建模的前提。我一般先跑ADF检验和KPSS检验ADF拒绝单位根、KPSS不拒绝平稳两个都过了才放心。对数收益率绝大多数情况下是平稳的但极端行情期间比如连续涨跌停可能出现异常值这些异常值会严重干扰GARCH的波动率估计。我的处理方式是用3倍标准差或者MAD中位数绝对偏差识别异常值标记但不删除在模型里用虚拟变量吸收或者用稳健估计。直接删除异常值会损失信息而且可能删掉真正的日历效应信号。提示复权方式要统一。前复权和后复权算出来的收益率在除权日附近不同回测和建模必须用同一种复权口径否则日历效应的估计会在除权日附近出现系统性偏差。2.2 星期效应和月份效应的虚拟变量编码星期效应用4个虚拟变量周一、周二、周三、周四周五作为基准组避免虚拟变量陷阱。月份效应用11个虚拟变量12月作为基准。这里有个细节如果样本跨越多年月份效应可能和年度趋势混淆我一般会加一个年份趋势项或者用去趋势后的收益率。编码的时候要注意基准组的选择不是随意的。基准组应该是理论上“最中性”的那一类比如周五作为星期效应的基准是因为周五效应在很多市场相对不显著12月作为月份基准是因为年末效应往往被单独讨论。基准组选错了系数解释会变得别扭但不影响模型拟合优度。2.3 节假日相对位置变量的设计节假日效应是这套建模里最需要动脑筋的部分。我的做法是构造“节前第N个交易日”和“节后第N个交易日”的虚拟变量N取1到3。比如春节前第1个交易日、春节前第2个交易日、春节后第1个交易日等。这里的关键是节假日的识别——需要一份交易日历把每年法定节假日的休市日期标出来然后倒推节前节后的交易日。具体构造逻辑对每个节假日找到节前最后一个交易日标记为“节前1”再往前一个交易日标记为“节前2”节后第一个交易日标记为“节后1”以此类推。不同节假日可以合并成一组虚拟变量比如所有长假合并也可以分开建模春节单独一组、国庆单独一组。我建议先合并看整体效应再拆分看个体差异因为分开建模样本量会骤降估计不稳定。变量类型构造方式基准组注意事项星期效应周一至周四各一个虚拟变量周五避免与节假日变量共线月份效应1月至11月各一个虚拟变量12月跨年样本需加趋势项月初月末每月前3日、后3日各一组月中与月份效应可能共线节前效应节前第1至3个交易日非节前日不同节日可合并或拆分节后效应节后第1至3个交易日非节后日注意节后与月初的重叠3. ARMA-GARCH模型的定阶与估计实操3.1 均值方程定阶先看ACF/PACF再用信息准则兜底ARMA定阶我一般分两步走。第一步看收益率序列的ACF和PACF图初步判断p和q的范围。股价收益率的自相关通常很弱ACF在低阶就截尾所以p和q一般不超过3。第二步用AIC/BIC在候选阶数网格里搜选信息准则最小的组合。这里要注意定阶是在加入日历虚拟变量之前做的先把ARMA结构定下来再把日历变量加进去否则日历变量会影响自相关的估计。实际操作中我发现股价收益率的ARMA阶数往往很低p1, q1或者p0, q1就够高阶ARMA容易过拟合。如果ACF/PACF显示自相关很弱甚至可以直接用AR(1)或者干脆不加ARMA项只保留日历虚拟变量加GARCH。定阶不是越复杂越好简约原则在时序建模里同样适用。3.2 方差方程定阶GARCH(1,1)通常是起点也是终点GARCH的定阶比ARMA简单得多GARCH(1,1)在绝大多数金融收益率序列上表现足够好这是业界共识。GARCH(1,1)的方差方程是$\sigma_t^2 \omega \alpha \epsilon_{t-1}^2 \gamma \sigma_{t-1}^2$其中$\alpha$捕捉波动率对冲击的敏感度$\gamma$捕捉波动率的持续性。$\alpha \gamma$接近1说明波动率高度持续这也是股价数据的典型特征。什么时候需要更高阶如果GARCH(1,1)的残差还有明显的ARCH效应用Ljung-Box检验残差平方可以考虑GARCH(2,1)或者EGARCH、GJR-GARCH来处理杠杆效应。杠杆效应指的是负收益对波动率的冲击大于正收益股价数据里很常见。我一般先跑GARCH(1,1)检验残差如果杠杆效应显著就换GJR-GARCH这个流程比较稳。3.3 联合估计把日历虚拟变量塞进均值方程联合估计是整个流程的核心。用Python的arch库均值方程可以写成ARX形式把日历虚拟变量作为外生回归元传进去。代码结构大致是这样import pandas as pd import numpy as np from arch import arch_model # 假设 ret 是收益率序列cal_vars 是日历虚拟变量DataFrame # 均值方程常数 AR(1) 日历虚拟变量 # 方差方程GARCH(1,1) model arch_model( ret, meanARX, lags1, xcal_vars, # 日历虚拟变量作为外生回归元 volGARCH, p1, q1, distt # 用t分布吸收厚尾 ) res model.fit(dispoff) print(res.summary())这里有几个参数选择要解释。distt用t分布而不是正态分布是因为股价收益率有厚尾正态分布会低估极端风险t分布的自由度参数可以自适应地吸收厚尾。lags1是AR(1)如果定阶结果是ARMA(1,1)需要手动构造滞后残差项arch库的ARX对MA项支持有限这时候可以用ARMA均值模型配合外生变量或者用statsmodels先做ARMA再对残差做GARCH两步法但效率略低。注意arch库的ARX均值模型里外生变量的系数就是日历效应的估计值。解读时要注意这些系数是在控制AR项之后的净效应不能和简单的分组均值直接对比。4. 结果解读系数显著性、经济含义与稳健性4.1 日历虚拟变量系数的解读陷阱模型跑出来之后日历虚拟变量的系数$\beta_k$表示在控制自相关和波动率之后该交易日相对基准组的平均收益率偏移。比如周一虚拟变量系数是-0.0015意思是周一平均比周五低0.15%对数收益率。这个解读看似直接但有几个陷阱。第一个陷阱是统计显著不等于经济显著。样本量大的时候0.01%的偏移也可能显著但扣掉交易成本后毫无意义。我一般会同时看系数大小和t值系数绝对值小于0.05%的基本可以忽略。第二个陷阱是多重检验问题。同时检验星期、月份、节假日几十个虚拟变量假阳性率会上升我一般用Bonferroni校正或者FDR控制把显著性阈值调严。第三个陷阱是样本外稳定性。日历效应在不同年份可能反转我习惯把样本切成前后两段分别估计看系数符号是否一致。4.2 节假日效应的方向判断节前避险还是节前抢筹节假日效应的方向没有普适规律得看具体市场和时期。节前效应可能是负的资金避险、落袋为安也可能是正的节前抢筹、布局节后行情。节后效应同理可能是情绪释放带来的正收益也可能是利好兑现后的回调。模型给出的系数只是统计描述方向判断要结合当时的市场环境。我做过一个粗略的统计把节前效应按节假日类型分组长假的节前效应往往比短假更显著因为长假期间不确定性更大资金行为更极端。节后效应则和假期期间的累积信息量有关信息量大的假期比如跨年节后波动和收益偏移都更明显。这些规律不是绝对的但可以作为解读系数时的参考框架。4.3 稳健性检验子样本、不同基准组、加入控制变量稳健性检验是让结论可信的关键一步。我一般做三组检验。第一组是子样本检验把样本按年份或市场状态牛市/熊市切分看日历效应是否稳定。第二组是换基准组比如星期效应从周五基准换成周三基准看其他系数的相对关系是否一致绝对数值会变但相对排序应该稳定。第三组是加控制变量比如加入市场因子收益率、换手率、波动率滞后项看日历效应系数是否还显著。这三组检验里子样本检验最能暴露问题。我遇到过星期效应在全样本显著、但分年份看只有某几年显著的情况这说明所谓的“星期效应”可能是特定时期的偶然现象不是稳定规律。这种情况下把结论写成“样本期内存在星期效应”比写成“存在星期效应”更严谨。5. 代码实现中的坑与调优经验5.1 数据对齐交易日历和收益率索引的匹配数据对齐是实操中最容易出错的地方。日历虚拟变量的索引必须和收益率序列的索引严格一致否则会出现“张冠李戴”——把周三的虚拟变量对到了周四的收益率上。我的做法是先构造一个完整的交易日索引然后所有变量都基于这个索引生成最后用reindex对齐对齐后检查缺失值。节假日变量的对齐更麻烦因为节假日的识别依赖交易日历。我一般用交易所公布的交易日历文件把休市日期标出来然后倒推节前节后交易日。这里要注意不同年份的节假日安排可能调整比如调休交易日历必须用当年的实际安排不能用固定规则推算。5.2 模型不收敛的常见原因和排查顺序arch库的模型不收敛是家常便饭我总结了一个排查顺序。第一步看数据尺度收益率如果用的是小数0.01表示1%数值太小可能导致优化困难我一般把收益率乘以100变成百分数收敛会好很多系数解读时再除回来。第二步看异常值极端值会让似然函数在边界附近震荡先处理异常值再拟合。第三步看初始值arch库支持传初始参数GARCH的$\alpha$和$\gamma$初始值给0.05和0.9比较稳。第四步换优化器默认的优化器不行就换SLSQP或者BFGS。还有一个隐蔽的原因外生变量共线。日历虚拟变量之间如果高度共线比如节前效应和月初效应重叠设计矩阵接近奇异优化会失败。排查方法是算外生变量的相关系数矩阵相关系数超过0.8的变量考虑合并或剔除。5.3 用t分布还是正态分布厚尾处理的实测对比厚尾处理对日历效应的估计有实质影响。我用同一份数据对比过正态分布和t分布正态分布下极端收益被赋予过高的似然导致波动率估计偏高日历虚拟变量的标准误被放大显著性被低估t分布下自由度参数自动下调厚尾被吸收日历效应的t值普遍更高。实测下来t分布的自由度参数估计值在4到8之间符合股价数据的典型厚尾程度。如果t分布还不够可以试GED广义误差分布或者偏t分布捕捉收益率的偏度。偏t分布适合有明显负偏的市场但参数更多收敛更慢样本量不够的时候不建议用。6. 从日历效应到可落地的策略思路6.1 日历效应的样本外衰减问题这里必须泼一盆冷水日历效应在样本外很容易衰减甚至消失。原因很简单一旦某个日历规律被广泛知晓套利资金会把它抹平。我见过不少策略在样本内漂亮、样本外失效根源就是把统计规律当成了稳定盈利来源。所以日历效应的正确用法不是“照着系数做多空”而是作为因子纳入多因子框架或者作为择时的辅助信号配合其他逻辑使用。我的做法是把日历效应系数做成一个“日历因子”和其他因子动量、价值、波动率一起做横截面回归看它的增量解释力。如果日历因子在控制其他因子后还有显著alpha才值得单独拿出来用。单独用日历效应做策略风险收益比通常不划算。6.2 把日历因子做成择时信号的工程化思路如果确实想把日历效应用起来工程化思路是这样的先用滚动窗口估计日历效应系数比如用过去3年数据得到每个交易日的预期偏移然后把预期偏移和当日其他信号比如动量、波动率状态结合生成仓位建议。滚动窗口的好处是系数能自适应市场变化避免用全样本系数导致的前视偏差。回测的时候要特别注意交易成本。日历效应的单日偏移通常很小0.1%量级如果双边交易成本超过0.2%策略基本没戏。所以日历信号更适合用在低频调仓或者作为仓位微调的依据不适合高频进出。6.3 模型扩展加入宏观变量和情绪指标ARMA-GARCH的框架很容易扩展。均值方程里除了日历虚拟变量还可以加宏观变量利率、通胀、情绪指标换手率、融资余额、市场状态变量波动率 regime。方差方程里可以加日历虚拟变量检验节假日是否影响波动率——这是另一个有意思的方向节前波动率抬升是常见的现象用GARCH-X或者直接把虚拟变量放进方差方程都能检验。扩展的时候要注意变量数量和信息准则的平衡。每加一个变量AIC/BIC都会变如果加变量后BIC反而上升说明这个变量带来的拟合提升不足以抵消复杂度惩罚应该剔除。我一般用BIC做变量筛选因为它对复杂度的惩罚更重选出来的模型更简约。7. 我踩过的几个具体坑第一个坑是收益率尺度。早期我用小数收益率直接拟合模型频繁不收敛排查了半天才发现是数值太小导致优化器步长失效。乘以100之后问题消失这个细节文档里不会写但实操中很常见。第二个坑是节假日变量的共线。春节前效应和1月末效应在有些年份重叠两个虚拟变量相关系数超过0.9模型系数完全不可解释。后来我把春节单独拎出来不和月份效应同时进模型问题才解决。第三个坑是过度解读系数符号。有段时间我发现节前效应系数为正就以为节前必涨结果样本外完全反向。后来才明白系数是控制其他变量后的净效应市场环境变了净效应的方向也会变不能当成铁律。第四个坑是忽略波动率的日历效应。一开始我只在均值方程里放日历变量后来发现节前波动率明显抬升这个信息对风险管理很有用才把日历变量也放进方差方程。均值效应和波动率效应是两回事都得看。这套建模流程跑下来最大的体会是日历效应和节假日效应的统计检验不难难的是区分“真规律”和“数据窥探”。样本内显著、样本外稳定、经济逻辑说得通三个条件都满足才值得当真。ARMA-GARCH只是工具工具用对了结论还得靠人判断。