Skip to the content.

← 目次← 前: 17次: 19 →

18. 確率過程とリスク指標

テーマ: 幾何ブラウン運動による株価シミュレーションと VaR / 期待ショートフォール

学習点: NumPy の累積積, 分位点, ヒストグラムのテキスト描画, リスク指標の定義

依存: NumPy / 難易度: 中級

実行方法

uv run 18_random_walk_var.py

スクリプト冒頭の PEP 723 メタデータ(# /// script)により、必要なライブラリは uv が自動的に仮想環境へ導入します。事前の pip install は不要です。

解説

何をするプログラムか

株価を幾何ブラウン運動(GBM)でモデル化し、5 万通りの 1 年間(252 営業日)の値動きをシミュレートして、投資のリスクを多面的に測るプログラムです。銀行や運用会社が日常的に使うリスク指標 VaR(バリュー・アット・リスク)と期待ショートフォール(ES)を、1 億円のポジションに対して計算します。

さらに、期末のリターンだけでは見えない「途中でどれだけ沈んだか」(最大ドローダウン)、および「長く持てば安全か」という時間分散効果の論点まで検証し、リスクを 1 つの数字で語ることの危うさを体感させる構成になっています。

コードの読みどころ

理論的背景

GBM では対数収益率が i.i.d. の正規分布に従い、価格は対数正規分布になります。VaR_α は損失分布の α 分位点、ES_α(CVaR)は VaR を超えた場合の損失の条件付き期待値です。VaR は「その先」の損失の大きさを無視するうえ、一般に劣加法性(分散投資でリスク指標が増えない性質)を満たしません。ES は劣加法性を含む整合的リスク尺度の公理を満たすことが知られています(Artzner et al. 1999)。バーゼル規制が VaR から ES へ移行した背景でもあります。

実行結果の見方

まず 1 年後価格の平均 3,183.2・中央値 3,094.1 が理論値(3,185.5 / 3,095.1)とほぼ一致し、平均>中央値という対数正規分布の歪みが出ている点を確認してください。「期待収益率 6%」は典型的な結果ではなく、分布の右裾に引っ張られた値です。

リスク指標の表では、どの信頼水準でも ES が VaR を上回ること(例: 99% で VaR 351 万円、ES 402 万円)が重要です。最大ドローダウンでは、期待収益率が正でも 58.5% の経路が途中 20% 以上の下落を経験します。最後の表は、保有期間を延ばすと元本割れ確率は 46%→28% に低下する一方、年率リターンの標準偏差の縮小と金額ベースの損失拡大が併存するという、時間分散効果論争(Samuelson 1963)の要点を示しています。

ソースコード

# /// script
# requires-python = ">=3.11"
# dependencies = [
#     "numpy",
# ]
# ///
"""18: 確率過程とリスク指標 -----------------------------------------------
テーマ: 幾何ブラウン運動による株価シミュレーションと VaR / 期待ショートフォール
学習点: NumPy の累積積, 分位点, ヒストグラムのテキスト描画, リスク指標の定義
根拠: GBM では対数収益率が i.i.d. 正規に従う。
      VaR_α は損失分布の α 分位点、ES_α(期待ショートフォール, CVaR)は
      VaR を超える損失の条件付き期待値。ES は劣加法性を満たす整合的
      リスク尺度である一方、VaR は一般に満たさない(Artzner et al. 1999)。
"""
import numpy as np

rng = np.random.default_rng(2024)


def simulate_gbm(S0, mu, sigma, days, n_paths):
    dt = 1 / 252
    z = rng.standard_normal((n_paths, days))
    logret = (mu - sigma ** 2 / 2) * dt + sigma * np.sqrt(dt) * z
    return S0 * np.exp(np.cumsum(logret, axis=1)), logret


def histogram(data, bins=25, width=50):
    counts, edges = np.histogram(data, bins=bins)
    mx = counts.max()
    for c, lo, hi in zip(counts, edges[:-1], edges[1:]):
        print(f"  {lo:>8.2%}{hi:>8.2%} |{'█' * int(c / mx * width)} {c}")


def main() -> None:
    S0, mu, sigma, days, n = 3000.0, 0.06, 0.24, 252, 50_000
    paths, logret = simulate_gbm(S0, mu, sigma, days, n)
    final = paths[:, -1]
    total_ret = final / S0 - 1

    print(f"初期価格 {S0:,.0f} / 期待収益率 {mu:.1%} / ボラティリティ {sigma:.1%}")
    print(f"シミュレーション {n:,} 本 × {days} 営業日\n")
    print(f"1年後価格: 平均 {final.mean():,.1f} / 中央値 {np.median(final):,.1f}")
    print(f"  理論平均 S0·e^(μT) = {S0 * np.exp(mu):,.1f}")
    print(f"  理論中央値 S0·e^((μ-σ²/2)T) = {S0 * np.exp(mu - sigma**2/2):,.1f}")
    print("  ※ 対数正規分布は右に歪むため 平均 > 中央値。"
          "「期待収益率」を中央的な結果と誤読しない。\n")

    print("[1年後リターンの分布]")
    histogram(total_ret)

    print("\n[リスク指標(保有期間1日, ポジション1億円)]")
    position = 100_000_000
    daily = logret[:, 0]
    for a in [0.90, 0.95, 0.99, 0.999]:
        var = -np.quantile(daily, 1 - a) * position
        tail = daily[daily <= np.quantile(daily, 1 - a)]
        es = -tail.mean() * position
        print(f"  信頼水準 {a:>6.1%}: VaR = {var:>13,.0f}円 / "
              f"ES = {es:>13,.0f}円")
    print("  ※ ES は VaR を必ず上回り、裾の深さを反映する。")

    print("\n[最大ドローダウン(経路依存リスク)]")
    running_max = np.maximum.accumulate(paths, axis=1)
    dd = (paths - running_max) / running_max
    mdd = dd.min(axis=1)
    print(f"  平均MDD = {mdd.mean():.2%} / 中央値 {np.median(mdd):.2%}")
    for q in [0.05, 0.25, 0.50]:
        print(f"  下位{q:>5.0%}のMDD = {np.quantile(mdd, q):.2%}")
    print(f"  20%以上下落を経験した経路の割合 = {(mdd <= -0.20).mean():.1%}")
    print("  ※ 期末リターンが正でも途中で大きく沈む経路が多数ある。")

    print("\n[時間分散効果の検証] 保有期間とマイナスで終わる確率")
    for years in [1, 3, 5, 10, 20]:
        p, _ = simulate_gbm(S0, mu, sigma, 252 * years, 5_000)
        print(f"  {years:>2}年: 元本割れ確率 = {(p[:, -1] < S0).mean():>6.2%} / "
              f"年率換算リターンの標準偏差 = "
              f"{np.std(np.log(p[:, -1] / S0) / years):.3%}")
    print("  ※ 年率リターンのばらつきは縮むが、金額ベースの損失幅は拡大する。")
    print("    これが「時間分散効果」をめぐる論争(Samuelson 1963)の要点。")


if __name__ == "__main__":
    main()

実行結果

初期価格 3,000 / 期待収益率 6.0% / ボラティリティ 24.0%
シミュレーション 50,000 本 × 252 営業日

1年後価格: 平均 3,183.2 / 中央値 3,094.1
  理論平均 S0·e^(μT) = 3,185.5
  理論中央値 S0·e^((μ-σ²/2)T) = 3,095.1
  ※ 対数正規分布は右に歪むため 平均 > 中央値。「期待収益率」を中央的な結果と誤読しない。

[1年後リターンの分布]
   -61.04%〜 -50.11% | 55
   -50.11%〜 -39.19% |███ 645
   -39.19%〜 -28.27% |██████████████ 2651
   -28.27%〜 -17.34% |███████████████████████████████ 5569
   -17.34%〜  -6.42% |██████████████████████████████████████████████ 8281
    -6.42%〜   4.50% |██████████████████████████████████████████████████ 8856
     4.50%〜  15.43% |████████████████████████████████████████████ 7894
    15.43%〜  26.35% |██████████████████████████████████ 6157
    26.35%〜  37.27% |██████████████████████ 4061
    37.27%〜  48.19% |██████████████ 2565
    48.19%〜  59.12% |████████ 1516
    59.12%〜  70.04% |████ 821
    70.04%〜  80.96% |██ 469
    80.96%〜  91.89% |█ 226
    91.89%〜 102.81% | 121
   102.81%〜 113.73% | 58
   113.73%〜 124.65% | 31
   124.65%〜 135.58% | 12
   135.58%〜 146.50% | 6
   146.50%〜 157.42% | 2
   157.42%〜 168.35% | 1
   168.35%〜 179.27% | 2
   179.27%〜 190.19% | 0
   190.19%〜 201.12% | 0
   201.12%〜 212.04% | 1

[リスク指標(保有期間1日, ポジション1億円)]
  信頼水準  90.0%: VaR =     1,929,181円 / ES =     2,639,445円
  信頼水準  95.0%: VaR =     2,473,210円 / ES =     3,101,749円
  信頼水準  99.0%: VaR =     3,514,002円 / ES =     4,022,414円
  信頼水準  99.9%: VaR =     4,642,763円 / ES =     5,027,057円
  ※ ES は VaR を必ず上回り、裾の深さを反映する。

[最大ドローダウン(経路依存リスク)]
  平均MDD = -23.22% / 中央値 -21.83%
  下位   5%のMDD = -39.30%
  下位  25%のMDD = -28.44%
  下位  50%のMDD = -21.83%
  20%以上下落を経験した経路の割合 = 58.5%
  ※ 期末リターンが正でも途中で大きく沈む経路が多数ある。

[時間分散効果の検証] 保有期間とマイナスで終わる確率
   1年: 元本割れ確率 = 46.18% / 年率換算リターンの標準偏差 = 24.025%
   3年: 元本割れ確率 = 40.70% / 年率換算リターンの標準偏差 = 14.011%
   5年: 元本割れ確率 = 37.68% / 年率換算リターンの標準偏差 = 10.750%
  10年: 元本割れ確率 = 34.30% / 年率換算リターンの標準偏差 = 7.474%
  20年: 元本割れ確率 = 28.48% / 年率換算リターンの標準偏差 = 5.341%
  ※ 年率リターンのばらつきは縮むが、金額ベースの損失幅は拡大する。
    これが「時間分散効果」をめぐる論争(Samuelson 1963)の要点。

← 目次← 前: 17次: 19 →