Wie schnell ändert es sich?
Die Ableitung einer Funktion f an der Stelle x ist ihre momentane Änderungsrate: die Steigung der Kurve an diesem Punkt. In der Finanzwelt ist fast jede Sensitivität eine Ableitung. Das Delta einer Option ist, wie stark sich ihr Preis je Einheit Bewegung des Basiswerts ändert, ihr Gamma ist, wie schnell sich das Delta selbst ändert, und die Duration einer Anleihe ist eine Ableitung des Preises nach der Rendite. Die Analysis liefert exakte Formeln für einfache Funktionen (die Ableitung von x**2 ist 2*x), aber echte Bewertungsfunktionen sind oft zu kompliziert oder nur als Code verfügbar. Dann schätzen wir die Ableitung numerisch.
# A function is just a Python function
def f(x):
return x * x
# Slope between two nearby points approaches the true slope (2x = 6 at x = 3)
x = 3.0
for h in (1.0, 0.1, 0.001):
slope = (f(x + h) - f(x)) / h
print(h, round(slope, 4))
# 1.0 7.0
# 0.1 6.1
# 0.001 6.001Finite Differenzen: vorwärts und zentral
Die Vorwärtsdifferenz blickt einen kleinen Schritt h voraus: (f(x + h) - f(x)) / h. Ihr Fehler schrumpft proportional zu h. Die zentrale Differenz blickt je einen Schritt in beide Richtungen: (f(x + h) - f(x - h)) / (2 * h). Die Fehler beider Seiten heben sich auf, ihr Fehler schrumpft wie h zum Quadrat, und sie ist für quadratische Funktionen exakt. Die zweite Ableitung (Gamma) verbindet drei Punkte: (f(x + h) - 2 * f(x) + f(x - h)) / h**2. Übergeben Sie die Funktion selbst als Argument (f), eine Fertigkeit aus den Funktionsthemen.
import math
def forward_diff(f, x, h=1e-6):
return (f(x + h) - f(x)) / h
def central_diff(f, x, h=1e-5):
return (f(x + h) - f(x - h)) / (2 * h)
def second_derivative(f, x, h=1e-4):
return (f(x + h) - 2 * f(x) + f(x - h)) / h**2
print(round(central_diff(math.sin, 0.0), 6)) # 1.0 (cos 0)
print(round(central_diff(lambda x: x**3, 2.0), 4)) # 12.0 (3 x^2)
print(round(second_derivative(lambda x: x**3, 2.0), 3)) # 12.0 (6 x)
# A quadratic: central is exact even for a big step, forward is not
print(central_diff(lambda x: x * x, 3.0, h=0.5)) # 6.0
print(forward_diff(lambda x: x * x, 3.0, h=0.5)) # 6.5Die Schrittweite wählen
Ein kleineres h senkt den Abbruchfehler der Formel, aber das Subtrahieren zweier fast gleicher Zahlen verliert Stellen: Dieser Rundungsfehler wächst, wenn h schrumpft, und bei einer zweiten Ableitung wächst er schneller, weil zusätzlich durch h**2 geteilt wird. Ein zu kleines h (etwa 1e-12) kann Unsinn liefern. Ein guter Kompromiss ist etwa 1e-5 für erste Ableitungen mit zentralen Differenzen und etwa 1e-4 für zweite. In echtem Bewertungscode ist „bump and reprice“ genau das: den Pricer mit nach oben und unten um einen kleinen Betrag verschobenem Spot erneut laufen lassen.
import math
def central_diff(f, x, h):
return (f(x + h) - f(x - h)) / (2 * h)
# True derivative of exp at 0 is 1; watch the error as h shrinks
for h in (1e-1, 1e-3, 1e-5, 1e-8, 1e-12):
error = abs(central_diff(math.exp, 0.0, h) - 1.0)
print(f"h={h:g} error={error:.1e}")
# h=0.1 error=1.7e-03
# h=0.001 error=1.7e-07
# h=1e-05 error=1.2e-11
# h=1e-08 error=5.3e-10 (rounding error takes over)
# h=1e-12 error=3.3e-05 (much worse: too small)Ableitungen nutzen: das Newton-Verfahren
Eine Ableitung zeigt, in welche Richtung man gehen muss, um eine Nullstelle zu erreichen, einen Punkt mit f(x) = 0. Das Newton-Verfahren startet bei einer Schätzung x und ersetzt sie wiederholt durch x - f(x) / f'(x), indem es der Tangente bis zur Null folgt. Es konvergiert nahe einer Nullstelle sehr schnell, weshalb man damit aus einem Optionspreis die implizite Volatilität gewinnt. Brechen Sie ab, wenn abs(f(x)) unter einer Toleranz liegt, geben Sie nach einer Höchstzahl von Iterationen auf, und lösen Sie einen Fehler aus, wenn die Ableitung null ist oder die Schleife nicht konvergiert, statt eine falsche Zahl zurückzugeben.
def central_diff(f, x, h=1e-5):
return (f(x + h) - f(x - h)) / (2 * h)
def newton_root(f, x0, tol=1e-10, max_iter=50):
x = x0
for _ in range(max_iter):
if abs(f(x)) < tol:
return x
slope = central_diff(f, x)
if slope == 0:
raise ValueError("flat slope: Newton cannot continue")
x = x - f(x) / slope
raise ValueError("did not converge")
# The square root of 2 is the positive root of x^2 - 2
root = newton_root(lambda x: x * x - 2, 1.0)
print(round(root, 8)) # 1.41421356
# x^2 + 1 never reaches zero
try:
newton_root(lambda x: x * x + 1, 1.0)
except ValueError:
print("no root found") # no root foundx**2 oder sin), bevor Sie ihnen bei einem Pricer vertrauen. Bibliotheken wie numpy und scipy liefern getestete Versionen; zu wissen, wie sie arbeiten, zeigt, wann man ihnen trauen kann.- Vorwärts
(f(x+h) - f(x)) / h; zentral(f(x+h) - f(x-h)) / (2h); zweite(f(x+h) - 2f(x) + f(x-h)) / h**2. - Weder zu groß noch zu klein: etwa
1e-5für erste und1e-4für zweite Ableitungen. - Newton:
x = x - f(x) / f'(x), bisabs(f(x))winzig ist; bei Nichtkonvergenz einen Fehler auslösen.
Als Nächstes: Integrale im Code, viele dünne Streifen aufaddieren, um Flächen, Summen und Erwartungswerte zu finden.
Lab: Was bewertet wird
- Mit
forward_diffeine Steigung nach vorn schätzen25 Pkt. - Mit
central_diffeine Steigung von beiden Seiten schätzen25 Pkt. - Mit
second_derivativedie Krümmung (Gamma) schätzen25 Pkt. - Mit
newton_rootdie Nullstelle einer Funktion finden25 Pkt.