固定部位大小會忽略市場環境。波動度目標化根據當前市場波動程度,自動調整每次下注的規模。
同一支策略,在平靜的那幾個月無聊到你想關掉它;在市場開始劇烈跳動的那個星期,回撤深得跟回測平均完全對不起來。訊號沒有變,變的是市場——而你每次下注的大小一直沒變。
波動度目標化是讓部位大小跟市場的已實現波動反向連動:波動比目標高就投少一點,比目標低就投多一點,但有上限。
基本的 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_year | lookback(30 天窗口) |
|---|---|---|
5min | 105,120(12 × 24 × 365) | 8,640 根 |
1h(預設) | 8,760 | 720 根 |
4h | 2,190 | 180 根 |
8h | 1,095 | 90 根 |
1d | 365 | 30 根 |
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_VOL | 0.30 | 年化目標波動 30% | 照你能接受的 MDD ÷ 2 |
VOL_CAP | 2.0 | 最大 2 倍槓桿 | 完全不想要槓桿就設 1.0 |
VOL_LOOKBACK | 720 | 波動估計的窗口長度(根 K 棒) | 換週期就要重算,見上面那張表 |
PERIODS_PER_YEAR | 8760 | 年化用的每年期數(1h 週期) | 換週期就要重算,見上面那張表 |
注意:add_realized_vol 必須在 fetch_data 裡呼叫(訊號計算之前),因為 apply_vol_scaling 會讀取 df['realized_vol'],而這個欄位是第一個函式就地寫入的。
上限的存在是因為在持續低波動的環境下,波動度目標化可能產生極端槓桿。如果已實現波動降到 5%,沒有上限的倍數會是 6 倍。單日不利的走勢就可能造成毀滅性的回撤,即使訊號本身完全正確。
預設 2× 的上限讓策略最多只用到配置資金的兩倍。如果你完全不想有槓桿,把 vol_cap=1.0——波動度目標化就只會在高波動時縮減部位,不會在低波動時放大超過全額。
一個實用的經驗法則:target_vol ≈ 可接受 MDD ÷ 2。這來自趨勢追蹤策略中年化波動度和最大回撤的經驗關係。
| target_vol | 隱含典型 MDD | 適合情境 |
|---|---|---|
| 10% | 約 −20% | 保守型,資金保全優先 |
| 20% | 約 −40% | 中等 |
| 30%(預設) | 約 −60% | 積極型——用滿風險預算 |
periods_per_year 跟你的週期對得上嗎?預設 8760 是 1h 用的。5min 是 105,120、4h 是 2,190、8h 是 1,095、1d 是 365。填錯,年化那一步就跟著錯,後面每一格都是錯的。
lookback 還是 30 天嗎?720 根=1h × 30 天。換週期要跟著換:5min 8,640 根、4h 180 根、8h 90 根、1d 30 根。日線只用 30 根偏短,可以考慮 252 根(一年)換一個更穩的估計。
target_vol 跟你能接受的 MDD 對得上嗎?經驗法則是 target_vol ≈ 可接受 MDD ÷ 2:10% 對約 −20%、20% 對約 −40%、預設的 30% 對約 −60%。(MDD 怎麼讀,見〈回測是什麼?回測報告怎麼看〉。)
vol_cap 給你的槓桿是你要的嗎?預設 2.0 表示最多用到配置資金的兩倍。設 1.0,它就只會在高波動時縮部位,不會在低波動時放大超過全額。
個別策略的 TARGET_VOL 跟組合 Manager 的 target_vol_pct 一致嗎?Manager 決定整體槓桿用的是同一套邏輯(槓桿 = target_vol ÷ 組合已實現波動)。兩邊不一致,多個策略組起來會重複計算風險。
lookback 根算出來的:窗口越長,估計越穩、反應也越慢;窗口越短,越容易對一次短期飆升過度反應。兩邊都不會讓你提前知道波動要跳。