σμ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 AcademyRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Rutas
AO
+1,2 k estudiantes
Iniciar sesión
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Ruta de Python›Módulo 8 · Modelos estocásticos y aprendizaje automático›Tema 8.2·~55 minAvanzado

Movimiento browniano geométrico

Posición en la rutaMódulo 8 de 11 · Tema 2 de 10
8.2 Movimiento browniano geométrico
—Puntuación del lab— / 100XP total—Racha—

Los precios cambian en porcentaje

El movimiento browniano aritmético añade el mismo ruido en dólares cueste la acción 5 o 500, y puede bajar de cero. Una acción se comporta distinto: un movimiento del 1 % es un 1 % a cualquier precio. El movimiento browniano geométrico pone el azar en el cambio porcentual: dS = mu * S * dt + sigma * S * dW. Es el modelo de Black–Scholes. mu es el rendimiento esperado anual y sigma la volatilidad, la magnitud que se cotiza en el mercado.

import math

def gbm_step(s, mu, sigma, dt, z):
    return s * math.exp((mu - 0.5 * sigma ** 2) * dt + sigma * math.sqrt(dt) * z)

print(round(gbm_step(100.0, 0.08, 0.2, 1 / 252, 0.0), 4))     # a calm day: only the (mu - sigma^2/2) drift acts
print(round(gbm_step(100.0, 0.08, 0.2, 1 / 252, 1.0), 4))     # a one-sigma up day
print(round(gbm_step(100.0, 0.08, 0.2, 1 / 252, -50.0), 4))   # an absurd fall: still positive

La solución exacta y el menos un medio sigma al cuadrado

Resolver la ecuación da S(t) = S(0) * exp((mu - sigma**2 / 2) * t + sigma * W(t)). ¿Por qué ese extraño - sigma**2 / 2? Porque la exponencial es convexa: por la desigualdad de Jensen, el ruido eleva el promedio de exp(ruido), y restar sigma**2 / 2 lo cancela exactamente, de modo que el precio *esperado* crece a tasa mu. Las *trayectorias* típicas crecen más despacio, a mu - sigma**2 / 2, por eso el precio mediano está por debajo de la media. El siguiente tema, el lema de Itô, deriva este término.

import numpy as np

def gbm_path(s0, mu, sigma, dt, z):
    log_moves = (mu - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * z
    return s0 * np.exp(np.concatenate([[0.0], np.cumsum(log_moves)]))

rng = np.random.default_rng(1)
z = rng.standard_normal(252)
path = gbm_path(100.0, 0.08, 0.2, 1 / 252, z)
print(path[:4].round(3), round(float(path[-1]), 3))

Muchas trayectorias: la media y la mediana

Simula miles de trayectorias de un año y mira dónde terminan. El precio final *medio* se acerca a S0 * exp(mu * T), la fórmula del precio esperado. La *mediana* es menor, S0 * exp((mu - sigma**2/2) * T), y con alta volatilidad la diferencia es grande: la mayoría de las trayectorias acaba por debajo de la media, impulsada por unas pocas espectaculares. Esta asimetría a la derecha, una distribución lognormal, es la forma de los precios de las acciones en horizontes largos.

import numpy as np

rng = np.random.default_rng(5)
s0, mu, sigma, T, steps, paths = 100.0, 0.08, 0.4, 1.0, 252, 20000
dt = T / steps
z = rng.standard_normal((paths, steps))
final = s0 * np.exp(np.sum((mu - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * z, axis=1))

print(round(float(final.mean()), 1), round(s0 * np.exp(mu * T), 1))                      # close to each other: the expected price
print(round(float(np.median(final)), 1), round(s0 * np.exp((mu - 0.5 * sigma ** 2) * T), 1))   # the median is lower
print(round(float(np.mean(final < s0)), 3))                                              # the share of paths that lose money

Estimar mu y sigma

A partir de precios muestreados cada dt años, los rendimientos logarítmicos r = diff(log(prices)) son normales independientes de media (mu - sigma**2/2) * dt y desviación típica sigma * sqrt(dt). Así sigma = std(r) / sqrt(dt) y mu = mean(r) / dt + sigma**2 / 2. La estimación de la volatilidad es buena con unos cientos de puntos; la deriva es notoriamente ruidosa, porque su error estándar solo disminuye con el *tiempo* total, no con el número de observaciones.

import numpy as np

def estimate_gbm(prices, dt):
    r = np.diff(np.log(prices))
    sigma = r.std(ddof=1) / np.sqrt(dt)
    return r.mean() / dt + 0.5 * sigma ** 2, sigma

rng = np.random.default_rng(8)
dt = 1 / 252
for years in (1, 10, 100):
    n = int(years * 252)
    log_moves = (0.10 - 0.5 * 0.25 ** 2) * dt + 0.25 * np.sqrt(dt) * rng.standard_normal(n)
    prices = 100 * np.exp(np.concatenate([[0.0], np.cumsum(log_moves)]))
    mu, sigma = estimate_gbm(prices, dt)
    print(years, "years:", round(float(mu), 3), round(float(sigma), 3))           # true mu 0.10 and sigma 0.25
El MBG es un buen comienzo, no la verdadLos precios reales tienen colas gruesas, saltos y una volatilidad que se mueve (tema GARCH), nada de lo cual contiene el MBG. Su virtud es que todo es explícito: los precios son lognormales, el precio esperado tiene fórmula y de ahí salen los precios de opciones. Los modelos que corrigen sus fallos (saltos, volatilidad estocástica) se construyen sobre él.
  • MBG: S(t) = S(0) * exp((mu - sigma**2 / 2) * t + sigma * W(t)); los cambios porcentuales son aleatorios y los precios siguen siendo positivos.
  • El - sigma**2 / 2 anula la convexidad de exp: el precio esperado es S0 * exp(mu * t), pero la trayectoria mediana crece a mu - sigma**2 / 2.
  • Estime con los rendimientos log: sigma = std(r) / sqrt(dt), mu = mean(r) / dt + sigma**2 / 2; la volatilidad se estima bien, la deriva mal.

A continuación: el lema de Itô, la regla de la cadena que explica de dónde sale el - sigma**2 / 2.

Lab: qué se califica

  • Dar un paso exacto con gbm_step25 pts
  • Simular una trayectoria de precios con gbm_path25 pts
  • Promediar todos los futuros con gbm_expected25 pts
  • Recuperar los parámetros con estimate_gbm25 pts
Explorador · lab-103-1
▾ lab-103-1
…
bash · en el navegador
learner@quantcraft:~/lab-103-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-103-1 vuelve a los archivos iniciales y se limpia el terminal. Tu solución actual se perderá: no se puede deshacer.

Se restablecerá
  • main.py → código inicial
  • Historial del terminal y salida de pruebas
  • Cambios sin enviar
Se conservará
  • Mejor puntuación del lab
  • Tu XP y tu racha
  • Progreso de la lección y notas

Inicia sesión para ejecutar tu código.

Todas las clases se pueden leer gratis. Crea una cuenta o inicia sesión para ejecutar código, lanzar las pruebas y enviar este lab.

  • IDE en el navegador con terminal bash — sin instalar nada
  • Corrección instantánea, XP y tu lugar en la ruta
Crear una cuentaIniciar sesión
O
Continuar con GitHub