σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academyロードマップ
Ctrl K
AO
+1.2k 人の学習者
ログイン参加する ↗
ロードマップ
AO
+1.2k 人の学習者
ログイン
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python ロードマップ›モジュール 11 · 本番システムと最先端モデル›トピック 11.7·~1 時間 30 分エキスパート

Hestonモデル

ロードマップ上の位置モジュール 11/11 · トピック 7/13
11.7 Hestonモデル
—ラボスコア— / 100合計XP—連続日数—

ボラティリティも動くとき

Black-Scholes はボラティリティが一定だと仮定しますが、実際のボラティリティは暴落時に上がり、平穏な時期に落ち着きます。オプション市場はそれを反映し、インプライド・ボラティリティは権利行使価格ごとに異なります(スマイルまたはスキュー)。Heston モデルは幾何的な価格過程を保ちつつ、*分散* v も確率的にします。分散は速度 kappa で長期水準 theta に引き戻され、sqrt(v) に比例する大きさ xi のノイズで揺さぶられ、価格と分散のショックには相関 rho があります。負の rho(ボラティリティが上がると価格が下がる、レバレッジ効果)が、株式オプションに見られる右下がりのスキューを生みます。

# The Heston equations, in words:  dS = mu S dt + sqrt(v) S dW      dv = kappa (theta - v) dt + xi sqrt(v) dZ      corr(dW, dZ) = rho
kappa, theta, xi = 2.0, 0.04, 0.3             # reversion speed, long-run variance (a 20% volatility), volatility of variance
print(round(theta ** 0.5, 2))                 # 0.2    the long-run volatility
print(2 * kappa * theta, xi ** 2)             # 0.16 0.09   the Feller condition 2 kappa theta >= xi^2 holds: the variance stays positive

シミュレーション:切り詰めと相関

シミュレーションは小さな区間 dt ごとに進めます。分散の更新は v + kappa * (theta - v) * dt + xi * sqrt(v * dt) * dz ですが、単純なオイラー法では v が 0 を下回り、平方根が失敗することがあります。完全切り詰めの方式では、右辺に v が現れるところすべてで max(v, 0) を使い(負の値は 0 として働く)、生の結果は次のステップのために保持します。Feller 条件 2 * kappa * theta >= xi^2 は、真の過程が 0 に達しないパラメータ領域です。成り立たない場合、切り詰めはさらに重要になります。2つのショックには相関が必要です。独立な正規乱数 z1、z2 を引き、dz = z1、dw = rho * z1 + sqrt(1 - rho^2) * z2 とします。

# One variance step with full truncation, and the correlated shocks for the price
import math

def variance_step(v, kappa, theta, xi, dz, dt):
    vp = max(v, 0.0)
    return v + kappa * (theta - vp) * dt + xi * math.sqrt(vp * dt) * dz

def correlated_normals(z1, z2, rho):
    return z1, rho * z1 + math.sqrt(1 - rho ** 2) * z2

print(round(variance_step(0.04, 2.0, 0.04, 0.3, 0.5, 1 / 252), 6))      # 0.04189   at the long-run level only the noise moves it
dz, dw = correlated_normals(1.0, 1.0, -0.7)
print(round(dz, 4), round(dw, 4))             # 1.0 0.0141   a negative rho: the price shock pulls against the variance shock
print(variance_step(-0.01, 2.0, 0.04, 0.3, 1.0, 1 / 252))               # -0.00968...   still negative, but it moved up by kappa * theta * dt: the negative value acted as 0

完全なパスと妥当性の確認

1ステップで2つの状態変数を一緒に更新します。価格はステップの*開始時*の分散を使って S * exp((mu - v/2) * dt + sqrt(v * dt) * dw) と動き、その後に分散が動きます。期間内のステップ数だけ繰り返せば1本のパスができ、多数のパスは終端価格の分布を与え、その上で割り引いたペイオフを平均すればオプションの価格になります。良い妥当性チェックがあります。ドリフト mu のもとでは、ボラティリティのパラメータにかかわらず、終端価格の平均は S0 * exp(mu * T) に近くなるはずです。そうでなければ価格のステップにバグがあります。kappa, theta, xi, rho をオプション価格に合わせる較正は、この先のトピックで扱います。

# A short Monte Carlo: simulate many paths, and check the average terminal price against S0 * exp(mu * T)
import math
import random

def simulate(s0, v0, mu, kappa, theta, xi, rho, steps, years, rng):
    dt = years / steps
    s, v = s0, v0
    for _ in range(steps):
        z1, z2 = rng.gauss(0, 1), rng.gauss(0, 1)
        dz, dw = z1, rho * z1 + math.sqrt(1 - rho ** 2) * z2
        vp = max(v, 0.0)
        s *= math.exp((mu - vp / 2) * dt + math.sqrt(vp * dt) * dw)
        v = v + kappa * (theta - vp) * dt + xi * math.sqrt(vp * dt) * dz
    return s

rng = random.Random(11)
finals = [simulate(100.0, 0.04, 0.05, 2.0, 0.04, 0.3, -0.7, 50, 1.0, rng) for _ in range(4000)]
mean = sum(finals) / len(finals)
print(abs(mean / (100.0 * math.exp(0.05)) - 1) < 0.02)       # True   the average terminal price follows S0 * exp(mu * T)
print(min(finals) > 0)                                       # True   prices stay positive
# A check that costs nothing: with xi = 0 and v0 = theta the variance never moves (Black-Scholes with volatility sqrt(theta))
import math

kappa, theta, xi = 2.0, 0.04, 0.0
v = theta
for z in (1.5, -2.0, 0.3, 0.9):
    vp = max(v, 0.0)
    v = v + kappa * (theta - vp) * (1 / 252) + xi * math.sqrt(vp / 252) * z
print(v == theta)                                    # True   no noise and already at the long-run level
print(round(math.sqrt(v), 2))                        # 0.2    a constant 20% volatility
オイラー法は単純だが厳密ではないオイラー法には離散化誤差があり、dt を小さくすると減ります。粗い格子はオプション価格に偏りを生みます。十分なステップ数と多数のパスを使いましょう(標準誤差はパス数の平方根に反比例して下がります)。既知の答えでシミュレーションを確認します。xi = 0 にすると、モデルはボラティリティ sqrt(theta) の Black-Scholes に退化します。実運用では、特性関数による Heston の準解析的な価格付けのほうが速くて正確です。シミュレーションは、ペイオフが経路に依存するときの柔軟な道具です。
  • Heston は分散を確率的にする。速度 kappa で theta に回帰し、ノイズは xi * sqrt(v)、価格とは rho で相関する(株式では負)
  • 完全切り詰め(右辺で max(v, 0))でシミュレートし、dw = rho z1 + sqrt(1 - rho^2) z2 でショックに相関を与え、Feller 条件 2 kappa theta >= xi^2 を確認する
  • 価格のステップはステップ開始時の分散を使う。終端価格の平均が S0 * exp(mu * T) に従うこと、xi = 0 で Black-Scholes が再現されることを確認する

次はローカルボラティリティです。価格と時間に依存するボラティリティ曲面を扱います。

ラボ:採点される内容

  • feller_condition で分散を正に保つ25点
  • variance_step で分散を1ステップ進める25点
  • correlated_normals でショックに相関を与える25点
  • heston_step で価格と分散を動かす25点
エクスプローラー · lab-136-1
▾ lab-136-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-136-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-136-1 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ

コードを実行するにはログインしてください。

レクチャーはすべて無料で読めます。コードの実行、テスト、ラボの提出にはアカウントの作成かログインが必要です。

  • ブラウザ内IDEとbashターミナル。インストール不要
  • 即時採点、XP、ロードマップでの現在地
アカウントを作成ログイン
または
GitHubで続ける