波動率目標化與基於GARCH預測的交易
本系列的前三篇文章教會了你如何預測波動率。我們在第一部分中構建了單變數GARCH(1,1)模型,在第二部分中通過GJR和Student-t新息加入了槓桿效應和厚尾特性,並在第三部分中用DCC-GARCH對整個協方差矩陣隨時間的演變進行了建模。每一部分的結尾,我們都打印出一個數字:明天的預期波動率。然後,說實話,我們就止步於此——彷彿產出預測本身就是目的。
但事實並非如此。波動率預測不是盈虧。從來沒有人因為QLIKE得分低而獲得報酬。預測只有在恰好改變了你原本會做出的不同決策的那一刻才變得有價值——買多少、何時止損、分配多少資本。如果你的預測沒有改變倉位,它的統計準確性就只是一種私人愛好。
這最後一部分要做的就是閉合這個迴圈。我們把第一到三部分的預測投入到最純粹的決策場景中使用:波動率目標化(volatility targeting)——調整倉位大小,使投資組合的已實現波動率達到一個恆定目標。然後我們要做這個部落格比任何單一模型都更看重的事情:誠實地評估。我們將GARCH與樸素但強勁的基準(滾動已實現波動率、EWMA)進行比較,使用對我們永遠無法觀測到真實波動率這一事實具有穩健性的損失函數,執行一個帶成本且無前視偏差的walk-forward回測,並坦率地說明波動率目標化能帶來什麼、不能帶來什麼。劇透一下:它在改善風險調整後收益和抑制回撤方面的可靠性,遠高於它在製造阿爾法方面的能力。
為什麼波動率目標化是正確的檢驗方式

使用波動率預測還有更花哨的方式——期權定價、VaR限額、動態對沖——但波動率目標化是最能以最少的其他因素汙染來分離出預測價值的一種方式。這個想法歸結為一個方程。
持有一個具有訊號所隱含方向(暫且只考慮"做多")的風險資產。不採用固定倉位,而是讓敞口與預測波動率成反比縮放:
其中是你對下一期波動率的預測(僅使用截至時刻的資料得出),是你希望策略執行的年化波動率——比如15%或20%。當模型預測市場平靜時,你會加槓桿至(或超過)1.0;當它預測風暴來臨時,你會縮減倉位。縮放後倉位的已實現波動率,在一階近似下為
只要成立。所以整個操作的質量都取決於一件事:你的預測與下一期實際波動率有多接近。更好的預測會產生更平坦的已實現波動率曲線,並且——正如我們將看到的——帶來更好的Sharpe比率。這就是為什麼這是正確的檢驗方式。預測不是裝飾品;它是分母本身。
為什麼這能提升Sharpe比率並控制回撤
兩個經驗事實在這裡起作用。
波動率比收益率更具可預測性得多。 在日頻層面,明天BTC收益率的方向接近於拋硬幣;但幅度並非如此。波動率具有聚集性——大波動之後往往跟著大波動——這正是GARCH存在的全部原因(第一部分推導了編碼這一特性的方差AR(1)結構)。對一日前波動率預測得到0.4-0.6的是家常便飯;同樣的數字用於收益率預測,那將是文藝復興級別的訊號。波動率目標化利用了可預測的量,而對不可預測的量保持中立。
Sharpe比率並非隨時間恆定;它在波動率飆升時會下降。 加密貨幣中的高波動率狀態——去槓桿連環爆倉、交易所故障、一切都跳空30%的日子——往往具有更差的單位風險收益,而非更好。通過在預測波動率高時機械地削減敞口,你就低配了那些對回撤貢獻最大、對複合收益貢獻最小的時段。Moreira and Muir(2017)在股票領域證明了波動率管理型投資組合——正是這種縮放——能提升Sharpe比率,並相對於未管理的因子產生正的阿爾法。這個機制並非魔法;它只是拒絕在可預見的動盪時段裡持有固定美元倉位。
回撤方面的好處更為直接。最大回撤主要由倉位收益分佈的尾部決定。由於的波動率被固定在接近的水平,固定名義本金策略在波動率爆發期間遭受的肥厚左尾被壓縮了:你一開始的倉位就已經很小了。波動率目標化並不能預測崩盤,但它在市場動盪時系統性地保持低敞口,而動盪恰恰是崩盤發生的時候。
與Kelly和分數式頭寸規模的關係
波動率目標化是Kelly準則的近親。對於一個具有期望超額收益和方差的單一資產,增長最優(全額Kelly)的比例為
如果你假設Sharpe比率大致恆定——這是一個很強的假設,但也是"市場對風險給出穩定定價"這一說法隱含的假設——那麼,,這恰好就是的波動率目標化。換句話說,在期望收益隨波動率成比例變化的假設下,波動率目標化就是Kelly頭寸規模化。 我們在Kelly準則與策略頭寸規模一文中詳細討論了全額和分數式Kelly,以及為什麼沒有理智的人會採用全額Kelly交易。那裡得出的實用經驗在這裡同樣適用:使用理論倉位的一部分,因為分母(你的波動率預測)尤其是分子(期望收益)中的估計誤差,會使全額頭寸規模變得危險地激進。
還有兩點值得牢記。首先,波動率目標化是基於收益的對稱離散度來設定頭寸規模的,但加密貨幣的收益並不對稱——一旦涉及槓桿和強平,下跌20%的成本並非上漲20%的映象。我們在損益不對稱性一文中直接處理了這種不對稱性,而GJR/EGARCH預測(第二部分)已經通過對負面衝擊反應更強烈,將部分不對稱性融入了之中。其次,波動率預測是一個點估計;更完整的風險視角會為其附加一個區間。交易中的保形預測展示瞭如何將模型輸出轉化為可用於頭寸規模設定的、不依賴分佈假設的區間,這與本文誠實評估的主題自然契合。
競爭對手:GARCH必須擊敗的物件

這裡正是把真正的評估與演示區分開來的嚴謹之處。在給GARCH加冕之前,你必須給它提供廉價、顯而易見、卻出人意料地難以擊敗的對手。如果你精心設計的GJR-t模型無法勝過五行程式碼的EWMA,那你就學到了寶貴的一課,併為自己省下了大量的生產複雜度。
我們基準比較了四種下一期波動率預測方法。
(a)滾動已實現波動率(滾動標準差)
最樸素的預測:明天的波動率等於過去個日收益率的樣本標準差。
它只有一個超參數(視窗,通常為20-60天),沒有模型。它的缺陷在於視窗內的每個觀測值權重相同,然後在退出視窗時會突然跌落懸崖——這就是"重影"或"回聲"效應,一個崩盤日會使預測值膨脹恰好天,然後一夜之間消失,無論市場是否真的已經平靜下來。
(b)EWMA / RiskMetrics()
指數加權移動平均通過對較早的平方收益率賦予幾何衰減權重,解決了回聲問題:
這是RiskMetrics(J.P. Morgan, 1996)估計量。對於日頻資料採用標準的,有效記憶長度約為天,但衰減是平滑的——沒有懸崖式跌落。請注意EWMA的本質:它是一個**且的整合GARCH(1,1)**,即沒有均值迴歸、沒有長期方差的GARCH。如果你接受,它就零自由參數,是本文中最難以擊敗的單一基準。相當一部分"GARCH擊敗X"的論文,在樣本外都悄悄未能擊敗EWMA。
import numpy as np
import pandas as pd
def ewma_vol(returns: pd.Series, lam: float = 0.94, sigma0: float | None = None) -> pd.Series:
"""RiskMetrics EWMA conditional volatility (returns in decimal, e.g. 0.03).
sigma2_t = lam * sigma2_{t-1} + (1 - lam) * r_t^2
Returns a series aligned to `returns` where value at t uses info up to t.
"""
r2 = np.square(returns.values)
var = np.empty_like(r2)
var[0] = sigma0**2 if sigma0 is not None else r2[0]
for t in range(1, len(r2)):
var[t] = lam * var[t - 1] + (1.0 - lam) * r2[t - 1] # note: r_{t-1}, no look-ahead
return pd.Series(np.sqrt(var), index=returns.index, name="ewma_vol")
有一個容易讓人栽跟頭的細微之處:針對期的預測(可用於設定持有至期的倉位)必須由之前觀測到的收益率構建而成。在上面的遞迴中,var[t]使用的是r2[t-1],因此該序列是真正意義上的一步前預測。把這個索引對齊正確,是回測與幻想之間的分界線——關於這一點,在walk-forward部分會有更多討論。
(c)GARCH(1,1)和GJR-t(第一、二部分)
我們的主角登場。標準GARCH(1,1):
其長期方差為,一步預測直接從遞迴式中得出(第一部分)。GJR-GARCH擴充加入了槓桿項,使負面衝擊比正面衝擊更能提升方差:
再配合Student-t新息來處理加密貨幣的厚尾特性,這就是第二部分中的GJR-t。GARCH能夠勝過EWMA的原因在於均值迴歸:受到衝擊後,GARCH會以由決定的速率將預測值拉回,而EWMA(作為整合模型)永不迴歸。當波動率飆升然後恢復正常——這是常見情形——GARCH的預測能更快、更準確地衰減回落。當波動率真正具有持續性時,兩者幾乎無法區分。
(d)基於已實現方差的HAR-RV(如果你有日內資料)
如果你有日內K線資料——而在24/7運轉的加密貨幣市場中你幾乎總是有——你可以構建一個比日收益率平方噪聲小得多的波動率代理指標:已實現方差(realized variance),即當日日內收益率平方之和。
Corsi(2009)的異質自迴歸模型(Heterogeneous Autoregressive model)利用過去的日、周、月平均值來預測明天的已實現方差——這是一種粗糙但出奇有效的方法,用三個迴歸變數捕捉長記憶持續性:
其中和是過去5天和22天日的滾動平均值。這是一個普通的OLS迴歸,利用了質量更高的日內代理指標,並且往往是四種方法中日波動率預測最好的一種——通常正是因為是比更乾淨的目標而擊敗了GARCH。
import numpy as np
import pandas as pd
def realized_variance(intraday_returns: pd.Series, day_index) -> pd.Series:
"""Daily realized variance = sum of squared intraday (log) returns per day.
`intraday_returns` indexed by timestamp; `day_index` maps to calendar day.
"""
return intraday_returns.pow(2).groupby(day_index).sum()
def har_features(rv: pd.Series) -> pd.DataFrame:
"""Build HAR regressors from a daily realized-variance series."""
df = pd.DataFrame({"rv": rv})
df["rv_d"] = df["rv"].shift(1) # yesterday
df["rv_w"] = df["rv"].shift(1).rolling(5).mean() # trailing week
df["rv_m"] = df["rv"].shift(1).rolling(22).mean() # trailing month
df["target"] = df["rv"] # predict today's RV
return df.dropna()
def fit_har(rv: pd.Series):
"""Fit HAR-RV by OLS. Returns (coef_dict, predict_fn)."""
df = har_features(rv)
X = np.column_stack([np.ones(len(df)), df["rv_d"], df["rv_w"], df["rv_m"]])
y = df["target"].values
beta, *_ = np.linalg.lstsq(X, y, rcond=None)
names = ["const", "rv_d", "rv_w", "rv_m"]
def predict(rv_d, rv_w, rv_m):
return float(beta @ np.array([1.0, rv_d, rv_w, rv_m]))
return dict(zip(names, beta)), predict
關於log-HAR的一點說明:由於是右偏且嚴格為正的,許多從業者會用log HAR特徵對進行迴歸,這能改善擬合併保證預測為正。當你對結果取指數還原時,應該加上一個半方差的Jensen修正項,,否則你會系統性地低估預測值。
有了這四種預測方法——RV、EWMA、GARCH/GJR-t、HAR——問題就變成了:當我們永遠無法看到它們都在試圖預測的那個真實量時,我們如何判斷哪一個最好?
誠實地評估波動率預測

這是本文的核心內容,所以在這裡請放慢腳步。
你想把預測與真實條件方差進行比較。但是一個潛在(latent)量——它是資料生成過程的一個參數,永遠無法直接觀測到。你能得到的只是每天一個已實現的收益率。所以每一次波動率評估實際上都是把你的預測與真值的一個含噪聲代理指標進行比較。兩種標準代理指標:
- 平方收益率。在零均值模型下對是無偏的(),但噪聲極大:單個日收益率只是對標準差的單觀測估計。代理指標在真實波動率很高時也可能為0(平靜的一天),或者在幸運的尾部日出現巨大數值。
- 來自日內資料的已實現方差。噪聲要小得多——日內取樣平均掉了單個收益率的特異性噪聲——這正是HAR-RV有效的原因,也是為什麼如果你有日內資料的話,應該用作為代理指標。
幾乎每個人都會中招的細微之處在於:由於代理指標是有噪聲的,損失函數的選擇並非無關緊要。 用錯誤的損失函數對兩個預測進行排名,噪聲代理指標可能會顛倒排名,告訴你更差的預測反而更好。Patton(2011)精確地推導出哪些損失函數在以下意義上是"穩健的":按對含噪聲代理指標的期望損失對預測進行排名,得到的排名與你在真實(不可觀測的)方差上得到的排名相同。只有一個特定的函數族符合條件。實踐中有兩個成員至關重要。
MSE與QLIKE
方差層面的均方誤差:
其中是預測值,由或代理。MSE在Patton意義上是穩健的(其排名對代理指標是一致的),但它是對稱且依賴尺度的:它對相同絕對幅度的過度預測和不足預測的懲罰是一樣的,並且它對高波動率時期誤差的權重要遠大於平靜時期的誤差。一個在平靜的95%的日子裡表現完美、但在三個危機日的方差預測上出錯的模型,在MSE下會顯得很糟糕,即便它在危機中的表現恰恰是你真正想要的。
QLIKE(準似然)損失是主力工具:
它是方差上高斯似然所隱含的損失,同樣在Patton意義上是穩健的,並且具有兩個使其成為波動率評估首選的性質。首先,它是在正確方向上不對稱的:它對方差的低估懲罰要重於高估。對風險管理者或波動率目標化者而言,這正是正確的不對稱性——低估波動率意味著你在關鍵時刻承擔了過大的倉位,這是代價高昂的錯誤。其次,它(大致上)是尺度不變的:因為它依賴於比率,無論是發生在平靜日還是危機日,10%的預測誤差所付出的代價大致相同,所以評估不會像MSE那樣被少數幾個高方差觀測值所劫持。這種對代理指標異方差性的穩健性,正是當整個重點在於波動率劇烈變化時所需要的。
注意,當且僅當時取等號。數值越低越好,與MSE相同。
import numpy as np
def qlike(proxy: np.ndarray, forecast_var: np.ndarray) -> np.ndarray:
"""Per-observation QLIKE loss. `proxy` is r^2 or RV (a variance proxy),
`forecast_var` is h = sigma_hat^2. Both strictly positive, same units.
"""
ratio = proxy / forecast_var
return ratio - np.log(ratio) - 1.0
def mse_var(proxy: np.ndarray, forecast_var: np.ndarray) -> np.ndarray:
"""Per-observation MSE on the variance scale."""
return np.square(proxy - forecast_var)
有兩點操作上的提醒。讓代理指標和預測值保持相同單位(都是日方差,或都是年化的),否則比率就毫無意義。並且永遠不要讓forecast_var觸及零——把它裁剪到一個較小的下限,因為會毒害整個平均值。
Mincer-Zarnowitz迴歸
單一的損失數值告訴你哪個預測更好;它不會告訴你一個預測為何出錯。Mincer-Zarnowitz(1969)迴歸可以做到這一點。將代理指標對預測值做迴歸:
在一個最優的無偏預測下,且:平均而言,已實現方差等於預測值。偏離這個結果可以診斷出病理:
- 且:這是預測波動過度的經典特徵——它反應過度,預測出了不會完全實現的極端值。這在由原始平方收益率驅動的模型中非常常見。
- :預測反應不足,隨真實方差變化的幅度太小。
- 迴歸較低:即使平均看起來還不錯,預測在逐日追蹤方差方面表現較差。由於代理指標噪聲很大,不要因為對的MZ 通常只有0.05-0.20而感到驚訝;對而言這個值會高得多。對的無論預測多好,都被嚴格限制在遠低於1的水平,純粹是因為代理指標的噪聲所致。
對的聯合檢驗給出了一個正式的校準檢驗。實踐中,用MZ作為診斷工具來理解一個預測,用QLIKE來排名預測。
Diebold-Mariano檢驗:差異是真實的嗎?
假設GARCH的平均QLIKE是0.183,EWMA的是0.191。GARCH"贏了"。但0.008是真實的優勢還是抽樣噪聲?Diebold-Mariano(1995)檢驗正是回答這個問題。定義每期損失差
針對兩個預測和(此處為QLIKE)。原假設是預測準確性相等,。檢驗統計量是均值差經其長期(HAC)標準誤標準化後的結果,因為是序列相關的:
其中是Newey-West型長期方差估計。超過的DM統計量在5%水平上拒絕準確性相等的原假設。至關重要的是,DM是關於預測而非巢狀模型的檢驗,它處理了損失序列中的序列依賴性,而對進行樸素的檢驗會忽略這一點。
import numpy as np
from scipy import stats
def diebold_mariano(loss_a: np.ndarray, loss_b: np.ndarray, h: int = 1):
"""Diebold-Mariano test of equal predictive accuracy.
loss_a, loss_b: per-period losses (e.g. QLIKE) for forecasts A and B.
h: forecast horizon; Newey-West lag = h - 1 (>=0), plus a small-sample buffer.
Returns (DM_stat, p_value). Negative DM => A has lower loss (A better).
"""
d = np.asarray(loss_a) - np.asarray(loss_b)
T = len(d)
d_bar = d.mean()
lag = max(h - 1, 0)
gamma0 = np.mean((d - d_bar) ** 2)
lrv = gamma0
for k in range(1, lag + 1):
w = 1.0 - k / (lag + 1)
cov = np.mean((d[k:] - d_bar) * (d[:-k] - d_bar))
lrv += 2.0 * w * cov
dm = d_bar / np.sqrt(lrv / T)
adj = np.sqrt((T + 1 - 2 * h + h * (h - 1) / T) / T)
dm *= adj
p = 2.0 * (1.0 - stats.t.cdf(abs(dm), df=T - 1))
return float(dm), float(p)
那條註釋裡的示例結果是誠實且常見的結果,也正是這一節存在的全部原因:GARCH經常會給出略低於EWMA的平均損失,而這種優勢也同樣經常無法通過DM顯著性檢驗。如果你只報告平均QLIKE,你會說服自己相信一些本會被DM檢驗否決的優勢。請報告DM統計量。這與我們在沒有穩健優勢的誠實評估一文中對策略收益所採用的嚴謹態度是一樣的——一個戰勝基準的點估計,在你排除它是噪聲之前,都算不上是真正的優勢。
回測:Walk-Forward波動率目標化策略

現在我們把兩個部分——預測器和頭寸規模規則——組合成一個策略,並用唯一有意義的方式來評估它:walk-forward、樣本外、含成本。
該策略刻意保持簡單,因為簡單性正是能讓我們把結果歸因於波動率預測本身、而非某個巧妙訊號的原因。純多頭、波動率目標化的BTC。 每天預測次日波動率,將倉位設為,持有隔夜,然後重複。多頭/空倉變體在趨勢過濾器為負時將倉位關閉;小型投資組合變體則使用第三部分中的DCC協方差矩陣而非單一資產的方差來設定倉位規模。我們將完整描述純多頭案例,並說明其擴充方式。
Walk-forward機制與無前視偏差契約
這個回測中最重要的單一性質是:用於設定第天倉位的每一個量,都必須僅使用第天收盤時可獲得的資料計算得出。 GARCH參數在截至的滾動視窗上重新估計;預測是該擬合結果給出的一步前預測;倉位由該預測設定;所獲得的收益是,其中是模型從未見過的下一天的收益率。在全樣本上重新擬合GARCH然後"預測"過去,是人們不慎捏造出漂亮回測結果的最常見方式。我們在walk-forward最佳化一文中處理了這個前視陷阱及一般方法論。
import numpy as np
import pandas as pd
from arch import arch_model
def walk_forward_voltarget(
returns: pd.Series, # daily log returns in decimal (e.g. 0.021)
proxy_var: pd.Series, # variance proxy aligned to returns (RV or r^2)
target_vol_annual: float = 0.20,
window: int = 750, # rolling estimation window (days)
refit_every: int = 5, # refit GARCH weekly, forecast daily (cost saver)
w_max: float = 3.0, # leverage cap
cost_bps: float = 5.0, # per-unit-turnover cost in basis points
ann: int = 365, # crypto trades 365 days/yr
):
"""Long-only vol-targeted BTC, GARCH(1,1)-t forecasts, strictly walk-forward.
Returns a DataFrame with position, forecast vol, net returns, and turnover.
"""
idx = returns.index
target_daily = target_vol_annual / np.sqrt(ann)
fcast_vol = pd.Series(index=idx, dtype=float, name="fcast_vol")
last_res = None
for i in range(window, len(returns) - 1):
if (i - window) % refit_every == 0 or last_res is None:
train = returns.iloc[i - window:i + 1] * 100.0 # scale for the optimizer
am = arch_model(train, mean="Constant", vol="GARCH",
p=1, o=1, q=1, dist="t") # GJR-t, Part 2
last_res = am.fit(disp="off")
f = last_res.forecast(horizon=1, reindex=False)
var_next = f.variance.values[-1, 0] / (100.0 ** 2) # unscale
fcast_vol.iloc[i + 1] = np.sqrt(var_next)
raw_w = (target_daily / fcast_vol).clip(upper=w_max)
position = raw_w.shift(0) # w_{i+1} known at close i
gross_ret = position * returns # earned on day i+1
turnover = position.diff().abs().fillna(0.0)
cost = turnover * (cost_bps / 1e4)
net_ret = (gross_ret - cost).dropna()
out = pd.DataFrame({
"position": position,
"fcast_vol": fcast_vol,
"gross_ret": gross_ret,
"turnover": turnover,
"net_ret": net_ret,
}).dropna()
return out
def performance_stats(net_ret: pd.Series, ann: int = 365) -> dict:
"""Sharpe, realized vol, max drawdown, CAGR, annual turnover."""
mu = net_ret.mean() * ann
sigma = net_ret.std() * np.sqrt(ann)
sharpe = mu / sigma if sigma > 0 else np.nan
equity = (1.0 + net_ret).cumprod()
peak = equity.cummax()
max_dd = (equity / peak - 1.0).min()
cagr = equity.iloc[-1] ** (ann / len(net_ret)) - 1.0
return {
"sharpe": round(sharpe, 2),
"realized_vol": round(sigma, 3),
"max_drawdown": round(max_dd, 3),
"cagr": round(cagr, 3),
}
有幾點實現細節,其重要性遠超其表面看起來的樣子:
- 為最佳化器進行縮放。 當收益率以百分比表示時,
arch擬合在數值上會更順暢,因此有了* 100的縮放以及在還原方差時相應的/ 100**2。忘記還原縮放,你的目標波動率就會偏差10000倍。 - 重新擬合的節奏。 每天都重新估計GARCH參數既昂貴又幾乎沒有任何好處——參數在周與周之間是穩定的。每週重新擬合(
refit_every=5)同時每天預測(即使不重新擬合,遞迴式也會用新的收益率更新)是標準的折中方案。這與聯合風險的copula模型一文中copula流程的快取建議異曲同工。 - 上限不是裝飾性的。 當預測波動率在死寂平靜的市場狀態下崩潰時,可能會暴漲到5倍、10倍槓桿。不設上限的波動率目標化會在波動率狀態即將轉變、預測即將最為錯誤的那一刻,欣然給你帶來災難性的槓桿。設定上限(此處為3倍),並認識到這個上限恰恰會在事後看來最平靜、也最危險的時期被觸及。
- 成本隨換手率而變,而波動率目標化正是一臺換手率製造機。 預測中的每一次波動都會重新調整倉位規模。在一個波動率反覆無常的低波動率資產上,你可能每天都在churning整個倉位。
cost_bps項不是一個可以四捨五入忽略的細節;對於高換手率的波動率目標化策略而言,它可能吃掉毛Sharpe改善中相當大的一部分。
輸出結果大致是什麼樣的(示意性)
在多年期的BTC日頻資料上執行此策略,比較四種預測方法作為倉位規模分母時,往往會產生具有以下形態的表格。下面的數字是示意性的——是精心挑選出來展示典型模式的,而非真實回測的輸出——但排序和量級具有代表性,反映了從業者所報告的情況。
| 頭寸規模預測方法 | Sharpe | 已實現波動率 | 目標波動率 | 最大回撤 | 年化換手率 |
|---|---|---|---|---|---|
| 固定名義本金(無目標化) | 0.71 | 0.68 | — | -0.78 | 0.1x |
| 滾動RV(60天) | 0.94 | 0.24 | 0.20 | -0.41 | 12x |
| EWMA() | 1.02 | 0.21 | 0.20 | -0.35 | 19x |
| GARCH(1,1)-t | 1.05 | 0.21 | 0.20 | -0.33 | 22x |
| HAR-RV(日內代理指標) | 1.09 | 0.20 | 0.20 | -0.31 | 20x |
兩個值得構建的變體
純多頭案例分離出了預測本身的作用,但有兩種擴充方式常見到值得明確展示。
帶趨勢關口的多頭/空倉。 波動率目標化設定了倉位規模,但不給出任何方向性觀點——它始終是多頭。一個廉價而誠實的改進是:當一個緩慢的趨勢過濾器轉為負值時,關閉倉位,這樣你只在上升趨勢中持有波動率目標化的多頭倉位,其餘時間保持空倉。這使得倉位規模邏輯保持不變,只是在其上疊加了一個粗略的狀態過濾器;它並不假裝能擇時入場,只是避免在明顯的下降趨勢中持有倉位。
def apply_trend_gate(position: pd.Series, price: pd.Series, fast: int = 20, slow: int = 100):
"""Zero out the (already vol-targeted) position when the fast SMA is below
the slow SMA. Both SMAs use only past prices, so no look-ahead is introduced.
"""
fast_ma = price.rolling(fast).mean().shift(1) # shift: known at prior close
slow_ma = price.rolling(slow).mean().shift(1)
gate = (fast_ma > slow_ma).astype(float) # 1.0 in uptrend, 0.0 otherwise
return position * gate.reindex(position.index).fillna(0.0)
趨勢關口從下行方向削減了換手率(你不再在熊市中反覆調整一個正在萎縮的倉位),但也帶來了自身的狀態風險——它在震盪橫盤市場中會被反覆打臉,在轉折點上會滯後。它是否有幫助是一個必須用與倉位規模規則本身同樣嚴謹的walk-forward、DM檢驗來回答的經驗問題;趨勢過濾器正是那種樣本內看起來很棒、樣本外卻蒸發殆盡的附加元件的典型例子。
基於DCC協方差的投資組合波動率目標化。 對於一個包含多個資產的賬簿,標量預測變成了投資組合波動率,其中是來自第三部分DCC-GARCH的隨時間變化的協方差矩陣。你選擇基礎權重(等權重、市值加權,或均值方差傾斜),計算在下投資組合的預測波動率,然後縮放整個權重向量以達到投資組合目標。
def portfolio_voltarget_weights(base_w, cov_forecast, target_vol_daily, w_gross_max=3.0):
"""Scale a base weight vector so forecast portfolio vol hits the target.
base_w: (n,) base allocation (need not sum to 1).
cov_forecast: (n, n) one-step-ahead covariance from DCC-GARCH (daily units).
"""
base_w = np.asarray(base_w, dtype=float)
port_var = float(base_w @ cov_forecast @ base_w)
port_vol = np.sqrt(max(port_var, 1e-12))
scale = min(target_vol_daily / port_vol, w_gross_max / np.abs(base_w).sum())
return scale * base_w
這自然地與投資組合構建文獻聯絡了起來:基礎權重可以來自Markowitz均值方差,或者基於風險的方法如HRP/CVaR,而波動率目標化隨後作為一個疊加層,將總體風險縮放到一個恆定水平。DCC矩陣之所以重要,是因為相關性在崩盤中會飆升(第三部分)——一個在平靜市場中看起來多元化的投資組合,可能恰恰在關鍵時刻,其預測波動率會遠高於靜態協方差所暗示的水平,而這一疊加層會相應地削減總敞口。
你應該始終繪製的診斷圖
永遠不要只相信匯總表格。對於任何波動率目標化策略,在相信任何Sharpe數字之前,繪製以下三樣東西並用肉眼檢查。第一,策略的已實現滾動波動率相對於目標線——它應該緊貼目標線;系統性地高於目標線,意味著你的預測偏低(這是代價高昂的方向)。第二,倉位/槓桿序列——留意上限被觸及的情況,以及在回撤發生前槓桿的飆升,這是預測被狀態轉變打了個措手不及的特徵。第三,預測與代理指標的散點圖(即Mincer-Zarnowitz圖)——一個斜率遠離1的散點雲會告訴你,預測的尺度存在QLIKE平均值可能掩蓋的偏差。這三張圖能捕捉到比任何單一統計量都多的漏洞和自我欺騙。
用你應該閱讀每一份回測表格的方式來閱讀這張表:看什麼是穩健的、什麼是邊際的。穩健的事實一目瞭然。每一個波動率目標化變體在Sharpe上都碾壓了固定名義本金策略,在回撤和已實現波動率的穩定性上更是如此——固定名義本金策略以68%的年化波動率和78%的回撤執行,這簡直無法投資。而且每一種目標化方法交付的已實現波動率都接近20%的目標,這正是該機制有效運作的全部承諾所在。邊際的事實是預測方法之間的差異:HAR略勝GARCH,GARCH略勝EWMA,EWMA略勝滾動RV,但差距很小——只有十分之一個Sharpe點——而且如果用Diebold-Mariano檢驗預測、或用自助法(bootstrap)檢驗收益,這些差距往往無法通過顯著性檢驗。這個在精細預測方法和樸素預測方法之間存在的、微小、脆弱、依賴於市場狀態的差距,正是整個系列的誠實結論。
誠實面對這能給你帶來什麼
這個部落格有一整個關於"回測但不自欺"的合集,所以讓我們把它應用到我們自己的結果上,而不是悄悄地祈禱你不會這麼做。
波動率目標化能改善風險調整後收益和回撤。它不會憑空製造阿爾法。 再看一眼那張表。目標化帶來的Sharpe改善是真實的,也是值得擁有的——但把它分解開來看,其中大部分來自於不在高波動率狀態下持有恆定倉位,這機械性地避開了最糟糕的回撤,並穩定了複利路徑。這個策略仍然是做多BTC;它沒有市場未曾給出的任何觀點。如果BTC在你的樣本期內Sharpe為負,波動率目標化會給你一個沒那麼差的負Sharpe,而不是正的。它重塑了收益分佈——更薄的尾部、更穩定的波動率、更好的幾何複利——但原始的方向性優勢就是底層多頭本身的優勢。不要讓一條漂亮的淨值曲線欺騙你,讓你以為發現了阿爾法,而實際上你發現的是風險管理。Moreira-Muir在股票因子中通過波動率管理發現了真實的阿爾法,但那個結果是關於該因子隨時間變化的風險收益權衡的,它不會自動轉移到不同樣本期內的單一加密貨幣資產上。
GARCH相對於EWMA的預測質量優勢通常很小,且依賴於市場狀態。 這是第一到三部分不太舒服的回報。你構建了越來越精細的模型——槓桿項、Student-t尾部、動態相關性——而每一個模型相對於樸素EWMA、對波動率目標化盈虧的邊際貢獻,往往都在噪聲範圍之內。GARCH的優勢(衝擊後的均值迴歸)主要體現在特定的市場狀態中:急劇飆升然後恢復正常的情形。在磨人的趨勢或持續性高波動率狀態中,它與EWMA幾乎沒有差別。這並不意味著GARCH毫無用處——均值迴歸結構、可解釋的參數、模擬未來路徑並據預測為期權定價的能力,這些都是EWMA所不具備的價值——但如果你唯一的用途是設定頭寸規模,在為複雜性付出代價之前先執行DM檢驗,並且要知道狀態檢測正在從另一個角度告訴你同樣的事情:哪個模型勝出取決於市場狀態。
回測Sharpe是實盤Sharpe的上界,而波動率目標化會擴大這個差距。 由於該策略是高換手率、槓桿縮放的,它對樸素回測所忽略的摩擦因素異常敏感:你的成交價比你用來設定倉位規模的收盤價要差,槓桿永續倉位的資金費率是持續累積的,槓桿上限還會與保證金和強平機制產生互動,而簡單的w * return忽略了這一切。這些因素每一個都會使實盤表現劣於回測。我們在回測-實盤一致性一文中系統地處理了這個差距;對於波動率目標化而言,具體的應對方法是使用保守的成本估計、現實(較低)的槓桿上限,並且在計算出訊號的下一根K線的開盤時執行,而非收盤時。
關於波動率風險溢價的一點補充。 以上所有內容都在預測已實現波動率。還有一個平行的、可交易的物件:隱含波動率,它體現在期權定價中,平均而言會高於隨後的已實現波動率——這就是波動率風險溢價,是承擔波動率飆升風險的補償。這個差距本身就是一種收益來源(賣出方差可以獲取它,買入方差可以對沖尾部風險),而這是一個與波動率目標化截然不同的博弈:它是對波動率價格的押注,而非對波動率預測的運用。我們在這裡不深入探討,但相關機制的起點是Black-Scholes期權定價一文中的定價模型,而一個好的已實現波動率預測(第一、二部分)正是你判斷隱含波動率是貴是便宜所需要的輸入。將你的GARCH預測與期權市場的隱含波動率進行比較,是本系列所構建一切內容中較為誠實的用途之一。
實踐中的考量
一些能把一個可用的波動率目標化策略與一個脆弱的策略區分開來的雜項要點。
- 在正確的期限上進行估計。 如果你為持有一天的倉位設定規模,就預測一日波動率。如果你按周再平衡,就預測(並以此為目標)周波動率,或者把日GARCH預測在該期限上聚合起來——多步GARCH預測會向迴歸,而樸素的""縮放方式忽略了這一點。第一部分涵蓋了多步GARCH預測。
- 24/7市場中的年化處理。 加密貨幣一年365天交易,沒有周末和假期,所以要用對日波動率進行年化,而不是股票市場的。搞錯這一點會在你毫無察覺的情況下,讓你的目標偏差約20%。
- 分母可以是一個協方差矩陣。 對於多資產賬簿,用來自第三部分DCC-GARCH的投資組合波動率替代標量的,並縮放整個權重向量以達到投資組合目標波動率。這將波動率目標化與均值方差頭寸規模化(加密貨幣的Markowitz方法)以及基於風險的配置(HRP和CVaR流程)聯絡了起來——波動率目標化是縮放至投資組合風險預算的單一資產特例。
- 波動率目標化以一種微妙的方式具有順週期性。 當所有人都運行同樣的規則時,一次波動率飆升會迫使同步去槓桿,這會壓低價格,從而推高已實現波動率,進而迫使更多的去槓桿。這種反饋迴圈(在2018年的"volmageddon"以及各種加密貨幣去槓桿連環爆倉事件中都有充分記錄)意味著,當許多參與者都使用這個規則時,它的效果反而會變差。這不是放棄它的理由,但確實是限制槓桿、且不要假設你在波動率飆升期間的成交會與平靜市場中的成交相似的理由。
- 對預測值設下限並裁剪。 接近零的預測波動率會產生無限的槓桿。始終將限定在一個合理的最小值,並給倉位設上限,並記錄每種限制被觸及的頻率——如果上限大多數時候都被觸及,說明你的目標對該資產而言過於激進。
總結
- 波動率預測在改變決策之前沒有任何價值。波動率目標化——將敞口設定為(設上限)——是檢驗預測價值最純粹的方式,因為預測質量直接對映為更平坦的已實現波動率曲線和更高的Sharpe比率。
- 波動率目標化提升了風險調整後收益,尤其是控制了回撤,因為波動率是可預測的(它具有聚集性),而方向不是;也因為Sharpe比率在該規則會自動降低配置的高波動率狀態下會下降。它是在期望收益隨波動率成比例變化這一假設下的Kelly頭寸規模化。
- 誠實地將GARCH與強勁的基準進行比較:滾動已實現波動率、EWMA/RiskMetrics(,一個零自由參數的IGARCH),以及基於日內已實現方差代理指標的HAR-RV。EWMA和HAR都很難被擊敗。
- 你無法觀測到真實波動率,所以要用對代理指標噪聲穩健的損失函數來對照代理指標(或更好的)進行評估。相對於MSE,優先選擇QLIKE:它對低估預測懲罰更重(代價更高的錯誤),而且尺度不變,因此不會被少數幾個高波動率的日子所劫持。用Mincer-Zarnowitz來診斷偏差,用Diebold-Mariano檢驗來判斷某個預測的優勢是真實的還是噪聲。
- 在一個walk-forward、考慮成本的回測中,波動率目標化在Sharpe和回撤方面可靠地擊敗了固定名義本金策略,而四種預測方法聚集得很接近——GARCH相對EWMA的優勢很小、依賴於市場狀態,且往往在統計上不顯著。請報告DM檢驗結果,而不僅僅是平均損失。
- 要誠實:波動率目標化是風險管理,不是阿爾法。它重塑了你原本已有的任何方向性押注的收益分佈;它不會憑空創造優勢。而且它是高換手率、槓桿縮放的,所以實盤結果比通常情況更滯後於回測。
參考文獻:
- Patton, A. J. (2011). Volatility forecast comparison using imperfect volatility proxies. Journal of Econometrics, 160(1), 246-256. DOI
- Corsi, F. (2009). A simple approximate long-memory model of realized volatility. Journal of Financial Econometrics, 7(2), 174-196. DOI
- Diebold, F. X., & Mariano, R. S. (1995). Comparing predictive accuracy. Journal of Business & Economic Statistics, 13(3), 253-263. DOI
- Mincer, J., & Zarnowitz, V. (1969). The evaluation of economic forecasts. In Economic Forecasts and Expectations, NBER.
- Moreira, A., & Muir, T. (2017). Volatility-managed portfolios. Journal of Finance, 72(4), 1611-1644. DOI
- J.P. Morgan / Reuters (1996). RiskMetrics Technical Document, 4th ed.
- Bollerslev, T. (1986). Generalized autoregressive conditional heteroskedasticity. Journal of Econometrics, 31(3), 307-327. DOI
- Glosten, L. R., Jagannathan, R., & Runkle, D. E. (1993). On the relation between the expected value and the volatility of the nominal excess return on stocks. Journal of Finance, 48(5), 1779-1801. DOI
- Harvey, D., Leybourne, S., & Newbold, P. (1997). Testing the equality of prediction mean squared errors. International Journal of Forecasting, 13(2), 281-291. DOI
MarketMaker.cc Team
量化研究與策略