波动率目标化怎么运作

固定仓位大小会忽略市场环境。波动率目标化根据当前市场波动程度,自动调整每次下注的规模。

最后更新 2026-09
要点总结
  • 波动率目标化让仓位大小跟已实现波动反向联动:市场比目标吵就投少一点,比目标静就投多一点,但有上限。
  • 两步:先算对数收益的滚动标准差再年化,得到已实现波动;再把信号乘上 target_vol ÷ realized_vol,并用 vol_cap 砍顶。
  • 默认 target_vol=0.30、vol_cap=2.0、lookback=720、periods_per_year=8760,这组是为 1h 策略校准的——换周期一定要重算。
  • 经验法则:target_vol ≈ 可接受 MDD ÷ 2。默认的 30% 对应的典型 MDD 约 −60%。
  • 它调的是仓位大小,不是信号的方向;信号错的时候,它只决定你错多大。

同一支策略,在平静的那几个月无聊到你想关掉它;在市场开始剧烈跳动的那个星期,回撤深得跟回测平均完全对不起来。信号没有变,变的是市场——而你每次下注的大小一直没变。

波动率目标化是什么

波动率目标化是让仓位大小跟市场的已实现波动反向联动:波动比目标高就投少一点,比目标低就投多一点,但有上限。

基本的 Type A 策略输出 1.0(做多)或 0.0(空仓)。Runner 在信号为 1.0 时把全部配置资金都投入。这样做有效,但有个隐性问题:不管市场是平静还是剧烈波动,策略下的注都一样大。

在低波动期,1% 的日线波动已经算大。在高波动期,1% 的波动只是噪声。如果策略在两种环境都用同样的仓位大小入场,实际的金额风险就会大幅波动——高波动期的回撤会远大于回测平均所暗示的水平。

信号说「买」,没说「买多少」。
固定仓位等于预设每一天的市场都一样吵——同样一单,在平静期和危机期是两种不同的风险。

公式

Blave Agent 的波动率目标化分两步:

第一步——测量已实现波动率

log_return = log(Close / Close.shift(1))
realized_vol = log_return.rolling(lookback).std() * sqrt(periods_per_year)

计算对数收益率的滚动标准差,再乘以 sqrt(periods_per_year) 换算成年化。结果就是年化已实现波动率——和 Sharpe ratio 使用的波动率单位相同。

默认值:lookback=720 根 K 线、periods_per_year=8760。这是为1h 周期策略校准的:720 根 = 30 天 × 24 小时;8760 = 24 × 365。

第二步——缩放信号

scale = (target_vol / realized_vol).clip(upper=vol_cap)
scaled_signal = signal * scale

信号(1.0 = 全额做多)乘以波动率比值,上限为 vol_cap,得到小数仓位大小。

默认值:target_vol=0.30(年化 30%)、vol_cap=2.0(最大 2 倍杠杆)。

具体示例

已实现波动(年化)目标波动原始倍数套用上限后仓位大小
10%30%3.0×2.0×(上限)配置资金的 200%
20%30%1.5×1.5×配置资金的 150%
30%30%1.0×1.0×100%——基准
60%30%0.5×0.5×配置资金的 50%
90%30%0.33×0.33×配置资金的 33%

已实现波动在 30% 时,策略以全额运行。危机期间波动飙升到 90% 时,策略自动缩减至三分之一仓位。平静期波动降至 10% 时,放大到上限 2 倍。

不同周期的参数调整

默认值(lookback=720、periods_per_year=8760)是为1h 策略设定的。其他周期必须重新计算:

周期periods_per_yearlookback(30 天窗口)
5min105,120(12 × 24 × 365)8,640 根
1h(默认)8,760720 根
4h2,190180 根
8h1,09590 根
1d36530 根
日线策略用 30 根的 lookback 很短。考虑用 252 根(一年)来取得更稳定的波动估计,避免对短期飙升过度反应。

怎么加进策略里

VOL_TARGETING    = True
TARGET_VOL       = 0.30   # 年化目标波动 30%
VOL_CAP          = 2.0    # 最大 2 倍杠杆
VOL_LOOKBACK     = 720    # lookback 窗口长度(根 K 线)
PERIODS_PER_YEAR = 8760   # 1h 周期用

def fetch_data(hdrs):
    from lib.data import fetch_kline
    from lib.strategy import add_realized_vol
    df = fetch_kline(SYMBOL, INTERVAL, START, END, hdrs)
    if VOL_TARGETING:
        add_realized_vol(df, VOL_LOOKBACK, PERIODS_PER_YEAR)
    return df

def compute_signals(df):
    from lib.strategy import apply_vol_scaling
    signal = pd.Series(np.nan, index=df.index)
    # ... 信号逻辑 ...
    if VOL_TARGETING:
        return apply_vol_scaling(signal, df, TARGET_VOL, VOL_CAP)
    return signal
参数默认意思什么时候要改
TARGET_VOL0.30年化目标波动 30%照你能接受的 MDD ÷ 2
VOL_CAP2.0最大 2 倍杠杆完全不想要杠杆就设 1.0
VOL_LOOKBACK720波动估计的窗口长度(根 K 线)换周期就要重算,见上面那张表
PERIODS_PER_YEAR8760年化用的每年期数(1h 周期)换周期就要重算,见上面那张表

注意:add_realized_vol 必须在 fetch_data 里调用(信号计算之前),因为 apply_vol_scaling 会读取 df['realized_vol'],而这个字段是第一个函数就地写入的。

vol_cap 为什么默认是 2×

上限的存在是因为在持续低波动的环境下,波动率目标化可能产生极端杠杆。如果已实现波动降到 5%,没有上限的倍数会是 6 倍。单日不利的走势就可能造成毁灭性的回撤,即使信号本身完全正确。

默认 2× 的上限让策略最多只用到配置资金的两倍。如果你完全不想有杠杆,把 vol_cap=1.0——波动率目标化就只会在高波动时缩减仓位,不会在低波动时放大超过全额。

目标波动和隐含最大回撤

一个实用的经验法则:target_vol ≈ 可接受 MDD ÷ 2。这来自趋势跟踪策略中年化波动率和最大回撤的经验关系。

target_vol隐含典型 MDD适合场景
10%约 −20%保守型,资金保全优先
20%约 −40%中等
30%(默认)约 −60%积极型——用满风险预算

怎么知道你的参数设对了

1

periods_per_year 跟你的周期对得上吗?默认 8760 是 1h 用的。5min 是 105,120、4h 是 2,190、8h 是 1,095、1d 是 365。填错,年化那一步就跟着错,后面每一格都是错的。

2

lookback 还是 30 天吗?720 根=1h × 30 天。换周期要跟着换:5min 8,640 根、4h 180 根、8h 90 根、1d 30 根。日线只用 30 根偏短,可以考虑 252 根(一年)换一个更稳的估计。

3

target_vol 跟你能接受的 MDD 对得上吗?经验法则是 target_vol ≈ 可接受 MDD ÷ 2:10% 对约 −20%、20% 对约 −40%、默认的 30% 对约 −60%。(MDD 怎么读,见〈回测是什么?回测报告怎么看〉。)

4

vol_cap 给你的杠杆是你要的吗?默认 2.0 表示最多用到配置资金的两倍。设 1.0,它就只会在高波动时缩仓位,不会在低波动时放大超过全额。

5

个别策略的 TARGET_VOL 跟组合 Manager 的 target_vol_pct 一致吗?Manager 决定整体杠杆用的是同一套逻辑(杠杆 = target_vol ÷ 组合已实现波动)。两边不一致,多个策略组起来会重复计算风险。

波动率目标化不会帮你做的事

  • 它调的是仓位大小,不是信号的对错。缩放乘在信号后面——信号错的时候,它只决定你错多大。
  • target_vol ≈ 可接受 MDD ÷ 2 是趋势跟踪策略上的经验关系,不是保证。表里写的是「典型」MDD,不是上限。
  • 已实现波动是用过去 lookback 根算出来的:窗口越长,估计越稳、反应也越慢;窗口越短,越容易对一次短期飙升过度反应。两边都不会让你提前知道波动要跳。