Saltar al contenido principal
Módulo 4 · Fundamentos

Casos prácticos

Valoración de Opciones

Módulo 4: Casos Prácticos Resueltos

Valoración de Opciones — Laboratorio en Python


📋 Introducción

Estos ejercicios convierten la teoría de Black-Scholes en código funcional. Implementarás la fórmula desde cero, calcularás las griegas, extraerás volatilidad implícita y visualizarás la sonrisa de volatilidad. Ejecuta cada bloque y experimenta cambiando los parámetros.

Requisitos Previos

pip install numpy scipy matplotlib yfinance

🧪 Caso Práctico 1: Implementar Black-Scholes desde Cero

Objetivo

Programar la fórmula de Black-Scholes para Calls y Puts europeas.

Código

import numpy as np
from scipy.stats import norm

def black_scholes(S, K, T, r, sigma, tipo="call"):
    """
    Precio de una opción europea según Black-Scholes.
    S: precio del activo
    K: strike
    T: tiempo al vencimiento (en años)
    r: tasa libre de riesgo
    sigma: volatilidad
    tipo: 'call' o 'put'
    """
    d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
    d2 = d1 - sigma*np.sqrt(T)

    if tipo == "call":
        precio = S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)
    elif tipo == "put":
        precio = K*np.exp(-r*T)*norm.cdf(-d2) - S*norm.cdf(-d1)
    else:
        raise ValueError("tipo debe ser 'call' o 'put'")
    return precio

# Ejemplo: opción sobre activo a 100€
S, K, T, r, sigma = 100, 100, 1.0, 0.05, 0.20

call = black_scholes(S, K, T, r, sigma, "call")
put = black_scholes(S, K, T, r, sigma, "put")

print(f"Parámetros: S={S}, K={K}, T={T}año, r={r:.0%}, σ={sigma:.0%}")
print(f"Precio Call: {call:.4f}€")
print(f"Precio Put:  {put:.4f}€")

# Verificar la paridad Put-Call: C - P = S - K·e^(-rT)
paridad_izq = call - put
paridad_der = S - K*np.exp(-r*T)
print(f"\nParidad Put-Call: {paridad_izq:.4f} = {paridad_der:.4f} ✓")

Interpretación

  • Para una opción “at the money” (S=K), la Call vale más que la Put porque el activo tiende a crecer a la tasa libre de riesgo
  • La paridad Put-Call (C − P = S − K·e^(−rT)) es una relación de no-arbitraje que SIEMPRE se cumple. Si tu código la respeta, está bien implementado
  • Experimenta: sube la volatilidad a 0.40 y observa cómo ambas opciones se encarecen (efecto vega)

🧪 Caso Práctico 2: Calcular las Cinco Griegas

Objetivo

Implementar las griegas y entender numéricamente qué mide cada una.

Código

import numpy as np
from scipy.stats import norm

def griegas(S, K, T, r, sigma, tipo="call"):
    d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
    d2 = d1 - sigma*np.sqrt(T)

    # Delta
    if tipo == "call":
        delta = norm.cdf(d1)
    else:
        delta = norm.cdf(d1) - 1

    # Gamma (igual para call y put)
    gamma = norm.pdf(d1) / (S*sigma*np.sqrt(T))

    # Vega (por cada 1% de cambio en volatilidad → /100)
    vega = S*norm.pdf(d1)*np.sqrt(T) / 100

    # Theta (por día → /365)
    if tipo == "call":
        theta = (-S*norm.pdf(d1)*sigma/(2*np.sqrt(T))
                 - r*K*np.exp(-r*T)*norm.cdf(d2)) / 365
    else:
        theta = (-S*norm.pdf(d1)*sigma/(2*np.sqrt(T))
                 + r*K*np.exp(-r*T)*norm.cdf(-d2)) / 365

    # Rho (por cada 1% → /100)
    if tipo == "call":
        rho = K*T*np.exp(-r*T)*norm.cdf(d2) / 100
    else:
        rho = -K*T*np.exp(-r*T)*norm.cdf(-d2) / 100

    return {"delta": delta, "gamma": gamma, "vega": vega,
            "theta": theta, "rho": rho}

# Calcular para una Call at-the-money
S, K, T, r, sigma = 100, 100, 1.0, 0.05, 0.20
g = griegas(S, K, T, r, sigma, "call")

print("=== GRIEGAS DE UNA CALL (S=K=100) ===")
print(f"Delta: {g['delta']:.4f}  (cambio por 1€ del activo)")
print(f"Gamma: {g['gamma']:.4f}  (cambio del delta por 1€)")
print(f"Vega:  {g['vega']:.4f}  (cambio por 1% de volatilidad)")
print(f"Theta: {g['theta']:.4f}  (pérdida por día)")
print(f"Rho:   {g['rho']:.4f}  (cambio por 1% de tipos)")

Interpretación

  • Delta ~0.6: una Call at-the-money con esta configuración tiene delta algo superior a 0.5
  • Theta negativo: confirma el decaimiento temporal (la opción pierde valor cada día)
  • Vega positivo: más volatilidad encarece la opción
  • Comprobación: verifica que el delta de una Put = delta de la Call − 1

🧪 Caso Práctico 3: Visualizar Cómo Cambian Precio y Delta

Objetivo

Graficar el precio de una opción y su delta en función del precio del activo.

Código

import numpy as np
import matplotlib.pyplot as plt

K, T, r, sigma = 100, 0.5, 0.05, 0.25
precios_activo = np.linspace(60, 140, 200)

precios_call = [black_scholes(S, K, T, r, sigma, "call") for S in precios_activo]
deltas = [griegas(S, K, T, r, sigma, "call")["delta"] for S in precios_activo]
payoff = [max(S - K, 0) for S in precios_activo]  # valor al vencimiento

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Precio de la opción vs payoff al vencimiento
axes[0].plot(precios_activo, precios_call, label="Precio Call (hoy)", linewidth=2)
axes[0].plot(precios_activo, payoff, "--", label="Payoff (vencimiento)", color="gray")
axes[0].axvline(K, color="red", linestyle=":", alpha=0.5, label="Strike")
axes[0].set_title("Precio de la Call vs. Payoff al vencimiento")
axes[0].set_xlabel("Precio del activo")
axes[0].set_ylabel("Valor de la opción")
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# Delta vs precio del activo
axes[1].plot(precios_activo, deltas, color="darkgreen", linewidth=2)
axes[1].axvline(K, color="red", linestyle=":", alpha=0.5, label="Strike")
axes[1].set_title("Delta de la Call vs. precio del activo")
axes[1].set_xlabel("Precio del activo")
axes[1].set_ylabel("Delta")
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

Interpretación

Gráfico izquierdo:

  • La curva del precio (hoy) está siempre por encima del payoff (vencimiento): la diferencia es el “valor temporal”
  • Cerca del vencimiento, el precio convergería hacia la línea de payoff

Gráfico derecho:

  • El delta va de ~0 (muy out of the money) a ~1 (muy in the money)
  • La transición es suave alrededor del strike: ahí el gamma es máximo (el delta cambia rápido)
  • Esta forma de “S” es por qué el delta se interpreta como probabilidad de ejercicio

🧪 Caso Práctico 4: El Efecto del Tiempo y la Volatilidad

Objetivo

Visualizar cómo el valor temporal se erosiona (theta) y cómo la volatilidad encarece las opciones (vega).

Código

import numpy as np
import matplotlib.pyplot as plt

S, K, r = 100, 100, 0.05

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Efecto del TIEMPO: precio según días al vencimiento
dias = np.linspace(0.01, 1.0, 100)
sigma = 0.20
precios_tiempo = [black_scholes(S, K, T, r, sigma, "call") for T in dias]
axes[0].plot(dias*365, precios_tiempo, color="purple", linewidth=2)
axes[0].set_title("Efecto del tiempo (Theta): valor de la Call")
axes[0].set_xlabel("Días al vencimiento")
axes[0].set_ylabel("Precio de la Call")
axes[0].grid(True, alpha=0.3)
axes[0].annotate("El valor cae más rápido\ncerca del vencimiento",
                 xy=(20, precios_tiempo[5]), xytext=(120, 3),
                 arrowprops=dict(arrowstyle="->", color="red"))

# Efecto de la VOLATILIDAD: precio según sigma
T = 0.5
vols = np.linspace(0.05, 0.60, 100)
precios_vol = [black_scholes(S, K, T, r, s, "call") for s in vols]
axes[1].plot(vols*100, precios_vol, color="darkorange", linewidth=2)
axes[1].set_title("Efecto de la volatilidad (Vega): valor de la Call")
axes[1].set_xlabel("Volatilidad (%)")
axes[1].set_ylabel("Precio de la Call")
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

Interpretación

Gráfico izquierdo (Theta):

  • El valor de la opción cae a medida que se acerca el vencimiento
  • La caída se acelera cerca de cero días: por eso theta no es lineal y los traders vigilan las últimas semanas

Gráfico derecho (Vega):

  • El precio sube de forma casi lineal con la volatilidad
  • A mayor volatilidad, mayor probabilidad de movimientos grandes → más valor en el derecho asimétrico
  • Esto es por qué “valorar opciones es pronosticar volatilidad”

🧪 Caso Práctico 5: Calcular la Volatilidad Implícita

Objetivo

Dado el precio de mercado de una opción, “invertir” Black-Scholes para hallar la volatilidad implícita.

Código

import numpy as np
from scipy.optimize import brentq

def volatilidad_implicita(precio_mercado, S, K, T, r, tipo="call"):
    """
    Encuentra la sigma que iguala el precio Black-Scholes al precio de mercado.
    Usa búsqueda de raíces (Brent).
    """
    def objetivo(sigma):
        return black_scholes(S, K, T, r, sigma, tipo) - precio_mercado

    try:
        # Buscar sigma entre 1% y 500%
        iv = brentq(objetivo, 0.01, 5.0)
        return iv
    except ValueError:
        return np.nan

# Ejemplo: una Call cotiza a 12€ en el mercado
S, K, T, r = 100, 100, 1.0, 0.05
precio_mercado = 12.0

iv = volatilidad_implicita(precio_mercado, S, K, T, r, "call")
print(f"Precio de mercado de la Call: {precio_mercado}€")
print(f"Volatilidad implícita: {iv:.2%}")

# Verificar: meter esa IV en Black-Scholes debe dar el precio de mercado
verificacion = black_scholes(S, K, T, r, iv, "call")
print(f"Verificación (BS con esa IV): {verificacion:.4f}€  ✓")

Interpretación

  • La volatilidad implícita es la σ que hace que Black-Scholes “coincida” con el precio de mercado
  • Como no se puede despejar analíticamente, se usa un método numérico (búsqueda de raíces)
  • Es la opinión del mercado sobre la volatilidad futura. Si la IV es alta, las opciones están caras (el mercado espera turbulencia)
  • Experimenta: sube el precio de mercado a 18€ y verás cómo la IV aumenta

🧪 Caso Práctico 6: Construir la Sonrisa de Volatilidad

Objetivo

Extraer la volatilidad implícita de opciones reales con distintos strikes y visualizar la sonrisa.

Código

import yfinance as yf
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime

# Descargar la cadena de opciones de un activo real
ticker = yf.Ticker("SPY")
fechas = ticker.options  # fechas de vencimiento disponibles
fecha_venc = fechas[3]    # elige un vencimiento intermedio

cadena = ticker.option_chain(fecha_venc)
calls = cadena.calls

# Precio actual del subyacente
S = ticker.history(period="1d")["Close"].iloc[-1]
r = 0.05

# Tiempo al vencimiento en años
hoy = datetime.now()
venc = datetime.strptime(fecha_venc, "%Y-%m-%d")
T = (venc - hoy).days / 365
print(f"Subyacente SPY: {S:.2f}, vencimiento: {fecha_venc} (T={T:.3f} años)")

# yfinance ya trae 'impliedVolatility', pero la recalculamos para aprender
strikes, ivs = [], []
for _, fila in calls.iterrows():
    K = fila["strike"]
    precio_op = (fila["bid"] + fila["ask"]) / 2  # precio medio
    # Filtrar opciones con precios razonables y cercanas al dinero
    if precio_op > 0.5 and 0.8*S < K < 1.2*S:
        iv = volatilidad_implicita(precio_op, S, K, T, r, "call")
        if not np.isnan(iv) and 0.01 < iv < 2:
            strikes.append(K)
            ivs.append(iv)

# Visualizar la sonrisa
plt.figure(figsize=(12, 6))
plt.plot(strikes, np.array(ivs)*100, "o-", color="crimson")
plt.axvline(S, color="blue", linestyle="--", alpha=0.6, label=f"Precio actual ({S:.0f})")
plt.title(f"Sonrisa de volatilidad - SPY Calls ({fecha_venc})")
plt.xlabel("Strike")
plt.ylabel("Volatilidad implícita (%)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Interpretación

Qué observas:

  • La volatilidad implícita NO es plana entre strikes → forma una curva (sonrisa o skew)
  • En índices como el SPY, suele verse un “skew”: las opciones de strike bajo (Puts de protección) tienen IV más alta
  • Esto demuestra empíricamente que el mercado no cree en la hipótesis de volatilidad constante de Black-Scholes

Conexión con el Módulo 2: la sonrisa es la huella de las colas pesadas y la skewness negativa de los retornos. El mercado cobra más por la protección contra cracks porque sabe que ocurren más de lo que predice la normal.

Nota: los datos de opciones de yfinance pueden ser limitados o tener retrasos. Si la cadena viene vacía, prueba con otro ticker líquido como AAPL o con otra fecha de vencimiento.


🎓 Proyecto del Módulo

Enunciado

Construye un análisis de valoración de opciones completo:

  1. Implementa Black-Scholes y las cinco griegas (puedes reutilizar el código)
  2. Elige un activo y un strike; valora la Call y la Put
  3. Verifica la paridad Put-Call
  4. Calcula y muestra las cinco griegas
  5. Extrae la volatilidad implícita de una opción real de ese activo
  6. Escribe un párrafo: ¿la volatilidad implícita es mayor o menor que la histórica? ¿Qué implica?

Solución de Referencia (Plantilla)

import yfinance as yf
import numpy as np

# (Reutiliza black_scholes, griegas y volatilidad_implicita de arriba)

mi_ticker = "AAPL"  # cámbialo
tk = yf.Ticker(mi_ticker)
S = tk.history(period="1d")["Close"].iloc[-1]

# Volatilidad histórica (1 año)
hist = tk.history(period="1y")
ret = np.log(hist["Close"]/hist["Close"].shift(1)).dropna()
vol_hist = ret.std()*np.sqrt(252)

# Parámetros
K = round(S)      # strike at-the-money
T = 0.25          # 3 meses
r = 0.05

print(f"{mi_ticker}: precio={S:.2f}, vol histórica={vol_hist:.2%}")

# Valorar con la volatilidad histórica como estimación
call = black_scholes(S, K, T, r, vol_hist, "call")
put = black_scholes(S, K, T, r, vol_hist, "put")
print(f"Call (vol hist): {call:.2f}€, Put: {put:.2f}€")

# Griegas
g = griegas(S, K, T, r, vol_hist, "call")
print(f"Griegas Call: {dict((k, round(v,4)) for k,v in g.items())}")

# Tu análisis aquí: compara con la IV real de yfinance

Criterios de Evaluación

  • Black-Scholes correcto: la paridad Put-Call se cumple (25%)
  • Griegas correctas: valores y signos coherentes (25%)
  • Volatilidad implícita: bien extraída (25%)
  • Análisis: comparación razonada implícita vs. histórica (25%)

📝 Resumen del Módulo Práctico

Has aprendido a:

✓ Implementar Black-Scholes para Calls y Puts desde cero ✓ Verificar la paridad Put-Call como control de calidad ✓ Calcular las cinco griegas y entender qué mide cada una ✓ Visualizar el efecto del precio, el tiempo y la volatilidad ✓ Extraer la volatilidad implícita invirtiendo la fórmula ✓ Construir la sonrisa de volatilidad con datos reales

“Has programado el modelo que ganó un Nobel y cambió las finanzas. Pero recuerda: el modelo no captura los cracks, los saltos ni el pánico. La sonrisa de volatilidad es el mercado recordándote, cada día, que ningún modelo es perfecto.”

En el Módulo 5 nos centraremos en medir el riesgo y el rendimiento de carteras y estrategias: Sharpe, Sortino, VaR, CVaR y drawdown — las métricas con las que se juzga todo en finanzas cuantitativas.


Fin de los Casos Prácticos del Módulo 4