---
title: "3.5.4 Herramienta de payoff, griegas y escenarios"
description: "Herramienta propia en Python para opciones: payoff, griegas agregadas, P&L explicado y rejilla precio × volatilidad × tiempo, con pruebas."
url: "https://pythonparatrading.com/curso-python-trading/nivel-3/herramienta-de-payoff-griegas-y-escenarios/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-3/herramienta-de-payoff-griegas-y-escenarios.png"
---

# 3.5.4 · Herramienta propia: payoff, griegas agregadas y escenarios

> Una posición con cuatro patas ya no cabe en la cabeza: hay que poder calcular su valor, sus griegas y su P&L en cualquier escenario. Esta lección construye esa herramienta desde cero en unas 150 líneas, la prueba contra QuantLib y contra sus propias derivadas, y la usa con un iron condor abierto en la cadena sintética el 19 de febrero de 2020, justo antes de la caída.

- Curso: [Nivel 3: Derivados: futuros, spreads, forex y opciones](https://pythonparatrading.com/curso-python-trading/nivel-3/)
- Módulo: 3.5 · Estructuras de opciones en Python
- Duración: 70 min
- Requisitos: [3.4.2 · Árboles binomiales, Black-Scholes-Merton, Black-76 y Monte Carlo](https://pythonparatrading.com/curso-python-trading/nivel-3/binomial-black-scholes-black-76-y-monte-carlo/), [3.4.3 · Griegas: delta, gamma, vega, theta, rho, vanna, volga y charm](https://pythonparatrading.com/curso-python-trading/nivel-3/griegas-de-primer-y-segundo-orden/), [3.5.2 · Estructuras de volatilidad y rango: straddles, strangles, cóndores y mariposas](https://pythonparatrading.com/curso-python-trading/nivel-3/straddles-strangles-condors-y-butterflies/)

## Al terminar esta lección

- Fijar por escrito las convenciones de una herramienta de opciones (precio por unidad y por contrato, T en días naturales / 365, tipos continuos, vega por punto y theta por día natural) y comprobarlas contra QuantLib.
- Representar una posición como una lista de patas y calcular su payoff al vencimiento, su P&L antes del vencimiento y sus griegas agregadas en USD con `numpy` y broadcasting.
- Escribir pruebas de estilo pytest (paridad put-call, sintéticos, diferencias finitas) antes de usar la herramienta con datos.
- Explicar el P&L de un día con la aproximación de Taylor por griegas y reconocer cuándo domina la vega.
- Construir una rejilla de escenarios precio × volatilidad × tiempo y contrastarla con lo que pasó, separando el error del desplazamiento paralelo del ruido de los precios medios.

## Qué tiene que hacer la herramienta

Las lecciones 3.5.1 a 3.5.3 dibujaron estructuras de dos, tres y cuatro patas. Para operarlas hace falta algo más que
un dibujo: saber cuánto vale la posición si el índice cae un 5 % mañana, cuánto pierde si la volatilidad sube diez
puntos, qué parte del P&L de ayer se debe a cada griega y cuándo deja de ser fiable la aproximación lineal. Las
plataformas de los brókers enseñan una parte; una herramienta propia permite ponerla en un backtest, en un control de
riesgo diario o en una alerta, con las convenciones que uno ha elegido y probado.

La de esta lección hace cinco cosas, y nada más:

1. Valora una opción europea y sus griegas con Black-Scholes-Merton (lección 3.4.2), vectorizado.
2. Agrega patas en una posición: valor, delta, gamma, vega y theta en USD.
3. Calcula el payoff al vencimiento y el P&L antes del vencimiento en cualquier precio, volatilidad y fecha.
4. Explica el P&L de un día con las griegas de la víspera.
5. Calcula una rejilla de escenarios precio × volatilidad × tiempo de una sola vez.

```mermaid
flowchart TB
  C[Cadena del día] --> P[Pata]
  P --> Q[Posicion]
  B[bsm] --> Q
  Q --> G[Griegas en USD]
  Q --> L[P&L y payoff]
  G --> E[P&L explicado]
  L --> R[Rejilla]
  T[Pruebas] -.-> B
  T -.-> Q
```

Antes del código, las convenciones. La mitad de los errores con opciones son de unidades, no de modelo:

| Magnitud | Convención de la herramienta | Error típico |
|---|---|---|
| Precio | Por unidad del subyacente (puntos de ORX); la posición multiplica por 100 USD por punto | Mezclar precio por acción con precio por contrato |
| Tiempo | T = días naturales hasta el vencimiento / 365 | Usar sesiones / 252 en la fórmula y días naturales en la theta |
| Tipo libre y dividendos | Continuos, en tanto por uno (`tipo_1m` / 100) | Pasar 0,55 en vez de 0,0055 |
| Volatilidad | Anual, en tanto por uno (0,18 es un 18 %) | Pasar 18 |
| Delta | USD por punto del índice en la posición (cantidad × 100 × delta) | Confundirla con la delta en USD (× S) |
| Vega | USD por punto de volatilidad (por 0,01) | Por unidad de volatilidad (100 veces más) |
| Theta | USD por día natural | Por año (365 veces más) o por sesión |

Las opciones de ORX imitan a las SPX: europeas, liquidación en efectivo y 100 USD por punto. En las SPX reales el
multiplicador también es 100 y el estilo es europeo, como dice la ficha de Cboe; las mensuales se liquidan con un
valor calculado con los precios de apertura del día del vencimiento (en los datos del curso, al cierre, por
simplicidad). Una herramienta para opciones americanas sobre acciones necesita otro motor (el binomial de 3.4.2);
la estructura de patas y posición es la misma.

## El motor: Black-Scholes-Merton vectorizado

La función `bsm` devuelve precio y griegas de una opción europea con rendimiento por dividendo continuo. Acepta arrays
en `S`, `K`, `T` y `vol` y los combina por broadcasting: con un array de 141 precios devuelve 141 precios, y con una
malla de 7 × 5 × 3 escenarios, una malla. Un solo signo `w` (+1 call, −1 put) evita duplicar las fórmulas:

$$
V = w\left(S e^{-qT} N(w d_1) - K e^{-rT} N(w d_2)\right), \qquad
d_{1,2} = \frac{\ln(S/K) + (r - q \pm \sigma^2/2)\,T}{\sigma\sqrt{T}}
$$

```python
import numpy as np
import pandas as pd
from scipy.special import ndtr

AÑO = 365.0  # T = días naturales / 365


def bsm(tipo, S, K, T, vol, r, q):
    """Precio y griegas de una opción europea, por unidad.

    T en años, r y q continuos, vol en tanto por uno. Vega por
    punto de volatilidad; theta por día natural. Con T <= 0
    devuelve el valor intrínseco."""
    S, K, T, vol = np.broadcast_arrays(
        *(np.asarray(x, dtype=float) for x in (S, K, T, vol)))
    vivo = T > 0
    Tv = np.where(vivo, T, 1.0)
    raiz = vol * np.sqrt(Tv)
    d1 = (np.log(S / K) + (r - q + vol**2 / 2) * Tv) / raiz
    d2 = d1 - raiz
    dq, dr = np.exp(-q * Tv), np.exp(-r * Tv)
    n1 = np.exp(-d1**2 / 2) / np.sqrt(2 * np.pi)
    w = 1.0 if tipo == "C" else -1.0  # +1 call, -1 put
    precio = w * (S * dq * ndtr(w * d1) - K * dr * ndtr(w * d2))
    delta = w * dq * ndtr(w * d1)
    theta = (-S * dq * n1 * vol / (2 * np.sqrt(Tv))
             + w * q * S * dq * ndtr(w * d1)
             - w * r * K * dr * ndtr(w * d2))
    intr = np.maximum(w * (S - K), 0.0)
    return {
        "precio": np.where(vivo, precio, intr),
        "delta": np.where(vivo, delta, w * (intr > 0)),
        "gamma": np.where(vivo, dq * n1 / (S * raiz), 0.0),
        "vega": np.where(vivo, S * dq * n1 * np.sqrt(Tv) / 100, 0.0),
        "theta": np.where(vivo, theta / AÑO, 0.0),
    }


prueba = bsm("P", 5000, 4900, 30 / AÑO, 0.18, 0.04, 0.015)
{k: round(float(v), 4) for k, v in prueba.items()}
```
```output
{'precio': 56.0149, 'delta': -0.3234, 'gamma': 0.0014, 'vega': 5.145, 'theta': -1.4266}
```

La put de prueba (S = 5000, K = 4900, 30 días, 18 %, r = 4 %, q = 1,5 %) vale 56,01 puntos, 5.601 USD por contrato.
Su delta, −0,3234, dice que gana 0,32 puntos por cada punto que cae el índice; su vega, 5,145, que gana 5,15 puntos
(514,5 USD por contrato) si la volatilidad sube del 18 % al 19 %; su theta, −1,4266, que pierde 1,43 puntos por día
natural con todo lo demás igual. Con `T <= 0` devuelve el valor intrínseco y griegas nulas (delta 0 o ±1): es lo que
permite calcular el payoff con la misma función.

Antes de fiarse de ella, se compara con una implementación independiente. QuantLib (lección 3.4.4) tiene un motor
analítico para la misma opción; la única precaución es darle el mismo convenio de tiempo (`Actual365Fixed`) y recordar
que su `vega()` es por unidad de volatilidad y su `theta()` por año (`thetaPerDay()` divide entre 365):

```python
import QuantLib as ql

fecha_ql = ql.Date(2, 1, 2025)
ql.Settings.instance().evaluationDate = fecha_ql
dc = ql.Actual365Fixed()  # el mismo T = días / 365


def curva(tipo):
    return ql.YieldTermStructureHandle(
        ql.FlatForward(fecha_ql, tipo, dc))


vol = ql.BlackVolTermStructureHandle(
    ql.BlackConstantVol(fecha_ql, ql.NullCalendar(), 0.18, dc))
proceso = ql.BlackScholesMertonProcess(
    ql.QuoteHandle(ql.SimpleQuote(5000.0)), curva(0.015),
    curva(0.04), vol)
put = ql.VanillaOption(ql.PlainVanillaPayoff(ql.Option.Put, 4900),
                       ql.EuropeanExercise(fecha_ql + 30))
put.setPricingEngine(ql.AnalyticEuropeanEngine(proceso))

ref = {"precio": put.NPV(), "delta": put.delta(),
       "gamma": put.gamma(), "vega": put.vega() / 100,
       "theta": put.thetaPerDay()}
pd.DataFrame({"propia": {k: float(v) for k, v in prueba.items()},
              "QuantLib": ref}).assign(
    dif=lambda t: t["propia"] - t["QuantLib"]).round(6)
```
```output
           propia   QuantLib  dif
precio  56.014945  56.014945 -0.0
delta   -0.323396  -0.323396  0.0
gamma    0.001391   0.001391 -0.0
vega     5.145002   5.145002  0.0
theta   -1.426610  -1.426610 -0.0
```

Coinciden hasta la sexta cifra decimal en las cinco magnitudes. Si la theta de QuantLib se hubiera comparado con
`theta()` en vez de `thetaPerDay()`, la diferencia sería de 365 veces: es el tipo de error que esta comparación
existe para atrapar.

## Patas y posición

Una pata es un registro inmutable: tipo, cantidad con signo, strike, días hasta el vencimiento, volatilidad y precio
de entrada. El tipo `S` representa el propio subyacente (para coberturas delta). La validación al crearla detiene los
dos errores de convención más frecuentes: un tipo que no existe y una volatilidad escrita en porcentaje.

```python
from dataclasses import dataclass


@dataclass(frozen=True)
class Pata:
    tipo: str              # "C", "P" o "S" (el subyacente)
    cantidad: float        # contratos: + comprados, - vendidos
    strike: float = 0.0
    dias: float = 0.0      # días naturales hasta el vencimiento
    iv: float = 0.0        # volatilidad de la pata, tanto por uno
    prima: float = 0.0     # precio de entrada por unidad

    def __post_init__(self):
        if self.tipo not in ("C", "P", "S"):
            raise ValueError(f"tipo {self.tipo!r}: usa C, P o S")
        if self.tipo == "S":
            return
        if self.strike <= 0:
            raise ValueError("una opción necesita strike > 0")
        if not 0 < self.iv < 3:
            raise ValueError(f"iv {self.iv}: en tanto por uno "
                             "(0.18 es un 18 %)")
```

```python raises
Pata("P", -1, strike=4900, dias=30, iv=18)
```
```output
ValueError: iv 18: en tanto por uno (0.18 es un 18 %)
```

La posición suma sus patas. Todo lo que calcula sale de una sola función, `griegas`, que acepta un precio, un
desplazamiento de volatilidad `dvol` (el mismo para todas las patas: un desplazamiento paralelo) y `dt` días
transcurridos. Como `bsm` hace broadcasting, si `S`, `dvol` y `dt` son arrays de formas compatibles, el resultado es la
malla entera. El P&L es el valor de mercado menos lo que costó la posición (negativo si se cobró prima); el payoff es
el P&L en el primer vencimiento de la posición, de modo que un calendario (lección 3.5.3) se evalúa con la pata larga
todavía viva, valorada con su volatilidad.

```python
GRIEGAS = ("delta", "gamma", "vega", "theta")


@dataclass
class Posicion:
    patas: list
    r: float           # tipo continuo, tanto por uno
    q: float           # rentabilidad por dividendo continua
    mult: float = 100  # USD por punto del índice

    def griegas(self, S, dvol=0.0, dt=0.0):
        """Valor y griegas en USD con el subyacente en S, la
        volatilidad de cada pata desplazada dvol (tanto por uno)
        y dt días naturales transcurridos. S, dvol y dt admiten
        arrays que se combinan por broadcasting."""
        S = np.asarray(S, dtype=float)
        tot = dict.fromkeys(("valor",) + GRIEGAS, 0.0)
        for p in self.patas:
            if p.tipo == "S":
                g = {"precio": S, "delta": 1.0, "gamma": 0.0,
                     "vega": 0.0, "theta": 0.0}
            else:
                vol = np.maximum(p.iv + np.asarray(dvol), 0.01)
                T = (p.dias - np.asarray(dt)) / AÑO
                g = bsm(p.tipo, S, p.strike, T, vol,
                        self.r, self.q)
            n = p.cantidad * self.mult
            tot["valor"] = tot["valor"] + n * g["precio"]
            for k in GRIEGAS:
                tot[k] = tot[k] + n * g[k]
        return tot

    def coste(self):
        return sum(p.cantidad * self.mult * p.prima
                   for p in self.patas)

    def pnl(self, S, dvol=0.0, dt=0.0):
        return self.griegas(S, dvol, dt)["valor"] - self.coste()

    def vencimiento(self):
        """Días hasta el primer vencimiento de la posición."""
        return min(p.dias for p in self.patas if p.tipo != "S")

    def payoff(self, S):
        return self.pnl(S, dt=self.vencimiento())
```

Dos simplificaciones deliberadas. La pata `S` no tiene theta: se ignora el coste de financiar el subyacente y el
dividendo durante unos días. Y la volatilidad de cada pata se mueve en paralelo con `dvol`: la herramienta no tiene
superficie. Las dos se discuten al final, contra los datos.

## Pruebas antes de los datos

Una herramienta de riesgo que nadie ha probado es peor que no tenerla: da números con la misma seguridad cuando
están mal. Las pruebas siguientes son funciones `test_*` con `assert`, el formato de pytest; aquí se ejecutan en un
bucle para que la salida sea determinista, y en un proyecto irían en `test_opciones.py` y se lanzarían con
`pytest -q`. Cada una comprueba una propiedad que no depende de los datos:

- **Paridad put-call**: $C - P = S e^{-qT} - K e^{-rT}$ hasta 10⁻⁹ (lección 3.4.1).
- **Sintético**: call comprada y put vendida del mismo strike es un forward. Gamma y vega nulas en cualquier precio y
  delta igual a $100\,e^{-qT}$.
- **Diferencias finitas**: delta y gamma frente a derivadas centrales con medio punto de índice; vega frente a la
  diferencia entre ±0,5 puntos de volatilidad (un punto en total); theta frente a la diferencia entre ±0,5 días. Comprueba
  la fórmula y las unidades a la vez.
- **Payoff**: al vencimiento, el P&L de una put comprada es (máx(K − S, 0) − prima) × 100.
- **QuantLib**: las cinco magnitudes de la celda anterior.

```python
def test_paridad_put_call():
    S, K, T, r, q = 5000.0, 4900.0, 0.25, 0.04, 0.015
    c = bsm("C", S, K, T, 0.2, r, q)["precio"]
    p = bsm("P", S, K, T, 0.2, r, q)["precio"]
    assert abs(c - p - (S * np.exp(-q * T)
                        - K * np.exp(-r * T))) < 1e-9


def test_sintetico_no_tiene_gamma_ni_vega():
    pos = Posicion([Pata("C", 1, 5000, 30, 0.2),
                    Pata("P", -1, 5000, 30, 0.2)], 0.04, 0.015)
    g = pos.griegas(np.array([4500.0, 5000.0, 5500.0]))
    assert np.allclose(g["gamma"], 0) and np.allclose(g["vega"], 0)
    assert np.allclose(g["delta"], 100 * np.exp(-0.015 * 30 / AÑO))


def test_griegas_frente_a_diferencias_finitas():
    pos = Posicion([Pata("P", -2, 4800, 45, 0.21, 40.0),
                    Pata("C", 1, 5200, 45, 0.15, 25.0)], 0.04, 0.015)
    S, h = 5000.0, 0.5
    g = pos.griegas(S)
    v = lambda s=S, dv=0.0, dt=0.0: pos.griegas(s, dv, dt)["valor"]
    cerca = lambda a, b: np.isclose(a, b, rtol=1e-4)
    # derivadas centrales: 1 punto de índice, 1 punto de vol, 1 día
    assert cerca((v(S + h) - v(S - h)) / (2 * h), g["delta"])
    assert cerca((v(S + h) - 2 * v(S) + v(S - h)) / h**2, g["gamma"])
    assert cerca(v(dv=0.005) - v(dv=-0.005), g["vega"])
    assert cerca(v(dt=0.5) - v(dt=-0.5), g["theta"])


def test_payoff_al_vencimiento():
    pos = Posicion([Pata("P", 1, 4900, 30, 0.2, 50.0)], 0.04, 0.0)
    S = np.array([4000.0, 4900.0, 5500.0])
    assert np.allclose(pos.payoff(S),
                       100 * (np.maximum(4900 - S, 0) - 50))


def test_coincide_con_quantlib():
    for k in ref:
        assert abs(float(prueba[k]) - ref[k]) < 1e-6, k


for nombre, f in sorted(globals().copy().items()):
    if nombre.startswith("test_") and callable(f):
        f()
        print("ok ", nombre)
```
```output
ok  test_coincide_con_quantlib
ok  test_griegas_frente_a_diferencias_finitas
ok  test_paridad_put_call
ok  test_payoff_al_vencimiento
ok  test_sintetico_no_tiene_gamma_ni_vega
```

```bash
pytest -q test_opciones.py
```

## Un iron condor real: la cadena del 19 de febrero de 2020

Con la herramienta probada, una posición de los datos. La cadena diaria `opciones3_cadena.parquet` tiene bid, ask y la
volatilidad implícita verdadera del modelo (`iv_modelo`) de cada opción de ORX. El 19 de febrero de 2020 es el máximo
del índice antes de la caída plantada de la pandemia (−32 % hasta el 23 de marzo, README de los datos): un buen día
para vender un iron condor y ver qué dice la herramienta antes y durante la crisis. Las patas se compran al ask y se
venden al bid, con la volatilidad de su strike:

```python
ind = pd.read_csv("opciones3_indice.csv", parse_dates=["fecha"])
ind = ind.set_index("fecha")
cad = pd.read_parquet("opciones3_cadena.parquet")
cad["medio"] = (cad["bid"] + cad["ask"]) / 2

hoy, venc = pd.Timestamp("2020-02-19"), pd.Timestamp("2020-03-20")
S0 = ind.at[hoy, "cierre"]
r0 = ind.at[hoy, "tipo_1m"] / 100
q0 = ind.at[hoy, "rentabilidad_dividendo"] / 100
dias = (venc - hoy).days
dia = cad[(cad["fecha"] == hoy) & (cad["vencimiento"] == venc)]
dia = dia.set_index(["tipo", "strike"])


def pata(tipo, strike, cantidad, fecha_cadena=dia):
    fila = fecha_cadena.loc[(tipo, strike)]
    precio = fila["ask"] if cantidad > 0 else fila["bid"]
    return Pata(tipo, cantidad, strike, dias, fila["iv_modelo"],
                precio)


condor = Posicion([pata("P", 2800, 1), pata("P", 2900, -1),
                   pata("C", 3050, -1), pata("C", 3100, 1)], r0, q0)
print(f"ORX {S0}  r {r0:.4f}  q {q0:.4f}  {dias} días")
pd.DataFrame(condor.patas).drop(columns="dias")
```
```output
ORX 2995.33  r 0.0055  q 0.0168  30 días
  tipo  cantidad  strike      iv  prima
0    P         1    2800  0.1601    4.5
1    P        -1    2900  0.1288   11.8
2    C        -1    3050  0.0762    6.9
3    C         1    3100  0.0700    1.0
```

ORX cierra en 2995,33, el tipo a un mes es del 0,55 % y la rentabilidad por dividendo del 1,68 %. La volatilidad
implícita va del 16,0 % del strike 2800 al 7,0 % del 3100: el skew de un índice en calma (lección 3.4.5). El condor
vende la put 2900 y la call 3050 (deltas de −0,19 y +0,20) y compra la put 2800 y la call 3100 como alas:

```python
filas = {}
for p in condor.patas:
    g = Posicion([p], r0, q0).griegas(S0)
    filas[f"{p.cantidad:+.0f} {p.tipo} {p.strike:.0f}"] = g
tabla = pd.DataFrame(filas).T.astype(float)
tabla.loc["condor"] = tabla.sum()
tabla["delta_usd"] = tabla["delta"] * S0
print(f"Crédito cobrado: {-condor.coste():.0f} USD")
tabla.round(2)
```
```output
Crédito cobrado: 1320 USD
             valor  delta  gamma    vega  theta  delta_usd
+1 P 2800   436.58  -7.04   0.10  115.78 -31.54  -21089.86
-1 P 2900 -1193.66  19.21  -0.25 -234.45  52.08   57527.59
-1 C 3050  -699.47 -19.48  -0.42 -236.50  28.23  -58355.88
+1 C 3100    96.98   4.02   0.14   74.31  -8.30   12047.03
condor    -1359.57  -3.30  -0.43 -280.86  40.48   -9871.12
```

Se cobran 13,2 puntos, 1.320 USD por condor. La tabla separa lo que aporta cada pata. La vega del condor, −280,86 USD
por punto, sale casi entera de las dos patas vendidas (−234 y −237), compensadas en parte por las alas; la theta,
+40,48 USD al día, es lo que la posición cobra cada día si nada se mueve. La delta agregada, −3,30 USD por punto
(−9.871 USD de delta en dólares, el equivalente a estar corto de 3,3 unidades de ORX), es casi neutral: las deltas de
las patas, de ±19 USD por punto, se compensan. La columna `valor` es el valor de mercado con signo: −1.359,57 USD,
algo más que el crédito cobrado porque se entró vendiendo al bid y comprando al ask.

### Payoff y P&L antes del vencimiento

```python
S = np.arange(2600.0, 3301.0, 5.0)
curvas = pd.DataFrame({"vencimiento": condor.payoff(S),
                       "hoy": condor.pnl(S),
                       "en 15 días": condor.pnl(S, dt=15)},
                      index=S)
alv = curvas["vencimiento"]
print(f"Máx. ganancia {alv.max():.0f} · máx. pérdida {alv.min():.0f}")
# puntos de equilibrio: interpolación lineal donde cambia el signo
i = np.flatnonzero(np.diff(np.sign(alv.to_numpy())))
eq = [np.interp(0, alv.iloc[[j, j + 1]].sort_values(),
                alv.iloc[[j, j + 1]].sort_values().index) for j in i]
print("Equilibrio al vencimiento:", np.round(eq, 1))
curvas.loc[[2700, 2850, 2900, 2950, 3000, 3050, 3100]].round(0)
```
```output
Máx. ganancia 1320 · máx. pérdida -8680
Equilibrio al vencimiento: [2886.8 3063.2]
        vencimiento     hoy  en 15 días
2700.0      -8680.0 -7290.0     -8113.0
2850.0      -3680.0 -2911.0     -3186.0
2900.0       1320.0 -1373.0     -1091.0
2950.0       1320.0  -306.0       305.0
3000.0       1320.0   -60.0       707.0
3050.0       1320.0  -813.0      -303.0
3100.0      -3680.0 -2107.0     -2243.0
```

La figura de la cabecera dibuja las tres columnas en todo el rango. Al vencimiento, el condor gana el crédito entero
(+1.320 USD) entre 2900 y 3050 y pierde como máximo 8.680 USD por debajo de 2800 (100 puntos de anchura menos 13,2 de
crédito) y 3.680 por encima de 3100 (50 de anchura menos 13,2). Los puntos de equilibrio son strike ± crédito:
2886,8 y 3063,2. Las alas no son simétricas porque los strikes se eligieron por delta y la volatilidad de la put
vendida (12,9 %) es casi el doble que la de la call vendida (7,6 %).

La curva de hoy es otra cosa: −60 USD con ORX en 3000, la horquilla pagada al entrar, y por debajo de cero en todo el
rango. Ganar con un condor vendido es cuestión de tiempo: a 15 días, con la misma volatilidad, la curva ya sube a
+707 USD en 3000. Esa es la theta, vista como desplazamiento de la curva entera.

### Griegas agregadas a lo largo del precio

Una griega en un solo punto no dice qué pasa si el índice se mueve. La misma función, con un array de precios, da el
perfil completo:

```python
Sg = np.array([2800.0, 2900.0, 2975.0, 3050.0, 3150.0])
perfil = pd.concat(
    {f"{d} días": pd.DataFrame(condor.griegas(Sg, dt=dias - d),
                               index=Sg)[list(GRIEGAS)]
     for d in (30, 5)}, axis=1)
perfil.round(1)
```
```output
       30 días                    5 días
         delta gamma   vega theta  delta gamma   vega  theta
2800.0    33.1   0.1  117.3 -39.1   49.0   0.7  122.1 -194.0
2900.0    27.2  -0.2 -104.7  10.2   47.0  -0.8 -112.0  141.1
2975.0     5.1  -0.4 -259.9  37.6    4.2  -0.2  -35.2   44.1
3050.0   -23.2  -0.2 -181.9  27.0  -47.2  -1.3 -123.6   91.2
3150.0   -14.1   0.2  126.2 -13.9   -2.6   0.2   22.3  -15.9
```

*Fig. — Griegas agregadas del iron condor frente al nivel de ORX, a 30 y a 5 días del vencimiento (delta en USD por punto, vega en USD por punto de volatilidad): la delta se vuelve más empinada cerca de los strikes vendidos y la vega se concentra en ellos.* Dos paneles con ORX de 2700 a 3250 en horizontal. Arriba, la delta en USD por punto: a 30 días es suave, entre +33 y −25; a 5 días (destacada) llega a +70 en 2850 y a −47 entre 3050 y 3100, y es casi cero en el centro (−1,9 en 3000). Abajo, la vega en USD por punto de volatilidad: a 30 días, −282 en 3000 y positiva fuera de las alas (hasta +180 en 2750 y +126 en 3150); a 5 días es menor y se concentra junto a cada strike.

A 30 días, la delta del condor se mueve con suavidad entre +33 y −25 USD por punto. A 5 días del vencimiento el perfil
se endurece: +70 en 2850 y −47 en 3050, con casi cero en el centro. Es la gamma de las patas vendidas concentrándose
alrededor de sus strikes: cinco días antes del vencimiento, una caída de 125 puntos (un 4 %) lleva la delta de +4 a
+70 USD por punto, el equivalente a 70 unidades del índice. La vega cambia de signo con el precio: en el centro la posición está corta de
volatilidad (−282 USD por punto en 3000), pero fuera de las alas está larga (+180 en 2750). Con el índice muy por
debajo de 2800, más volatilidad significa más probabilidad de volver, y el spread de puts vale menos. La rejilla de
escenarios lo confirmará.

## Explicar el P&L de un día

El control diario de una cartera de opciones es explicar el P&L: descomponer lo que se ganó o perdió con las griegas
de la víspera y ver cuánto queda sin explicar. Con la aproximación de Taylor de segundo orden en el precio y de
primero en la volatilidad y el tiempo,

$$
\Delta V \approx \delta\,\Delta S + \tfrac{1}{2}\,\Gamma\,(\Delta S)^2 + \nu\,\Delta\sigma + \theta\,\Delta t
$$

con $\Delta\sigma$ en puntos y $\Delta t$ en días naturales. La función usa la volatilidad de cada strike en cada
fecha (no un desplazamiento común) y compara con el P&L real a precios medios:

```python
def explica(pos, fecha, fecha_cad):
    """P&L de un día de la posición: real (a precios medios) y
    su aproximación de Taylor con las griegas de la víspera."""
    s1 = ind.at[fecha, "cierre"]
    dt = (fecha - hoy).days
    real = 0.0
    comp = dict.fromkeys(["delta", "gamma", "vega", "theta"], 0.0)
    for p in pos.patas:
        g = bsm(p.tipo, S0, p.strike, p.dias / AÑO, p.iv, r0, q0)
        fila = fecha_cad.loc[(p.tipo, p.strike)]
        n = p.cantidad * pos.mult
        dS, dvol = s1 - S0, (fila["iv_modelo"] - p.iv) * 100
        comp["delta"] += n * g["delta"] * dS
        comp["gamma"] += n * 0.5 * g["gamma"] * dS**2
        comp["vega"] += n * g["vega"] * dvol
        comp["theta"] += n * g["theta"] * dt
        real += n * (fila["medio"] - dia.at[(p.tipo, p.strike),
                                            "medio"])
    comp["suma"] = sum(comp.values())
    comp["real"] = real
    return pd.Series(comp).round(0)


f1 = pd.Timestamp("2020-02-20")
cad1 = cad[(cad["fecha"] == f1) & (cad["vencimiento"] == venc)]
cad1 = cad1.set_index(["tipo", "strike"])
print(f"ORX {S0} a {ind.at[f1, 'cierre']}")
explica(condor, f1, cad1)
```
```output
ORX 2995.33 a 2998.58
delta    -11.0
gamma     -2.0
vega    -406.0
theta     40.0
suma    -379.0
real    -372.0
dtype: float64
```

*Fig. — P&L de un día del iron condor (19 a 20-02-2020) explicado con las griegas de la víspera: el índice sube 3 puntos y la posición pierde por la vega.* Barras horizontales en USD: delta −11, gamma −2, vega −406 (destacada), theta +40; la suma de los cuatro términos, −379, frente al P&L real a precios medios, −372. ORX pasa de 2995,33 a 2998,58 y la volatilidad implícita de los cuatro strikes sube entre 1,1 y 1,7 puntos.

El 20 de febrero ORX sube 3,25 puntos y el condor pierde 372 USD. La delta (−11 USD) y la gamma (−2) apenas cuentan,
la theta aporta +40, y la vega explica casi todo: −406 USD, porque la volatilidad implícita de los cuatro strikes
subió entre 1,1 y 1,7 puntos con el índice casi plano. La suma, −379, se queda a 7 USD del P&L real. Es la primera
señal de la crisis en los datos: el índice no se ha movido, pero la superficie sí. Una posición corta de vega puede
perder dinero un día de subida del índice.

<aside class="callout" data-kind="nota">

Lo que queda sin explicar es tan informativo como lo explicado. Si un día el residuo es grande, o hay un salto (la
aproximación de segundo orden no vale para movimientos de varias desviaciones), o la superficie ha cambiado de forma
(skew, estructura temporal), o hay un error en los datos o en la posición. En una mesa, un residuo inexplicado se
investiga el mismo día.

</aside>

## Rejilla de escenarios: precio × volatilidad × tiempo

Las griegas valen para movimientos pequeños. Para los grandes hay que revalorar la posición entera en cada escenario.
La rejilla combina movimientos del índice, desplazamientos paralelos de la volatilidad y días transcurridos en una
malla tridimensional y la evalúa con una sola llamada a `pnl`:

```python
def rejilla(pos, S0, movs, dvols, dts):
    """P&L en USD para cada combinación (broadcasting 3D)."""
    m, v, t = np.meshgrid(movs, dvols, dts, indexing="ij")
    pnl = pos.pnl(S0 * (1 + m), v, t)
    idx = pd.MultiIndex.from_arrays(
        [m.ravel(), v.ravel(), t.ravel()],
        names=["mov", "dvol", "dt"])
    return pd.Series(pnl.ravel(), idx)


movs = np.array([-0.20, -0.10, -0.05, -0.02, 0, 0.02, 0.05])
dvols = np.array([-0.03, 0.0, 0.05, 0.10, 0.25])
esc = rejilla(condor, S0, movs, dvols, [0, 10, 29])
tabla10 = esc.xs(10, level="dt").unstack("dvol")
tabla10.index = [f"{x:+.0%}" for x in tabla10.index]
tabla10.columns = [f"{x * 100:+.0f} pt" for x in tabla10.columns]
tabla10.round(0)
```
```output
       -3 pt   +0 pt   +5 pt  +10 pt  +25 pt
-20% -8677.0 -8677.0 -8674.0 -8652.0 -8268.0
-10% -8269.0 -7885.0 -7192.0 -6590.0 -5663.0
-5%  -3359.0 -3260.0 -3202.0 -3305.0 -3949.0
-2%    221.0  -237.0 -1065.0 -1837.0 -3255.0
+0%   1043.0   433.0  -691.0 -1541.0 -2991.0
+2%   -132.0  -661.0 -1269.0 -1773.0 -2875.0
+5%  -3591.0 -3222.0 -2744.0 -2590.0 -2912.0
```

*Fig. — Rejilla de escenarios del iron condor a 10 días: P&L en USD por movimiento de ORX y desplazamiento paralelo de la volatilidad, en cuatro tramos.* Mapa de 19 movimientos de ORX (de −12 % a +6 %) por 8 cambios de volatilidad (de −5 a +30 puntos), a 10 días. Solo 8 de las 152 celdas ganan: movimientos de −2 % a +2 % con la volatilidad igual o más baja. 19 pierden menos de 2000 USD, 84 entre 2000 y 5000 y 41 más de 5000, sobre todo con caídas del 7 % o más. Destacada, la celda más cercana a lo que pasó el 28-02-2020 (−2 % y +10 puntos): −1837 USD.

La tabla y la figura son la rejilla a 10 días. Con el índice y la volatilidad sin cambios el condor gana 433 USD (diez
días de theta menos la horquilla); si además la volatilidad baja 3 puntos, 1.043. Basta con que suba 5 puntos para
perder 691 con el índice quieto, y 2.991 si sube 25. En la figura solo 8 de las 152 celdas ganan: movimientos de −2 %
a +2 % con la volatilidad igual o más baja. Es el perfil de una venta de prima: la ganancia es estrecha y la pérdida,
ancha.

La tabla enseña también la vega positiva de las alas. Con el índice un 10 % más abajo, la pérdida es de 7.885 USD con
la volatilidad sin cambios y de 5.663 con 25 puntos más: el spread de puts está tan dentro del dinero que más
volatilidad lo abarata. Con −20 % la pérdida es la máxima, 8.677, haga lo que haga la volatilidad.

### La rejilla frente a lo que pasó

La prueba de una rejilla de escenarios es compararla con la realidad. Tres fechas de la caída: para cada una, el
escenario de la rejilla con el movimiento real del índice y un desplazamiento paralelo igual al cambio de la
volatilidad en el dinero; la misma posición revalorada con la volatilidad que tenía ese día cada uno de sus strikes;
y el P&L real a precios medios.

```python
from dataclasses import replace


def marca_real(pos, fecha):
    """P&L real a precios medios de la cadena de esa fecha."""
    g = cad[(cad["fecha"] == fecha) & (cad["vencimiento"] == venc)]
    g = g.set_index(["tipo", "strike"])["medio"]
    return sum(p.cantidad * pos.mult * g[(p.tipo, p.strike)]
               for p in pos.patas) - pos.coste()


atm0 = dia.at[("P", 3000), "iv_modelo"]
filas = []
for f in ["2020-02-28", "2020-03-09", "2020-03-16"]:
    f = pd.Timestamp(f)
    s1 = ind.at[f, "cierre"]
    g = cad[(cad["fecha"] == f) & (cad["vencimiento"] == venc)]
    g = g.set_index(["tipo", "strike"])
    # strike listado más cercano al dinero
    k = g.xs("P").index[np.abs(g.xs("P").index - s1).argmin()]
    datm = g.at[("P", k), "iv_modelo"] - atm0
    dt = (f - hoy).days
    # la misma posición con la iv que tiene ese día cada strike
    con_iv = Posicion([replace(p, iv=g.at[(p.tipo, p.strike),
                                          "iv_modelo"])
                       for p in condor.patas], r0, q0)
    filas.append({"fecha": f.date(), "mov": s1 / S0 - 1,
                  "dvol_atm": datm * 100,
                  "rejilla": condor.pnl(s1, datm, dt),
                  "iv_por_strike": con_iv.pnl(s1, 0.0, dt),
                  "real": marca_real(condor, f)})
pd.DataFrame(filas).set_index("fecha").round(
    {"mov": 3, "dvol_atm": 1, "rejilla": 0, "iv_por_strike": 0,
     "real": 0})
```
```output
              mov  dvol_atm  rejilla  iv_por_strike    real
fecha
2020-02-28 -0.021       8.8  -1753.0        -1258.0 -1258.0
2020-03-09 -0.064      37.9  -4611.0        -4126.0 -4130.0
2020-03-16 -0.326      62.9  -8679.0        -8679.0 -9070.0
```

El 28 de febrero ORX ha caído un 2,1 % y la volatilidad en el dinero ha subido 8,8 puntos. La rejilla predice −1.753
USD; con la volatilidad de cada strike, −1.258, igual que el P&L real. La diferencia no es del modelo de valoración
sino del escenario: la volatilidad «en el dinero» de ese día es la del strike 2950, más bajo que el 3000 del día de
entrada y con más volatilidad por el skew. Los strikes de la posición subieron entre 7,1 y 8,7 puntos, no 8,8. Un
escenario con desplazamiento paralelo mezcla el cambio de la superficie con el deslizamiento a lo largo del skew
(*sticky strike* frente a *sticky moneyness*, lección 3.4.5). El 9 de marzo pasa lo mismo: −4.611 frente a −4.130.

El 16 de marzo, con ORX un 32,6 % más abajo, la rejilla y la revaloración por strike coinciden en la pérdida máxima,
−8.679, pero el P&L a precios medios es de −9.070: 391 USD más que la pérdida máxima posible al vencimiento. La put
2900 cotiza 874,2/894,1 y la 2800, 771,4/789,1: horquillas de casi 20 puntos, con ruido en el medio. El medio del spread
de puts es 103,9 puntos, por encima de los 100 de anchura, algo que no puede durar hasta el vencimiento. Cerrar ese día
a mercado (comprar la 2900 al ask y vender la 2800 al bid) costaría 122,7 puntos. La lección 3.5.5 vuelve sobre esta
decisión.

<aside class="callout" data-kind="riesgo">

Una rejilla con desplazamientos paralelos es el mínimo, no el máximo. No recoge que en una caída la volatilidad sube
más en los strikes bajos, ni saltos de un día (el 16 de marzo de 2020 el índice cae un 12,8 % en una sesión), ni la
liquidez: en crisis las horquillas se abren y la valoración a medios deja de ser un precio ejecutable. Los escenarios
de estrés de verdad se construyen con episodios históricos de la superficie entera (lección 3.6.1).

</aside>

## Lo que falta y cómo extenderla

La herramienta cabe en unas 150 líneas porque deja fuera muchas cosas, y conviene saber cuáles:

- **Superficie de volatilidad.** `dvol` es un número; una versión mejor recibiría una función de strike y vencimiento
  (por ejemplo, el SVI de la lección 3.4.5) y revaloraría cada pata con su volatilidad en el escenario.
- **Opciones americanas y dividendos discretos.** Las opciones sobre acciones necesitan el árbol binomial (3.4.2) y su
  frontera de ejercicio; la lección 3.5.5 lo usa con la acción KRN.
- **Varios subyacentes.** Las griegas de patas sobre subyacentes distintos no se suman sin un modelo de correlación
  (beta, o escenarios conjuntos).
- **Costes.** El P&L se calcula a precios medios; la horquilla de salida es un coste real, mayor en crisis.

Con esas limitaciones a la vista, la herramienta ya sirve para lo que se usa en el resto del módulo: dibujar el
payoff de cualquier estructura, vigilar las griegas de una cartera y someterla a escenarios antes de abrirla.

## Ejercicios

1. **Cobertura delta con la pata S.** Añade al condor del 19-02-2020 una pata `S` con la cantidad que deja la delta
   agregada en cero y comprueba con `griegas` que la delta es nula en 2995,33 y que la gamma y la vega no cambian.

**Solución.**

La delta del condor es −3,30 USD por punto; una pata `S` aporta cantidad × 100 por punto, así que hay que comprar
0,033 unidades de contrato (3,3 unidades del índice). Gamma, vega y theta no cambian porque el subyacente no las tiene.

```python
d0 = condor.griegas(S0)["delta"]
cubierto = Posicion(condor.patas + [Pata("S", -d0 / 100, prima=S0)],
                    r0, q0)
g1 = cubierto.griegas(S0)
print({k: round(float(g1[k]), 4) for k in GRIEGAS})
```
```output
{'delta': 0.0, 'gamma': -0.4257, 'vega': -280.8596, 'theta': 40.4759}
```

2. **Un calendario.** Con la cadena del mismo día, vende la put 2950 del vencimiento del 20-03-2020 y compra la del
   17-04-2020. Calcula su payoff en el primer vencimiento (con la pata larga valorada con su volatilidad) y su vega
   agregada. ¿Por qué el payoff de un calendario depende de la volatilidad y el de un condor no?

**Solución.**

La pata larga sigue viva en el primer vencimiento, así que su valor depende de la volatilidad que tenga entonces. La
vega del calendario es positiva (+145,2 USD por punto hoy): la opción lejana tiene más vega que la cercana. En el
primer vencimiento, con ORX en 2950, el calendario gana 2.180 USD con la volatilidad sin cambios y 3.807 si sube 5
puntos; en 2800 y 3100 pierde entre 1.297 y 1.448 USD (675 y 693 con 5 puntos más). En un condor todas las patas
vencen a la vez y el payoff solo depende del precio.

```python
v2 = pd.Timestamp("2020-04-17")
lejos = cad[(cad["fecha"] == hoy) & (cad["vencimiento"] == v2)]
lejos = lejos.set_index(["tipo", "strike"])
f_l = lejos.loc[("P", 2950.0)]
cal = Posicion([pata("P", 2950, -1),
                Pata("P", 1, 2950, (v2 - hoy).days, f_l["iv_modelo"],
                     f_l["ask"])], r0, q0)
Sc = np.array([2800.0, 2900.0, 2950.0, 3000.0, 3100.0])
print("payoff:", cal.payoff(Sc).round(0))
print("payoff con +5 pt:",
      cal.pnl(Sc, 0.05, cal.vencimiento()).round(0))
print("vega hoy:", round(float(cal.griegas(S0)["vega"]), 1))
```
```output
payoff: [-1297.   214.  2180.   174. -1448.]
payoff con +5 pt: [-675. 1661. 3807. 1680. -693.]
vega hoy: 145.2
```

3. **Escenario con skew.** Sustituye el desplazamiento paralelo por uno que dependa del strike: `dvol` de cada pata
   igual a 0,088 + 0,0003 × (3000 − K) para el 28-02-2020. ¿Se acerca el P&L al real (−1.258 USD)? ¿Qué parámetro
   estarías ajustando a posteriori?

**Solución.**

Con ese desplazamiento la pata 2800 sube 14,8 puntos y la 3100 5,8: el escenario exagera el empinamiento del skew,
porque en los datos las cuatro patas subieron entre 7,1 y 8,7 puntos. El coeficiente 0,0003 se ha elegido después de
ver el resultado: es ajustar el escenario al dato. Un escenario de estrés útil fija la forma del desplazamiento con
episodios pasados (por ejemplo, la pendiente media del cambio de volatilidad por strike en las caídas de la muestra) y
la aplica a fechas que no se usaron para estimarla.

## Autoevaluación

1. La herramienta devuelve para un iron condor una vega de −280,86. ¿Qué significa, con las convenciones de la lección?

   - A. Que la posición pierde 280,86 USD si la volatilidad implícita de todas las patas sube un punto (del 12 % al 13 %, por ejemplo)
   - B. Que pierde 280,86 USD si la volatilidad sube del 12 % al 112 %
   - C. Que pierde 280,86 puntos del índice por día
   - D. Que pierde 2,8086 USD por contrato y por punto

   *Respuesta A: Que la posición pierde 280,86 USD si la volatilidad implícita de todas las patas sube un punto (del 12 % al 13 %, por ejemplo).* La vega de `bsm` está dividida entre 100 (por punto de volatilidad, no por unidad) y la posición la multiplica por cantidad × 100 USD por punto del índice. Es una sensibilidad lineal: para cambios grandes de volatilidad hay que revalorar (la rejilla), no multiplicar.

2. El 20-02-2020 ORX sube 3,25 puntos y el condor vendido pierde 372 USD. ¿Qué término de la explicación por griegas da cuenta de casi todo?

   - A. La delta
   - B. La gamma
   - C. La vega: la volatilidad implícita de los cuatro strikes subió entre 1,1 y 1,7 puntos con el índice casi plano
   - D. La theta

   *Respuesta C: La vega: la volatilidad implícita de los cuatro strikes subió entre 1,1 y 1,7 puntos con el índice casi plano.* Delta −11 y gamma −2 USD son pequeñas porque el índice apenas se movió; theta suma +40. La vega aporta −406 USD. Una posición vendida de volatilidad puede perder un día de subida del índice si la volatilidad implícita sube.

3. ¿Por qué la prueba de diferencias finitas usa derivadas centrales con un punto de índice, un punto de volatilidad y un día?

   - A. Porque es más rápida que la fórmula analítica
   - B. Porque comprueba a la vez la fórmula y las unidades: si la vega estuviera por unidad de volatilidad o la theta por año, la comparación fallaría por un factor 100 o 365
   - C. Porque QuantLib no da griegas
   - D. Porque las griegas analíticas solo valen en el dinero

   *Respuesta B: Porque comprueba a la vez la fórmula y las unidades: si la vega estuviera por unidad de volatilidad o la theta por año, la comparación fallaría por un factor 100 o 365.* La diferencia central entre dvol = +0,005 y −0,005 es el cambio de valor por un punto de volatilidad; entre dt = +0,5 y −0,5 días, el cambio por un día. Si coinciden con `vega` y `theta`, las unidades son las que dice la documentación.

4. La rejilla con un desplazamiento paralelo igual al cambio de la volatilidad ATM predice −1.753 USD para el 28-02-2020; la posición revalorada con la volatilidad de cada strike da −1.258. ¿De dónde sale la diferencia?

   - A. De un error en la fórmula de Black-Scholes
   - B. De la horquilla
   - C. De que la volatilidad ATM de ese día es la de otro strike, más bajo y con más volatilidad por el skew: los strikes de la posición subieron 7 puntos, no 8,8
   - D. De los dividendos

   *Respuesta C: De que la volatilidad ATM de ese día es la de otro strike, más bajo y con más volatilidad por el skew: los strikes de la posición subieron 7 puntos, no 8,8.* Con el índice un 2,1 % más abajo, el strike en el dinero es el 2950, no el 3000. La volatilidad ATM mezcla el movimiento de la superficie con el desplazamiento a lo largo del skew. La rejilla paralela es una aproximación útil, pero no una superficie.

5. El 16-03-2020 la posición marcada a precios medios pierde 9.070 USD, más que la pérdida máxima al vencimiento (8.680). ¿Qué ocurre?

   - A. La herramienta calcula mal la pérdida máxima
   - B. El medio de dos puts muy dentro del dinero con horquillas de casi 20 puntos no respeta la cota del spread: la valoración a medios en crisis es ruidosa, y cerrar a bid/ask costaría aún más
   - C. El índice siguió cayendo después del vencimiento
   - D. Los dividendos aumentan la pérdida

   *Respuesta B: El medio de dos puts muy dentro del dinero con horquillas de casi 20 puntos no respeta la cota del spread: la valoración a medios en crisis es ruidosa, y cerrar a bid/ask costaría aún más.* La put 2900 cotiza 874,2/894,1 y la 2800, 771,4/789,1: el medio del spread es 103,9 puntos, por encima de los 100 de anchura. A vencimiento (europea, en efectivo) la pérdida queda en 8.680; cerrar ese día comprando a ask y vendiendo a bid costaría 122,7 puntos.

## Ideas clave

- Una herramienta de opciones empieza por las convenciones: precio por unidad y por contrato, T en días naturales / 365, tipos continuos, vega por punto y theta por día natural. Se escriben en el código y se prueban.
- Una posición es una lista de patas; valor, griegas, payoff y P&L son sumas por pata con broadcasting, así que una rejilla de miles de escenarios es una sola llamada.
- Las pruebas (paridad, sintéticos, diferencias finitas, QuantLib) van antes que los datos: detectan errores de signo y de unidades que un gráfico no enseña.
- Explicar el P&L de cada día con las griegas de la víspera es el control diario de una cartera de opciones: lo que no explican las griegas es un salto, un cambio de forma de la superficie o un error.
- Una rejilla con desplazamientos paralelos de volatilidad es una aproximación: no recoge el skew ni la dinámica de la superficie, y la valoración a medios en crisis tiene ruido de horquilla.

## Recursos

### Research

- [Usos de la volatilidad y volatilidad actual: VIX1D](https://pythonparatrading.com/usos-de-la-volatilidad/) · Research-0003 · 6 min

### Documentación

- [Cboe: especificaciones de las opciones sobre el S&P 500 (SPX)](https://www.cboe.com/tradable_products/sp_500/spx_options/specifications/) · cboe.com
- [QuantLib-Python: motores de valoración (AnalyticEuropeanEngine)](https://quantlib-python-docs.readthedocs.io/en/latest/pricing_engines.html) · quantlib-python-docs.readthedocs.io
- [NumPy: broadcasting](https://numpy.org/doc/stable/user/basics.broadcasting.html) · numpy.org
- [pytest: documentación](https://docs.pytest.org/en/stable/) · docs.pytest.org

### Libros

- [Sheldon Natenberg, Option Volatility and Pricing, 2.ª ed. (McGraw-Hill, 2015)](https://www.mheducation.com/highered/mhp/product/option-volatility-pricing-advanced-trading-strategies-techniques-2nd-edition.html) · mheducation.com

### Datos

- [Datos del nivel 3: cadena diaria de opciones de ORX (Parquet)](https://pythonparatrading.com/curso/datos/nivel-3/opciones3_cadena.parquet) · Descarga · opciones3_cadena.parquet

Lección anterior: [3.5.3 · Calendarios, diagonales, ratios, backspreads, risk reversals y jade lizard](https://pythonparatrading.com/curso-python-trading/nivel-3/calendarios-diagonales-ratios-y-risk-reversals/)

Lección siguiente: [3.5.5 · Gestión: ajustes, roll, cierre anticipado, asignación temprana y pin risk](https://pythonparatrading.com/curso-python-trading/nivel-3/gestion-de-posiciones-de-opciones/)
