Wenn auch die Volatilität sich bewegt
Black-Scholes nimmt konstante Volatilität an, aber die echte Volatilität steigt in Crashs und verblasst in ruhigen Zeiten, und der Optionsmarkt zeigt das: Die implizite Volatilität unterscheidet sich je Strike (das Smile oder der Skew). Das Heston-Modell behält den geometrischen Preisprozess bei, macht aber auch die *Varianz* v zufällig: Sie wird mit Geschwindigkeit kappa zu einem langfristigen Niveau theta zurückgezogen, von Rauschen der Größe xi geschüttelt, das mit sqrt(v) skaliert, und die Schocks von Preis und Varianz haben die Korrelation rho. Ein negatives rho (der Preis fällt, wenn die Volatilität steigt, der Leverage-Effekt) erzeugt den abwärts gerichteten Skew bei Aktienoptionen.
# 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 positiveSimulation: Abschneiden und Korrelation
Zur Simulation geht man in kleinen Intervallen dt voran. Das Varianz-Update lautet v + kappa * (theta - v) * dt + xi * sqrt(v * dt) * dz, aber ein einfacher Euler-Schritt kann v unter null drücken, wo die Wurzel versagt. Das Schema der vollen Abschneidung nutzt max(v, 0) überall, wo v rechts vorkommt (ein negativer Wert wirkt wie null), und behält das Rohergebnis für den nächsten Schritt. Die Feller-Bedingung 2 * kappa * theta >= xi^2 ist der Parameterbereich, in dem der wahre Prozess nie null erreicht; scheitert sie, zählt das Abschneiden noch mehr. Die beiden Schocks müssen korreliert sein: Ziehen Sie unabhängige Normalen z1, z2, setzen Sie dz = z1 und 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 0Ein ganzer Pfad und eine Plausibilitätsprüfung
Ein Schritt aktualisiert beide Zustandsvariablen gemeinsam: Der Preis nutzt die Varianz zu *Beginn* des Schritts, S * exp((mu - v/2) * dt + sqrt(v * dt) * dw), danach bewegt sich die Varianz. Wiederholen Sie das für die Zahl der Schritte im Horizont, und Sie haben einen Pfad; viele Pfade ergeben eine Verteilung von Endpreisen, und der Mittelwert eines abgezinsten Payoffs darüber bewertet eine Option. Eine gute Plausibilitätsprüfung: Mit der Drift mu sollte der mittlere Endpreis nahe S0 * exp(mu * T) liegen, egal welche Volatilitätsparameter gelten. Andernfalls steckt ein Fehler im Preisschritt. Die Kalibrierung von kappa, theta, xi, rho an Optionspreise folgt in den kommenden Themen.
# 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% volatilitydt schrumpft, und ein grobes Gitter verzerrt Optionspreise. Nutzen Sie genug Schritte und viele Pfade (der Standardfehler fällt wie eins durch die Wurzel der Pfadzahl). Prüfen Sie die Simulation an einer bekannten Antwort: Setzen Sie xi = 0, und das Modell fällt auf Black-Scholes mit Volatilität sqrt(theta) zusammen. In der Produktion ist die semi-analytische Heston-Bewertung über die charakteristische Funktion schneller und exakt; Simulation ist das flexible Werkzeug, wenn Payoffs vom Pfad abhängen.- Heston macht die Varianz zufällig: Sie kehrt mit Geschwindigkeit
kappazuthetazurück, hat Rauschenxi * sqrt(v)und ist überrhomit dem Preis korreliert (bei Aktien negativ). - Simulieren Sie mit voller Abschneidung (
max(v, 0)rechts), korrelieren Sie die Schocks mitdw = rho z1 + sqrt(1 - rho^2) z2und prüfen Sie die Feller-Bedingung2 kappa theta >= xi^2. - Der Preisschritt nutzt die Varianz zu Beginn des Schritts; prüfen Sie, dass der mittlere Endpreis
S0 * exp(mu * T)folgt undxi = 0Black-Scholes reproduziert.
Als Nächstes: lokale Volatilität, eine Volatilitätsfläche, die von Preis und Zeit abhängt.
Lab: Was bewertet wird
- Die Varianz mit
feller_conditionpositiv halten25 Pkt. - Die Varianz mit
variance_stepfortschreiben25 Pkt. - Die Schocks mit
correlated_normalskorrelieren25 Pkt. - Preis und Varianz mit
heston_stepbewegen25 Pkt.