σμ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 AcademySkills
Ctrl K
AO
+1,2 Tsd. Lernende
AnmeldenBeitreten ↗
Skills
AO
+1,2 Tsd. Lernende
Anmelden
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python-Skill›Modul 11 · Produktivsysteme und Spitzenmodelle›Thema 11.8·~1 Std. 30 Min.Experte

Lokale Volatilität

Position in der SkillModul 11 von 11 · Thema 8 von 13
11.8 Lokale Volatilität
—Lab-Punkte— / 100XP gesamt—Serie—

Eine Volatilität für jeden Strike und jede Zeit

Black-Scholes bewertet einen Call mit einer einzigen Volatilität, doch der Markt notiert für jeden Strike und jede Laufzeit eine andere implizite Volatilität. Das Local-Volatility-Modell reproduziert alle diese Preise zugleich, indem die Volatilität vom Preisniveau und von der Zeit abhängt, sigma(S, t), bei nur einer Zufallsquelle. Bruno Dupire zeigte 1994, dass diese Funktion keine Vermutung ist: Sie ist durch die Fläche der Call-Preise C(K, T) über eine Formel bestimmt, sodass sich die Optionspreise des Marktes direkt in ein Modell verwandeln lassen, mit dem man exotische Optionen konsistent bewertet.

# 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

Dupires Formel

Dupires Formel drückt die lokale Varianz durch drei Ableitungen der Call-Preisfläche aus: sigma_loc^2(K, T) = (dC/dT + (r - q) * K * dC/dK + q * C) / (0.5 * K^2 * d2C/dK2). Der Zähler enthält die Zeitsteigung der Preise (sie darf nicht negativ sein, sonst ist eine längere Option billiger als eine kürzere: eine Kalenderarbitrage), der Nenner den Butterfly d2C/dK2, proportional zur impliziten Wahrscheinlichkeitsdichte des Marktes für den Preis, der positiv sein muss. Bei echten Quotes, die verrauscht und spärlich sind, bildet man die Ableitungen durch finite Differenzen auf einer geglätteten Fläche: (f(x+h) - f(x-h)) / (2h) für die erste und (f(x+h) - 2 f(x) + f(x-h)) / h^2 für die zweite Ableitung. Eine verrauschte Fläche liefert wilde lokale Volatilitäten, daher ist es Standard, zuerst die implizite Volatilitätsfläche zu glätten.

# 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

Die Fläche in einer Simulation nutzen

Zur Simulation tabelliert man sigma_loc auf einem Preisgitter (für jede Zeitscheibe) und schlägt sie bei jedem Schritt zum aktuellen Preis nach, linear zwischen Gitterpunkten interpolierend und außerhalb des Gitters die Randwerte konstant haltend. Ein Schritt ist dann die übliche Log-Euler-Bewegung mit dieser Volatilität, S * exp((mu - sigma^2/2) dt + sigma sqrt(dt) dz). Verglichen mit Heston passt die lokale Volatilität die heutigen Optionspreise konstruktionsbedingt exakt an, doch ihre künftige Smile-Dynamik ist unrealistisch (der Skew flacht mit der Zeit ab), was für Produkte zählt, die auf den künftigen Smile reagieren. Viele Desks nutzen deshalb lokal-stochastische Volatilität, eine Mischung beider.

# 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
Ein perfekter Fit ist kein perfektes ModellWeil die lokale Volatilität aus den Preisen abgelesen wird, reproduziert sie diese konstruktionsbedingt, samt Rauschen und jeder Arbitrage in den Quotes. Prüfen Sie vor der Anwendung von Dupire, dass die Fläche arbitragefrei ist (nichtnegative Butterflys und Kalender-Spreads), deckeln und begrenzen Sie die entstehenden Volatilitäten nach unten und testen Sie die simulierten Preise gegen die Vanilla-Optionen, von denen Sie ausgingen. Ein Modell, das heute perfekt passt, kann morgen dennoch schlecht hedgen, da seine Vorstellung von der Bewegung des Smiles simpel ist.
  • Die lokale Volatilität sigma(S, t) reproduziert die gesamte Call-Preisfläche mit einer Zufallsquelle; Dupires Formel liefert sie direkt aus den Preisen.
  • Dupire: (dC/dT + (r - q) K dC/dK + q C) / (0.5 K^2 d2C/dK2); bilden Sie die Ableitungen per finiten Differenzen auf einer geglätteten, arbitragefreien Fläche.
  • Simulieren Sie, indem Sie bei jedem Schritt sigma_loc zum aktuellen Preis nachschlagen; ein perfekter Fit heute garantiert keine realistische Smile-Dynamik morgen.

Als Nächstes: das SABR-Modell, ein stochastisches Volatilitätsmodell mit Smile in geschlossener Form.

Lab: Was bewertet wird

  • Mit finite_difference numerisch ableiten25 Pkt.
  • Dupires Formel in local_variance anwenden25 Pkt.
  • Die Fläche mit lookup_local_vol auslesen25 Pkt.
  • Mit local_vol_step einen Schritt simulieren25 Pkt.
Explorer · lab-137-1
▾ lab-137-1
…
bash · im Browser
learner@quantcraft:~/lab-137-1$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

Alles in lab-137-1 wird auf die Startdateien zurückgesetzt und das Terminal geleert. Ihre aktuelle Lösung geht verloren – das lässt sich nicht rückgängig machen.

Wird zurückgesetzt
  • main.py → Startcode
  • Terminal-Verlauf und Testausgabe
  • Nicht eingereichte Änderungen
Bleibt erhalten
  • Beste Lab-Punktzahl
  • Ihre XP und Ihre Serie
  • Vorlesungsfortschritt und Notizen

Melde dich an, um deinen Code auszuführen.

Alle Lektionen kannst du kostenlos lesen. Erstelle ein Konto oder melde dich an, um Code auszuführen, Tests zu starten und dieses Lab abzugeben.

  • IDE im Browser mit Bash-Terminal — nichts zu installieren
  • Sofortige Bewertung, XP und dein Platz auf der Skill
Konto erstellenAnmelden
ODER
Weiter mit GitHub