Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor · · Updated 2026-08-21

VaR如何計算?三種方法比較

以固定SPY報酬序列示範歷史、參數與Monte Carlo VaR計算,並說明VaR未涵蓋的尾端損失,協助比較三種方法的差異。

風險值(value at risk,或 VaR)是先設定持有期間與信心水準,再從報酬率序列中讀取相應的百分位數。1日、99%信心水準的 VaR 為 2%,表示每100個交易日中,有99日的損失低於2%,另有1日會超過2%。對相同輸入資料,三種標準方法會給出不同答案,差異往往超出多數人的預期。

VaR 實際衡量的內容

VaR 是損失分布的分位數。將樣本中的每個日報酬率由最差到最好排序,從最差的一端往內數 1%,所落在的報酬率以正數損失表示,即為 1 日 99% 歷史 VaR。這種建構方式並未保證涵蓋最壞情境。它標示的是估計值不再描述的區域邊界。

讀者最常誤解的,正是這項特性。99% VaR 表示最差的 1% 交易日會落在該門檻之外,但不表示超出的幅度。最大回撤回答的是另一個問題:投資組合實際經歷的高點至低點損失。兩者可能以相反順序排列同一對投資組合。

以下每個數值都以同一組資料為基礎:SPY 自2010年初至2025年底的每日收盤價,並換算為收盤至收盤的百分比變化。這段期間固定不變,而非採用滾動視窗;每個圖表都以相同日期重新建立資料序列,因此各次執行的數值不會漂移。

查詢固定報酬序列:SPY按曆年計算的每日報酬,2010年至2025年
每個數據背後的精確 SQL 語法
WITH
px AS
(
    SELECT date, max(toFloat64(close)) AS close_px
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2009-12-01'
      AND date <  '2026-01-01'
    GROUP BY date
),
rets AS
(
    SELECT date, 100 * (close_px / prev_close - 1) AS ret_pct
    FROM
    (
        SELECT date, close_px,
               lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
        FROM px
    )
    WHERE date >= '2010-01-01' AND prev_close > 0
)
SELECT
    toString(toYear(date))        AS year,
    count()                       AS sessions,
    round(avg(ret_pct), 3)        AS mean_return_pct,
    round(stddevSamp(ret_pct), 2) AS daily_sigma_pct
FROM rets
GROUP BY year
ORDER BY year
Run this yourself

這組資料涵蓋 16 個曆年,僅 2010 就有 252 個交易時段。接下來的分析有兩項特徵十分重要。以這個期間而言,單日平均交易時段報酬幾乎無關緊要:20100.054%;相較之下,每日標準差 sigma 為 1.13%。而且 sigma 並非常數。2020 的每日波動率為 2.11%,2017 則為 0.43%。單一 sigma 無法同時描述這兩段期間。

風險值的計算方式:三種方法

歷史 VaR:從實際發生的結果讀取百分位數

將實際報酬排序,再取出相應的百分位數。這種方法不假設任何分配,這正是它的優點。但它假設樣本中已經包含估計值所要涵蓋的那類交易日。當信賴水準拉得足夠高時,整個觀察期間中最差的幾個交易日,便會決定結果。

參數 VaR:平均值減去 z 乘以 sigma

先以平均值和 sigma 概括報酬序列,再假設報酬遵循常態分配。VaR 等於 z 乘以 sigma 再減去平均值;其中 z 是標準常態分配的分位數:95% 為 1.645、99% 為 2.326、99.9% 為 3.090。計算速度很快,但其假設會以特定方向失效。每日股票報酬在分配中段的集中程度高於常態曲線,極端值的延伸距離也遠得多。這裡的 sigma 是 Sharpe ratio 的分母,因此面對厚尾資料時,也會產生相同的盲點。

Monte Carlo VaR:在固定種子的情況下進行模擬

從假設的過程中抽取大量合成樣本,再從這些抽樣結果讀取百分位數。下方面板使用 40,000 個標準常態抽樣值。這些數值由 Box-Muller transform 根據以雜湊值設定種子的均勻序列產生,再依該序列的平均值與 sigma 進行調整。種子設定在 SQL 中,因此每次重新執行時,抽樣結果都完全相同。模擬可以納入彈性、路徑相依性及多資產相關性,但不會因此增加真實性:如果輸入的是常態分配,輸出的就是加上抽樣雜訊的參數 VaR。若改為重新抽樣實際報酬,也就是 bootstrap 信賴區間 背後採用的技術,便能保留真實尾端風險。

查詢單一序列,三種方法:六個信心水準下的1日VaR
每個數據背後的精確 SQL 語法
WITH
px AS
(
    SELECT date, max(toFloat64(close)) AS close_px
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2009-12-01'
      AND date <  '2026-01-01'
    GROUP BY date
),
rets AS
(
    SELECT date, 100 * (close_px / prev_close - 1) AS ret_pct
    FROM
    (
        SELECT date, close_px,
               lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
        FROM px
    )
    WHERE date >= '2010-01-01' AND prev_close > 0
),
emp AS
(
    SELECT
        avg(ret_pct)                  AS mu,
        stddevSamp(ret_pct)           AS sd,
        quantileExact(0.100)(ret_pct) AS h90,
        quantileExact(0.050)(ret_pct) AS h95,
        quantileExact(0.025)(ret_pct) AS h975,
        quantileExact(0.010)(ret_pct) AS h99,
        quantileExact(0.005)(ret_pct) AS h995,
        quantileExact(0.001)(ret_pct) AS h999
    FROM rets
),
draws AS
(
    SELECT
        quantileExact(0.100)(z) AS z90,
        quantileExact(0.050)(z) AS z95,
        quantileExact(0.025)(z) AS z975,
        quantileExact(0.010)(z) AS z99,
        quantileExact(0.005)(z) AS z995,
        quantileExact(0.001)(z) AS z999
    FROM
    (
        SELECT sqrt(-2 * log(u1)) * cos(2 * pi() * u2) AS z
        FROM
        (
            SELECT
                (cityHash64('var-seed-u1', i) % 999999937 + 1) / 999999938.0 AS u1,
                (cityHash64('var-seed-u2', i) % 999999937 + 1) / 999999938.0 AS u2
            FROM (SELECT arrayJoin(range(40000)) AS i)
        )
    )
)
SELECT
    tupleElement(lvl, 1)                                                  AS confidence,
    round(-1 * tupleElement(lvl, 2), 2)                                   AS historical_var_pct,
    round(tupleElement(lvl, 3) * sd - mu, 2)                              AS parametric_var_pct,
    round(-1 * (mu + sd * tupleElement(lvl, 4)), 2)                       AS monte_carlo_var_pct,
    round(-1 * tupleElement(lvl, 2) - (tupleElement(lvl, 3) * sd - mu), 2) AS method_spread
FROM
(
    SELECT
        mu,
        sd,
        arrayJoin([
            ('90.0%', h90,  1.281552, z90,  1),
            ('95.0%', h95,  1.644854, z95,  2),
            ('97.5%', h975, 1.959964, z975, 3),
            ('99.0%', h99,  2.326348, z99,  4),
            ('99.5%', h995, 2.575829, z995, 5),
            ('99.9%', h999, 3.090232, z999, 6)
        ]) AS lvl
    FROM emp
    CROSS JOIN draws
)
ORDER BY tupleElement(lvl, 5)
Run this yourself

95.0% 水準下,三種方法的結果彼此相差不到一個百分點:歷史法為 1.66%、參數法為 1.74%、模擬法為 1.71%。到了 99.9%,三者開始分歧:歷史法為 5.85%,參數法為 3.31%;在相同資料下,兩者相差 2.54 個百分點。模擬法在每個水準都與參數法並列,這正是重點,而不是缺陷。模擬只會重現輸入給它的分配。

沿著欄位向下閱讀面板時,信賴水準每提高一級,門檻就會擴大。沿著列向右比較時,在分配中段,方法選擇幾乎不影響結果;但到了尾端,方法選擇便成為主導因素。未說明方法與觀察期間的 VaR 限額,其他人無法重現,也就不能算是一個完整的數字。

VaR 未能告訴你的事:預期短缺

預期短缺(expected shortfall)也稱為條件 VaR,會計算超過 VaR 的交易日損失平均值。VaR 標示尾端風險的起點;預期短缺則衡量尾端風險範圍內的損失程度。

查詢VaR與預期短缺比較:超過門檻的平均損失
每個數據背後的精確 SQL 語法
WITH
px AS
(
    SELECT date, max(toFloat64(close)) AS close_px
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2009-12-01'
      AND date <  '2026-01-01'
    GROUP BY date
),
rets AS
(
    SELECT date, 100 * (close_px / prev_close - 1) AS ret_pct
    FROM
    (
        SELECT date, close_px,
               lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
        FROM px
    )
    WHERE date >= '2010-01-01' AND prev_close > 0
),
qs AS
(
    SELECT
        quantileExact(0.100)(ret_pct) AS q90,
        quantileExact(0.050)(ret_pct) AS q95,
        quantileExact(0.025)(ret_pct) AS q975,
        quantileExact(0.010)(ret_pct) AS q99,
        quantileExact(0.005)(ret_pct) AS q995,
        quantileExact(0.001)(ret_pct) AS q999
    FROM rets
),
tails AS
(
    SELECT
        any(q90)                        AS var90,
        any(q95)                        AS var95,
        any(q975)                       AS var975,
        any(q99)                        AS var99,
        any(q995)                       AS var995,
        any(q999)                       AS var999,
        avgIf(ret_pct, ret_pct <= q90)  AS es90,
        avgIf(ret_pct, ret_pct <= q95)  AS es95,
        avgIf(ret_pct, ret_pct <= q975) AS es975,
        avgIf(ret_pct, ret_pct <= q99)  AS es99,
        avgIf(ret_pct, ret_pct <= q995) AS es995,
        avgIf(ret_pct, ret_pct <= q999) AS es999
    FROM rets
    CROSS JOIN qs
    HAVING countIf(ret_pct <= q999) > 0
)
SELECT
    tupleElement(lvl, 1)                                  AS confidence,
    round(-1 * tupleElement(lvl, 2), 2)                   AS historical_var_pct,
    round(-1 * tupleElement(lvl, 3), 2)                   AS expected_shortfall_pct,
    round(tupleElement(lvl, 3) / tupleElement(lvl, 2), 2) AS es_to_var_ratio
FROM
(
    SELECT
        arrayJoin([
            ('90.0%', var90,  es90,  1),
            ('95.0%', var95,  es95,  2),
            ('97.5%', var975, es975, 3),
            ('99.0%', var99,  es99,  4),
            ('99.5%', var995, es995, 5),
            ('99.9%', var999, es999, 6)
        ]) AS lvl
    FROM tails
)
ORDER BY tupleElement(lvl, 4)
Run this yourself

99.0%,這組序列的 VaR 為 3.09%,預期短缺為 4.43%,相當於門檻的 1.44 倍。在相同水準下,若呈常態分布,該比率應接近 1.15。即使在 99.9%,當門檻已達 5.85% 時,超過門檻的交易日平均損失仍為 8.14%。只根據 VaR 設定的限額,會將每次超標都視為相同事件;比率欄則顯示這種假設與實際情況相差多遠。

1日99% VaR 對機構與交易員而言,意思相同嗎?

不相同,差異主要在持有期間。隔夜不持倉的交易員只承擔數小時的風險,因此單一交易時段的門檻大致符合持有期間;但盤中價格路徑可能遠超過收盤至收盤的變動幅度。為長期負債提供資金的機構則持有部位多年,曝險持續數季;其1日99%數值更像是資本與監控工具,而不是對其所承擔風險的完整描述。銀行資本規則多年來以1日99% VaR為基礎,後來《巴塞爾市場風險架構》將衡量方式改為97.5% expected shortfall。

不同持有期間之間的標準換算方式,是以時間平方根進行調整:將單一交易時段的數值乘以持有期間交易時段數的平方根。這個步驟假設報酬彼此獨立,且 sigma 維持不變;上方逐年列出的 sigma 欄位已顯示,後者在現實中並不成立。

查詢時間平方根縮放與實測多交易時段損失比較,99%水準
每個數據背後的精確 SQL 語法
WITH
px AS
(
    SELECT date, max(toFloat64(close)) AS close_px
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2009-11-01'
      AND date <  '2026-01-01'
    GROUP BY date
),
multi AS
(
    SELECT
        date,
        100 * (close_px / p1  - 1) AS r1,
        100 * (close_px / p5  - 1) AS r5,
        100 * (close_px / p10 - 1) AS r10,
        100 * (close_px / p20 - 1) AS r20
    FROM
    (
        SELECT
            date,
            close_px,
            lagInFrame(close_px, 1)  OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p1,
            lagInFrame(close_px, 5)  OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p5,
            lagInFrame(close_px, 10) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p10,
            lagInFrame(close_px, 20) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p20
        FROM px
    )
    WHERE date >= '2010-01-01' AND p20 > 0
),
q AS
(
    SELECT
        quantileExact(0.01)(r1)  AS q1,
        quantileExact(0.01)(r5)  AS q5,
        quantileExact(0.01)(r10) AS q10,
        quantileExact(0.01)(r20) AS q20
    FROM multi
)
SELECT
    tupleElement(h, 1)                                             AS horizon,
    round(-1 * tupleElement(h, 3), 2)                              AS actual_var_pct,
    round(-1 * q1 * sqrt(tupleElement(h, 2)), 2)                   AS sqrt_scaled_var_pct,
    round(tupleElement(h, 3) / (q1 * sqrt(tupleElement(h, 2))), 2) AS actual_to_scaled_ratio
FROM
(
    SELECT
        q1,
        arrayJoin([
            ('1 session',   1.0,  q1,  1),
            ('5 sessions',  5.0,  q5,  2),
            ('10 sessions', 10.0, q10, 3),
            ('20 sessions', 20.0, q20, 4)
        ]) AS h
    FROM q
)
ORDER BY tupleElement(h, 4)
Run this yourself

第一列是同值檢查:在 1 session,調整後的數值等於實際衡量值,比例為 1。到了 20 sessions,兩者開始分離,而且方向並非通常對這項捷徑的警告方向:以單一交易時段數值進行調整得到 13.8%,而該持有期間實際衡量出的報酬則為 10.7%,比例為 0.78。在這段期間,調整後的數值高於實際衡量的多時段尾端風險。這裡有兩種模式相互拉扯。單一交易時段的厚尾分布在累加報酬後會變薄,因此彙總後的99%分位數擴大速度低於持有期間平方根。波動度群聚則朝相反方向發展,會在同一段期間集中多個劇烈交易時段。在這組序列中,前一種模式影響較大;但在另一段期間或另一項資產中,結果不保證朝任何一個方向,這正是重點:乘數是項假設,而非衡量結果。波動度群聚也是 波動度目標策略 進行部位調整所依據的特性。

還有一項限制需要明確說明。以上全部是針對廣泛指數基金的單一資產 VaR。由十檔具相關性股票構成的投資組合,會承擔 集中風險;投資組合 VaR 會透過相關性估計處理這項風險,而相關性往往在估計最重要的時刻出現最大變動。

方法說明與慣例
  • 百分位數採用精確分位數,而非抽樣估計;Monte Carlo 抽樣則來自寫入 SQL 的雜湊種子均勻序列,因此本文每個數值重新計算後都會相同。
  • 報酬採用收盤至收盤的價格變動,不進行股息再投資,這是單一交易時段 VaR 的常見慣例。
  • 持有期間面板採用重疊視窗:連續20個交易時段的報酬共用19天,因此其尾端風險實際上依據的獨立觀測值遠少於列數所顯示的數量。

常見問題

什麼是1日99% VaR?

這是在單一交易日衡量的損失水準,代表最差的100個交易日中,有1天的損失會超過該水準。在上述序列中,歷史估計值為3.09%。這個數值只界定門檻,並未說明超過門檻後的損失幅度。

哪種 VaR 計算方法最準確?

若不考慮具體假設,三種方法都不能說最準確,因為每種方法回答的問題不同。歷史 VaR 忠實反映所使用的樣本,但不會涵蓋樣本未呈現的情況。參數法 VaR 成本低,但會低估股票報酬分布尾端的風險。Monte Carlo 模擬的準確度,取決於輸入的分布。

VaR 與預期短缺有何不同?

VaR 是特定信心水準下的損失門檻。預期短缺則計算超過該門檻的交易日損失平均值。在這組序列中,99%信心水準下的預期短缺是 VaR 的1.44倍;若採用常態分布,則約為1.15倍。

可以將1日 VaR換算為10日 VaR嗎?

將1日 VaR乘以10的平方根,是標準的簡化算法。這項算法假設報酬彼此獨立,且波動率維持不變。期間面板顯示了兩者的差距:在20個交易日的期間內,按比例換算的數值為13.8%,實際測得的數值則為10.7%。


上述每個面板都附有產生該結果的 SQL。若要在 Strasmore terminal 上,針對其他 ticker 或期間執行相同的三項計算,請以自然語言提出要求。

#risk#value at risk#expected shortfall#quant#position sizing