σμ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 4 · Valoración de renta fija y opciones›Tema 4.15·~55 minIntermedio

Árboles binomiales

Posición en la rutaMódulo 4 de 11 · Tema 15 de 16
4.15 Árboles binomiales
—Puntuación del lab— / 100XP total—Racha—

Una acción que solo puede subir o bajar

Black–Scholes es una fórmula; un árbol binomial es un procedimiento, y puede valorar lo que la fórmula no puede. Se divide el tiempo hasta el vencimiento en pasos pequeños. En cada paso la acción sube un factor u o baja un factor d, nada más. Tras n pasos hay n + 1 precios posibles. Los pagos se conocen al vencimiento y cada nodo anterior vale el promedio descontado de los dos nodos a los que lleva, así que el árbol se rellena hacia atrás hasta el presente.

# One step: a stock at 100 goes to 110 or 90. What is a call with strike 100 worth?
import math

spot, strike, rate, dt = 100, 100, 0.05, 1.0
u, d = 1.10, 0.90
p = (math.exp(rate * dt) - d) / (u - d)             # the probability that makes the stock grow at the rate
up_value, down_value = max(spot * u - strike, 0), max(spot * d - strike, 0)
price = math.exp(-rate * dt) * (p * up_value + (1 - p) * down_value)
print(round(p, 4), round(up_value, 4), down_value)   # 0.7564 10.0 0
print(round(price, 4))                               # 7.1947

La probabilidad no es un pronóstico

El número p se elige para que la acción crezca en promedio al tipo libre de riesgo: p * u + (1 - p) * d = exp(rate * dt), de donde p = (exp(rate * dt) - d) / (u - d). Es la probabilidad neutral al riesgo: un artificio que hace que una cartera replicante valga su esperanza descontada, no una creencia sobre adónde irá la acción. Para reproducir la volatilidad observada, el árbol de Cox–Ross–Rubinstein (CRR) usa u = exp(sigma * sqrt(dt)) y d = 1 / u, así una subida seguida de una bajada vuelve al mismo precio y el árbol se recombina: n pasos solo dan n + 1 nodos finales.

# The CRR parameters and the prices at expiry of a 3-step tree
import math

def crr_parameters(rate, sigma, t, steps):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    return u, d, p

def terminal_prices(spot, u, d, steps):
    return [spot * u ** j * d ** (steps - j) for j in range(steps + 1)]

u, d, p = crr_parameters(0.05, 0.2, 1, 3)
print(round(u, 4), round(d, 4), round(p, 4))                     # 1.1224 0.8909 0.5438
print([round(price, 2) for price in terminal_prices(100, u, d, 3)])

Trabajar hacia atrás

Al vencimiento cada nodo es solo el pago, max(S - K, 0) para un call. Una columna antes, cada valor es exp(-rate * dt) * (p * value_up + (1 - p) * value_down), y la columna pierde un nodo. Tras n barridos queda un solo valor, el precio de hoy. En Python una columna es una lista, y un barrido es una comprensión de lista sobre pares vecinos. Al crecer el número de pasos, el precio converge al de Black–Scholes y el árbol reproduce lo que dice la fórmula.

# The tree price of a European call approaches the Black-Scholes price as steps grow
import math

def norm_cdf(x):
    return 0.5 * (1 + math.erf(x / math.sqrt(2)))

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

def european_call_tree(spot, strike, rate, sigma, t, steps):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    disc = math.exp(-rate * dt)
    values = [max(spot * u ** j * d ** (steps - j) - strike, 0) for j in range(steps + 1)]
    for _ in range(steps):
        values = [disc * (p * values[j + 1] + (1 - p) * values[j]) for j in range(len(values) - 1)]
    return values[0]

print("Black-Scholes", round(bs_call(100, 100, 0.05, 0.2, 1), 4))
for steps in (1, 5, 25, 100, 400):
    print(steps, round(european_call_tree(100, 100, 0.05, 0.2, 1, steps), 4))

El ejercicio anticipado

La verdadera fuerza del árbol es la opción que la fórmula no sabe valorar: una opción americana puede ejercerse en cualquier momento. En cada nodo el titular compara dos valores, la esperanza descontada de mantener y el pago de ejercer ahora, y toma el mayor. Ese único max adicional en cada nodo es todo el cambio del algoritmo. En un call sobre una acción sin dividendos el ejercicio anticipado nunca compensa, pero en un put sí puede: un put muy en el dinero vale más en efectivo hoy, de modo que el put americano cuesta más que el europeo.

# An American put against a European put: the early-exercise premium
import math

def tree(spot, strike, rate, sigma, t, steps, american):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    disc = math.exp(-rate * dt)
    values = [max(strike - spot * u ** j * d ** (steps - j), 0) for j in range(steps + 1)]
    for i in range(steps - 1, -1, -1):
        values = [disc * (p * values[j + 1] + (1 - p) * values[j]) for j in range(i + 1)]
        if american:
            values = [max(values[j], strike - spot * u ** j * d ** (i - j)) for j in range(i + 1)]
    return values[0]

for spot in (80, 100, 120):
    european = tree(spot, 100, 0.05, 0.2, 1, 200, False)
    american = tree(spot, 100, 0.05, 0.2, 1, 200, True)
    print(spot, round(european, 4), round(american, 4), "premium", round(american - european, 4))
Lento y general supera a rápido y especialPara opciones europeas simples la fórmula cerrada es instantánea y exacta, así que nunca se usaría un árbol. El árbol se justifica cuando el pago incluye una decisión (ejercicio anticipado), una barrera o un dividendo discreto: se añade la regla en cada nodo. El precio es el tiempo: n pasos cuestan unas n**2 / 2 actualizaciones de nodos, y la convergencia no es suave (el error oscila según el strike caiga entre nodos), por lo que los árboles se comprueban comparando varios números de pasos.
  • En cada paso la acción sube u o baja d; la elección CRR u = exp(sigma * sqrt(dt)), d = 1 / u recombina el árbol, así n pasos tienen n + 1 nodos finales.
  • Valorar hacia atrás: pagos al vencimiento, luego exp(-rate * dt) * (p * up + (1 - p) * down) por nodo; p es la probabilidad neutral al riesgo, no un pronóstico.
  • Muchos pasos convergen a Black–Scholes; una opción americana añade un max(continuación, ejercicio) por nodo, y para eso existen los árboles.

A continuación: los métodos de diferencias finitas, valorar resolviendo en una malla la ecuación en derivadas parciales de la opción.

Lab: qué se califica

  • Fijar los movimientos al alza y a la baja con crr_parameters25 pts
  • Listar los precios al vencimiento con terminal_prices25 pts
  • Recorrer el árbol hacia atrás con european_call_tree25 pts
  • Permitir el ejercicio anticipado con american_put_tree25 pts
Explorador · lab-80-1
▾ lab-80-1
…
bash · en el navegador
learner@quantcraft:~/lab-80-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

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