σμ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 3 · Probabilidad y simulación Monte Carlo›Tema 3.3·~45 minIntermedio

Colas gruesas y pruebas de normalidad

Posición en la rutaMódulo 3 de 11 · Tema 3 de 12
3.3 Colas gruesas y pruebas de normalidad
—Puntuación del lab— / 100XP total—Racha—

Lo que promete la campana

Muchos modelos de riesgo suponen que los rendimientos siguen una distribución normal, porque permite calcularlo todo con una media y una desviación típica. La normal es muy precisa sobre los extremos. La probabilidad de estar a más de k desviaciones típicas de la media, en cualquier dirección, es erfc(k / sqrt(2)): 31,7 % para 1, 4,55 % para 2, 0,27 % para 3, 0,0063 % para 4. Un día de 4 sigmas ocurriría entonces una vez cada 15 800 días bursátiles, más o menos una vez en 63 años, y un día de 5 sigmas una vez en varios miles de años.

import math
import numpy as np

for k in (1, 2, 3, 4, 5):
    probability = math.erfc(k / math.sqrt(2))      # P(|Z| > k) for a normal variable
    print(k, f"{probability:.2e}")
# 1 3.17e-01
# 2 4.55e-02
# 3 2.70e-03
# 4 6.33e-05
# 5 5.73e-07

print(round(1 / math.erfc(4 / math.sqrt(2))))                  # 15787   trading days between 4-sigma days, if returns were normal
print(round(1 / math.erfc(5 / math.sqrt(2)) / 252))            # 6922    years between 5-sigma days

Los mercados tienen colas gruesas

Los mercados reales producen días de 4 y 5 sigmas muchas veces a lo largo de una carrera, en 1987, 2008, 2020. Sus distribuciones de rendimientos tienen colas gruesas: más días extremos de los que permite la campana (y un centro más alto y estrecho). Una forma sencilla de verlo es el ratio de cola: la proporción observada de rendimientos más allá de k desviaciones típicas dividida por la que daría una normal. Un ratio cercano a 1 significa colas normales; 5, extremos cinco veces demasiado frecuentes; con datos diarios y k = 4 puede llegar a cientos. Los modelos de riesgo que suponen normalidad subestiman justo los días que más importan.

returns = np.array([0.010, 0.012, 0.008, 0.011, 0.009, 0.010, 0.012, -0.060, 0.010, 0.009])

def tail_ratio(x, k):
    observed = np.mean(np.abs(x - x.mean()) > k * x.std(ddof=1))     # share beyond k standard deviations
    return observed / math.erfc(k / math.sqrt(2))                    # divided by what a normal would give

print(round(float(tail_ratio(returns, 2)), 2))    # 2.2   the one crash is 2.2 times too frequent at 2 sigma (a tiny sample, though)

El test de Jarque-Bera

Una prueba formal pregunta si la muestra podría provenir plausiblemente de una distribución normal. El estadístico de Jarque-Bera combina las dos medidas de forma del tema anterior: JB = n / 6 * (S² + K² / 4), con S la asimetría y K la curtosis en exceso. En una muestra normal ambos están cerca de 0 y JB es pequeño. Bajo normalidad, JB sigue una distribución chi-cuadrado con 2 grados de libertad, cuya cola tiene la forma cerrada exp(-JB / 2): es el valor p. Si es menor que 0,05 se rechaza la normalidad. El valor crítico es JB = 5,99. Nuestra muestra de 10 días con un desplome tiene JB = 22,4 y un valor p de 0,00001.

x = returns
n = len(x)
deviations = x - x.mean()
m2, m3, m4 = (np.mean(deviations ** k) for k in (2, 3, 4))
skew, kurt = m3 / m2 ** 1.5, m4 / m2 ** 2 - 3

jb = n / 6 * (skew ** 2 + kurt ** 2 / 4)
p_value = math.exp(-jb / 2)
print(round(float(jb), 2), p_value < 0.05)     # 22.37 True   normality rejected
print(f"{p_value:.1e}")                         # 1.4e-05
# The p-value falls fast as the statistic grows: 5.99 is the 5% critical value
for jb in (0, 2, 5.99, 10, 22.37):
    print(jb, round(math.exp(-jb / 2), 5))
# 0 1.0
# 2 0.36788
# 5.99 0.05004
# 10 0.00674
# 22.37 1e-05
Rechazar la normalidad no es el finalCon cientos de rendimientos diarios, la prueba rechaza la normalidad para casi todos los activos, así que un rechazo es lo esperado. Lo que importa es cuánto difieren las colas: usa el ratio de cola para medir el problema y luego haz pruebas de estrés con desplomes reales, una distribución de colas más pesadas como la t de Student, o simulación histórica, en vez de fiarte de una campana.
  • Una variable normal está a más de k desviaciones con probabilidad erfc(k / sqrt(2)): 4,55 % para 2, 0,0063 % para 4.
  • El ratio de cola, proporción observada dividida por la normal, muestra las colas gruesas: los mercados están muy por encima de 1 a 4 sigmas.
  • Jarque-Bera: n / 6 * (S**2 + K**2 / 4); el valor p es exp(-JB / 2); por debajo de 0,05 se rechaza la normalidad (JB sobre 5,99).

A continuación: las pruebas de hipótesis: la prueba t, para decidir si un rendimiento medio es realmente distinto de cero.

Lab: qué se califica

  • Medir cuánto se aleja una muestra de la normal con jarque_bera25 pts
  • Convertir el estadístico en un valor p con normality_p_value25 pts
  • Calcular lo que promete la campana con normal_tail_probability25 pts
  • Contar cuánto más gruesas son las colas con tail_ratio25 pts
Explorador · lab-56-1
▾ lab-56-1
…
bash · en el navegador
learner@quantcraft:~/lab-56-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-56-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