σμ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 4 · Bewertung von Anleihen und Optionen›Thema 4.13·~50 Min.Mittelstufe

Implizite Volatilität

Position in der SkillModul 4 von 11 · Thema 13 von 16
4.13 Implizite Volatilität
—Lab-Punkte— / 100XP gesamt—Serie—

Die Formel rückwärts laufen lassen

Die Black-Scholes-Formel macht aus einer Volatilität einen Preis. Am Markt ist es umgekehrt: Der Preis wird gestellt, die Volatilität ist unbekannt. Die implizite Volatilität ist das sigma, mit dem die Formel den Marktpreis reproduziert. Es gibt keine algebraische Umkehrung, also sucht man: Preis für ein Probe-sigma berechnen, den Preisfehler (Modell minus Markt) ansehen und sigma verschieben, bis er null ist. Die einfachste Suche probiert ein Raster von Werten und behält den besten.

import math

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

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

market = 8.0                                   # the quoted price of the 100-strike, 6-month call on a stock at 100
for sigma in (0.10, 0.15, 0.20, 0.25, 0.30):
    model = bs_call(100, 100, 0.05, sigma, 0.5)
    print(sigma, round(model, 4), "error", round(model - market, 4))

Warum es genau eine Antwort gibt

Der Call-Preis steigt stetig mit sigma: Vega ist positiv, der Fehler überquert null also nur einmal. Die Antwort ist eindeutig, und man weiß, in welche Richtung man gehen muss: Ist das Modell zu teuer, sigma senken. Eine Lösung gibt es nur, wenn der Marktpreis streng zwischen dem *inneren Wert* max(S - K * exp(-rT), 0) (Volatilität null) und dem Kassakurs S (Volatilität unendlich) liegt. Außerhalb widerspricht die Notierung der Arbitragefreiheit, und die richtige Antwort ist ein Fehler, keine Zahl.

# The bounds: no volatility can explain a price outside them
import math

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

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

spot, strike, rate, t = 100, 90, 0.05, 1
floor = max(spot - strike * math.exp(-rate * t), 0)
print(round(floor, 4), spot)                                 # 14.3894 100
print(round(bs_call(spot, strike, rate, 0.0001, t), 4))      # 14.3894   tiny volatility: the floor
print(round(bs_call(spot, strike, rate, 5.0, t), 4))         # 98.8513   huge volatility: close to the spot

Das Intervall halbieren

Ein Raster ist grob: Die Antwort ist nur so fein wie das Raster. Besser ist das Vorzeichen des Fehlers. Man startet mit sigma zwischen einem sehr niedrigen und einem sehr hohen Wert, nimmt die Mitte und prüft den Fehler: positiv heißt, die Mitte ist zu hoch, man behält die untere Hälfte; negativ die obere. Jeder Schritt halbiert das Intervall, etwa dreißig Schritte ergeben acht Stellen. Das nächste Thema benennt und verallgemeinert das; hier ist es das Werkzeug, das den Markt liest.

# Implied volatility by halving the interval, then reprice to check it
import math

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

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

def implied_vol(price, spot, strike, rate, t, tol=1e-8):
    low, high = 0.0001, 5.0
    while high - low > tol:
        mid = (low + high) / 2
        if bs_call(spot, strike, rate, mid, t) > price:
            high = mid
        else:
            low = mid
    return (low + high) / 2

iv = implied_vol(8.0, 100, 100, 0.05, 0.5)
print(round(iv, 6))                                        # 0.240534
print(round(bs_call(100, 100, 0.05, iv, 0.5), 6))          # 8.0

Das Smile

Wäre Black-Scholes exakt richtig, würden alle Optionen auf dieselbe Aktie und denselben Tag dieselbe Volatilität implizieren. Tun sie nicht: Niedrige Strikes implizieren meist mehr als hohe (der *Skew*), oder beide Flügel mehr als die Mitte (das *Smile*). Das Muster zeigt, was das Modell übersieht (Sprünge, Fat Tails, Nachfrage nach Absicherung); darum werden Optionen in impliziter Volatilität notiert: eine einzige, über Strikes und Laufzeiten vergleichbare Zahl, aus der der Preis folgt.

# A smile: market prices at five strikes, converted to implied volatilities
import math

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

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

def implied_vol(price, spot, strike, rate, t, tol=1e-8):
    low, high = 0.0001, 5.0
    while high - low > tol:
        mid = (low + high) / 2
        low, high = (low, mid) if bs_call(spot, strike, rate, mid, t) > price else (mid, high)
    return (low + high) / 2

strikes = [90, 95, 100, 105, 110]
quotes = [bs_call(100, k, 0.05, v, 0.5) for k, v in zip(strikes, [0.28, 0.24, 0.21, 0.22, 0.25])]   # prices from a made-up smile
for strike, quote in zip(strikes, quotes):
    print(strike, round(quote, 4), round(implied_vol(quote, 100, strike, 0.05, 0.5), 4))
Preis und Volatilität sind dieselbe NotierungHändler nennen eine Volatilität, und der Käufer rechnet sie mit der vereinbarten Formel in einen Preis um; die Umrechnung ist eine Konvention, kein Glaube an das Modell. Da die Abbildung für gegebenen Strike, Tag und Zins eineindeutig ist, geht in keiner Richtung etwas verloren. Entscheidend ist die Eingabe: Die implizite Volatilität ist nur so gut wie Zins, Dividende und Laufzeit, die man ihr gibt.
  • Die implizite Volatilität ist das sigma, das bs_call dem Marktpreis gleichsetzt: durch Suche gefunden, da es keine Umkehrformel gibt.
  • Der Preis steigt mit sigma: Es gibt eine Antwort, das Fehlervorzeichen zeigt die Richtung; außerhalb der Grenzen gibt es keine.
  • Intervallhalbierung konvergiert schnell (etwa 30 Schritte für 8 Stellen); das Smile ist die implizite Volatilität über dem Strike.

Als Nächstes: Nullstellensuche mit Bisektion und Newton, die allgemeine Methode hinter dieser Suche und eine viel schnellere.

Lab: Was bewertet wird

  • Mit call_price_error Modell und Markt vergleichen25 Pkt.
  • Mit implied_vol_scan ein Raster von Volatilitäten durchsuchen25 Pkt.
  • Die Formel mit implied_vol umkehren25 Pkt.
  • Mit implied_vol_smile ein ganzes Smile lesen25 Pkt.
Explorer · lab-78-1
▾ lab-78-1
…
bash · im Browser
learner@quantcraft:~/lab-78-1$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

Alles in lab-78-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