すべての権利行使価格と時点に対するボラティリティ
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点