Das Modell mit dem Markt in Einklang bringen
Ein Bewertungsmodell hat Parameter, und die richtigen Werte stehen nicht im Lehrbuch, sondern im heutigen Markt. Kalibrierung wählt die Parameter so, dass die Modellpreise den beobachteten möglichst nahekommen, meist den Optionsquotes des Tages. Das Rezept hat drei Teile. Ein Ziel, das die Abweichung misst, fast immer eine Summe quadrierter Differenzen, womöglich gewichtet, damit liquide Quotes mehr zählen. Eine Suche, die die Parameter verschiebt, um sie zu verkleinern. Und Prüfungen, ob das Ergebnis sinnvoll ist: von Tag zu Tag stabil, in plausiblen Grenzen und gut bei Quotes, die nicht in die Anpassung eingingen.
# The objective: how far the model's prices are from the market's, as a (weighted) sum of squares
def sum_squared_error(model, market, weights=None):
if weights is None:
weights = [1.0] * len(model)
return sum(w * (m - y) ** 2 for w, m, y in zip(weights, model, market))
market = [4.20, 8.60, 14.10]
model = [4.00, 8.80, 14.20]
print(round(sum_squared_error(model, market), 4)) # 0.09 0.2^2 + 0.2^2 + 0.1^2
print(round(sum_squared_error(model, market, [1, 4, 1]), 4)) # 0.21 the middle quote counts four times as muchZwei Suchwerkzeuge: eine Nullstelle und ein Minimum
Zwei Aufgaben tauchen ständig auf. Erstens die implizite Volatilität: Zu einem Marktpreis die Volatilität finden, bei der das Modell ihn liefert, also eine Nullstelle von model(sigma) - price = 0. Da der Preis stetig mit der Volatilität steigt, funktioniert die Bisektion: Man beginnt mit einem Intervall, in dem die Differenz das Vorzeichen wechselt, halbiert es, behält die Hälfte, in der das Vorzeichen noch wechselt, und wiederholt; jeder Schritt verdoppelt die Genauigkeit und kann nicht scheitern. Zweitens ein Minimum: Für eine Funktion mit einem einzigen Tal verkleinert die Goldene-Schnitt-Suche das Intervall pro Schritt um den Faktor 0,618 und verwendet einen der beiden Funktionswerte wieder, braucht also eine Auswertung pro Schritt und keine Ableitungen. Bibliotheken (scipy.optimize) sind schneller und bewältigen viele Parameter, aber diese beiden zeigen die Ideen.
# Implied volatility by bisection: the root of (model price - market price) in sigma
import math
def norm_cdf(x):
return 0.5 * (1 + math.erf(x / math.sqrt(2)))
def bs_call(s, k, t, r, sigma):
d1 = (math.log(s / k) + (r + sigma ** 2 / 2) * t) / (sigma * math.sqrt(t))
return s * norm_cdf(d1) - k * math.exp(-r * t) * norm_cdf(d1 - sigma * math.sqrt(t))
def bisect_root(f, lo, hi, tol=1e-10):
f_lo = f(lo)
while hi - lo > tol:
mid = (lo + hi) / 2
f_mid = f(mid)
if f_lo * f_mid <= 0:
hi = mid
else:
lo, f_lo = mid, f_mid
return (lo + hi) / 2
market_price = bs_call(100.0, 105.0, 1.0, 0.02, 0.27) # a quote generated with a volatility of 27%
implied = bisect_root(lambda sigma: bs_call(100.0, 105.0, 1.0, 0.02, sigma) - market_price, 0.01, 2.0)
print(round(implied, 6)) # 0.27 recovered# Golden-section search: one function evaluation per step, the interval shrinks by 0.618 each time
import math
def golden_section_minimize(f, lo, hi, tol=1e-8):
ratio = (math.sqrt(5) - 1) / 2
a, b = lo, hi
c, d = b - ratio * (b - a), a + ratio * (b - a)
fc, fd = f(c), f(d)
while b - a > tol:
if fc < fd:
b, d, fd = d, c, fc
c = b - ratio * (b - a)
fc = f(c)
else:
a, c, fc = c, d, fd
d = a + ratio * (b - a)
fd = f(d)
return (a + b) / 2
evaluations = []
def valley(x):
evaluations.append(x)
return (x - 1.3) ** 2 + 0.5
print(round(golden_section_minimize(valley, 0.0, 5.0), 5), len(evaluations) < 60) # 1.3 True about 40 evaluations for 1e-8Eine Volatilität kalibrieren und das verantwortungsvoll tun
Setzt man die Teile zusammen, heißt eine einzelne Volatilität an mehrere Optionspreise zu kalibrieren, die Summe der quadrierten Fehler zwischen price_fn(strike, sigma) und den Marktpreisen über sigma zu minimieren, wobei price_fn übergeben wird, damit derselbe Code für jedes Modell funktioniert. Bei einem Modell mit mehreren Parametern (Heston hat fünf, SABR drei nach Festlegung von beta) ist die Suche mehrdimensional und kann in einem lokalen Minimum landen: Starten Sie von mehreren Anfangswerten, begrenzen Sie die Parameter und bevorzugen Sie relative oder Implied-Volatility-Fehler gegenüber rohen Preisfehlern, damit billige Out-of-the-money-Optionen nicht ignoriert werden. Prüfen Sie dann, dass die angepassten Parameter von Tag zu Tag stabil sind und das Modell Optionen gut bewertet, an die es nicht angepasst wurde. Ein perfekter, aber instabiler Fit ist Overfitting.
# Calibrate one volatility to three option prices: minimise the sum of squared errors over sigma
import math
def norm_cdf(x):
return 0.5 * (1 + math.erf(x / math.sqrt(2)))
def bs_call(s, k, t, r, sigma):
d1 = (math.log(s / k) + (r + sigma ** 2 / 2) * t) / (sigma * math.sqrt(t))
return s * norm_cdf(d1) - k * math.exp(-r * t) * norm_cdf(d1 - sigma * math.sqrt(t))
def golden_section_minimize(f, lo, hi, tol=1e-8):
ratio = (math.sqrt(5) - 1) / 2
a, b = lo, hi
c, d = b - ratio * (b - a), a + ratio * (b - a)
fc, fd = f(c), f(d)
while b - a > tol:
if fc < fd:
b, d, fd = d, c, fc
c = b - ratio * (b - a)
fc = f(c)
else:
a, c, fc = c, d, fd
d = a + ratio * (b - a)
fd = f(d)
return (a + b) / 2
def calibrate_sigma(strikes, market_prices, price_fn, lo, hi):
def error(sigma):
return sum((price_fn(k, sigma) - p) ** 2 for k, p in zip(strikes, market_prices))
return golden_section_minimize(error, lo, hi)
strikes = [90.0, 100.0, 110.0]
quotes = [bs_call(100.0, k, 1.0, 0.02, 0.23) for k in strikes] # market quotes made with sigma = 23%
sigma = calibrate_sigma(strikes, quotes, lambda k, s: bs_call(100.0, k, 1.0, 0.02, s), 0.01, 1.5)
print(round(sigma, 4)) # 0.23- Die Kalibrierung wählt Modellparameter, die eine (gewichtete) Summe quadrierter Preisfehler minimieren; prüfen Sie Stabilität über Tage und Genauigkeit bei nicht verwendeten Quotes.
- Die Bisektion findet eine Nullstelle (eine implizite Volatilität), indem sie ein Intervall mit Vorzeichenwechsel halbiert; der Goldene Schnitt findet das Minimum einer Ein-Tal-Funktion mit einer Auswertung pro Schritt.
- Übergeben Sie die Bewertungsfunktion, begrenzen Sie die Parameter, starten Sie bei Mehrparametermodellen von mehreren Schätzungen und verwerfen Sie unsinnige Fits.
Als Nächstes: neuronale Netze für Prognosen, eine Vorhersage aus Daten lernen, statt die Formel zu schreiben.
Lab: Was bewertet wird
- Die Abweichung mit
sum_squared_errormessen25 Pkt. - f(x) = 0 mit
bisect_rootlösen25 Pkt. - Den tiefsten Punkt mit
golden_section_minimizefinden25 Pkt. - Den Markt mit
calibrate_sigmaanpassen25 Pkt.