§01學習重點
- 用白話說出 Markowitz 均值–變異數優化的機制:效用函數怎麼把「報酬 vs 風險」壓成一道二次規劃,效率前緣從哪裡來
- 解釋為什麼「數學上最優」的組合實務上常常很荒謬:期望報酬估計誤差 \(\sigma/\sqrt{T}\) 的量級,以及優化器「誤差最大化」的機制
- 區分期望報酬建模的主要路線(歷史平均、多因子迴歸、正則化),並說出共變異數估計的維度困境與收縮估計的解法
- 走一遍 Black-Litterman 的完整流程:市場均衡反推先驗、觀點當資料、貝葉斯更新出後驗配置,並解釋它為什麼比「硬塞觀點」穩定
- 說出風險平價的直覺(均等化風險貢獻、不需要期望報酬)與它付出的代價
- 白話定義 VaR 與 Expected Shortfall(分位數 vs 尾端條件期望),親手用參數法、歷史模擬、蒙地卡羅三種算法計算並比較
- 用 Kupiec 檢定的直覺回測 VaR:破限次數該服從什麼分布、多少次算失格
- 描述壓力測試與情境分析的四種來源,以及風險限額在組織裡扮演的角色
§02課程內容
一、單期均值–變異數:一個誠實的起點
前面十七章的主角大多是「單一資產怎麼定價、怎麼預測」。本章換一個問題:手上有一籃子資產,錢該怎麼分?這是資產管理業的核心日常,也是第 1 章預告過、第 10 章已經用強化學習碰過的老題目。這一章我們把它的古典地基與現代修補一次講清楚,然後接上風險管理——因為「怎麼配」與「配錯了會多慘」是同一個問題的兩面。
Markowitz 在 1952 年給出的答案,今天看依然乾淨:投資人喜歡報酬、討厭波動,那就把兩者寫進同一個目標函數,讓數學去找平衡點。設 \(w\) 是各資產的權重向量、\(R\) 是期望報酬向量、\(C\) 是報酬的共變異數矩陣:
逐項拆解:\(w^\top R\) 是組合的期望報酬——各資產期望報酬按權重加權;\(w^\top C w\) 是組合的變異數——它不只吃各資產自己的波動,還吃兩兩之間的共變異,分散化的數學好處全藏在這一項;\(\lambda\) 是風險趨避係數——你願意用多少期望報酬去換一單位變異數的下降,每個投資人不同。目標函數是 \(w\) 的二次式、開口向下,所以一階條件直接給出全域最優解:
逐項拆解:\(C^{-1}\) 是共變異數矩陣的反矩陣——這三個字符是本章一半麻煩的來源,先記住它的存在;\(R\) 進來說「往期望報酬高的方向押」,\(C^{-1}\) 進來說「按風險與相關性打折」;\(\lambda\) 越大,整體部位越小。因為每個人的 \(\lambda\) 不同,沒有「人人適用的最優組合」——但把所有 \(\lambda\) 對應的最優組合收集起來,畫在「橫軸波動率、縱軸期望報酬」的平面上,就得到一條左凸的曲線:效率前緣。前緣上的每一點都是「給定報酬下波動最小」的組合;前緣下方的所有組合都被支配——同樣的波動,前緣上有報酬更高的選擇。
前緣上有一個特別的點值得單獨記住:最小變異組合。它是「什麼報酬都不管、只求波動最小」的解:
逐項拆解:\(\mathbf{1}\) 是全一向量,分母只是把權重正規化成加總為一。注意這條公式裡沒有 \(R\)——最小變異組合完全不需要知道期望報酬。這個看似平凡的性質,讀完下一節你會明白它有多珍貴。
二、輸入誤差放大器:為什麼「最優」組合常常很荒謬
均值–變異數優化在教科書裡無懈可擊,在實務上卻有個致命體質:它把輸入的 \(R\) 與 \(C\) 當成真理,但這兩樣東西都是從有限歷史資料裡估出來的。估得多不準?期望報酬的估計誤差有個殘酷的公式:
逐項拆解:\(\sigma\) 是資產的波動率,\(T\) 是資料年數,\(\epsilon_\mu\) 是「年化期望報酬估計值」的標準誤。代個數字:一檔波動率 18% 的股票資產(台灣讀者可以想像加權指數量級的波動),用 20 年資料估期望報酬,誤差是 \(0.18/\sqrt{20} \approx 4\%\)——而股票的合理年化超額報酬本身也就 4% 到 6%。換句話說,估計誤差跟訊號本身一樣大。想把誤差壓到 1%,需要三百多年資料;而三百年前的市場跟今天早就不是同一個制度。這不是統計技巧不夠好,是金融資料先天就這麼少——第 1 章說過的話在這裡兌現:我們沒有可重複的實驗。
誤差大還不是最糟的。最糟的是優化器對誤差的反應方式。跑一個實驗:四個自創資產類別,把期望報酬在自己的估計誤差範圍內隨機擾動 200 次,看「最優」組合怎麼變:
import numpy as np
rng = np.random.default_rng(18)
# 四個資產類別(年化,數字自創):台股、美股、投資級債、黃金
mu = np.array([0.065, 0.080, 0.030, 0.045])
sig = np.array([0.18, 0.16, 0.05, 0.14 ])
corr = np.array([[1.00, 0.65, 0.10, 0.15],
[0.65, 1.00, 0.05, 0.10],
[0.10, 0.05, 1.00, 0.20],
[0.15, 0.10, 0.20, 1.00]])
C = np.outer(sig, sig) * corr
Ci = np.linalg.inv(C)
one = np.ones(4)
def frontier_w(Rt, mu_):
"""完全投資效率前緣的閉式解(目標報酬 Rt)"""
a = one @ Ci @ mu_; b = mu_ @ Ci @ mu_; c = one @ Ci @ one
d = b * c - a * a
return ((c * Ci @ mu_ - a * Ci @ one) * Rt
+ (b * Ci @ one - a * Ci @ mu_)) / d
w0 = frontier_w(0.06, mu)
print("目標報酬 6% 的權重:", np.round(w0, 3))
# 輸出:[-0.036 0.57 0.282 0.184]
eps = sig / np.sqrt(20) # 20 年資料的估計誤差
W = np.array([frontier_w(0.06, mu + rng.normal(0, eps))
for _ in range(200)])
for i, n in enumerate(["台股", "美股", "投資級債", "黃金"]):
print(f"{n}: {W[:,i].min():+.2f} ~ {W[:,i].max():+.2f}")
# 輸出:台股 -0.82~+1.12|美股 -0.99~+1.20|投資級債 -0.49~+1.44|黃金 -0.71~+1.04
print("出現超過 -10% 放空的比例:", ((W < -0.10).any(axis=1)).mean())
# 輸出:0.565讀一下這組數字。原始解的權重是 \([-0.04, 0.57, 0.28, 0.18]\)——已經有點極端但還算像話。期望報酬只在自己的一倍估計誤差內晃動——這是統計上完全正常的抖動,不是什麼極端情境——美股的權重就從 −99% 甩到 +120%,投資級債從 −49% 甩到 +144%;超過一半的抽樣裡出現兩位數百分比的放空部位。同一批資產、同一個共變異數矩陣、統計上無法區分的期望報酬,優化器給出的「最優答案」可以完全相反。
比喻: 歌唱選秀只聽一場就打分數,然後把整張唱片合約押給分數最高的人。單場分數=真實力+當天發揮,於是「分數最高」的參賽者,往往不是實力最強的,而是當天超常發揮、被高估最多的那位。均值–變異數優化器就是這種評審:它把估計值當真實力,於是系統性地重押被雜訊高估的資產、放空被雜訊低估的資產。誤差沒有被平均掉——它被專門挑出來、加了槓桿。
這個現象有個標準名字:優化器是誤差最大化器(error maximizer)。而共變異數那一側也有自己的麻煩。\(N\) 個資產的共變異數矩陣有 \(N(N+1)/2\) 個參數要估——25 檔股票就是 325 個、500 檔就是十二萬五千多個;當資產數超過觀測期數(在金融裡極常見),矩陣直接奇異,反矩陣根本不存在。就算不奇異,取反矩陣這個動作會把每個特徵值變成倒數——原本最小、統計上最像雜訊的特徵值,反轉後權重反而最大。優化器不只放大期望報酬的誤差,還專門放大共變異數矩陣裡最不可信的部分。
回頭看第一節結尾的伏筆:最小變異組合不吃期望報酬,所以期望報酬的誤差傷不到它(作業一會實測)。這解釋了一個實務現象——許多機構寧可用最小變異、風險平價這類「不需要預測報酬」的配置法,也不敢把裸的均值–變異數優化推上線。第 2 章講參數不確定性時說過「不確定越大、部位該越小」;本節是同一句話的組合版:輸入有多不可信,優化就該多保守。
三、期望報酬建模:訊號從哪裡來
罵完優化器,得面對正題:如果還是想用期望報酬,它該從哪來?
最素的答案是歷史平均:過去 \(t\) 期的平均報酬當下一期的預測。注意這不是中性的選擇——它隱含一個動量假設:過去表現好的,未來繼續好。把它一般化,讓每一期的落後報酬帶自己的係數,係數為正是動量模型、為負是均值回歸模型;再把基本面變數(帳面市值比、本益比之類)加進迴歸式,就得到多因子期望報酬模型。這一步我們其實在第 5 章已經走過:Barra 式基本面因子模型用「看得到的因子曝險」去解釋橫斷面報酬,每期解出因子報酬——那裡是為了解釋與歸因,這裡把同一套機器轉個方向拿來預測。
因子迴歸在金融資料上有兩個標配的補丁。第一是正則化:金融訊號的訊噪比極低、因子之間多重共線性嚴重,放任最小平方法自由發揮,係數會大得離譜且極不穩定。Ridge 與 LASSO 在損失函數裡對係數大小收費,把係數往零收縮——這正是第 2 章收縮估計量的迴歸版本:用一點偏差換大量穩定性,小樣本下穩賺。第二是符號約束與經濟先驗:一個係數如果跟經濟邏輯反向(例如估值越貴、預期報酬越高),多半是雜訊在說話,實務上寧可約束它或直接丟掉。
模型建好了,怎麼知道訊號有沒有用?業界最常用的量尺是資訊係數(information coefficient, IC):訊號值與下一期實際報酬的相關係數。IC 有多重要,一條著名的關係式說得很直白:主動管理的資訊比率大約等於 IC 乘以「獨立下注次數」的平方根。訊號很弱(IC 很小)不要緊,只要它穩定、而且能鋪在夠多彼此獨立的標的上,聚沙成塔仍然可觀——這就是量化投資「廣度換深度」的商業模式。潑一盆冷水收尾:效率市場假說提醒我們,任何公開資訊算出來的訊號都應該預期只有微弱的解釋力,而且一旦太多人用,訊號本身會衰減。期望報酬建模是整個量化投資最難的一環,難不在數學,在於你在跟整個市場搶同一口飯。
四、共變異數建模:從滾動窗到收縮估計
共變異數矩陣拆開來是兩類零件:對角線上的波動率、對角線外的相關性。波動率的估計有一條從簡到繁的路線:滾動窗用最近 \(N\) 天等權平均——簡單,但窗內等權、窗外歸零,一筆大虧損掉出窗口的那天,估計值會無厘頭地跳一下;指數加權移動平均(EWMA)讓權重隨時間平滑衰減,用半衰期一個參數控制「新資料壓過舊資料」的速度;GARCH 更進一步把「波動率會叢聚、也會均值回歸」寫進模型——衝擊來時波動率跳升、之後按估計出的速度衰減回長期水位。第 6 章時間序列的工具箱在這裡全部派上用場。相關性用同一套框架估,但有個經驗法則:相關性比波動率更不穩定、更依賴市場狀態,所以估相關性時往往要用更長的記憶。
零件湊成矩陣後,上一節說的維度災難就來了。這裡的主力解法是收縮估計(shrinkage),由 Ledoit 與 Wolf 定型:
逐項拆解:\(\hat{\Sigma}\) 是樣本共變異數矩陣——無偏但在高維下噪音滿身;\(F\) 是結構化目標矩陣——一個參數很少、一定滿秩的簡單矩陣(常用「只留對角線」或「所有相關係數都等於平均值」);\(\delta\) 是收縮強度,把兩者摻在一起。往對角線收縮,等於把對角線外的相關性整體「調淡」——矩陣的條件數變好、反矩陣不再瘋狂放大雜訊,代價是抹掉一部分真實的相關結構。這跟第 2 章那個 \((k+4)/(n+8)\) 的勝率收縮估計量是同一個道理、同一個交易:接受一點系統性偏差,換取方差大幅下降,小樣本下總帳划算。作業二會讓你實測:25 檔股票、只有 60 個月資料時,樣本共變異數建出來的最小變異組合,真實波動比收縮版高出快兩個百分點——而且 \(\delta\) 從 0 掃到 1 會走出一條 U 形,跟第 1 章的過擬合曲線神似,因為這根本就是同一件事:\(\delta\) 太小是過擬合雜訊,太大是欠擬合結構。
還有一條更講究的路線值得知道名字:隨機矩陣理論。它回答「樣本共變異數矩陣的特徵值,多小才算純雜訊」——純隨機資料的特徵值有一個理論上界(Marchenko–Pastur 界),實測特徵值超出界外的才算有訊號,界內的可以整批清零再重建矩陣。收縮是「全體調淡」,隨機矩陣理論是「逐一鑑定」,兩者都在做同一件事:在餵給優化器之前,把矩陣裡的雜訊拆掉。
五、修正學派巡禮:約束、Black-Litterman、風險平價
知道了病因(輸入誤差)與病灶(優化器放大它),修正學派分三路進場。
第一路:加約束。 實務上的優化幾乎從不裸跑:禁止放空(\(w_i \ge 0\))、單一部位上限、對基準的追蹤誤差上限、換手率上限(管交易成本)、因子曝險區間。約束表面上是業務要求,深層作用其實是正則化:上一節實驗裡那些 −99%、+144% 的荒謬部位,一條禁止放空的約束就能整批攔下。約束把優化器的想像力關進籠子,籠子的形狀就是你對誤差的防禦。
第二路:Black-Litterman——把觀點做成貝葉斯更新。 這是第 2 章貝葉斯機器在資產配置上最漂亮的一次出場。它解決的痛點:PM 手上有觀點(「REITs 未來一年會贏過債券」),但把觀點直接改進期望報酬向量再優化,優化器會像上一節那樣把整個組合甩到極端。Black-Litterman 的流程分三步:
第一步,反向優化出先驗。別問「期望報酬是多少」,反過來問:「要讓市場現在的市值權重 \(w_{\mathrm{mkt}}\) 恰好是最優解,期望報酬得是多少?」把第一節的閉式解倒過來解,得到均衡隱含報酬 \(R_{\mathrm{eq}} = \lambda C w_{\mathrm{mkt}}\)。這是「全市場資金投票投出來的期望報酬」,當先驗分布的中心。第二步,觀點當資料:每條觀點寫成「某個資產組合的報酬是多少」,並附上信心(觀點的變異數 \(\Omega\)——信心越高、變異數越小)。第三步,貝葉斯更新:先驗與觀點都是常態分布,後驗有閉式解:
逐項拆解:\(P\) 是觀點矩陣(每列一條觀點,標出涉及哪些資產)、\(Q\) 是各觀點宣稱的報酬、\(\tau\) 縮放先驗的不確定性;整條公式就是精度加權平均——先驗按先驗的精度、觀點按觀點的精度,各自投票,精度高的話語權大。這正是第 2 章後驗公式的矩陣版:資料(觀點)越有信心,後驗越往觀點移;先驗越強,觀點越拉不動它。跑個三資產小例:
import numpy as np
sig = np.array([0.16, 0.05, 0.13]) # 股票、債券、REITs
corr = np.array([[1.00, 0.10, 0.55],
[0.10, 1.00, 0.15],
[0.55, 0.15, 1.00]])
Sigma = np.outer(sig, sig) * corr
w_mkt = np.array([0.55, 0.30, 0.15]) # 市值權重
lam, tau = 2.5, 0.05
R_eq = lam * Sigma @ w_mkt # 均衡隱含報酬(先驗中心)
print("均衡隱含報酬:", np.round(R_eq, 4)) # [0.0401 0.0033 0.0228]
P = np.array([[0.0, -1.0, 1.0]]) # 觀點:REITs 將贏過債券
Q = np.array([0.03]) # 幅度 3%
Omega = np.array([[0.001]]) # 觀點信心(變異數)
tSi = np.linalg.inv(tau * Sigma)
var_R = np.linalg.inv(tSi + P.T @ np.linalg.inv(Omega) @ P)
E_R = var_R @ (tSi @ R_eq + P.T @ np.linalg.inv(Omega) @ Q)
print("後驗期望報酬:", np.round(E_R, 4)) # [0.0431 0.0029 0.0273]
w_bl = np.linalg.inv(Sigma + var_R) @ E_R / lam
print("BL 配置:", np.round(w_bl / w_bl.sum(), 3)) # [0.55 0.175 0.275]
mu_naive = R_eq.copy(); mu_naive[2] = mu_naive[1] + 0.03 # 對照:硬塞觀點
w_nv = np.linalg.inv(Sigma) @ mu_naive / lam
print("硬塞觀點:", np.round(w_nv / w_nv.sum(), 3)) # [0.351 0.188 0.46 ]兩組輸出對照著讀。BL 的配置從市值權重 \([0.55, 0.30, 0.15]\) 移到 \([0.55, 0.175, 0.275]\):債券讓出的部位進了 REITs,而觀點沒碰到的股票紋絲不動留在 55%——觀點只在它談論的維度上動手。對照組把 3% 直接寫進期望報酬再優化,REITs 被甩到 46%,連沒被觀點提及的股票都被砍到 35%。同一條觀點,兩種植入方式,穩定性天差地遠。而且信心參數給了一個乾淨的旋鈕:把 \(\Omega\) 從 0.0005 放寬到 0.02,REITs 配置從 32% 平滑滑回 16%——觀點的話語權隨信心連續縮放,而不是全有或全無。
比喻: 公司編新年度預算有兩種開法。零基預算:每個科目從零開始重新論證——理論上最嚴謹,實務上誰的簡報做得漂亮誰拿錢,結果對「論證品質的誤差」極度敏感。基底預算:以去年實績為基底,只有提得出具體理由的科目才加減,理由越紮實、調幅越大。Black-Litterman 是基底預算:市場均衡配置是去年實績,你的觀點是調整理由,\(\Omega\) 是理由的紮實程度。它不保證預算編得對,但保證不會因為一頁簡報就把整張預算表掀了。
第三路:風險平價——乾脆不要期望報酬。 既然期望報酬是誤差重災區,risk parity 直接繞開它:不均等化資金,均等化風險貢獻。資產 \(i\) 的風險貢獻定義為 \(w_i \,\partial \sigma_p / \partial w_i\)——權重乘上「這檔資產每多配一點、組合波動多漲多少」,所有資產的風險貢獻加總恰好等於組合波動率,所以可以問「每檔貢獻了幾分之幾」。傳統 60/40 股債配置按資金看很平衡,按風險貢獻看是股票獨佔九成——因為股票波動大得多。風險平價會把債券權重拉得很高、股票壓得很低,讓每類資產對組合波動的貢獻相等;粗略的直覺版就是「按波動率倒數配權重」(相關性結構再做精修)。代價也明白:這種組合波動低、期望報酬也低,實務上要加槓桿才能達到目標報酬水位——而槓桿帶來融資成本、保證金追繳、以及「波動率估錯時錯誤被槓桿同步放大」的新風險。世上沒有免費的穩健。
最後補一句動態視角:本章通篇是單期問題——這一期配完,下一期重來。第 10 章已經示範過把它動態化的樣子:多期投資組合不等於把單期 Markowitz 重複 N 次,因為你的交易本身會推動價格(市場衝擊)、今天的持倉是明天決策的起點;G-learning 用熵正則化把這整條序列決策問題解到半解析。單期是地基,動態是樓——地基的輸入誤差問題,蓋了樓照樣在。
六、風險管理:VaR 與 Expected Shortfall
視角換到防守端。組合建好了,老闆問一句:「最壞會賠多少?」這句話的標準化答案就是 VaR(Value at Risk,風險值):
逐項拆解:\(F\) 是組合報酬分布的累積分布函數,\(F^{-1}(1-\alpha)\) 是它的 \((1-\alpha)\) 分位數;\(\alpha\) 是信心水準。白話:99% 日 VaR 等於 300 萬元,意思是「依照模型,一天賠超過 300 萬元的機率只有 1%——大約一百個交易日碰上一次」。VaR 直觀、可比較、一個數字說完,所以成了業界與監管共同的通用語言。
但 VaR 有一個結構性的盲點:它只告訴你門檻在哪,對門檻外面的世界隻字不提。賠超過 300 萬的那 1% 的日子,是平均賠 350 萬還是 3000 萬?VaR 看不出來——兩個尾巴形狀完全不同的分布可以有一模一樣的 VaR。它還有一條理論瑕疵:不滿足次可加性——兩個組合合併後的 VaR 可能比各自 VaR 相加還大,跟「分散化不該增加風險」的常識打架。補上這兩個洞的是 Expected Shortfall(ES,也叫 CVaR):
逐項拆解:條件符號右邊限定「報酬落在 VaR 門檻之外」的那些日子,對它們取期望。白話:真的出事的那些天,平均賠多深。ES 看進了尾巴內部,而且數學上被證明是「一致性風險測度」——次可加性成立,分散化的常識回來了。
比喻: VaR 是堤防的設計水位:「99% 的日子,水不會漫過這條線」。ES 回答的是另一個問題:「真的漫過來的那些日子,平均會淹多深」。防洪不能只看水位線——線外是及膝還是滅頂,對「要不要把重要設備搬上二樓」是完全不同的決策。兩個數字都要看,一個管頻率,一個管深度。
算 VaR/ES 有三種標準算法,各有性格。參數法:假設報酬服從某個分布(通常是常態),VaR 直接用公式 \(\mu - z_\alpha \sigma\) 算——最快,但常態假設在金融資料上是出了名的瘦尾。歷史模擬法:不做任何分布假設,直接拿過去 \(N\) 天的實際報酬當劇本重演,取分位數——老實,尾巴多厚就多厚,但「歷史沒發生過的它永遠算不出來」。蒙地卡羅法:指定一個模型(可以是厚尾的、可以帶隨機波動率),模擬幾萬條路徑再取分位數——最靈活,但風險評估的品質完全取決於模型的品質。拿一段厚尾資料(t 分布,超額峰度明顯)把三種算法排在一起:
import math
import numpy as np
rng = np.random.default_rng(2024)
df, T = 5, 1500 # t 分布(厚尾),1500 天
ret = rng.standard_t(df, size=T) * 0.012 / math.sqrt(df / (df - 2))
alpha, z99 = 0.99, 2.3263
mu_h, s_h = ret.mean(), ret.std()
var_p = -(mu_h - z99 * s_h) # 參數法(常態)
es_p = -(mu_h - s_h * math.exp(-z99**2/2) / math.sqrt(2*math.pi) / (1-alpha))
q = np.quantile(ret, 1 - alpha) # 歷史模擬法
var_h, es_h = -q, -ret[ret <= q].mean()
sim = rng.normal(mu_h, s_h, 100_000) # 蒙地卡羅(常態模型)
qs = np.quantile(sim, 1 - alpha)
print(f"參數法 VaR99={var_p:.4f} ES99={es_p:.4f}") # 0.0265 / 0.0305
print(f"歷史模擬 VaR99={var_h:.4f} ES99={es_h:.4f}") # 0.0321 / 0.0385
print(f"蒙地卡羅 VaR99={-qs:.4f} ES99={-sim[sim<=qs].mean():.4f}") # 0.0265 / 0.0304
# Kupiec 回測:常態 VaR99 應該 1% 的日子破限
x = int((ret < -var_p).sum())
p, phat = 1 - alpha, ((ret < -var_p).sum()) / T
LL = lambda pp, k: (T - k) * math.log(1 - pp) + k * math.log(pp)
LR = -2 * (LL(p, x) - LL(phat, x))
print(f"破限 {x} 次(預期 {T*p:.0f}),Kupiec LR = {LR:.2f}")
# 輸出:破限 26 次(預期 15),Kupiec LR = 6.68 → 超過 3.84,拒絕數字會說話:同一批資料,常態參數法說 99% VaR 是 2.65%,歷史模擬說是 3.21%——常態假設把尾端風險低估了兩成;ES 的差距更大(3.05% 對 3.85%),因為 ES 整根探進尾巴,尾巴越厚差越多。蒙地卡羅跟參數法幾乎一樣,不是巧合——這裡的蒙地卡羅用的正是常態模型,它只是把參數法的公式用模擬重算一遍。模擬方法的答案永遠不會好過餵給它的模型。
七、回測 VaR:Kupiec 檢定的直覺
風險模型跟交易模型一樣,需要對答案。VaR 的答案很好對:99% 日 VaR 如果算得準,破限(實際虧損超過 VaR)應該平均一百天發生一次——既不該更多,也不該更少。太多代表模型低估風險,公司在不知情下超載;太少代表模型過度保守,資本被白白閒置。破限與否是個伯努利事件,\(T\) 天裡的破限次數服從二項分布——這就是 Kupiec 檢定的全部直覺:數破限次數,檢定它跟名目機率合不合。
逐項拆解:\(L(p)\) 是「破限機率真的是名目值 \(p\)(例如 1%)」時觀測資料的概似,\(L(\hat p)\) 是用實際破限頻率 \(\hat p\) 算的概似;兩者差距越大,LR 越大。LR 超過卡方分布單自由度的 95% 臨界值 3.84,就拒絕「模型校準正確」。上一節的實驗正好示範了一次不及格:常態 VaR99 在 1500 天厚尾資料上破限 26 次、預期只有 15 次,LR = 6.68 > 3.84——統計上有足夠證據說這個模型系統性低估風險,該退回重修。同一批資料上的歷史模擬 VaR 破限 15 次,恰如其分。
兩個實務註腳。第一,監管把這個邏輯做成了制度:巴塞爾框架用滾動 250 天的破限次數把銀行模型分進綠、黃、紅三區,紅區直接觸發資本加成——破限次數不是學術統計量,是會影響資本成本的真金白銀。第二,Kupiec 只管頻率不管分布:26 次破限如果擠在同一個月出現,比均勻散開嚴重得多(那代表模型在波動叢聚時整段失靈),這需要進階的獨立性檢定——概念在此打住,方向留給有興趣的讀者。回測的完整紀律,第 19 章會在交易策略的脈絡下正面展開。
八、壓力測試、情境分析與風險限額
VaR 與 ES 有一個共同的先天限制:它們都從歷史資料長出來。歷史沒發生過的事,它們一個字都說不出。補這個洞的工具是壓力測試與情境分析:不問「機率分布怎麼說」,改問「如果某個劇本上演,組合會怎樣」。劇本有四種來源:歷史重演——把某段真實危機的各資產報酬套到今天的組合上;監管指定——銀行的壓力情境很多由監管機構直接開題,結果連動資本要求;主觀設計——風控主管根據當下市場結構自己編劇本(重要原則:劇本不該由被評估的 PM 自己寫,球員不能兼裁判);宏觀變數衝擊——先指定一個關鍵變數的衝擊(油價漲五成、台幣單日劇貶、波動率指數翻倍),再用相關性結構推算其他資產的連帶反應,拼出一致的全景。壓力測試的價值不在機率(它根本不談機率),在於暴露集中度:哪個劇本會讓公司傷筋動骨,答案往往出乎管理層意料。
風險測量的終點是風險限額:每個交易檯、每個策略、每個 PM 都有自己的 VaR 上限、集中度上限、虧損上限。限額的意義遠不止「控制風險」這四個字——它是組織的風險偏好寫成的憲法。第一,它把「公司願意承受多少痛」從一句口號變成可稽核的數字;第二,它強迫風險在事前被定價,而不是事後被追悔;第三,它創造了一個獨立於損益的制衡機關——風控部門敢對賺錢的檯子說不,靠的不是勇氣,是限額白紙黑字。對沖基金史上不缺聰明人栽在風險管理的案例:交易邏輯終究被證明是對的、但部位在被證明之前先爆掉的故事,每隔幾年就重演一次。市場不會因為你終究是對的,就讓你活到那一天。
九、誠實限界
本章的工具箱收尾前,把三塊警示牌釘牢。
相關性在危機中趨於一。 共變異數矩陣是用平時的資料估的,而平時的相關性結構在壓力下會變形——具體來說,風險資產之間的相關性會集體往 1 靠攏。原因不神祕:危機時所有人都在做同一件事——去槓桿、換現金,賣的是「賣得掉的」而不是「想賣的」。於是平時分散得漂漂亮亮的組合,那一天發現所有部位一起跌。
比喻: 平常散場,觀眾從十幾個出口各自離開,動線互不干擾——這是分散化。火警那晚,所有人湧向同一個出口——你以為的十幾條逃生路徑,瞬間塌縮成一條。資產相關性在危機中趨於 1 是同一個機制:平時各走各的資產,在流動性抽乾的那天被同一群人同時拋售。分散化在你最需要它的那天,最不管用。
一個務實的補法是雙狀態共變異數:壓力期與平靜期各估一套矩陣,按你對壓力機率的評估(或風險偏好)加權混用——保守的投資人可以刻意把壓力矩陣的權重調高。
模型風險。 本章每個數字都是模型的輸出:VaR 假設了分布、共變異數假設了平穩、壓力測試假設了傳導路徑。模型風險管不掉,只能管理:獨立驗證、上線前後的行為監控、以及永遠保留「模型外還有什麼」的追問。風險分兩種——已定義的風險(資料裡出現過、可以量化的)與未定義的風險(事前無從量化、事後人人都說早該想到的)。本章所有工具都只覆蓋前者;對後者,工具是謙遜、緩衝與槓桿紀律。另外提一句工程細節:組合裡若含選擇權這類非線性部位,VaR 不能只模擬標的價格,還得動用第 16 章的 Greeks 做泰勒近似(或整套重新定價);利率與信用部位則要面對整條曲線的變形,那是第 17 章的地盤。
VaR 是儀表板,不是護身符。 風險數字的作用是讓決策者看見風險,不是消滅風險。最危險的狀態不是 VaR 很高,而是全公司都盯著一個算錯的 VaR 安心睡覺。測量、回測、質疑、再測量——風險管理是動詞,不是報表。
§03原書對照
本課以三章原書為底重組:Kelliher《Quantitative Finance with Python》第 17 章(pp.459–498)、第 18 章(pp.499–521)、第 19 章(pp.523–544)。以下內容原書有、但本課未展開。其一,Lagrange 乘數的完整推導與「影子價格」詮釋——乘數衡量放鬆約束一單位能換多少目標函數,並連回選擇權校準的脈絡(pp.460–463)。其二,從投資人效用函數出發的公理化鋪陳:二次效用如何導出均值–變異數框架,與完全投資效率前緣的解析解——含四個標量常數的閉式權重公式(pp.463–467,式 17.39)。其三,共變異數估計誤差的量化公式與取樣頻率的角色:變異數誤差可藉提高取樣頻率壓低,期望報酬誤差不行(pp.474–476,式 17.61–17.62)。其四,CAPM 全章:切線組合與資本市場線的幾何、系統性與特異性風險的區分、Fama-French 三因子與歸因分析(pp.480–484)。其五,Michaud 重抽樣前緣:對輸入分布重複抽樣、對每條樣本解前緣、再平均權重,及其與原始前緣的實證對比(pp.488–490)。其六,常見約束的逐一數學化:因子曝險區間、換手率上限、追蹤誤差最小化的等價改寫——把基準變異數拆出後從目標函數剔除(pp.470–473,式 17.46–17.60)。其七,相關性非平穩的雙狀態處理:壓力期與平靜期各估一套共變異數、按壓力機率線性混合,並以分群方法劃分兩種狀態(p.479,式 17.65)。其八,以 VaR/CVaR 為目標的下方風險優化:常態假設下的解析改寫、CVaR 的次可加與凹性讓優化良態,及兩者的求解性質(pp.490–494,式 17.79–17.87)。其九,風險平價的邊際風險貢獻公式推導、迭代求解式,與四資產 ETF 的實算權重(pp.494–497)。其十,Fama-MacBeth 兩步迴歸與 Newey-West 標準誤修正——處理橫斷面與時序自相關對 t 統計量的膨脹(pp.503–505)。其十一,訊號去噪(detoning)、IC 的滾動診斷、與主動管理基本定律的正式表述(pp.505–508)。其十二,區間波動率估計量 Parkinson 與 Garman-Klass、GARCH(1,1) 的最大概似估計流程與標普指數兩個十年期的參數對照(pp.512–517)。其十三,負特徵值的修正(保跡數重標定)與 Marchenko–Pastur 特徵值顯著性邊界(pp.517–521,式 18.32–18.34)。其十四,風險型態分類(市場、模型、作業、交易對手)、邊際 VaR/CVaR 貢獻、與滾動 VaR 的樣本外驗證實例(pp.525–535)。其十五,非線性工具的完整重定價 vs Greeks 泰勒近似,利率信用的 DV01、關鍵利率部分 DV01、違約跳躍風險與主成分曲線位移(pp.535–544)。
§04作業和解答
作業一:最小變異組合為什麼穩
延續第二節的四資產實驗:(1) 把期望報酬在估計誤差內擾動 500 次,同時計算「6% 目標報酬的前緣組合」與「最小變異組合」的權重,比較兩者的權重標準差;(2) 最小變異組合對期望報酬誤差免疫,但它吃共變異數矩陣——改用「只有 60 個月資料重估 \(C\)」的設定再測它的權重穩定度;(3) 用「誤差進入公式的路徑」解釋兩組結果。
解答 SOLUTION
import numpy as np
rng = np.random.default_rng(42)
sig = np.array([0.18, 0.16, 0.05, 0.14])
corr = np.array([[1.00, 0.65, 0.10, 0.15], [0.65, 1.00, 0.05, 0.10],
[0.10, 0.05, 1.00, 0.20], [0.15, 0.10, 0.20, 1.00]])
C = np.outer(sig, sig) * corr
Ci = np.linalg.inv(C)
one = np.ones(4)
mu = np.array([0.065, 0.080, 0.030, 0.045])
eps = sig / np.sqrt(20)
def frontier_w(Rt, mu_):
a = one @ Ci @ mu_; b = mu_ @ Ci @ mu_; c = one @ Ci @ one
d = b * c - a * a
return ((c * Ci @ mu_ - a * Ci @ one) * Rt + (b * Ci @ one - a * Ci @ mu_)) / d
W_f = np.array([frontier_w(0.06, mu + rng.normal(0, eps)) for _ in range(500)])
W_m = np.array([Ci @ one / (one @ Ci @ one) for _ in range(500)])
print("前緣組合權重標準差:", np.round(W_f.std(axis=0), 3))
# 輸出:[0.296 0.333 0.284 0.262]
print("最小變異組合權重標準差:", np.round(W_m.std(axis=0), 3))
# 輸出:[0. 0. 0. 0.]
W_m2 = []
for _ in range(500): # (2) 用 60 個月資料重估 C
X = rng.multivariate_normal(np.zeros(4), C / 12, size=60)
Si = np.linalg.inv(np.cov(X, rowvar=False) * 12)
W_m2.append(Si @ one / (one @ Si @ one))
print("最小變異(重估C)權重標準差:", np.round(np.std(W_m2, axis=0), 3))
# 輸出:[0.048 0.053 0.056 0.045]解讀:(1) 前緣組合的權重標準差約 0.26 到 0.33——每檔資產的配置在正負三成的幅度晃動;最小變異組合是精確的零,因為 \(w_{\mathrm{mv}} = C^{-1}\mathbf{1}/(\mathbf{1}^\top C^{-1}\mathbf{1})\) 的公式裡根本沒有 \(\mu\),誤差連進場的門都沒有。(2) 換成共變異數帶誤差,最小變異組合的權重標準差變成約 0.05——不再是零,但比前緣組合小了六倍。原因有二:共變異數的估計誤差本來就比期望報酬小(一個月內的日資料就能把波動率估得七七八八,第四節說過取樣頻率幫得上波動率、幫不上期望報酬);而且四資產的低維設定下矩陣反轉還算溫馴。(3) 結論的一般形式:誤差傷害的大小,取決於它進入公式的路徑與被放大的次數——期望報酬直接被 \(C^{-1}\) 乘上去放大,共變異數誤差則主要透過反轉的條件數起作用,維度越高越兇(作業二接著測)。
作業二:收縮共變異數 vs 樣本共變異數
模擬一個 25 檔股票的市場(單因子結構:各股 beta 介於 0.6–1.4,市場月波動 4.5%,個股殘差月波動 3%–8%),但只給估計者 60 個月資料。(1) 分別用樣本共變異數與「往對角線收縮一半」的收縮矩陣建最小變異組合,比較兩者在真實共變異數下的年化波動;(2) 把收縮強度 \(\delta\) 從 0 掃到 1,描述曲線形狀並解釋;(3) 等權組合當基準,它輸給誰、贏過誰?
解答 SOLUTION
import numpy as np
rng = np.random.default_rng(555)
N = 25
beta = rng.uniform(0.6, 1.4, N)
sig_i = rng.uniform(0.03, 0.08, N)
C_true = np.outer(beta, beta) * 0.045**2 + np.diag(sig_i**2)
one = np.ones(N)
def minvar(C):
Ci = np.linalg.inv(C)
return Ci @ one / (one @ Ci @ one)
w = minvar(C_true)
print("神視角波動:", round(float(np.sqrt(w @ C_true @ w) * np.sqrt(12)), 4))
# 輸出:0.107
res = {"樣本": [], "收縮": []}
for _ in range(200):
X = rng.multivariate_normal(np.zeros(N), C_true, size=60)
S = np.cov(X, rowvar=False)
res["樣本"].append(minvar(S))
res["收縮"].append(minvar(0.5 * np.diag(np.diag(S)) + 0.5 * S))
for k, ws in res.items():
vols = [np.sqrt(w @ C_true @ w) * np.sqrt(12) for w in ws]
print(f"{k}: 平均 {np.mean(vols):.4f} 最差 {np.max(vols):.4f}")
# 輸出:樣本: 平均 0.1394 最差 0.1777|收縮: 平均 0.1210 最差 0.1342
w_eq = one / N
print("等權:", round(float(np.sqrt(w_eq @ C_true @ w_eq) * np.sqrt(12)), 4))
# 輸出:0.1531解讀:(1) 全知的最小變異組合年化波動 10.7%。樣本共變異數版平均做到 13.9%、最差一次 17.8%——它自以為在最小化變異,實際上在對雜訊優化:60 個月估 325 個參數,每筆觀測要養五個多參數,反矩陣把最不可信的小特徵值放大成主角。收縮版平均 12.1%、最差 13.4%——平均省下近兩個百分點的波動,最差情境的改善更大(穩健方法的價值永遠先看最差情境)。(2) \(\delta\) 掃描的真實波動:0 → 13.9%、0.25 → 12.0%、0.5 → 12.1%、0.75 → 12.9%、1 → 14.5%——標準 U 形。\(\delta\) 太小,雜訊沒壓住(過擬合);太大,把真實的相關結構也抹掉了(欠擬合)——這條 U 形跟第 1 章的測試誤差曲線是同一條曲線的化身,收縮強度就是這個問題的「模型複雜度」旋鈕,實務上用交叉驗證或 Ledoit-Wolf 的解析公式選。(3) 等權組合 15.3%:輸給所有會用共變異數的方法(它連「債券比股票穩」這種資訊都不用),但贏過「樣本共變異數的最差情境」——這正是等權組合在實證研究裡出名難打敗的原因:它沒有估計誤差,因為它什麼都不估。優化的價值必須先超過估計誤差的代價,才是淨收益。
作業三:滾動歷史 VaR 的樣本外回測
模擬 2250 天的厚尾日報酬(t 分布,自由度 5,日波動 1.2%)。(1) 每天用過去 250 天的歷史模擬法估 95% VaR,對之後 2000 天做樣本外回測,數破限次數並做 Kupiec 檢定;(2) 用常態參數法(同樣滾動 250 天)重跑一次,比較兩者——結果跟課文第六節「常態法在 99% 水準被拒絕」的實驗對照,你發現了什麼?(3) 對破限日做 ES 的事後檢查:實際平均虧損 vs 模型預測的 ES。
解答 SOLUTION
import math
import numpy as np
rng = np.random.default_rng(7)
df, T_all, win = 5, 2250, 250
ret = rng.standard_t(df, size=T_all) * 0.012 / math.sqrt(df / (df - 2))
def kupiec(breaches, n, p):
ph = breaches / n
LL = lambda pp, k: (n - k) * math.log(1 - pp) + k * math.log(pp)
return -2 * (LL(p, breaches) - LL(ph, breaches))
b_h = b_n = 0
for t in range(win, T_all):
w = ret[t - win:t]
if ret[t] < np.quantile(w, 0.05): # 歷史模擬 VaR95
b_h += 1
if ret[t] < w.mean() - 1.6449 * w.std(): # 常態參數 VaR95
b_n += 1
n = T_all - win
print(f"歷史模擬: 破限 {b_h}/{n}(預期 {n*0.05:.0f}),LR={kupiec(b_h, n, 0.05):.2f}")
# 輸出:破限 109/2000(預期 100),LR=0.83 → 無法拒絕
print(f"常態參數: 破限 {b_n}/{n},LR={kupiec(b_n, n, 0.05):.2f}")
# 輸出:破限 107/2000,LR=0.50 → 無法拒絕
tail, es_pred = [], []
for t in range(win, T_all): # (3) ES 事後檢查
w = ret[t - win:t]
q = np.quantile(w, 0.05)
if ret[t] < q:
tail.append(-ret[t]); es_pred.append(-w[w <= q].mean())
print(f"破限日實際平均虧損 {np.mean(tail):.4f} vs 預測 ES95 {np.mean(es_pred):.4f}")
# 輸出:0.0258 vs 0.0254解讀:(1) 歷史模擬 VaR95 破限 109 次、預期 100 次,LR = 0.83 遠低於 3.84——模型校準良好。注意這是真正的樣本外檢驗:每一天的 VaR 只用它之前的 250 天資料,沒有偷看未來(第 1 章說的前視偏差,在風險回測裡同樣是頭號地雷)。(2) 有趣的是常態參數法在 95% 水準也過關了(破限 107 次),但課文第六節同樣的常態假設在 99% 水準被 Kupiec 拒絕。這不是矛盾,是厚尾的解剖學:t 分布與常態分布在 95% 分位數附近差異還不大,要到更深的尾端(99%、99.9%)差距才急遽拉開。回測通過只證明模型在被測的那個分位數上沒露餡——95% VaR 的綠燈不保證 99% VaR 可信,更不保證 ES 可信。監管要求測哪個水準、你就只知道哪個水準的品質。(3) 破限日實際平均虧損 2.58% vs 模型 ES 預測 2.54%——歷史模擬的 ES 在這個設定下也對得上帳。這一步在實務上常被略過,但它是唯一直接檢驗「尾巴深度」而非「尾巴頻率」的體檢:VaR 頻率能過、ES 深度崩掉的模型,是最會給人虛假安全感的那種。
§05參考資料
- Idzorek, T. (2005). A Step-by-Step Guide to the Black-Litterman Model — Black-Litterman 最常被引用的實作指南,含信心水準設定的具體做法。
- Artzner, P., Delbaen, F., Eber, J.-M., & Heath, D. (1998). Coherent Measures of Risk — 一致性風險測度的公理化原典,VaR 為何失格、ES 為何合格的理論根據。
- Rockafellar, R. T., & Uryasev, S. (2000). Optimization of Conditional Value-at-Risk — CVaR 優化的奠基論文,證明 CVaR 可用線性規劃求解。
- Basel Committee (1996). Supervisory Framework for the Use of Backtesting — 監管版的 VaR 回測框架:綠黃紅三區與破限次數對資本要求的影響。
- scikit-learn:Ledoit-Wolf 收縮估計器文件 — 收縮共變異數的可直接呼叫實作,含最優收縮強度的解析解。
- Kenneth French Data Library — Fama-French 因子資料的官方來源,期望報酬因子模型的實證起點。
- Kelliher, C. (2022). Quantitative Finance with Python. CRC Press / Taylor & Francis — 本課程對照之原書,第 17–19 章 pp.459–544。