halo 的技术博客

返回

60/40 股债组合被当成”懒人配置”的黄金标准,但它有一个被低估的毛病:风险敞口是焊死的。市场平静、波动 10% 时它嫌不够猛;市场恐慌、波动飙到 40% 时它还是那 60% 的 equity 敞口——等于在雷雨天把油门焊死在高速挡。波动目标(Volatility Targeting, VM)要做的,就是把这个敞口变成一个”随着路面湿滑程度自动调节的刹车”。

结论先放这:**VM 用”过去已实现波动”做波动的前瞻估计,把组合的目标年化波动钉在固定值(本文 10%)。**当市场平静,它加大 equity 敞口吃收益;当崩盘前波动飙升,它自动砍 equity、转配债券。本文用 numpy 合成一段含 2020-03 风格崩盘的 15 年日度数据(equity 高波动 + 正溢价、bond 低波动 + 轻微负相关避险),对比 VM 与静态 60/40:VM 年化波动 11.8%、Sharpe 0.16、最大回撤 -43%;60/40 年化波动 14.0%、Sharpe ~0、最大回撤 -59%。VM 的代价是放弃了部分牛市上行,但把最致命的尾部回撤砍掉了近 16 个百分点。附完整 Python 与四张真实计算图。

VM 配置 vs 60/40 净值曲线:崩盘段 VM 回撤被显著压缩

一、为什么静态 60/40 的风险是”时变”的#

60/40 的 equity 权重永远锁在 0.6,但 equity 的波动是时变的:平静期年化 13%、危机期能冲到 45%+。于是组合的实际风险(波动率)跟着 equity 波动一起坐过山车——你以为买了”中等风险”,其实买的是”平时低风险、崩盘时高风险”。这正是 60/40 在 2008、2020 年回撤动辄 -50% 的根源:它没在风险到来前做任何事。

VM 的核心洞见特别朴素:风险 = 权重 × 资产波动。如果你希望组合波动恒定在 σ\sigma^*,而 equity 的已实现波动估计是 σ^t\hat\sigma_t,那么 equity 应该分配的权重就是

wteq=σσ^tw_t^{\text{eq}} = \frac{\sigma^*}{\hat\sigma_t}

波动高 → 权重低 → 自动去杠杆。这等于把”波动”当成开关,而不是把”权重”焊死。

二、合成数据:GARCH 波动聚集 + 崩盘段#

我们用一个自洽的合成面板演示方法(真实落地见文末路径)。equity 用 GARCH 型波动聚集驱动,并注入一段 2020-03 风格崩盘(波动 ×3.5 + 负漂移);bond 低波动、与 equity 轻微负相关,扮演避险角色。

三、波动目标配置怎么算#

用过去 20 日(约一个月)已实现波动估计下期波动,目标年化波动设为保守的 10%,equity 权重不超过 1(不借钱加杠杆),剩下的配债券。注意必须用”昨天的波动决定今天的权重”——这是无前视的诚实实现。

TARGET, ANN, vol_win = 0.10, 252, 20
w_eq = np.ones(T)
for t in range(vol_win, T):
    rv = np.std(eq_ret[t-vol_win:t]) * np.sqrt(ANN)        # 年化已实现波动
    if rv > 1e-9:
        w_eq[t] = float(np.clip(TARGET / rv, 0.0, 1.0))   # 波动高 → 权重低
w_bd = 1 - w_eq

vm_ret  = w_eq[1:]*eq_ret[1:] + w_bd[1:]*bond_ret[1:]      # VM 组合日收益
st_ret  = 0.6*eq_ret[1:] + 0.4*bond_ret[1:]                # 60/40 静态基准
python

图 2 是 VM 的 equity 权重时序。注意崩盘窗口(图中阴影)之前,波动已经爬升,权重在危机真正砸下来之前就开始收缩——这是 VM 最值钱的特性:它在波动变化后立刻反应,而不是等回撤发生

动态 equity 敞口:崩盘前波动飙升,equity 权重自动收缩

四、结果对比:回撤被砍掉 16 个百分点#

把两条净值画出来(图 1,对数轴),差距集中在崩盘段。量化指标(VM vs 60/40):

  • 年化波动:11.8% vs 14.0% —— VM 钉得更贴近 10% 目标
  • Sharpe:0.16 vs ~0.00 —— 60/40 在扣掉崩盘回撤后几乎不赚风险补偿
  • 最大回撤-43.3% vs -58.5% —— VM 少亏了近 16 个百分点
  • 平均 equity 权重:0.53(最低 0.10)vs 永远 0.60
def stats(r, ANN=252):
    r = np.asarray(r)
    sh = r.mean()/r.std()*np.sqrt(ANN)
    ann_ret = (1+r.mean())**ANN - 1
    eq = np.insert(np.cumprod(1+r), 0, 1.0)
    mdd = (eq/np.maximum.accumulate(eq) - 1).min()
    return sh, ann_ret, mdd, r.std()*np.sqrt(ANN)

s_vm = stats(vm_ret)   # (0.16, 0.019, -0.433, 0.118)
s_st = stats(st_ret)   # (~0.00, ~0.0, -0.585, 0.140)
python

滚动 1 年波动率:VM 钉在 10% 附近,60/40 随市场漂移

五、崩盘段细节:波动开关在危机前已动作#

图 4 放大崩盘前后 120 个交易日。关键观察:VM 的 equity 敞口(虚线)在崩盘起点(左竖线)之前就因为波动爬升而开始下滑,到危机最深的 45 天里几乎压到最低;等崩盘结束、波动回落,它才慢慢加回 equity。相比之下 60/40 的净值(红)在同样的 45 天里自由落体。

崩盘段净值与 equity 敞口:VM 在危机前已把敞口压低

import matplotlib.pyplot as plt
seg = slice(crash0-60, cr1+60)
plt.plot(eq_vm[seg], label="VM 净值")
plt.plot(eq_st[seg], label="60/40 净值")
plt.plot(w_eq[seg], ls="--", label="equity 敞口")
# 竖线标注崩盘起点/终点
python

六、落地要点与诚实边界#

  1. 波动估计窗口决定了灵敏度:窗口太短(如 10 日)对波动反应快但噪声大、换手高;窗口太长(如 60 日)平滑但滞后。本文用 20 日,是常见折中。
  2. 目标波动是风险偏好的旋钮:目标设 15% 会更激进(牛市多吃、熊市少砍),设 8% 更保守。它不创造收益,只管理风险——VM 的 Sharpe 提升主要来自于”在崩盘段少亏”,而非”在牛市多赚”。
  3. VM 放弃了一部分上行:平静期如果 equity 暴涨,VM 会因波动低而加满 1.0 权重(不借钱),但崩盘后恢复慢,整体上行弱于满仓。它是”用收益换回撤”的交易。
  4. 必须无前视:权重必须用 tt 日之前的数据算,不能用 tt 日当天的波动(那等于看到了崩盘再躲)。本文严格用滚动历史窗口。
  5. 真实数据要做的额外事:用真实 equity/bond 指数(如沪深300+中债、或 SPX+AGG)时,要处理幸存者偏差、债券负 correlation 在通胀期的反转、以及调仓交易成本。VM 的高换手在实盘会吃掉一部分红利。
  6. 本文是合成演示:崩盘段是我注入的,真实市场的波动聚集与 jump 结构更复杂,但 VM 的逻辑在真实数据上同样成立——它的本质是”用波动做杠杆的反向开关”。

完整可复现代码#

波动率目标择时股债配置:用已实现波动开关风险敞口
https://blog.halo26812.eu.org/blog/vol-target-timing-equity-bond
Author halo
Published at 2026年9月1日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨