σμ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.8·~1 時間 30 分エキスパート

ローカルボラティリティ

ロードマップ上の位置モジュール 11/11 · トピック 8/13
11.8 ローカルボラティリティ
—ラボスコア— / 100合計XP—連続日数—

すべての権利行使価格と時点に対するボラティリティ

Black-Scholes は1つのボラティリティでコールを評価しますが、市場は権利行使価格と満期ごとに異なるインプライド・ボラティリティを提示します。ローカルボラティリティモデルは、ボラティリティを価格水準と時間に依存させ sigma(S, t)、乱数の源は1つのままにして、それらすべての価格を一度に再現します。ブルーノ・デュピールは1994年に、この関数は推測ではなく、コール価格の曲面 C(K, T) から公式で決まることを示しました。そのため市場のオプション価格を直接モデルに変換し、それと整合的にエキゾチックオプションを評価できます。

# What "one volatility" means: a call priced with Black-Scholes at three strikes, all with the same sigma
import math

def norm_cdf(x):
    return 0.5 * (1 + math.erf(x / math.sqrt(2)))

def bs_call(s, k, t, r, q, sigma):
    d1 = (math.log(s / k) + (r - q + sigma ** 2 / 2) * t) / (sigma * math.sqrt(t))
    d2 = d1 - sigma * math.sqrt(t)
    return s * math.exp(-q * t) * norm_cdf(d1) - k * math.exp(-r * t) * norm_cdf(d2)

for k in (90, 100, 110):
    print(k, round(bs_call(100.0, k, 1.0, 0.03, 0.01, 0.2), 4))      # 90 14.6592   100 8.8273   110 4.8947   one sigma, three different prices

デュピールの式

デュピールの式は、局所分散をコール価格曲面の3つの微分で表します:sigma_loc^2(K, T) = (dC/dT + (r - q) * K * dC/dK + q * C) / (0.5 * K^2 * d2C/dK2)。分子には価格の時間方向の傾きが含まれ(非負でなければなりません。さもないと長い満期のオプションのほうが短いものより安くなり、カレンダー裁定が生じます)、分母にはバタフライ d2C/dK2 があります。これは市場が暗黙に織り込む価格の確率密度に比例し、正でなければなりません。実際の気配はノイズが多く疎なので、微分は平滑化した曲面上で有限差分により求めます。1階は (f(x+h) - f(x-h)) / (2h)、2階は (f(x+h) - 2 f(x) + f(x-h)) / h^2 です。ノイズのある曲面は荒れたローカルボラティリティを生むため、まずインプライド・ボラティリティ曲面を平滑化するのが標準的な手順です。

# Dupire's formula recovers a constant volatility from Black-Scholes prices (finite differences on the price surface)
import math

def norm_cdf(x):
    return 0.5 * (1 + math.erf(x / math.sqrt(2)))

def bs_call(s, k, t, r, q, sigma):
    d1 = (math.log(s / k) + (r - q + sigma ** 2 / 2) * t) / (sigma * math.sqrt(t))
    return s * math.exp(-q * t) * norm_cdf(d1) - k * math.exp(-r * t) * norm_cdf(d1 - sigma * math.sqrt(t))

def local_variance(call, dc_dt, dc_dk, d2c_dk2, strike, r, q):
    return (dc_dt + (r - q) * strike * dc_dk + q * call) / (0.5 * strike ** 2 * d2c_dk2)

s, k, t, r, q, sigma = 100.0, 105.0, 1.0, 0.03, 0.01, 0.2
hk, ht = 0.5, 0.002
c = bs_call(s, k, t, r, q, sigma)
dc_dt = (bs_call(s, k, t + ht, r, q, sigma) - bs_call(s, k, t - ht, r, q, sigma)) / (2 * ht)
dc_dk = (bs_call(s, k + hk, t, r, q, sigma) - bs_call(s, k - hk, t, r, q, sigma)) / (2 * hk)
d2c_dk2 = (bs_call(s, k + hk, t, r, q, sigma) - 2 * c + bs_call(s, k - hk, t, r, q, sigma)) / hk ** 2
print(round(math.sqrt(local_variance(c, dc_dt, dc_dk, d2c_dk2, k, r, q)), 3))        # 0.2   the constant volatility comes back

シミュレーションで曲面を使う

シミュレーションでは、sigma_loc を価格のグリッド(時間スライスごと)に表にしておき、各ステップで現在の価格のところを参照します。グリッド点の間は線形補間、グリッドの外側は端の値で一定にします。1ステップは、そのボラティリティを使った通常の対数オイラーの動き S * exp((mu - sigma^2/2) dt + sigma sqrt(dt) dz) です。Heston と比べると、ローカルボラティリティは構成上、今日のオプション価格を正確に再現しますが、将来のスマイルの動きは非現実的で(時間とともにスキューが平坦化する)、将来のスマイルに敏感な商品では問題になります。そのため多くのデスクは、両者を混ぜたローカル・確率的ボラティリティを使います。

# A lookup on a strike grid (linear inside, flat outside) and one simulation step with the volatility it returns
import math

def lookup_local_vol(strikes, vols, k):
    if k <= strikes[0]:
        return vols[0]
    if k >= strikes[-1]:
        return vols[-1]
    for i in range(1, len(strikes)):
        if k <= strikes[i]:
            weight = (k - strikes[i - 1]) / (strikes[i] - strikes[i - 1])
            return vols[i - 1] + weight * (vols[i] - vols[i - 1])

strikes, vols = [90.0, 100.0, 110.0], [0.25, 0.20, 0.18]
print(lookup_local_vol(strikes, vols, 95.0), round(lookup_local_vol(strikes, vols, 105.0), 4), lookup_local_vol(strikes, vols, 200.0))      # 0.225 0.19 0.18

s = 100.0
sigma = lookup_local_vol(strikes, vols, s)
print(round(s * math.exp((0.05 - sigma ** 2 / 2) * (1 / 252) + sigma * math.sqrt(1 / 252) * 0.5), 4))       # 100.6439
# A calendar arbitrage shows up as a negative numerator: a longer option priced below a shorter one
def numerator(call, dc_dt, dc_dk, strike, r, q):
    return dc_dt + (r - q) * strike * dc_dk + q * call

print(round(numerator(10.0, 1.2, -0.45, 100.0, 0.03, 0.01), 6))           # 0.4     prices rise with maturity: a valid local variance
print(numerator(10.0, -2.0, -0.45, 100.0, 0.03, 0.01) < 0)                # True    prices fall with maturity: no model can fit this
完全なフィットは完全なモデルではないローカルボラティリティは価格から読み取るので、構成上その価格を、ノイズも気配に含まれる裁定も含めて再現します。デュピールを使う前に、曲面が裁定を含まないこと(バタフライとカレンダースプレッドが非負)を確認し、得られたボラティリティに上限と下限を設け、シミュレーションで得た価格を出発点にしたバニラオプションと照らしましょう。今日に完全に合うモデルでも、スマイルの動き方についての考えが単純なので、明日のヘッジはうまくいかないことがあります。
  • ローカルボラティリティ sigma(S, t) は1つの乱数源でコール価格曲面全体を再現し、デュピールの式が価格から直接それを与える
  • デュピール:(dC/dT + (r - q) K dC/dK + q C) / (0.5 K^2 d2C/dK2)。微分は平滑化した裁定のない曲面上の有限差分で求める
  • 各ステップで現在の価格の sigma_loc を参照してシミュレートする。今日の完全なフィットは明日の現実的なスマイルの動きを保証しない

次は SABR モデルです。閉じた形のスマイルを持つ確率的ボラティリティモデルです。

ラボ:採点される内容

  • finite_difference で数値微分する25点
  • local_variance でデュピールの式を適用する25点
  • lookup_local_vol で曲面を読む25点
  • local_vol_step で1ステップをシミュレートする25点
エクスプローラー · lab-137-1
▾ lab-137-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-137-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

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

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

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

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

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

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