12 檔 ETF 對照:六模型 ES 波動比 0.671–0.768;Gaussian 共同下跌 0.3180 對訓練參照 0.4211。
圖:相同訓練歷史下,情境翻倍降低抽樣波動,但未自動修正 Gaussian 同時下跌的差距;底部 OOS 僅比較三種單期 VaR。

做蒙地卡羅風險模擬時,結果一抖動,最順手的辦法往往是再多產生一倍情境。但多跑出來的 756 筆模擬觀測,究竟補進了什麼風險資訊?我想把「數字更穩定」和「風險結構更接近歷史」分開算清楚。

這次我使用 12 檔 ETF 的真實歷史價格、六種情境生成方法,以及 50 組配對亂數種子做對照。從 756 筆增加到 1,512 筆後,部分估計值的隨機波動的確縮小;但 Gaussian 生成情境的下跌共振率仍約 0.3180,固定訓練樣本的參照則是 0.4211。

「下跌共振」其實就是:SPY 大跌的日子,其他 ETF 有多少也同時大跌? 我先用訓練期為每檔 ETF 固定各自最差 5% 的日報酬門檻,之後檢查生成情境時不重新訂門檻。

要評估情境生成器,我現在會分開問三件事:多抽一些是否更穩?同日共跌與接連幾天的風險是否保留下來?真正面對樣本外市場時,風險門檻是否校準? 前兩問看固定歷史上的生成情境,第三問是另外一項範圍較窄的單日 VaR 檢驗,不能混成六個生成器的樣本外競賽。

12 檔 ETF 與固定的 756 日訓練期

資料來自 Boris Marjanovic 的 Huge Stock Market Dataset,來源標示 CC0,並說明價格已調整股利與分割。我挑的 12 檔 ETF 橫跨股票、公債、信用債、黃金、商品、美元與不動產;只看 SPY 自己的跌幅,無法看出跨資產一起下跌的問題。完整代碼列在文末。

我將 12 個原始檔裡的調整後 Close 依日期取交集,不替缺漏日期補值。這份 Kaggle 資料的 HYG 檔案從 2007-04-11 才有紀錄,所以共同樣本也是由這天起,到 2017-11-10 為止,共 2,668 個價格日。換成每日簡單報酬率後,12 檔 ETF 每日等權平均,組合定義從頭到尾不變。

情境實驗只用最初 756 個日報酬訓練,約三個交易年,包含 2008 年金融衝擊。固定同一段歷史,是要讓不同方法面對相同條件,不讓各自選有利的訓練窗口。Student-t 的自由度也固定為 5,不看了後面的樣本外結果才回頭調整。

方法如何產生情境
Gaussian使用訓練期均數及共變異數,產生多變量常態日報酬
Student-t df=5自由度固定為 5 的多變量 t 分布,並匹配訓練期共變異數
IID Bootstrap對訓練期每日整列 ETF 報酬,獨立、有放回抽樣
Moving-block 5 / 20 / 60一次抽取連續 5、20 或 60 個交易日的報酬區塊

這裡先防一個會害對照失真的坑:如果兩次模擬換了亂數,觀察到的差異可能只是運氣。 所以我把種子固定在 1000–1049,共 50 組。每組先產生 1,512 筆,再取同一序列的前 756 筆作為小樣本。說白了,就是拿這 756 天的歷史反覆抽;模擬筆數增加,不會憑空多出真實行情。

Student-t 還有另一個陷阱:換上「厚尾」分布,若忘了匹配變異數,連波動度也會被一併放大。程式先用訓練共變異數的 Cholesky 因子產生相關常態向量,再乘上 √(3/χ²₅)(χ²₅ 為自由度 5 的卡方變數)。這等同於將多變量 t 的尺度矩陣設為訓練共變異數的 3/5;少了這步,生成共變異數會膨脹為原本的 5/3 倍。

情境翻倍,Monte Carlo 誤差縮小多少?

先看單日 ES(5%),也就是落在最差 5% 尾端時的平均報酬。我對每個種子各算一次 ES,再比較 1,512 筆與 756 筆情境時,50 次 ES 結果的跨種子標準差。這量到的是抽樣波動,而不是對未來市場的預測準確率。

情境生成方法ES 跨種子標準差比:1,512 / 756
Gaussian0.718
Student-t df=50.671
IID Bootstrap0.746
Moving-block 50.768
Moving-block 200.681
Moving-block 600.734

六組比值都落在 0.671–0.768,小於 1。這 50 組配對結果的 ES 確實更集中。常見的 1/√2≈0.707 可當作抽樣波動減少的直覺,但 ES 是尾部統計量,不必精確照著它收斂。

共跌卻是另一回事:只挑 SPY 低於訓練期固定 5% 門檻的生成日,每天看其餘 11 檔有多少比例也低於各自固定門檻,最後對 SPY 觸發日取平均。Gaussian 相對訓練參照的共跌率誤差,從 N=756 時約 −0.1036 到 N=1,512 時約 −0.1031,幾乎沒變。

多抽一倍,讓模型輸出比較穩,卻不會自動補回它沒有生成的共同下跌場景。

IID Bootstrap 記得誰同一天跌,卻忘了大波動會不會接著來

單日共跌接近,不代表連續壞行情也像歷史。下面所有指標都建立在 12 檔 ETF 等權組合的日報酬序列上。絕對報酬的一階自相關 ACF(1) 粗略觀察大幅波動是否接連發生,60 日 ES 與最大回撤則觀察更長的風險路徑。

還要先防一個算式上的坑:連跌幾天,不能直接把每日百分比相加當成最後跌幅。 我沿生成序列逐日滑動,取連續 60 日,以 ∏(1+rₜ)-1 複利連乘計算窗口報酬;其中最差 5% 窗口的平均報酬就是 60 日 ES。每個窗口的最大回撤另在該窗口的複利淨值路徑上計算,再取所有窗口回撤的 5% 分位數。

下表是 N=1,512、50 個種子的生成指標平均值,與固定訓練期參照並列:

情境來源下跌共振率絕對報酬 ACF(1)60 日 ES(5%)60 日最大回撤 5% 分位
訓練樣本參照0.42110.3000-21.01%-25.45%
Gaussian0.3180-0.0011-15.77%-16.53%
Student-t df=50.3549-0.0005-15.97%-17.00%
IID Bootstrap0.42650.0059-15.15%-16.26%
Block 50.42090.2408-14.45%-15.75%
Block 200.42270.2842-16.54%-18.56%
Block 600.41130.2684-17.49%-20.48%

不用每個數字都讀。IID Bootstrap 每次抽的是同一天整列 ETF 報酬,所以共跌率 0.4265 接近訓練參照 0.4211。可是不同日期之間獨立抽樣,ACF(1) 只剩 0.0059,而歷史參照是 0.3000。

也就是說,它還記得哪些資產可能同一天跌,卻幾乎忘了大波動會不會連續發生。

Block 20 的 ACF(1) 為 0.2842,已接近這段訓練歷史。Block 60 的 60 日 ES 從 IID 的 −15.15% 移向 −17.49%,但仍不及參照的 −21.01%。區塊也不是越長越好:Block 5 的 60 日 ES 甚至比 IID 更淺。

為何要用重疊窗口?如果強制不重疊,756 個訓練日只能切成約 12 段 60 日,1,512 個生成日也不過約 25 段。拿十來段獨立路徑估最差 5%,我不太相信它夠穩。改用逐日滑動,訓練側有 697 個窗口,生成側有 1,453 個;但彼此重疊,當然不能算成那麼多次獨立風險事件。訓練期又包含 2008 年,−21.01% 只是這份歷史的參照,不是必須精準命中的靶心。

Moving-block:區塊接縫與樣本邊緣權重是兩回事

看到 Block 的 ACF 上升,我一開始也想直接解釋:它保存了日期順序。回頭檢查抽樣器,才發現自己混在一起講了兩種機制。

第一種是區塊接縫。同一區塊內的日期原本相鄰,但換到下一塊時,可能突然跳到另一段歷史。塊長為 L 時,連續兩天跨過接縫的比例約為 1/L。在簡化的獨立拼塊近似下,一階自相關大致依 1−1/L 的量級衰減。把訓練 ACF 0.3000 代入,L=5 與 L=20 約為 0.240、0.285,和實測 0.2408、0.2842 接近。但 L=60 不符合這麼簡單的比例,這個算式只是機制直覺,不能當實驗結果。

第二種是歷史序列兩端被抽中的權重不同。普通非循環 Moving-block 的合法起點從 0 到 756−L;首尾日期出現機率比中間低,位置權重最大可差 L 倍。我另以允許首尾接續、起點均勻的 Circular Block,沿用 50 組種子補做對照,看看這個差異在本次資料究竟影響多少:

區塊長度ACF(1):一般 / 循環60 日 ES:一般 / 循環
50.2408 / 0.2411-14.45% / -14.35%
200.2842 / 0.2884-16.54% / -16.37%
600.2684 / 0.2792-17.49% / -17.23%

變化不大。Block 20 的 ACF 只從 0.2842 到 0.2884,60 日 ES 從 −16.54% 到 −16.37%。邊界日期的抽樣權重確實不同,但它不足以解釋這份資料裡 Block 和 IID 的主要差距。而 Circular 會人為把歷史最後一天接到第一天,我也不會因為它修正了一個問題就宣稱它比較好。

這組追加對照,讓我不再把 Block 的所有效果都歸給「保留時間順序」一句話。方法背景可參考 Circular Block Resampling。

單日 VaR:三種方法在完整樣本外區間都未通過校準

現在改問另一道更窄的題:真實的下一個交易日到了,模型給的尾部門檻到底準不準? 這不是替前面的六種生成器排樣本外名次,只比較歷史經驗分位數、Gaussian 與自由度固定為 5 的 Student-t 這三種單日 VaR 方法。每個目標日前,我都用前面最近 756 日的真實組合報酬重新估算門檻,而非一直沿用最初那個固定訓練窗口。

Student-t 的 5% 分位仍做訓練變異數匹配。所謂 VaR「突破」,就是隔天實際組合報酬跌穿模型估計的左尾 5% 門檻。滾動檢驗共有 1,911 個互不重疊的單日目標:

單日 95% VaR 方法突破次數突破率Kupiec p 值Christoffersen 獨立性 p 值
歷史經驗522.72%6.16e-70.0145
Gaussian512.67%3.16e-70.0122
Student-t df=5623.24%1.75e-40.0171

名目突破率若為 5%,1,911 日平均預期約 95.55 次突破,實際三法卻只有 51–62 次。突破比較少,不代表校準通過。 Kupiec 看突破頻率,Christoffersen 看突破是否獨立;兩組 p 值都偏小。

這個判斷只針對整段 1,911 日。滾動訓練窗口早期還包含 2008 年事件,往後逐步移出。依市場階段切開後會怎麼樣,我這次沒另做。三法相鄰兩次連續突破依序只有 5 / 5 / 6 次,閱讀獨立性檢驗時值得一起看。

至於 Block 60 的 60 日 ES,我沒有硬塞進同一張表。多日目標若逐日滑動,觀測會大量重疊,必須另行設計可解釋這些相依性的樣本外回測。我目前沒有完成一套滿意的口徑;把單日的 Kupiec/Christoffersen 檢驗直接搬過去反而誤導,所以這裡只報告單日 VaR。

另一個反直覺細節:Student-t 常被簡稱為「厚尾所以 VaR 更保守」,卻不能推到每個信賴水準。自由度 5、方差匹配時,標準化的左側 5% 分位約 −1.56085,反而比常態的 −1.64485 更接近零。單看分布名稱,不能替每個門檻判保守程度。

下一筆模擬算力,我會先花在哪裡?

下次再有人說「結果不夠穩,情境翻倍就好」,我不會馬上加算力。我會先問:不穩的是抽樣噪音嗎?我在意的同跌和連續虧損,生成器保留了多少?真正對應那個預測期限的樣本外校準做過嗎? 多抽一倍,主要只回答第一題。

如果明天硬要在 Block 20、Block 60、Student-t 中選一個線上兜底,我也不會拿這份固定訓練期的表直接封冠。Block 20 的 ACF 接近這段歷史,不保證未來覆蓋率;Student-t 的厚尾也不保證 95% VaR 門檻更嚴。先選風險期限,再做對應的 OOS 檢驗,才有選型依據。

這次我最想記下來的是:Gaussian 的共跌差距沒有因情境翻倍而消失;補跑 Circular Block 後,我也必須收窄原本太順手的歸因。以後再看到「百萬筆風險情境」,我會追問:它重複的是哪段真實資訊,又保留了什麼風險結構?

若想先從小案例親手檢查尾部統計,我還寫過 vectorbt 裡 NaN 如何改變 CVaR 的尾端樣本分母,有可跟著算的 Python 合成範例。


資料來源與固定參數:Kaggle Huge Stock Market Dataset v3;SPY、IWM、EFA、EEM、TLT、IEF、LQD、HYG、GLD、DBC、UUP、VNQ。調整後收盤價的日期交集為 2007-04-11 至 2017-11-10,共 2,668 筆,沒有補缺漏日。固定 CSV SHA-256:0a20c095f0d9400fbf7d30e17a0c664716f147c460b03bf5439cd980ef8bf38a。情境實驗使用 756 日訓練報酬與 50 組配對種子;另有 1,911 日的單期 VaR 滾動檢驗。這是歷史風險建模方法對照,不是投資建議。