Von gleichverteilt zu allem
Ein Generator liefert gleichmäßig zwischen 0 und 1 verteilte Zahlen. Ein Modell braucht andere Formen: eine Münze, die in 30 % der Fälle gewinnt, eine Wartezeit zwischen Trades, eine normale Tagesrendite. Der Trick ist immer derselbe: eine Transformation auf gleichverteilte Zahlen anwenden. Die allgemeinste ist die Inversionsmethode: Ist F die Verteilungsfunktion des Gewünschten, dann hat F⁻¹(u) für gleichverteiltes u genau diese Verteilung. Ganze Arrays durchlaufen die Transformation auf einmal.
import math
import numpy as np
# Exponential waiting times with rate 2 (a mean of 0.5): F(x) = 1 - exp(-rate * x), so x = -ln(1 - u) / rate
u = np.array([0.1, 0.5, 0.9])
print((-np.log(1 - u) / 2).round(4).tolist()) # [0.0527, 0.3466, 1.1513] small u, short wait
rng = np.random.default_rng(42)
waits = -np.log(1 - rng.random(100000)) / 2
print(round(waits.mean(), 2)) # 0.5 the mean is 1 / rateDiskrete Ergebnisse und die Normalverteilung
Für eine diskrete Ergebnistabelle erledigt rng.choice(values, size=n, p=probs) die Inversion für Sie: Es zieht jeden Wert mit seiner Wahrscheinlichkeit, etwa einen Tag mit -2 %, 0 % oder +3 % bei Wahrscheinlichkeiten von 20 %, 50 % und 30 %. Bei der Normalverteilung hat die Umkehrung der Verteilungsfunktion keine einfache Formel; ein klassischer Trick ist die Box-Muller-Transformation: Aus zwei unabhängigen Gleichverteilten u1 und u2 ist z = sqrt(-2 ln u1) * cos(2 pi u2) eine standardnormale Ziehung. In der Praxis erledigt rng.standard_normal(n) das schneller, aber es einmal zu schreiben zeigt, dass es keine Magie gibt.
rng = np.random.default_rng(3)
days = rng.choice(np.array([-0.02, 0.0, 0.03]), size=100000, p=[0.2, 0.5, 0.3])
print(round(days.mean(), 3), round(0.2 * -0.02 + 0.3 * 0.03, 3)) # 0.005 0.005 the sample mean matches the table
rng = np.random.default_rng(42)
u1, u2 = 1 - rng.random(100000), rng.random(100000) # 1 - random() is in (0, 1]: never log(0)
z = np.sqrt(-2 * np.log(u1)) * np.cos(2 * math.pi * u2)
print(abs(z.mean()) < 0.02, round(z.std(), 2)) # True 1.0 mean 0 and standard deviation 1Von Normalziehungen zu Preisen
Eine standardnormale Ziehung z ist ein Schock; das Modell entscheidet, wie er einen Preis bewegt. Das übliche Aktienmodell, die geometrische Brownsche Bewegung, besagt, dass der Preis nach t Jahren S = S0 * exp((mu - 0,5 sigma²) t + sigma sqrt(t) z) ist. Er ist lognormalverteilt: immer positiv, mit dem langen rechten Rand echter Preise. Der Term -0,5 sigma² korrigiert den Mittelwert, sodass der erwartete Preis genau mit mu wächst: E[S] = S0 * exp(mu t). Der Median liegt unter dem Mittelwert, weshalb ein typisches Ergebnis schlechter ist als das durchschnittliche.
s0, mu, sigma, t = 100, 0.08, 0.2, 1
z = np.array([-1.0, 0.0, 1.0]) # a one-sigma loss, no shock, a one-sigma gain
prices = s0 * np.exp((mu - 0.5 * sigma ** 2) * t + sigma * math.sqrt(t) * z)
print(prices.round(2).tolist()) # [86.94, 106.18, 129.69] all positive, the upside is larger
draws = np.random.default_rng(0).standard_normal(100000)
simulated = s0 * np.exp((mu - 0.5 * sigma ** 2) * t + sigma * math.sqrt(t) * draws)
print(round(simulated.mean(), 1), round(s0 * math.exp(mu * t), 1)) # 108.3 108.3 the mean grows at mu
print(round(float(np.median(simulated)), 1)) # 106.1 the median is lower# NumPy ships ready-made samplers too; the transformations above show what they do inside
z = np.random.default_rng(0).standard_normal(100000)
print(abs(z.mean()) < 0.02, round(z.std(), 2)) # True 1.0 standard_normal: mean 0, standard deviation 1S0 * exp(mu t) für den Preis. Ein Vorzeichenfehler oder eine vergessene Wurzel erzeugt plausibel aussehende, aber falsche Zahlen, und nur eine solche Kontrolle deckt es auf.- Inversionsmethode: die Umkehrfunktion der Verteilungsfunktion auf Gleichverteilte anwenden; exponentiell:
-ln(1 - u) / rate. rng.choice(values, size=n, p=probs)zieht aus einer diskreten Tabelle; Box-Mullersqrt(-2 ln u1) * cos(2 pi u2)erzeugt Normalziehungen.- Preis nach
tJahren:S0 * exp((mu - 0,5 sigma²) t + sigma sqrt(t) z), lognormal; prüfen Sie stets eine große Stichprobe gegen den exakten Mittelwert.
Als Nächstes: Monte-Carlo-Schätzung, die Zufallsziehungen nutzt, um Mittelwerte und Wahrscheinlichkeiten ohne Formel zu berechnen.
Lab: Was bewertet wird
- Mit
sample_discreteaus einer Ergebnistabelle ziehen25 Pkt. - Mit
exponential_from_uniformdie Verteilungsfunktion umkehren25 Pkt. - Mit
box_mullerglockenförmige Zahlen erzeugen25 Pkt. - Mit
terminal_pricesSchocks in künftige Preise umwandeln25 Pkt.