VaRの計算方法 三つの手法を比較
固定したSPYリターン系列で、ヒストリカル、パラメトリック、モンテカルロのVaRを計算し、VaRが示さない期待ショートフォールも解説します。
バリュー・アット・リスク(VaR)は、対象期間と信頼水準を定めたうえで、リターン系列からパーセンタイルを読み取って算出する。1日99% VaRが2%であれば、100セッションのうち99セッションでは損失が2%以内に収まり、残りの一セッションでは2%を超えることを意味する。同じ入力データを使ってこの問いに答える代表的な手法は三つあり、その結果は多くの人が予想する以上に乖離する。
VaRが実際に測定するもの
VaRは、損失分布の分位点です。標本内の全営業日リターンを悪い順から良い順に並べ、悪い側から1%進んだ位置のリターンを正の損失として示したものが、1日99%のヒストリカルVaRです。この計算方法は、最悪のケースを示すものではありません。推計が説明できる範囲の境界を示すだけです。
この性質が、最も頻繁に誤解されています。99% VaRは、最悪の1%の営業日がその閾値を超えることを示しますが、どの程度超えるかは示しません。最大ドローダウンは別の問いに答える指標です。実際にポートフォリオが経験したピークからボトムまでの損失を示します。そのため、同じ2つのポートフォリオでも、両者の順位が逆になることがあります。
以下の数値はすべて、2010年初めから2025年末までのSPYの日次終値を一つの系列として使用し、終値から終値までの騰落率に変換したものです。対象期間はローリング方式ではなく固定しています。各パネルは同じ日付から系列を再構築しているため、実行のたびに数値が変わることはありません。
各数値の背後にある正確な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この系列は16暦年にわたり、2010だけで252セッションを含みます。以下の分析全体に関係する特徴は2つあります。この期間では、平均セッションの影響はほぼ無視できます。2010の0.054%に対し、日次標準偏差sigmaは1.13%です。また、sigmaは一定ではありません。2020の1日当たりの値は2.11%、2017は0.43%でした。1つのsigmaで両方を説明することはできません。
バリュー・アット・リスクの算出方法、三つの手法
ヒストリカルVaR:実績からパーセンタイルを読み取る
実際のリターンを並べ替え、パーセンタイルを求める。分布を仮定しない点が利点だ。一方、この方法は、推計対象となるような値動きがサンプルにすでに含まれていることを前提とする。信頼水準を十分に高くすると、対象期間全体で最悪だった少数のセッションだけで結果が決まる。
パラメトリックVaR:平均からz倍のシグマを差し引く
系列の平均とシグマを要約統計量として求め、リターンが正規分布に従うと仮定する。VaRは、標準正規分布の分位点であるzにシグマを掛け、平均を差し引いて算出する。zは95%で1.645、99%で2.326、99.9%で3.090だ。計算は瞬時に終わるが、仮定は特定の方向に外れる。日次の株式リターンは、分布の中心部では正規曲線より狭い範囲に集中する一方、極端な局面では正規分布よりはるかに遠くまで広がる。ここでのシグマは、シャープレシオの分母でもあり、ファットテールのデータに対して同じ死角をもたらす。
モンテカルロVaR:乱数の種を固定してシミュレーションする
仮定した過程から大規模な人工サンプルを生成し、その抽出値からパーセンタイルを読み取る。下のパネルでは、ハッシュでシードを固定した一様乱数列をもとに、Box-Muller変換で40,000個の標準正規乱数を生成し、系列の平均とシグマに合わせて調整している。シードはSQL内にあるため、再実行しても乱数は同一になる。シミュレーションは、経路依存性や複数資産間の相関を扱う柔軟性をもたらすが、現実性を保証するわけではない。正規分布を与えれば、サンプリング誤差を上乗せしたパラメトリック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)95.0%の水準では、三つの手法の差は1ポイント以内に収まる。1.66%がヒストリカル、1.74%がパラメトリック、1.71%がシミュレーションによる値だ。99.9%まで外れると差が広がる。ヒストリカルは5.85%、パラメトリックは3.31%で、同一データに対する差は2.54パーセントポイントとなる。シミュレーションの列は、すべての水準でパラメトリックの列に並ぶ。これは欠点ではなく、重要な教訓だ。シミュレーションは、与えられた分布をそのまま再現する。
列を下に向かって読むと、信頼水準が上がるたびに閾値が広がる。行を横に読むと、分布の中央部では手法の選択による差はほとんど表れないが、テールではその差が決定的になる。手法と対象期間を示さずに提示されたVaR上限は、他の人が再現できる数値ではない。
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)99.0%時点で、この系列のVaRは3.09%、期待ショートフォールは4.43%です。これは閾値の1.44倍に相当します。同じ水準で正規分布を前提とした場合、この比率はおよそ1.15になります。閾値がすでに5.85%に達している99.9%時点でも、超過日の平均損失は8.14%です。VaRだけで限度を設定すると、すべての超過を同じ事象として扱うことになります。比率欄は、その前提がどれほど実態に即していないかを示します。
1日99% VaRは、機関投資家とトレーダーに同じ意味を持つのでしょうか。
いいえ。相違の中心は保有期間です。オーバーナイトでポジションを持たないトレーダーは数時間だけリスクを負うため、1セッションの閾値は保有期間とおおむね一致します。ただし、日中の値動きは、終値から終値までの変動幅を大きく上回ることがあります。長期債務の資金調達を担う機関投資家は、ポジションを何年も保有します。エクスポージャーは四半期単位で積み上がるため、1日99% VaRは、抱えているリスクそのものではなく、自己資本管理やモニタリングのための指標として機能します。銀行の自己資本規制は長年、1日99% VaRを基礎としてきましたが、その後、バーゼルの市場リスク・フレームワークは指標を97.5% Expected Shortfallへ移行しました。
保有期間をつなぐ標準的な方法は、時間の平方根でスケーリングすることです。1セッションの数値に、保有期間のセッション数の平方根を掛けます。この手法は、リターンが独立しており、シグマが一定であることを前提とします。しかし、上の年別シグマの列は、その前提が後半ですでに崩れていることを示しています。
各数値の背後にある正確な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)最初の行は恒等性の確認です。1 sessionでは、スケーリング後の数値が実測値と一致し、比率は1です。20 sessionsでは両者が乖離します。ただし、一般に注意される方向とは異なります。1セッションの数値をスケーリングすると13.8%になる一方、その保有期間のリターンを実測すると10.7%となり、比率は0.78です。この期間では、スケーリング後の数値が複数セッションの実測テールを上回っています。ここでは二つのパターンが逆方向に作用します。単一セッションのファットテール分布は、リターンを合算すると薄くなります。その結果、集計後の99%分位点は、保有期間の平方根よりも緩やかに拡大します。一方、ボラティリティ・クラスタリングは逆に作用し、一つの期間内に大幅な変動を集中させます。この系列では前者の影響が後者を上回っています。ただし、別の期間や別の資産で同じ方向になる保証はありません。これが要点です。この乗数は仮定であり、実測値ではありません。このクラスタリングは、ボラティリティ・ターゲティングがポジションの規模を調整する際に依拠する特性でもあります。
もう一つ限界があります。明確に述べると、ここまでの内容は広範な指数ファンドを対象とした単一資産VaRです。相関する銘柄10銘柄からなるポートフォリオには、集中リスクがあります。ポートフォリオVaRは相関の推定値を通じてこのリスクを扱いますが、相関はまさに推定値が重要になる局面で最も大きく変動します。
手法上の注記と前提
- パーセンタイルはサンプル推定ではなく、厳密な分位点から算出しています。モンテカルロ抽出には、SQLに記述したハッシュシード付き一様乱数列を使用しているため、ここに示すすべての数値は同じ値として再計算されます。
- リターンは配当を再投資しない終値から終値までの価格変化です。これは1セッションVaRで一般的な前提です。
- 保有期間のパネルでは重複ウィンドウを使用しています。連続する20セッションのリターンは19日分が重複するため、テールの基礎となる独立観測数は、行数が示す数より大幅に少なくなります。
よくある質問
1日99% VaRとは何ですか?
単一セッションで測定した場合、100取引日のうち最悪の1日で超える損失水準です。上記の系列では、過去データに基づく推計値は3.09%です。この数値はしきい値を示すもので、それを超える損失の大きさは示しません。
どのVaR計算手法が最も正確ですか?
前提が異なるため、抽象的に最も正確な手法はありません。ヒストリカルVaRは、与えられた標本には忠実ですが、標本に含まれない事象については何も示しません。パラメトリックVaRは計算コストが低い一方、株式のテールリスクを過小評価します。モンテカルロ法の精度は、入力する分布に左右されます。
VaRと期待ショートフォールの違いは何ですか?
VaRは、設定した信頼水準における損失のしきい値です。期待ショートフォールは、そのしきい値を超えた日の損失を平均したものです。この系列では、99%水準の期待ショートフォールはVaRの1.44倍です。正規分布では、およそ1.15倍です。
1日VaRを10日VaRに換算できますか?
平方根10倍する方法が標準的な簡便法です。ただし、リターンが独立しており、ボラティリティが一定であることを前提とします。期間別パネルはその差を示しています。20セッションでは、スケーリング後の数値が13.8%、実測値が10.7%でした。
上記の各パネルには、作成に使用したSQLが記載されています。別のtickerや期間について同じ三つの計算を実行するには、Strasmore terminalで平易な英語を使って依頼してください。