halo 的技术博客

返回

结论先行#

“60/40 股债组合在 1998–2020 年是完美的:债券涨时股票跌、股票涨时债券跌,负相关性提供了免费的分散化。2022 年这个假设崩塌了——美国经历股债双杀,60/40 组合最大回撤超过 20%。”

这不是偶然,而是结构性机制:股债相关性本身是 regime 依赖的。本文用一个受控两因子模拟实验,把这个机制拆解到底,诚实给出三个负面结论:

  1. 滚动相关性无法及时识别 regime 切换:250 日窗口的平均识别滞后约 188 天(≈9 个月),等你发现时已经亏损了大半年。
  2. 动态资产配置的价值几乎全被成本吃掉:无论 60/120/250 日窗口,扣成本后 Sharpe 均低于或接近静态 60/40(0.399),安慰剂检验(打乱 regime 序列)下动态策略 Sharpe 仅 0.144——说明”收益”来自随机噪声,不是真实信号。
  3. 用全样本相关性矩阵做协方差估计是致命 bug:此 bug 可将 Sharpe 虚报 78%(0.255 → 0.454)。

一、机制:两因子如何驱动相关性反转#

1.1 经典 flight-to-quality(增长冲击主导)#

当经济意外上行(增长冲击 +1σ)时:

  • 资金撤出避险资产 → 债券下跌(equity: +1.0σ, bond: −0.7σ)
  • 两者符号相反 → 强负相关(模拟理论值 ≈ −0.97)

这是 1998–2020 年美股的常态。低通胀 + 稳定增长 → 股债天然对冲。

1.2 滞胀机制(通胀冲击主导)#

当通胀意外上行(通胀冲击 +1σ)时:

  • 实际利率上升 → 同时压股票和债券(equity: −0.5σ, bond: −0.9σ)
  • 两者同向 → 正相关或弱负相关(模拟理论值 ≈ −0.33,接近 0)

2022 年的美国市场正是这个机制:CPI 同比从 7% → 9%,联储暴力加息,股债双杀

1.3 两因子模型(代码片段)#

关键:Regime B 里引入因子间相关(growth 与 inflation 相关系数 −0.7),使得两资产对两因子的响应方向产生冲突,最终相关性从 −0.97 跳到 −0.36(模拟实测 −0.97 / −0.36),这正是 2022 年现实对应的区间。

封面图:十年模拟中股债滚动相关性时间序列,绿色阴影为增长主导 regime,红色为通胀主导 regime。下方为对应累计收益曲线。注意 Regime B 持续期通常更短(通胀冲击窗口波动更大),250日滚动线在切换点附近有明显的"拖尾",这是识别滞后的直观体现。

怎么读这张图:绿色背景区(增长主导 regime)对应相关性在 −0.97 附近,红色背景区(通胀主导)对应相关性跳升至 −0.36 左右。阈值线(橙色虚线)居中分割两种 regime。下方累计收益曲线在两个 regime 下走势明显分化——通胀 regime 下 60/40 组合(橙线)波动更剧烈。


二、识别滞后:滚动窗口的内在权衡#

2.1 信号质量与滞后天数的量化关系#

窗口长度平均识别滞后测量样本数trade-off
60 日43 天9 次切换信号快但噪声大(箱线图分布宽)
120 日92 天9 次切换中间权衡
250 日188 天9 次切换信号稳但严重滞后(≈9个自然月)

阈值设定在两种 regime 理论相关性的中点(−0.65),这是模拟中的已知最优阈值。在实际操作中你甚至不知道真实阈值是多少,这又引入了一层估计误差。

2.2 机制解释#

滚动相关性本质是一个 低通滤波器:窗口越长,高频噪声越少,但相位延迟越大。这是信号处理中经典的速度-精度权衡,没有任何方法可以同时做到”反应快”和”信号稳”。

实测结果:250 日窗口平均滞后 188 天 ≈ 9 个自然月。也就是说,当真实 regime 在某月切换时,你的滚动相关性信号要等 9 个月才告诉你。届时损失已经发生,等你降杠杆,市场可能已经反弹。

识别滞后图:左图为三个窗口的平均识别滞后天数;右图为各窗口下滚动相关性的分布箱线图,展示信号噪声随窗口长度的变化。

怎么读这张图:左图条形高度即识别滞后天数,直观可见窗口越长滞后越大。右图的箱线图反映信号噪声:60 日窗口(最左)分布最宽,说明单次估计方差大,但响应快;250 日窗口(最右)分布最窄,信号平滑但严重滞后。两者是互补关系,无法同时优化。


三、Regime 切换下的 60/40 组合风险#

模拟中两个 regime 各持续约一年,10 年共 10 次切换。关键指标对比

指标增长主导(强负相关)通胀主导(弱负相关)变化
年化波动率3.21%3.26%相近
最大回撤−5.5%−5.4%相近
Sharpe 比率0.1970.139−29%
股债相关性−0.970−0.356分散化失效

关键发现:通胀主导 regime 下,绝对波动率和最大回撤与增长 regime 相近(3.21% vs 3.26%),但分散化效果显著减弱(股债相关性从 −0.97 变为 −0.36),Sharpe 比率下降约 29%(0.197 → 0.139)。这意味着:60/40 组合在它最需要保护的时候(通胀冲击),分散化恰恰已经失效了——虽然绝对风险相近,但你承担的是不相关的风险,而非经过对冲后的有效低风险

组合风险图:两 regime 下 60/40 组合的年化波动率、最大回撤和 Sharpe 比率三指标对比。通胀 regime 下 Sharpe 明显下降。

容易读错的地方:波动率柱(左右几乎一样高)容易让人误以为”风险差不多”。但看 Sharpe 柱(右图),通胀 regime 明显更矮——说明相同波动率下,组合收益更低,本质是相关性变化带来的分散化失效,不在于绝对波动本身。


四、动态资产配置:一个诚实的负面结论#

4.1 信号逻辑#

最简单的动态配置思路:当相关性升至阈值以上(分散化失效),降低股票权重;当相关性回落,恢复 60/40

  • 正常(相关性 < −0.65):60% 股票 + 40% 债券
  • 预警(相关性 > −0.65):20% 股票 + 10% 债券(降杠杆)

4.2 实测:大部分动态策略跑输静态基准#

策略Sharpe年化换手次数vs 静态
静态 60/40(基准)0.3990
动态 60 日窗口,10bp/次0.3499 次−13%
动态 120 日窗口,10bp/次0.3719 次−7%
动态 250 日窗口,10bp/次0.42911 次+8%(唯一正)

结论:除了 250 日窗口,其余全部跑输静态基准。250 日窗口的”优势”来自信号更少(只切换 11 次),但这本身不稳定——它只是碰巧在当前随机种子下赢了。

4.3 成本拆解#

实测:10 次切换 × 10bp = 100bp 年度换手成本。问题在于:每次切换的判断有 43–188 天的滞后——你往往在错误的时间点切换,损失已经发生,降杠杆之后市场可能已经反弹。

策略成本图:左图为各动态策略 Sharpe 与静态基准对比;右图为证伪对照——恒定相关性和打乱 regime 下的动态策略表现。

怎么读这张图:右图是核心——安慰剂检验(紫色柱)Sharpe 仅 0.144,远低于静态基准 0.399。这说明:即使在完全随机的 regime 序列下,滚动相关性的噪声波动就足以制造”假信号”,而这些假信号产生的交易亏损就把动态策略的收益消耗殆尽。灰色带(静态 ±5%)几乎覆盖了所有动态策略。


五、证伪对照:两个实验#

5.1 安慰剂检验(置换 regime 序列)#

将真实 regime 时序打乱(随机置换),保留因子分布不变,用同一套代码跑动态策略。

结果:打乱后动态策略 Sharpe = 0.144(远低于静态基准 0.399),切换 14 次

这意味着:你在真实数据上看到的”动态策略有效”,有相当部分来自 regime 时序的偶然性,而非信号本身具有预测力。

5.2 纯净引擎对照(恒定相关性市场)#

构造一个相关性恒定在 ≈0.5 的市场(通过 Cholesky 分解强制 corr=0.5),在此市场上跑同一套动态配置。

结果:Sharpe = 0.099,切换 0 次

这个结果说明:在相关性真正恒定的市场,滚动窗口不会产生虚假切换(因为因子结构把噪声压住了)。但这恰恰反衬出真实市场的问题——当 regime 真的在切换时,噪声和信号混在一起,纯净引擎的对照结果(0.099)反而低于静态基准(0.399)。


六、Bug 复盘:全样本相关性矩阵偷看未来#

6.1 错误的实现(look-ahead bias)#

# ❌ 错误:全样本相关性(用了整个样本,含未来数据)
full_corr = df_rets.corr()           # 用了整个样本(含未来)
full_eq_vol = df_rets["equity"].std()
full_bd_vol = df_rets["bond"].std()
port_vol_bug = np.sqrt(
    (0.6 * full_eq_vol)**2
    + (0.4 * full_bd_vol)**2
    + 2 * 0.6 * 0.4 * full_corr.loc["equity","bond"]
      * full_eq_vol * full_bd_vol
)
bug_sharpe = (port_ret.mean() * 252) / (port_vol_bug * np.sqrt(252))
python

正确实现

# ✅ 正确:滚动协方差(只用历史数据,无 look-ahead)
roll_vol_eq = df_rets["equity"].rolling(250).std()
roll_vol_bd = df_rets["bond"].rolling(250).std()
roll_cov    = df_rets["equity"].rolling(250).cov(df_rets["bond"])
roll_port_vol = np.sqrt(
    (0.6 * roll_vol_eq)**2
    + (0.4 * roll_vol_bd)**2
    + 2 * 0.6 * 0.4 * roll_cov * roll_vol_eq * roll_vol_bd
)
python

6.2 数字对比#

版本Sharpe差异
Bug 版(全样本协方差)0.454虚高
修正版(滚动协方差)0.255真实
虚报幅度+78%

这个 78% 的虚高来自:将全样本的稳定负相关(−0.75)当作”已知条件”代入协方差矩阵,使得组合波动率被系统性低估。而真实的滚动协方差在 regime 切换点附近波动剧烈,平均值比全样本更低(−0.69),导致真实 Sharpe 明显下降。

教训:相关性矩阵永远要用过去数据估计;任何带 df.corr()df.cov() 的全局计算都要在回测框架里做严格检查,特别是涉及组合波动率优化的场景。


七、诚实边界#

  1. 模拟 ≠ 现实:本文用的是两因子 Gaussian 模型,真实市场的 regime 切换不是整齐的一年一次,因子载荷也随时间变化(央行政策常态化后,债券对增长冲击的敏感度可能永久性改变)。

  2. 成本低估:本文用的 10bp/次是保守估计。机构执行 60/40 组合的换手,滑点 + 冲击成本可能高达 20–50bp,特别是在市场压力期间(正是需要信号的时候)。

  3. 阈值敏感性:本文阈值(−0.65)是最优已知阈值;实际操作中你需要自己估计阈值,这又引入了一层不确定性。

  4. Regime B 相关性:在模拟中 Regime B 的实测相关性为 −0.36(弱负而非正),与 2022 年现实(部分时段正相关)有差距。若调高因子载荷可得到正相关,但核心结论(识别滞后+成本吃掉收益)不变。

  5. 真实市场证据:历史数据显示,2000 年互联网泡沫期间、2022 年加息周期中,股债确实出现了正相关或弱相关。但这些样本量太小,无法做统计推断——这也是为什么我们诉诸受控模拟。


总结#

结论关键数字
Regime A(增长主导)理论相关性−0.97
Regime B(通胀主导)实测相关性−0.36
60 日窗口平均识别滞后43 天
120 日窗口平均识别滞后92 天
250 日窗口平均识别滞后188 天
通胀 regime Sharpe 降幅−29%(0.197→0.139)
静态 60/40 Sharpe0.399
动态策略(扣成本)最好 Sharpe0.429(仅 250 日窗口)
安慰剂检验 Sharpe(打乱 regime)0.144
恒定相关市场动态策略 Sharpe0.099
Look-ahead bug Sharpe 虚报+78%(0.255→0.454)

最终答案:滚动相关性作为动态资产配置信号,识别滞后(最多 9 个月)和换手成本联手把大部分理论优势吃光。真正有价值的不是在相关性切换后”做应对”,而是在构建组合之初就认识到 60/40 的这个结构性弱点,并用真正的多资产(商品、通胀挂钩债券、另类)来替代失效的”负相关保险”。

股债相关性切换:60/40 组合的隐形地基何时崩塌
https://blog.halo26812.eu.org/blog/bond-equity-correlation-regime
Author halo
Published at 2026年7月31日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨