halo 的技术博客

返回

一个被广泛验证的事实:因子长期有效,但短期表现高度依赖宏观环境。价值因子在复苏/过热期吃香,动量因子在扩张期最强,质量因子在滞胀/衰退里最稳,小盘(规模)因子在流动性宽松时爆发。如果你把四个因子静态等权拿 20 年,你拿到的是一层被「环境噪声」稀释的平均收益;而如果你能识别当前处于哪种宏观状态、把敞口向该状态利好因子倾斜,就能在不动用杠杆的情况下,把单位风险对应的收益(夏普)抬上去。

本文的任务不是「预测因子下个月涨不涨」,而是用可观测、可滞后的宏观状态,做一层轻量的因子暴露再平衡,并诚实讨论它能做和不能做的边界。

一、核心思想:状态 → 因子映射是「慢信号」#

关键认知:宏观状态是慢变量,因子收益差是「状态依赖」的,但慢到足以被滞后观测

我们构造 4 个宏观状态(用 GDP 增速 g 与通胀 π 两个拇指规则切分):

状态g / π主导因子(文献一致方向)
A 扩张Hg, Lπ动量最强、质量次强
B 放缓Lg, Lπ价值最强
C 过热Hg, Hπ价值 / 规模占优
D 滞胀Lg, Hπ质量防御,动量转负

这张表不是从数据里「优化」出来的,而是来自数十年的风格周期实证研究(如 Fama-MacBeth 横截面、风格因子周期文献)。正因为它是先验的、方向性的,我们才敢在回测里用它——否则就是样本内过拟合

二、数据合成机制(自洽、仅用于演示方法)#

为了可复现且不依赖特定市场数据,我们用自洽合成面板:240 个月度观测,宏观状态用高持续性 4 态马尔可夫链生成(停留概率 0.88,保证 4 态都频繁出现),每个状态绑定一组 (g, π) 目标值并叠加小噪声;4 个风格因子(价值/动量/规模/质量)的月度超额收益 = 状态基准 + 特质噪声。

状态→因子收益基准(方向见上表,非样本优化):

prof = {
    "A": np.array([0.004, 0.011, 0.003, 0.009]),   # 扩张: 动量最强
    "B": np.array([0.009, 0.003, 0.004, 0.008]),   # 放缓: 价值最强
    "C": np.array([0.010, 0.002, 0.008, 0.005]),   # 过热: 价值/规模
    "D": np.array([0.003,-0.004, 0.001, 0.010]),   # 滞胀: 质量防御, 动量负
}
eps = rng.normal(0, 0.022, (T, 4))
factor_ret = np.vstack([prof[regime_code[t]] for t in range(T)]) + eps
python

宏观双指标时序与 4 状态着色如下——你可以看到状态切换是块状、慢变的,这正是它能被滞后识别的前提:

宏观双指标时序与 4 状态着色

三、动态倾斜:用「滞后」状态规避前瞻偏差#

最危险的错误是用当期状态决定当期权重——那等于「看到未来再下注」,回测必然虚高。正确做法是把状态滞后 1 期:用 t-1 月末识别出的状态,决定 t 月的持仓。

def tilt_alloc(code_series, lag=1):
    W = np.zeros((T, 4))
    for t in range(T):
        tc = code_series[max(0, t - lag)]      # 滞后状态(真实可得)
        base = prof[tc].copy()
        base = np.clip(base, 0, None)          # 负收益因子降权到 0
        if base.sum() <= 0:
            base = np.ones(4)
        W[t] = base / base.sum()               # 利好因子加权, 利空归零
    return W

W_dyn = tilt_alloc(regime_code, lag=1)         # 动态择时(真实可用)
W_perf = tilt_alloc(regime_code, lag=0)        # 完美预知(上限, 显式暴露 look-ahead)
dyn_ret = np.array([factor_ret[t] @ W_dyn[t] for t in range(T)])
dyn_nav = np.cumprod(1 + dyn_ret)
python

动态敞口随时间在 4 因子间漂移,倾斜完全由滞后状态驱动:

动态择时因子敞口随宏观状态漂移

四、结果:夏普抬升,但不靠魔法#

合成面板下(240 月,无交易成本):

策略年化波动夏普最大回撤
静态等权6.38%3.89%1.13−4.91%
动态择时(滞后)8.46%4.87%1.33−5.75%
完美预知(上限)8.75%4.91%1.38−5.75%
沪深300代理7.90%15.68%0.38−35.97%

夏普从 1.13 抬到 1.33(提升约 0.20),而完美预知上限只到 1.38——这说明:① 滞后识别已经吃到了绝大部分可用增益;② 剩余 gap 来自状态切换的 1 月延迟,这是真实、不可消除的成本,不是 bug。

净值对比:动态择时 vs 完美预知 vs 静态等权 vs 沪深300

状态→因子映射指纹图进一步验证了方法的经济逻辑:动量在 A(扩张)高、在 D(滞胀)翻负;价值在 B/C 强;质量在 D 独占鳌头。这张图就是整个策略的「指纹」,也是它和纯数据挖掘的根本区别:

状态→因子映射指纹

五、六类真实陷阱#

  1. 宏观数据滞后(最现实):真实 GDP 是季度、且初值会大幅修订,通胀也有 1–2 月延迟。本文用「月末已知 g/π」是理想化;实务里你只能用上月甚至上季的数据,状态识别会再慢 1–2 步,增益进一步缩水。
  2. 状态误判:马尔可夫链 + 阈值切分在边界附近会「抖动」误判。一次误判导致错配 1 个月——单次代价小,但 20 年里累积的换手成本不可忽视。
  3. 交易成本被忽略:本文未计摩擦。动态组合平均月度换手约 0.11(即每月约 11% 的权重在动),若单边成本 10bps,年化约 26bps——会吃掉一部分增益,但不致命。
  4. 完美预知上限的诚实披露:lag=0 版本夏普 1.38 是理论上限,代表「如果你能预知状态」的回报。任何声称超过这个数的实盘方案,几乎一定混入了前瞻偏差。
  5. 状态→因子映射是外生先验:表 1 的方向来自文献,但不同市场/年代可能漂移(如 2000s 美国价值强、2010s 动量强)。把一张静态映射硬套 20 年,会漏掉机制变化。
  6. 合成数据不代表真实分布:真实因子收益有更厚的尾部、更强的横截面相关和 regime 内的波动聚集,本文的 AR(1) + 高斯噪声是高度简化的代理,结论方向可信、量级需真实数据复核。

六、落地清单#

把「因子择时」变成日常动作,建议固定三件事:

  • 月度用可得的宏观数据(PMI、CPI、工业增加值同比)给当前状态打标签,不要追求精确 g/π,块状判断即可;
  • 再平衡只在状态切换时触发(本文 240 月切换 36 次,平均 6–7 月一次),避免高频鞭锯;
  • 映射表每年用 Fama-MacBeth 或风格周期文献复核一次,允许微调权重但不允许逐月重估。

结语#

因子择时不是「预测因子涨跌」,而是承认因子的环境依赖性、用慢变量做一层轻量倾斜。它不性感、不承诺翻倍,但本文用自洽合成证明:仅用滞后宏观状态,就能把等权夏普从 1.13 抬到 1.33,且离完美预知上限(1.38)仅一步之遥——这「一步」就是诚实标注的、不可消除的识别延迟。把它当作组合的风险预算微调器,而不是收益放大器,才是它正确的位置。

因子择时:用宏观状态动态调整因子暴露
https://blog.halo26812.eu.org/blog/factor-timing-dynamic
Author halo
Published at 2026年7月19日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨