Lección 3.5.3 · 75 min
Calendarios, diagonales, ratios, backspreads, risk reversals y jade lizard
Con dos vencimientos, el P&L de una estructura ya no es una línea quebrada: depende de la volatilidad que quede en la opción larga. Con cantidades distintas en cada pata, aparece un lado con riesgo sin límite. Y con el skew, una misma figura se abre con débito por las calls y con crédito por las puts. La lección construye estas estructuras con la cadena del índice ORX y sigue cada una durante 142 meses.
- Módulo
- 3.5 · Estructuras de opciones en Python
- Duración
- 75 min
- Requisitos
- 3.4.3 · Griegas: delta, gamma, vega, theta, rho, vanna, volga y charm, 3.4.5 · Sonrisa, skew y estructura temporal; superficie de volatilidad con SVI, 3.5.1 · Estructuras direccionales y verticales: call, put, covered call, collar y spreads, 3.5.2 · Estructuras de volatilidad y rango: straddles, strangles, cóndores y mariposas
Lecciones del nivel 3
3.1 · Futuros en profundidad
3.2 · Spreads de futuros
- 3.2.1 Spreads de calendario, butterflies y condors
- 3.2.2 Spreads intercommodity: crack, spark y crush
- 3.2.3 Spreads de tipos y de índices: 2s10s, NOB, NQ/ES y Europa frente a EE. UU.
- 3.2.4 Ratios de cobertura: por nominal, por DV01 y por volatilidad
- 3.2.5 Datos y backtesting de spreads: cotizados, sintéticos y por patas
- 3.2.6 Estacionalidad de spreads: análisis multi-año, patrones y trampas
3.3 · Forex avanzado
3.4 · Teoría y valoración de opciones
- 3.4.1 Payoffs, paridad put-call y límites de arbitraje
- 3.4.2 Árboles binomiales, Black-Scholes-Merton, Black-76 y Monte Carlo
- 3.4.3 Griegas: delta, gamma, vega, theta, rho, vanna, volga y charm
- 3.4.4 Volatilidad implícita: Newton-Raphson, Brent, py_vollib y QuantLib
- 3.4.5 Sonrisa, skew y estructura temporal; superficie de volatilidad con SVI
- 3.4.6 Volatilidad realizada frente a implícita; prima de riesgo de volatilidad; VIX y VSTOXX
- 3.4.7 Modelos avanzados: Heston, SABR y volatilidad local
3.5 · Estructuras de opciones en Python
- 3.5.1 Estructuras direccionales y verticales: call, put, covered call, collar y spreads
- 3.5.2 Estructuras de volatilidad y rango: straddles, strangles, cóndores y mariposas
- 3.5.3 Calendarios, diagonales, ratios, backspreads, risk reversals y jade lizard
- 3.5.4 Herramienta propia: payoff, griegas agregadas y escenarios
- 3.5.5 Gestión: ajustes, roll, cierre anticipado, asignación temprana y pin risk
3.6 · Estrategias sistemáticas de volatilidad y opciones
3.7 · Cripto derivados y estrategias específicas
Dos vencimientos, cantidades distintas y el skew
Las lecciones 3.5.1 y 3.5.2 combinaron opciones del mismo vencimiento con cantidades de una en una. Esta lección rompe las dos reglas y usa una tercera cosa que hasta ahora solo aparecía de pasada: el skew.
- Estructuras temporales. Un calendario vende una opción de vencimiento corto y compra la del mismo strike y vencimiento largo. Una diagonal hace lo mismo con strikes distintos. Un doble calendario junta un calendario de puts por debajo del índice y uno de calls por encima. Ganan con el paso del tiempo de la pata corta, y su resultado depende de la volatilidad que le quede a la larga.
- Ratios y backspreads. Un ratio 1×2 compra una opción y vende dos más alejadas; un backspread hace lo contrario. Con más opciones de un lado que del otro, uno de los extremos no tiene límite.
- Estructuras del skew. Un risk reversal vende una put fuera del dinero y compra una call fuera del dinero (o al revés). Un jade lizard vende una put y un call spread, con el crédito suficiente para no tener riesgo al alza.
Los datos son los de las lecciones anteriores: la cadena sintética del índice ORX del 31 de diciembre de 2025, ahora con dos vencimientos, el 16 de enero (16 días) y el 20 de febrero de 2026 (51 días); precios por unidad (× 100 USD por contrato), T en días naturales / 365, r y q continuos de cada vencimiento, theta por día natural y vega por punto de volatilidad. La primera celda repite las funciones de la 3.5.1. Los datos son sintéticos, con superficie, skew y prima de volatilidad plantados: enseñan la mecánica, no lo que rinde una estructura en el mercado real.
import numpy as np
import pandas as pd
from scipy.stats import norm
cadena = pd.read_parquet("opciones3_cadena.parquet")
indice = pd.read_csv("opciones3_indice.csv", parse_dates=["fecha"],
index_col="fecha")
verdad = pd.read_parquet("opciones3_verdad.parquet")
HOY = pd.Timestamp("2025-12-31")
ENE = pd.Timestamp("2026-01-16")
VTO = FEB = pd.Timestamp("2026-02-20")
S0 = indice.loc[HOY, "cierre"]
def mercado(vto, fecha=HOY):
"""Tipo r y dividendo q continuos (tanto por uno) de un
vencimiento: r de la curva; q implícito en el forward."""
v = verdad[(verdad.fecha == fecha)
& (verdad.vencimiento == vto)].iloc[0]
T = v.dias / 365 # días naturales / 365
S = indice.loc[fecha, "cierre"]
return v.tipo_libre, v.tipo_libre - np.log(v.forward / S) / T
def bsm(S, K, T, sig, tipo, r, q):
"""Precio (puntos), delta, gamma (por punto), vega (por
punto de volatilidad) y theta (por día natural).
S puede ser un array; con T = 0 devuelve el payoff."""
S = np.asarray(S, dtype=float)
if T <= 0:
pago = np.maximum(S - K, 0) if tipo == "C" else \
np.maximum(K - S, 0)
signo = 1.0 if tipo == "C" else -1.0
delta = np.where(signo * (S - K) > 0, signo, 0.0)
cero = np.zeros_like(S)
return dict(precio=pago, delta=delta, gamma=cero,
vega=cero, theta=cero)
raiz = sig * np.sqrt(T)
d1 = (np.log(S / K) + (r - q + sig**2 / 2) * T) / raiz
d2 = d1 - raiz
dq, dr = np.exp(-q * T), np.exp(-r * T)
gamma = dq * norm.pdf(d1) / (S * raiz)
vega = S * dq * norm.pdf(d1) * np.sqrt(T)
decay = -S * dq * norm.pdf(d1) * sig / (2 * np.sqrt(T))
if tipo == "C":
precio = S * dq * norm.cdf(d1) - K * dr * norm.cdf(d2)
delta = dq * norm.cdf(d1)
theta = (decay - r * K * dr * norm.cdf(d2)
+ q * S * dq * norm.cdf(d1))
else:
precio = K * dr * norm.cdf(-d2) - S * dq * norm.cdf(-d1)
delta = -dq * norm.cdf(-d1)
theta = (decay + r * K * dr * norm.cdf(-d2)
- q * S * dq * norm.cdf(-d1))
return dict(precio=precio, delta=delta, gamma=gamma,
vega=vega / 100, theta=theta / 365)
def pata(tipo, K, n, vto=VTO, fecha=HOY):
"""Una pata: n > 0 comprada, n < 0 vendida. tipo "S" es
el propio índice (una cartera que lo replica)."""
if tipo == "S":
return dict(tipo="S", K=0.0, n=n, bid=S0, ask=S0,
iv=0.0, vto=vto, r=0.0, q=0.0)
o = cadena[(cadena.fecha == fecha)
& (cadena.vencimiento == vto)
& (cadena.tipo == tipo)
& (cadena.strike == K)].iloc[0]
rr, qq = mercado(vto, fecha)
return dict(tipo=tipo, K=K, n=n, bid=o.bid, ask=o.ask,
iv=o.iv_modelo, vto=vto, r=rr, q=qq)
def apertura(patas):
"""Coste en puntos al medio y al cruzar la horquilla:
las compradas al ask, las vendidas al bid (> 0 = débito)."""
medio = sum(p["n"] * (p["bid"] + p["ask"]) / 2 for p in patas)
real = sum(p["n"] * (p["ask"] if p["n"] > 0 else p["bid"])
for p in patas)
return medio, real
def valor(patas, S, fecha, griega="precio"):
"""Valor (o griega) de la estructura si el índice vale S
en `fecha`, con la IV de cada pata congelada."""
S = np.asarray(S, dtype=float)
total = np.zeros_like(S)
for p in patas:
if p["tipo"] == "S":
x = {"precio": S, "delta": np.ones_like(S)}
total += p["n"] * x.get(griega, np.zeros_like(S))
continue
T = (p["vto"] - fecha).days / 365
g = bsm(S, p["K"], T, p["iv"], p["tipo"], p["r"], p["q"])
total += p["n"] * g[griega]
return total
REJILLA = np.arange(1.0, 3 * S0, 1.0) # de 1 punto a 3 veces S0
def resumen(patas):
"""Coste, máximo, mínimo y puntos muertos al vencimiento
(el primero de la estructura), en puntos por unidad."""
medio, real = apertura(patas)
fin = min(p["vto"] for p in patas)
pnl = valor(patas, REJILLA, fin) - real
i = np.nonzero(np.diff(np.sign(pnl)))[0]
muertos = REJILLA[i] - pnl[i] / (pnl[i + 1] - pnl[i])
muertos = np.unique(np.round(muertos, 1))
final = pnl[-1] - pnl[-2] # pendiente hacia S grande
tol = 0.01 # con dos vencimientos, el dividendo deja una
return {"medio": round(float(medio), 2), # pendiente ínfima
"real": round(float(real), 2),
"horquilla": round(float(real - medio), 2),
"maximo": "ilimitado" if final > tol
else round(float(pnl.max()), 2),
"minimo": "ilimitado" if final < -tol
else round(float(pnl.min()), 2),
"muertos": [round(float(x), 1) for x in muertos]}
def griegas(patas, S=S0, fecha=HOY):
"""Griegas agregadas por unidad (× 100 USD por contrato)."""
g = {k: float(valor(patas, S, fecha, k))
for k in ["delta", "gamma", "vega", "theta"]}
return {"delta": round(g["delta"], 3),
"gamma": round(g["gamma"], 5),
"vega": round(g["vega"], 2),
"theta": round(g["theta"], 2)}
def con_delta(fecha=HOY, vto=VTO):
"""La cadena de un vencimiento con la delta BSM de cada fila."""
d = cadena[(cadena.fecha == fecha)
& (cadena.vencimiento == vto)].copy()
rr, qq = mercado(vto, fecha)
S, T = indice.loc[fecha, "cierre"], (vto - fecha).days / 365
for t in ("C", "P"):
m = d.tipo == t
d.loc[m, "delta"] = bsm(S, d.strike[m].to_numpy(), T,
d.iv_modelo[m].to_numpy(), t,
rr, qq)["delta"]
return d
def strike_delta(d, tipo, objetivo):
x = d[d.tipo == tipo]
return float(x.strike.iloc[(x.delta - objetivo).abs().argmin()])Tres vencimientos, tres volatilidades
La cadena diaria lista los tres vencimientos mensuales más cercanos. Lo que cambia entre ellos es lo que mueve a las estructuras temporales:
def iv_delta(d, tipo, objetivo):
"""IV del strike con la delta más cercana a la buscada."""
k = strike_delta(d, tipo, objetivo)
return d.iv_modelo[(d.tipo == tipo) & (d.strike == k)].iloc[0]
filas = []
for vto in sorted(cadena.vencimiento[cadena.fecha == HOY].unique()):
d = con_delta(HOY, vto)
c = d[(d.tipo == "C") & (d.strike == 6100)].iloc[0]
r_, q_ = mercado(vto)
filas.append({"vencimiento": vto.date(),
"dias": (vto - HOY).days, "r": r_, "q": q_,
"call_6100": (c.bid + c.ask) / 2,
"iv_6100": c.iv_modelo,
"iv_c25": iv_delta(d, "C", 0.25),
"iv_p25": iv_delta(d, "P", -0.25)})
pd.DataFrame(filas).set_index("vencimiento").round(4) dias r q call_6100 iv_6100 iv_c25 iv_p25
vencimiento
2026-01-16 16 0.0369 0.0117 46.2 0.0946 0.0740 0.1195
2026-02-20 51 0.0368 0.0117 119.3 0.1258 0.1007 0.1539
2026-03-20 79 0.0366 0.0117 167.6 0.1390 0.1093 0.1728La volatilidad implícita de la call 6.100 sube con el plazo: 0,0946 a 16 días, 0,1258 a 51 y 0,1390 a 79. Es la estructura temporal normal de un mercado tranquilo, en contango (lección 3.4.5); en los datos se invierte en las crisis. El skew también crece en puntos con el plazo: la diferencia entre la call y la put de 25D (delta de 0,25 en valor absoluto) es −4,55 puntos en enero, −5,32 en febrero y −6,35 en marzo.
Un calendario que vende enero y compra febrero no compra «la volatilidad de febrero», sino la del tramo que la opción de febrero tiene por delante cuando vence la de enero. Como las varianzas totales se suman en el tiempo, esa volatilidad a plazo se despeja de las dos implícitas:
t1, t2 = 16 / 365, 51 / 365
s1, s2 = 0.0946, 0.1258 # IV de la call 6100
var_plazo = (s2**2 * t2 - s1**2 * t1) / (t2 - t1)
print(f"volatilidad a plazo 16 de enero-20 de febrero: "
f"{np.sqrt(var_plazo):.4f}")volatilidad a plazo 16 de enero-20 de febrero: 0.1377Un 13,77 %, por encima de cualquiera de las dos implícitas: el mercado descuenta que el tramo de enero a febrero será más movido que los próximos 16 días. Si la volatilidad a plazo es cara o barata frente a la que se espera, es la pregunta de un calendario. La lección 3.6.2 trata las operaciones sobre la estructura temporal de forma sistemática.
El calendario de calls
calendario = [pata("C", 6100, -1, ENE), pata("C", 6100, 1, FEB)]
print(resumen(calendario))
print(griegas(calendario))
for p in calendario:
print(p["vto"].date(), p["n"],
{k: round(float(valor([p], S0, HOY, k)), 3)
for k in ["vega", "theta", "gamma"]}){'medio': 73.1, 'real': 73.9, 'horquilla': 0.8, 'maximo': 28.15, 'minimo': -73.9, 'muertos': [6041.7, 6168.3]}
{'delta': 0.032, 'gamma': -0.00192, 'vega': 3.97, 'theta': 0.38}
2026-01-16 -1 {'vega': -5.082, 'theta': 1.704, 'gamma': -0.003}
2026-02-20 1 {'vega': 9.049, 'theta': -1.323, 'gamma': 0.001}El calendario 6.100 cuesta 73,9 puntos al cruzar la horquilla (73,1 al medio): se vende la call de enero al bid y se compra la de febrero al ask. Sus griegas son la suma de dos opciones muy distintas. La de enero, a 16 días, tiene más theta (+1,70 puntos al día vendida) y más gamma; la de febrero tiene más vega (9,05 frente a 5,08). El saldo: theta positiva (+0,38), vega positiva (+3,97), gamma negativa (−0,00192) y delta casi nula (0,032). El calendario cobra el paso del tiempo como una venta de prima, pero es largo en volatilidad: si la implícita sube, gana.
resumen lo valora el 16 de enero, el primer vencimiento: la call de enero liquida su intrínseco y la de febrero se
valora con BSM a 35 días con su volatilidad de hoy. La ganancia máxima, 28,15 puntos, está en el strike; la pérdida
máxima es el débito entero, cuando el índice se aleja tanto en cualquier dirección que las dos calls valen lo mismo (las
dos casi nada, o las dos su intrínseco). Los puntos muertos, 6.041,7 y 6.168,3, dejan un rango del ±1 % alrededor del
strike.
Fuente Mermaid
flowchart TB H[31 dic: abrir] --> C[Vende 6100 enero] H --> L[Compra 6100 feb.] C --> E[16 ene: intrínseco] L --> V[16 ene: BSM con IV] E --> P[P&L del calendario] V --> P
El 16 de enero, la pata de febrero ya no tiene una volatilidad conocida. La figura de la cabecera dibuja el P&L ese día con tres supuestos:
def con_iv(patas, dv):
"""Las mismas patas con la IV desplazada dv (tanto por uno)."""
return [{**p, "iv": p["iv"] + dv} for p in patas]
coste = apertura(calendario)[1]
S = np.array([5800.0, 5950, 6050, 6100, 6150, 6250, 6400])
tabla = pd.DataFrame({
f"IV {dv * 100:+.0f}": valor(con_iv(calendario, dv), S, ENE)
- coste for dv in (-0.03, 0.0, 0.03)}, index=S)
tabla.index.name = "ORX el 16 de enero"
tabla.round(1) IV -3 IV +0 IV +3
ORX el 16 de enero
5800.0 -70.0 -61.8 -49.8
5950.0 -50.9 -32.9 -13.5
6050.0 -18.5 3.7 25.9
6100.0 5.7 28.2 50.7
6150.0 -14.8 6.8 28.8
6250.0 -41.1 -24.3 -5.4
6400.0 -56.2 -48.4 -36.6Con el índice en 6.100, el calendario gana 28,2 si la volatilidad de febrero sigue igual, 50,7 si sube 3 puntos y solo 5,7 si baja 3. Tres puntos de volatilidad valen unos 22 puntos de P&L, casi lo mismo que la ganancia máxima: la vega de la call de febrero a 35 días es de unos 7,5 puntos por punto. Un descenso de 3 puntos no es un escenario raro: lleva la volatilidad de febrero (0,1258) al nivel de la de enero (0,0946), algo que pasa cada vez que el mercado se calma y la estructura temporal se aplana. En 5.800 o en 6.400 el calendario pierde entre 36 y 70 puntos con cualquier volatilidad: lejos del strike, la vega ya no salva nada.
Diagonal y doble calendario
diagonal = [pata("C", 6200, -1, ENE), pata("C", 6000, 1, FEB)]
doble = [pata("P", 5900, -1, ENE), pata("P", 5900, 1, FEB),
pata("C", 6300, -1, ENE), pata("C", 6300, 1, FEB)]
for nombre, e in [("diagonal 6000 feb / 6200 ene", diagonal),
("doble calendario 5900/6300", doble)]:
print(nombre, resumen(e))
print(" ", griegas(e))diagonal 6000 feb / 6200 ene {'medio': 181.9, 'real': 182.7, 'horquilla': 0.8, 'maximo': 62.5, 'minimo': -182.7, 'muertos': [6116.6]}
{'delta': 0.506, 'gamma': -0.00119, 'vega': 5.62, 'theta': -0.7}
doble calendario 5900/6300 {'medio': 71.7, 'real': 72.65, 'horquilla': 0.95, 'maximo': 33.83, 'minimo': -93.41, 'muertos': [5831.8, 5992.6, 6239.6, 6348.8]}
{'delta': 0.072, 'gamma': 0.00064, 'vega': 10.86, 'theta': -0.49}- Diagonal 6.000 febrero / 6.200 enero. Compra una call de febrero dentro del dinero y vende una de enero fuera. Cuesta 182,7 y tiene delta 0,506: es una posición alcista, una covered call con la opción de febrero en lugar del índice (la lección 3.5.1 explica la covered call). Gana como mucho 62,5 si el índice acaba en 6.200 el 16 de enero, y su theta es negativa (−0,70): la call de 6.000 de febrero pierde valor temporal más deprisa de lo que lo cobra la de 6.200 de enero, muy fuera del dinero a 16 días. Una diagonal no es un calendario con otro strike: es otra posición.
- Doble calendario 5.900/6.300. Dos calendarios, de puts en 5.900 y de calls en 6.300. Cuesta 72,65 y tiene dos jorobas, con cuatro puntos muertos (5.831,8, 5.992,6, 6.239,6 y 6.348,8): gana si el índice acaba cerca de uno de los dos strikes y pierde si se queda en el centro o se va lejos. Su vega es la más alta de la lección (10,86) y hoy su gamma es positiva y su theta negativa: con el índice entre los dos strikes, las opciones cortas de enero están a 16 días muy fuera del dinero y valen poco; las largas de febrero dominan. Se vuelve corta en gamma cuando el índice se acerca a uno de los strikes.
Ratios y backspreads
ratio_c = [pata("C", 6100, 1), pata("C", 6300, -2)]
ratio_p = [pata("P", 6100, 1), pata("P", 5900, -2)]
back_c = [pata("C", 6100, -1), pata("C", 6300, 2)]
back_p = [pata("P", 6100, -1), pata("P", 5900, 2)]
for nombre, e in [("ratio de calls 1x2 6100/6300", ratio_c),
("ratio de puts 1x2 6100/5900", ratio_p),
("backspread de calls 6100/6300", back_c),
("backspread de puts 6100/5900", back_p)]:
print(nombre, resumen(e))
print(" ", griegas(e))ratio de calls 1x2 6100/6300 {'medio': 64.5, 'real': 65.6, 'horquilla': 1.1, 'maximo': 134.4, 'minimo': 'ilimitado', 'muertos': [6165.6, 6434.4]}
{'delta': 0.095, 'gamma': -0.00115, 'vega': -4.21, 'theta': 0.16}
ratio de puts 1x2 6100/5900 {'medio': -5.0, 'real': -3.7, 'horquilla': 1.3, 'maximo': 203.7, 'minimo': -5695.3, 'muertos': [5696.3]}
{'delta': 0.047, 'gamma': -0.00047, 'vega': -5.74, 'theta': 1.09}
backspread de calls 6100/6300 {'medio': -64.5, 'real': -63.4, 'horquilla': 1.1, 'maximo': 'ilimitado', 'minimo': -136.6, 'muertos': [6163.4, 6436.6]}
{'delta': -0.095, 'gamma': 0.00115, 'vega': 4.21, 'theta': -0.16}
backspread de puts 6100/5900 {'medio': 5.0, 'real': 6.3, 'horquilla': 1.3, 'maximo': 5692.7, 'minimo': -206.3, 'muertos': [5693.7]}
{'delta': -0.047, 'gamma': 0.00047, 'vega': 5.74, 'theta': -1.09}El ratio de calls 1×2 compra la 6.100 y vende dos 6.300. Cuesta 65,6 y gana como mucho 134,4 si el índice acaba en 6.300; por encima de 6.434,4 pierde sin límite, porque queda una call vendida descubierta. El ratio de puts, con el mismo strike central y la put de 5.900, cobra 3,7 puntos: gana eso si el índice sube, gana hasta 203,7 en 5.900 y pierde sin límite por debajo de 5.696,3 (un 6,5 % por debajo del índice). La diferencia es el skew: la put de 25D tiene una volatilidad del 15,39 % y la call de 25D, del 10,07 %. Dos puts de 5.900 valen casi lo mismo que una de 6.100; dos calls de 6.300 valen menos de la mitad que una de 6.100.
Fuente Mermaid
flowchart TB S[Skew de puts] --> RP[Ratio de puts] S --> RR[Risk reversal] S --> JL[Jade lizard] RP --> C[Cola izquierda] RR --> C JL --> C
Los backspreads son la imagen especular. El backspread de calls vende la 6.100 y compra dos 6.300: se abre con un crédito de 63,4 y gana sin límite con subidas fuertes, pero pierde hasta 136,6 si el índice acaba en 6.300. El de puts vende la 6.100 y compra dos 5.900: cuesta 6,3, pierde hasta 206,3 en 5.900 y gana con las caídas fuertes. Es una protección de la cola izquierda con un punto débil en la zona de caída moderada. Sus griegas lo dicen: vega +5,74 y theta −1,09; paga el tiempo y el skew.
Risk reversal y jade lizard
risk_rev = [pata("P", 5900, -1), pata("C", 6300, 1)]
jade = [pata("P", 6000, -1), pata("C", 6200, -1), pata("C", 6300, 1)]
for nombre, e in [("risk reversal 5900/6300", risk_rev),
("jade lizard 6000/6200/6300", jade)]:
print(nombre, resumen(e))
print(" ", griegas(e))
iv_c = risk_rev[1]["iv"]
iv_p = risk_rev[0]["iv"]
print(f"IV call 25D {iv_c:.4f} IV put 25D {iv_p:.4f}"
f" risk reversal {100 * (iv_c - iv_p):.2f} puntos")risk reversal 5900/6300 {'medio': -29.5, 'real': -28.8, 'horquilla': 0.7, 'maximo': 'ilimitado', 'minimo': -5870.2, 'muertos': [5871.2]}
{'delta': 0.475, 'gamma': 0.00034, 'vega': -0.76, 'theta': 0.26}
jade lizard 6000/6200/6300 {'medio': -114.9, 'real': -113.7, 'horquilla': 1.2, 'maximo': 113.7, 'minimo': -5885.3, 'muertos': [5886.3]}
{'delta': 0.195, 'gamma': -0.00137, 'vega': -10.42, 'theta': 1.36}
IV call 25D 0.1007 IV put 25D 0.1539 risk reversal -5.32 puntosEl risk reversal 5.900/6.300 vende la put de 25D y compra la call de 25D. Cobra 28,8 puntos: la put cuesta más que la call porque su volatilidad es 5,32 puntos mayor. Ese −5,32 es la definición de mercado del risk reversal como medida del skew (la volatilidad de la call de 25D menos la de la put de 25D), y la estructura es la forma de operarlo: delta 0,475, vega casi nula (−0,76), theta +0,26. Es un largo sintético en el índice con la mitad de delta, que cobra el skew a cambio de la cola izquierda: por debajo de 5.871,2 pierde como una put vendida.
El jade lizard 6.000/6.200/6.300 vende la put 6.000 y el call spread 6.200/6.300. Cobra 113,7 puntos, más que el ancho del call spread (100): si el índice sube por encima de 6.300 gana 13,7 en lugar de perder. No tiene riesgo al alza. Tiene todo el riesgo a la baja (punto muerto en 5.886,3) y es la estructura más vendida en volatilidad de la lección: vega −10,42, theta +1,36.
Cuánto cuestan las patas de más
estructuras = {"calendario": calendario, "diagonal": diagonal,
"doble calendario": doble, "ratio de calls": ratio_c,
"ratio de puts": ratio_p, "backspread de puts": back_p,
"risk reversal": risk_rev, "jade lizard": jade}
filas = []
for nombre, e in estructuras.items():
medio, real = apertura(e)
filas.append({"estructura": nombre,
"contratos": sum(abs(p["n"]) for p in e),
"prima_medio": medio, "horquilla": real - medio,
"pct_prima": 100 * (real - medio) / abs(medio)})
pd.DataFrame(filas).set_index("estructura").round(2) contratos prima_medio horquilla pct_prima
estructura
calendario 2 73.1 0.80 1.09
diagonal 2 181.9 0.80 0.44
doble calendario 4 71.7 0.95 1.32
ratio de calls 3 64.5 1.10 1.71
ratio de puts 3 -5.0 1.30 26.00
backspread de puts 3 5.0 1.30 26.00
risk reversal 2 -29.5 0.70 2.37
jade lizard 3 -114.9 1.20 1.04Con dos patas y prima neta grande, la horquilla es menor del 1,1 % de la prima (calendario, diagonal, jade lizard). Las estructuras de prima neta casi nula son otra historia: el ratio de puts y el backspread de puts, con una prima al medio de 5 puntos, pierden un 26 % de ella al cruzar la horquilla. Una estructura que «se abre gratis» al medio no se abre gratis.
El skew en doce años
El risk reversal es también una serie: cuánto más cara es la put que la call de la misma delta, mes a mes. La celda prepara de paso las entradas de la historia de la sección siguiente, que necesita la cadena del vencimiento corto y la del largo:
vtos = np.sort(cadena.vencimiento.unique())
sesiones = indice.index
por_dia = dict(tuple(cadena.groupby(["fecha", "vencimiento"])))
entradas = [] # (sesión, vto corto, vto largo, cadenas)
for ant, v1, v2 in zip(vtos[:-2], vtos[1:-1], vtos[2:]):
if v2 > sesiones[-1]:
break
f = sesiones[sesiones.searchsorted(ant, side="right")]
if (f, v1) in por_dia and (f, v2) in por_dia:
entradas.append((f, v1, v2, con_delta(f, v1),
con_delta(f, v2)))
rr = pd.DataFrame({
"rr25": [100 * (iv_delta(d1, "C", 0.25) - iv_delta(d1, "P", -0.25))
for f, v1, v2, d1, d2 in entradas],
"iv_atm": [100 * iv_delta(d1, "C", 0.50)
for f, v1, v2, d1, d2 in entradas]},
index=[e[0] for e in entradas])
print(len(rr), "entradas;", rr.rr25.describe().round(2).to_dict())
rr.loc[[rr.rr25.idxmin(), rr.rr25.idxmax()]].round(2)142 entradas; {'count': 142.0, 'mean': -7.08, 'std': 2.79, 'min': -22.65, '25%': -8.19, '50%': -6.7, '75%': -5.1, 'max': -2.91}
rr25 iv_atm
2020-03-23 -22.65 62.80
2014-01-21 -2.91 8.97En las 142 entradas el risk reversal de 25D del vencimiento mensual siguiente es siempre negativo: la mediana, −6,7 puntos; el menos negativo, −2,91 en enero de 2014, con la volatilidad ATM en el 8,97 %; el más, −22,65 el 23 de marzo de 2020, con la ATM en el 62,80 %. El README de los datos da −5,4 puntos de mediana en calma para cortes de 25 a 35 días; aquí entran todos los regímenes, y el skew en puntos crece con el nivel de la volatilidad. Un risk reversal que se vende «porque el skew está caro» se vende cuando el mercado ya está asustado: los −22,65 de marzo de 2020 llegaron después de la caída, no antes.
Doce años de cada estructura
La historia de esta lección tiene una diferencia con la de las anteriores: las estructuras temporales tienen patas que no vencen. La regla: la sesión siguiente a cada vencimiento mensual se abre la estructura con el vencimiento siguiente (corto) y el posterior (largo), al bid y al ask; en el vencimiento corto, las patas cortas liquidan su intrínseco y las largas se venden al bid de ese día, con la cadena real (lo que valían de verdad, con la volatilidad que tuvieran). Las estructuras de un solo vencimiento se liquidan como en la 3.5.1. Cuando el strike de una pata larga no está listado en su vencimiento se toma el más cercano.
def cerca(d, K):
"""El strike listado más cercano a K (las rejillas de dos
vencimientos no siempre coinciden)."""
ks = d.strike.unique()
return float(ks[np.abs(ks - K).argmin()])
def cierre(d, tipo, K, n):
"""Precio de cerrar una pata: vender al bid lo comprado y
recomprar al ask lo vendido."""
o = d[(d.tipo == tipo) & (d.strike == K)].iloc[0]
return o.bid if n > 0 else o.ask
def historia(receta):
"""Abre al bid/ask la sesión siguiente a un vencimiento y
cierra todo en el vencimiento siguiente: las patas cortas
liquidan su intrínseco; las largas se venden al bid."""
filas = []
for f, v1, v2, d1, d2 in entradas:
S, ST = indice.loc[f, "cierre"], indice.loc[v1, "cierre"]
d_fin = por_dia.get((v1, v2))
coste = final = 0.0
for tipo, K, n, cual in receta(d1, d2):
d = d1 if cual == 1 else d2
K = cerca(d, K)
o = d[(d.tipo == tipo) & (d.strike == K)].iloc[0]
coste += n * (o.ask if n > 0 else o.bid)
if cual == 1:
final += n * (max(ST - K, 0) if tipo == "C"
else max(K - ST, 0))
else:
final += n * cierre(d_fin, tipo, K, n)
filas.append({"entrada": f, "pnl": 100 * (final - coste) / S})
return pd.DataFrame(filas).set_index("entrada")["pnl"]
sd = strike_delta
recetas = {
"calendario ATM": lambda a, b: [
("C", sd(a, "C", 0.5), -1, 1), ("C", sd(a, "C", 0.5), 1, 2)],
"doble calendario 25D": lambda a, b: [
("P", sd(a, "P", -0.25), -1, 1), ("P", sd(a, "P", -0.25), 1, 2),
("C", sd(a, "C", 0.25), -1, 1), ("C", sd(a, "C", 0.25), 1, 2)],
"diagonal 60D/25D": lambda a, b: [
("C", sd(a, "C", 0.25), -1, 1), ("C", sd(b, "C", 0.60), 1, 2)],
"ratio de calls 1x2": lambda a, b: [
("C", sd(a, "C", 0.5), 1, 1), ("C", sd(a, "C", 0.25), -2, 1)],
"ratio de puts 1x2": lambda a, b: [
("P", sd(a, "C", 0.5), 1, 1), ("P", sd(a, "P", -0.25), -2, 1)],
"backspread de puts": lambda a, b: [
("P", sd(a, "C", 0.5), -1, 1), ("P", sd(a, "P", -0.25), 2, 1)],
"risk reversal 25D": lambda a, b: [
("P", sd(a, "P", -0.25), -1, 1), ("C", sd(a, "C", 0.25), 1, 1)],
"jade lizard 25D/10D": lambda a, b: [
("P", sd(a, "P", -0.25), -1, 1), ("C", sd(a, "C", 0.25), -1, 1),
("C", sd(a, "C", 0.10), 1, 1)],
}
hist = pd.DataFrame({k: historia(v) for k, v in recetas.items()})
pd.DataFrame({
"media": hist.mean(), "mediana": hist.median(),
"pct_pos": 100 * (hist > 0).mean(), "desv": hist.std(),
"peor": hist.min(),
"fecha_peor": hist.idxmin().dt.strftime("%Y-%m-%d"),
"mejor": hist.max()}).round(2) media mediana pct_pos desv peor fecha_peor mejor
calendario ATM -0.02 -0.04 47.89 0.59 -2.02 2020-04-20 2.04
doble calendario 25D 0.08 0.09 57.04 0.73 -2.74 2020-04-20 2.17
diagonal 60D/25D 0.10 0.61 61.27 1.88 -7.58 2020-04-20 6.63
ratio de calls 1x2 -0.31 -0.21 40.85 1.98 -12.86 2020-07-20 2.57
ratio de puts 1x2 0.33 -0.00 50.00 2.38 -21.33 2020-02-24 6.75
backspread de puts -0.38 -0.04 45.77 2.38 -6.88 2015-08-24 21.29
risk reversal 25D 0.76 0.36 87.32 3.82 -24.65 2020-02-24 19.57
jade lizard 25D/10D 0.22 0.81 70.42 2.55 -23.41 2020-02-24 3.87- Calendarios: el ATM pierde un 0,02 % de media y gana en el 48 % de los meses; el doble calendario gana un 0,08 % y en el 57 %. Sus peores entradas son pequeñas (−2,0 % y −2,7 %), las dos del 20 de abril de 2020. No es la prima de volatilidad lo que mueve un calendario, sino si el índice se queda cerca del strike y cómo cambia la estructura temporal.
- Diagonal 60D/25D: gana un 0,10 % de media y en el 61 % de los meses; su peor, −7,6 %, es una caída con la delta larga de la call de febrero.
- Ratio de calls 1×2: pierde un 0,31 % de media, y su peor entrada es la del 20 de julio de 2020 (−12,9 %): la subida del 23,5 % hasta el vencimiento atravesó la call vendida descubierta. Ratio de puts 1×2: gana un 0,33 %, con una mediana de cero y un peor mes de −21,3 % (24 de febrero de 2020). Backspread de puts: el espejo; pierde un 0,38 % de media y gana un 21,3 % en febrero de 2020; su peor entrada es la del 24 de agosto de 2015, el día del último de los saltos plantados del 20-24 de agosto: el índice cayó otro 6,9 % hasta el vencimiento y acabó cerca de las puts compradas, el punto débil del backspread, la caída moderada.
- Risk reversal 25D: el que más gana (0,76 % de media, positivo en el 87 % de los meses), porque el índice de los datos sube y el skew paga la call. Su peor entrada, −24,6 %, pierde casi como el índice entero (−28,1 % en la lección 3.5.1) aunque su delta inicial era la mitad: al caer, la put vendida gana delta hasta comportarse como un índice completo.
- Jade lizard 25D/10D: 0,22 % de media, 70 % de meses positivos, −23,4 % en el peor.
El 2020 mes a mes enseña cómo responden las estructuras temporales a un crash:
hist.loc["2020-01":"2020-07", ["calendario ATM", "doble calendario 25D",
"ratio de puts 1x2",
"backspread de puts"]].round(2) calendario ATM doble calendario 25D ratio de puts 1x2 backspread de puts
entrada
2020-01-21 0.11 0.40 -0.18 0.13
2020-02-24 -0.53 -1.01 -21.33 21.29
2020-03-23 -1.09 -0.59 -1.34 1.07
2020-04-20 -2.02 -2.74 -1.07 0.85
2020-05-18 1.50 0.03 1.82 -2.04
2020-06-22 -1.10 -1.23 -0.09 -0.05
2020-07-20 -1.60 -2.37 -0.27 0.17El calendario ATM no ganó con el estallido de la volatilidad: perdió un 0,53 % en la entrada de febrero, un 1,09 % en la de marzo y un 2,02 % en la de abril. Su vega positiva no compensa su gamma negativa cuando el índice se aleja del strike un 28 % o rebota un 30 %; y en marzo y abril, con la estructura temporal invertida, compraba la volatilidad de un mes posterior más barata que la que vendía. El ratio de puts y el backspread de puts son exactamente opuestos en febrero (−21,33 % y +21,29 %).
La receta del jade lizard tiene un problema que no se ve en la tabla:
sin_riesgo = []
for f, v1, v2, d1, d2 in entradas:
x = d1.set_index(["tipo", "strike"])
kp, kc = sd(d1, "P", -0.25), sd(d1, "C", 0.25)
kc2 = sd(d1, "C", 0.10)
credito = (x.loc[("P", kp), "bid"] + x.loc[("C", kc), "bid"]
- x.loc[("C", kc2), "ask"])
sin_riesgo.append(credito >= kc2 - kc)
print(f"{100 * np.mean(sin_riesgo):.1f} % de las entradas")2.1 % de las entradasSolo en el 2,1 % de las entradas el crédito superó el ancho del call spread de 25D/10D. El resto de los meses, la estructura de la tabla tenía riesgo al alza: no era un jade lizard, sino una put vendida con un call spread vendido. Una receta que elige strikes por delta tiene que comprobar la condición que define la estructura (crédito mayor o igual que el ancho) y, si no se cumple, cambiar los strikes (como el 6.000/6.200/6.300 del ejemplo, que la cumple) o no abrir.
Errores típicos
- Valorar un calendario con la volatilidad de hoy. Su resultado depende de la volatilidad de la pata larga el día que vence la corta: 3 puntos cambian el P&L tanto como su ganancia máxima.
- Confundir diagonal con calendario. Con la pata larga dentro del dinero, la diagonal es una posición direccional con theta negativa.
- Olvidar el lado sin límite. Ratios y backspreads tienen un extremo descubierto; los de puts, el de las crisis.
- Creer que un crédito es gratis. El ratio de puts y el risk reversal cobran el skew; lo pagan en la cola.
- Fiarse del nombre de la estructura. El jade lizard por delta solo lo fue en el 2,1 % de los meses.
- Ignorar la horquilla de las estructuras de prima casi nula. Un 26 % de la prima en el ratio de puts del ejemplo.
- Opciones americanas. Con acciones, una pata corta dentro del dinero de un calendario o de un ratio puede asignarse antes del vencimiento, sobre todo antes de un dividendo (lección 3.5.5).
Ejercicios
- Construye el calendario de puts 5.900 (vende enero, compra febrero). Compara su coste, su ganancia máxima, sus puntos muertos y sus griegas con los del calendario de calls 6.100.
Solución
cal_put = [pata("P", 5900, -1, ENE), pata("P", 5900, 1, FEB)]
print(resumen(cal_put))
griegas(cal_put){'medio': 44.7, 'real': 45.3, 'horquilla': 0.6, 'maximo': 59.68, 'minimo': -66.06, 'muertos': [5762.1, 6066.7]}
{'delta': -0.129, 'gamma': -0.00026, 'vega': 4.66, 'theta': 0.15}El calendario de puts 5.900 cuesta 45,3 (menos que el de calls 6.100, 73,9, porque su strike está un 3,1 % fuera del dinero) y gana como mucho 59,68 si el índice acaba en 5.900 el 16 de enero, con puntos muertos en 5.762,1 y 6.066,7. Su pérdida máxima (66,06) supera al débito: con el índice muy abajo, la put corta de enero liquida su intrínseco y la larga de febrero, muy dentro del dinero, vale algo menos que el suyo por el tipo de interés (lo mismo que se vio con la put dentro del dinero en la 3.5.2). Sus griegas son las de un calendario fuera del dinero: vega +4,66, theta +0,15, gamma pequeña (−0,00026) y una delta de −0,129 que no tiene el de calls en el dinero: necesita que el índice baje hacia 5.900.
- Separa las entradas del calendario ATM según la pendiente de la estructura temporal el día de la entrada (IV ATM del vencimiento largo menos la del corto). ¿Gana más el calendario cuando la estructura está en contango o cuando está invertida?
Solución
pendiente = pd.Series(
[100 * (iv_delta(d2, "C", 0.5) - iv_delta(d1, "C", 0.5))
for f, v1, v2, d1, d2 in entradas],
index=[e[0] for e in entradas])
cal = hist["calendario ATM"]
grupo = np.where(pendiente > 0, "contango", "invertida")
pd.DataFrame({"entradas": cal.groupby(grupo).size(),
"pendiente_media": pendiente.groupby(grupo).mean(),
"media": cal.groupby(grupo).mean(),
"pct_pos": 100 * (cal > 0).groupby(grupo).mean(),
"peor": cal.groupby(grupo).min()}).round(2) entradas pendiente_media media pct_pos peor
contango 92 1.22 -0.03 46.74 -1.60
invertida 50 -1.35 0.01 50.00 -2.02Casi no hay diferencia: 92 entradas en contango (pendiente media de +1,22 puntos) con una media de −0,03 % y un 46,7 % de meses positivos, y 50 con la estructura invertida (−1,35 puntos) con +0,01 % y un 50 %. La intuición («comprar el largo cuando está barato frente al corto») no separa nada en estos datos con una regla tan simple: lo que decide el calendario es dónde acaba el índice respecto al strike, y la peor entrada (−2,02 %) cae en el grupo invertido, en abril de 2020. Con 50 y 92 observaciones y una desviación de 0,59 puntos, el error típico de cada media ronda 0,06-0,08 puntos: cualquier diferencia de este tamaño es ruido.
- Un compañero propone vender cada mes el risk reversal de 25D (put vendida y call comprada) «porque en doce años ha ganado el 87 % de los meses y se abre con crédito». ¿Qué le dirías?
Solución
Que el crédito es la prima del skew por asumir la cola izquierda y que su 87 % de meses positivos sale de un índice que sube de media un 1 % al mes en los datos: es, sobre todo, un largo con media delta (0,475). Su peor entrada pierde un 24,6 % del índice, como el ratio de puts y el jade lizard, en el mismo mes. Si quiere ese riesgo, la pregunta es si el skew compensa la cola, y eso no se ve en el porcentaje de aciertos: hay que comparar la media con la de medio índice (el índice gana un 1,05 % al mes en la lección 3.5.1, así que medio índice ganaría algo más de un 0,5 %, y el risk reversal gana un 0,76 %), dimensionar por la pérdida en una caída como la de 2020 y no por el margen de un día tranquilo (lección 3.6.1), y recordar que en una crisis el skew se dispara (−22,7 puntos en marzo de 2020) y cerrar la put vendida cuesta mucho más de lo que se cobró.
Respuesta plegada en cada pregunta
Autoevaluación
El calendario de calls 6.100 (vende enero, compra febrero) cuesta 73,9 puntos. El 16 de enero, con el índice en 6.100, gana 28,2 si la volatilidad de febrero no cambia y 5,7 si baja 3 puntos. ¿Qué posición es, sobre todo?
- AUna apuesta direccional alcista
- BUna compra de la volatilidad de febrero que queda tras el vencimiento de enero (vega positiva en la pata larga) con el índice quieto cerca del strike
- CUna venta de volatilidad sin riesgo
- DUna posición sin griegas, porque las dos patas tienen el mismo strike
Ver respuesta
Respuesta B. Una compra de la volatilidad de febrero que queda tras el vencimiento de enero (vega positiva en la pata larga) con el índice quieto cerca del strike
Su vega agregada es positiva (3,97 puntos por punto de volatilidad): la pata larga tiene más vega que la corta. Su gamma es negativa: un movimiento grande hacia cualquier lado lo lleva a la pérdida máxima. Gana con el índice quieto y la volatilidad de febrero firme; 3 puntos de volatilidad mueven el resultado unos 22 puntos.
Con el mismo strike central, el ratio 1×2 de calls 6.100/6.300 cuesta 65,6 y el de puts 6.100/5.900 se abre con un crédito de 3,7. ¿Por qué?
- APorque las puts tienen más liquidez
- BPor el skew: la put de 25D (IV 0,1539) es mucho más cara que la call de 25D (IV 0,1007), y vender dos puts paga la put del dinero
- CPorque el tipo de interés favorece a las puts
- DPor un error de la cadena
Ver respuesta
Respuesta B. Por el skew: la put de 25D (IV 0,1539) es mucho más cara que la call de 25D (IV 0,1007), y vender dos puts paga la put del dinero
El risk reversal de 25D de febrero es −5,32 puntos: el mercado (aquí, el generador) cobra más por la cola izquierda. El ratio de puts cobra ese exceso, y por eso su riesgo está donde el skew dice que está el peligro: por debajo de 5.696,3 pierde sin límite.
El risk reversal 25D (put vendida, call comprada) ganó en el 87 % de los meses de la historia y su peor entrada perdió un 24,6 % del índice. ¿Qué es en realidad?
- AUna cobertura
- BUn largo sintético con delta de 0,475 financiado por el skew: gana con el índice alcista de los datos y pierde con las caídas como si tuviera medio índice
- CUna estrategia de volatilidad neutral
- DUna posición sin riesgo porque se abre con crédito
Ver respuesta
Respuesta B. Un largo sintético con delta de 0,475 financiado por el skew: gana con el índice alcista de los datos y pierde con las caídas como si tuviera medio índice
Su vega es casi nula (−0,76) y su delta, 0,475: es dirección. La put vendida concentra el riesgo en las caídas fuertes (24 de febrero de 2020), y el crédito de 28,8 puntos es la prima del skew por asumirlo.
Un jade lizard 6.000/6.200/6.300 (put 6.000 vendida, call spread 6.200/6.300 vendido) cobra 113,7. ¿Qué pasa si el índice sube mucho?
- APierde sin límite
- BGana 13,7 puntos: el crédito (113,7) supera el ancho del call spread (100), así que no tiene riesgo al alza
- CPierde 100 puntos
- DPierde el crédito
Ver respuesta
Respuesta B. Gana 13,7 puntos: el crédito (113,7) supera el ancho del call spread (100), así que no tiene riesgo al alza
Por encima de 6.300 el call spread pierde su ancho, 100, y el crédito cubre esa pérdida con 13,7 de sobra. La condición (crédito mayor o igual que el ancho) no se cumple sola: con strikes de 25D/10D, en la historia solo se cumplió en el 2,1 % de las entradas. El riesgo de verdad está abajo: por debajo de 5.886,3 pierde como una put vendida.
La volatilidad implícita de la call 6.100 es 0,0946 a 16 días y 0,1258 a 51 días. ¿Qué volatilidad compra un calendario que vende enero y compra febrero?
- A0,1258, la de febrero
- BLa media, 0,1102
- CLa volatilidad a plazo entre los dos vencimientos, la raíz de (0,1258²·51 − 0,0946²·16)/35, unos 0,1377
- DLa diferencia, 0,0312
Ver respuesta
Respuesta C. La volatilidad a plazo entre los dos vencimientos, la raíz de (0,1258²·51 − 0,0946²·16)/35, unos 0,1377
Las varianzas totales se suman en el tiempo: la de febrero es la de enero más la del tramo 16 de enero-20 de febrero. Despejando, la volatilidad de ese tramo es 0,1377, más alta que cualquiera de las dos implícitas. Es la volatilidad que queda viva en la opción larga cuando vence la corta.
Ideas clave
- Un calendario vende el vencimiento corto y compra el largo: vega positiva, gamma negativa; compra la volatilidad a plazo entre los dos vencimientos y gana con el índice quieto cerca del strike.
- Con dos vencimientos, el P&L en el vencimiento corto depende de la volatilidad de la pata larga: 3 puntos de volatilidad mueven el calendario del ejemplo unos 22 puntos, casi lo mismo que su ganancia máxima.
- Ratios y backspreads tienen un lado sin límite; el skew hace que el ratio de puts se abra con crédito y venda la cola izquierda.
- El risk reversal mide el skew (−5,32 puntos en febrero, mediana de −6,7 en doce años, −22,7 en marzo de 2020) y, como estructura, es un largo sintético financiado por él: gana en el 87 % de los meses y pierde un 24,6 % en el peor.
- Las recetas por delta no garantizan la estructura: el jade lizard 25D/10D solo tuvo el crédito por encima del ancho del call spread en el 2,1 % de las entradas.