Lesson 3.5.1 · 75 min

Directional and vertical structures: call, put, covered call, collar and spreads

An options structure is a sum of legs, and everything that matters about it comes from that sum: what it costs to open, what it can gain or lose, where its breakeven points are and how it responds to price, time and volatility. This lesson builds the directional structures and the verticals with the synthetic chain of the ORX index and follows them over 143 expirations.

Fig. — February 2026 6,100/6,300 bull call spread opened on 31 December 2025 for 92.7 points (at the ask and bid): P&L today, in 25 days and at expiry
Level 3 lessons
  1. 3.1 · Futures in depth

    1. 3.1.1 Main futures contracts
    2. 3.1.2 Futures curve: basis, cost of carry, contango and roll yield
    3. 3.1.3 Roll management, margins, SPAN and implied leverage
    4. 3.1.4 Diversified futures systems: Carver's approach
  2. 3.2 · Futures spreads

    1. 3.2.1 Calendar spreads, butterflies and condors
    2. 3.2.2 Intercommodity spreads: crack, spark and crush
    3. 3.2.3 Rate and index spreads: 2s10s, NOB, NQ/ES and Europe versus the U.S.
    4. 3.2.4 Hedge ratios: by notional, by DV01 and by volatility
    5. 3.2.5 Spread data and backtesting: listed, synthetic and legged
    6. 3.2.6 Seasonality of spreads: multi-year analysis, patterns and traps
  3. 3.3 · Advanced forex

    1. 3.3.1 FX microstructure: OTC, ECN, last look and sessions
    2. 3.3.2 Carry and crash risk, momentum and value in currencies; baskets
    3. 3.3.3 Currency risk in multi-currency portfolios; execution via OANDA, IBKR or MT5
  4. 3.4 · Option theory and pricing

    1. 3.4.1 Payoffs, put-call parity and arbitrage bounds
    2. 3.4.2 Binomial trees, Black-Scholes-Merton, Black-76 and Monte Carlo
    3. 3.4.3 Greeks: delta, gamma, vega, theta, rho, vanna, volga and charm
    4. 3.4.4 Implied volatility: Newton-Raphson, Brent, py_vollib and QuantLib
    5. 3.4.5 Smile, skew and term structure; volatility surface with SVI
    6. 3.4.6 Realized vs implied volatility; volatility risk premium; VIX and VSTOXX
    7. 3.4.7 Advanced models: Heston, SABR and local volatility
  5. 3.5 · Option structures in Python

    1. 3.5.1 Directional and vertical structures: call, put, covered call, collar and spreads
    2. 3.5.2 Volatility and range structures: straddles, strangles, condors and butterflies
    3. 3.5.3 Calendars, diagonals, ratios, backspreads, risk reversals and jade lizard
    4. 3.5.4 A custom tool: payoff, aggregate Greeks and scenarios
    5. 3.5.5 Management: adjustments, rolls, early closes, early assignment and pin risk
  6. 3.6 · Systematic volatility and options strategies

    1. 3.6.1 Systematic premium selling: put-write, strangles and their tail risk
    2. 3.6.2 Delta hedging, gamma scalping and dispersion
    3. 3.6.3 VIX and VSTOXX strategies: volatility futures and ETPs
    4. 3.6.4 Options backtesting: data, fills and event adjustments
  7. 3.7 · Crypto derivatives and specific strategies

    1. 3.7.1 Perpetuals: funding mechanics, mark price and liquidations
    2. 3.7.2 Funding arbitrage, basis with futures and cross-exchange arbitrage
    3. 3.7.3 Crypto options, on-chain data and DeFi
    4. 3.7.4 ccxt for trading on multiple exchanges
    5. 3.7.5 MiCA: the EU framework for crypto-assets and their service providers (CASPs)

A structure is a sum of legs

A long call is a directional bet with the loss limited to the premium. Almost everything else done with options is a combination: a long call and a short call with a higher strike, a stock with a short call on top, a long put to set a floor. Module 3.4 valued each option separately; this module puts them together. The idea that makes any structure manageable is linearity: the payoff, the value before expiration and each Greek of a structure are the sum of those of its legs, each with its sign (long or short) and its quantity.

Vstructure(S,t)=∑ini Vi(S,t),Δstructure=∑ini Δi,…V_{\text{structure}}(S, t) = \sum_i n_i \, V_i(S, t), \qquad \Delta_{\text{structure}} = \sum_i n_i \, \Delta_i, \quad \ldots

The structures in this lesson are grouped by what they need to happen:

ViewBullishBearishI already hold the indexLong callBull call or putLong putBear put or callCovered callProtective putCollar
Mermaid source
flowchart LR
  V[View] --> A[Bullish]
  V --> B[Bearish]
  V --> C[I already hold the index]
  A --> A1[Long call]
  A --> A2[Bull call or put]
  B --> B1[Long put]
  B --> B2[Bear put or call]
  C --> C1[Covered call]
  C --> C2[Protective put]
  C --> C3[Collar]
  • Simple directional: a long call or a long put. Risk limited to the premium, uncapped gain (for the put, until the index reaches zero).
  • On the underlying: the index (in practice, an ETF or a future that replicates it) with an option on top. The covered call sells a call and trades the upside above the strike for a premium; the protective put buys a put and pays for a floor; the collar does both and uses the premium of the call to pay for the put.
  • Verticals: two options of the same type and expiration with different strikes, one long and one short. They cap both gain and loss. Depending on which leg is more expensive, they are opened for a debit (you pay) or a credit (you collect).

The rest of the module follows the same template with volatility structures (3.5.2) and multi-expiration structures (3.5.3); lesson 3.5.4 gathers these functions into a tool and 3.5.5 covers the management of open positions.

The chain of December 31, 2025

The data is the daily chain of the fictitious ORX index (opciones3_cadena.parquet), which imitates SPX options: European, cash-settled, a multiplier of 100 USD per point (the same as SPX according to the Cboe specifications), three monthly expirations listed every day. It is synthetic data with a known volatility surface and a planted volatility risk premium (implied volatility trades on average above the volatility realized afterward); nothing measured here is evidence that a structure works in the real market.

Before valuing anything, it is worth fixing the conventions, because every library and every broker uses its own:

QuantityLesson convention
Option priceIn index points per unit; per contract, × 100 USD
Time TCalendar days to expiration / 365 (options expire at the close)
Rate rContinuous, as a decimal fraction, from the curve interpolated at T (tipo_libre in opciones3_verdad)
Dividend qContinuous yield, implied by the forward: q = r − ln(F/S)/T
Volatility σ\sigmaAnnual, as a decimal fraction (0.1258 = 12.58%)
DeltaOption points per index point; per contract, × 100 = USD per point
GammaChange in delta per index point
VegaOption points per volatility point (σ\sigma from 0.1258 to 0.1358)
ThetaOption points per calendar day
IN [01]python
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")
VTO = pd.Timestamp("2026-02-20")
S0 = indice.loc[HOY, "cierre"]


def mercado(vto, fecha=HOY):
    """Continuous rate r and dividend q (decimal fraction) of an
    expiration: r from the curve; q implied by the forward."""
    v = verdad[(verdad.fecha == fecha)
               & (verdad.vencimiento == vto)].iloc[0]
    T = v.dias / 365                   # calendar days / 365
    S = indice.loc[fecha, "cierre"]
    return v.tipo_libre, v.tipo_libre - np.log(v.forward / S) / T


r, q = mercado(VTO)
dias = (VTO - HOY).days
print(f"ORX {S0}  days {dias}  r {r:.4f}  q {q:.4f}")
print("published q:", indice.loc[HOY, "rentabilidad_dividendo"] / 100)
OUT [01]The market on December 31, 2025
ORX 6089.45  days 51  r 0.0368  q 0.0117
published q: 0.011732

The index closes at 6,089.45 and the February expiration is 51 calendar days away. The dividend implied by the forward (0.0117) matches the dividend yield published in opciones3_indice.csv (1.1732% a year): the true forward was built with it, so both routes give the same result. In a real chain that truth column does not exist and q (or the forward directly) is obtained from the put-call parity of the mids (lesson 3.4.1).

Valuation uses the Black-Scholes-Merton model with continuous dividend from lesson 3.4.2, with the analytical Greeks from 3.4.3, written so that they accept a vector of index prices (what is needed to draw a payoff) and so that, with zero time, they return the value at expiration:

IN [02]python
def bsm(S, K, T, sig, tipo, r, q):
    """Price (points), delta, gamma (per point), vega (per
    volatility point) and theta (per calendar day).
    S can be an array; with T = 0 it returns the 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)


fila = cadena[(cadena.fecha == HOY) & (cadena.vencimiento == VTO)
              & (cadena.tipo == "C") & (cadena.strike == 6100)]
fila = fila.iloc[0]
g = bsm(S0, 6100, dias / 365, fila.iv_modelo, "C", r, q)
print(f"bid {fila.bid}  ask {fila.ask}  iv {fila.iv_modelo}")
{k: round(float(x), 4) for k, x in g.items()}
OUT [02]BSM with continuous dividend and its Greeks
bid 118.8  ask 119.8  iv 0.1258
{'precio': 119.3813, 'delta': 0.5235, 'gamma': 0.0014, 'vega': 9.0491, 'theta': -1.3226}

The 6,100 call is quoted at 118.8-119.8 and BSM with its implied volatility gives 119.38, inside the bid-ask spread. The iv_modelo column in the data is the true implied volatility, without noise; in a real chain it is computed by inverting the mid (lesson 3.4.4) and the difference is small compared with the spread. The Greeks, with the units of the table: delta 0.5235 (52 USD per point and contract), vega 9.05 points per volatility point (905 USD per contract) and theta −1.32 points per calendar day (132 USD a day per contract). The check with QuantLib, with flat curves at the same r and q and the same Actual/365 day count, matches to four decimal places; QuantLib's vega() is per unit of volatility (it has to be divided by 100) and thetaPerDay() divides the annual theta by 365:

IN [03]python
import QuantLib as ql

hoy = ql.Date(31, 12, 2025)
ql.Settings.instance().evaluationDate = hoy
dc = ql.Actual365Fixed()
proceso = ql.BlackScholesMertonProcess(
    ql.QuoteHandle(ql.SimpleQuote(S0)),
    ql.YieldTermStructureHandle(ql.FlatForward(hoy, q, dc)),
    ql.YieldTermStructureHandle(ql.FlatForward(hoy, r, dc)),
    ql.BlackVolTermStructureHandle(
        ql.BlackConstantVol(hoy, ql.TARGET(), fila.iv_modelo, dc)))
opcion = ql.EuropeanOption(
    ql.PlainVanillaPayoff(ql.Option.Call, 6100),
    ql.EuropeanExercise(ql.Date(20, 2, 2026)))
opcion.setPricingEngine(ql.AnalyticEuropeanEngine(proceso))
print(f"price {opcion.NPV():.4f}  delta {opcion.delta():.4f}"
      f"  gamma {opcion.gamma():.4f}")
print(f"vega/100 {opcion.vega() / 100:.4f}"
      f"  theta/day {opcion.thetaPerDay():.4f}")
OUT [03]The same Greeks with QuantLib
price 119.3813  delta 0.5235  gamma 0.0014
vega/100 9.0491  theta/day -1.3226

Legs, opening and valuation

A leg is a dictionary with the type, the strike, the quantity (positive long, negative short), the day's bid and ask, the implied volatility, the expiration and its r and q. The index is represented as one more leg, of type "S", with delta 1 and no optionality. Three functions are enough:

  • apertura: what it costs to open the structure, at mid and crossing the bid-ask spread (every buy at the ask, every sell at the bid). This second figure is the one that counts: it is what an order that executes right away would pay. A positive price is a debit; a negative one, a credit.
  • valor: the value of the structure (or any of its Greeks) if the index were worth S on a date. For dates before expiration it assumes that the implied volatility of each leg does not change (sticky strike); what happens when it does change is covered in 3.5.3 and, with price × volatility × time scenarios, in 3.5.4.
  • resumen: the P&L at expiration on a grid from 1 to 3 times the index, with the maximum, the minimum and the breakeven points (the zero crossings, interpolated). If the slope at the end of the grid is not zero, the maximum or the minimum is unlimited.
IN [04]python
def pata(tipo, K, n, vto=VTO, fecha=HOY):
    """A leg: n > 0 long, n < 0 short. Type "S" is the index
    itself (a portfolio that replicates it)."""
    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):
    """Cost in points at mid and crossing the bid-ask spread:
    long legs at the ask, short legs at the bid (> 0 = debit)."""
    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"):
    """Value (or Greek) of the structure if the index is at S
    on `fecha`, with each leg's IV frozen."""
    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
IN [05]python
REJILLA = np.arange(1.0, 3 * S0, 1.0)   # from 1 point to 3 times S0


def resumen(patas):
    """Cost, maximum, minimum and breakeven points at expiration
    (the structure's first one), in points per unit."""
    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]          # slope toward large S
    return {"medio": round(float(medio), 2),
            "real": round(float(real), 2),
            "horquilla": round(float(real - medio), 2),
            "maximo": "unlimited" if final > 1e-9
            else round(float(pnl.max()), 2),
            "minimo": "unlimited" if final < -1e-9
            else round(float(pnl.min()), 2),
            "muertos": [round(float(x), 1) for x in muertos]}


def griegas(patas, S=S0, fecha=HOY):
    """Aggregate Greeks per unit (× 100 USD per contract)."""
    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)}


call = [pata("C", 6100, 1)]
put = [pata("P", 6100, 1)]
for nombre, e in [("call 6100", call), ("put 6100", put)]:
    print(nombre, resumen(e))
    print("   ", griegas(e))
OUT [05]Maximums and breakeven points at expiration
call 6100 {'medio': 119.3, 'real': 119.8, 'horquilla': 0.5, 'maximo': 'unlimited', 'minimo': -119.8, 'muertos': [6219.8]}
    {'delta': 0.523, 'gamma': 0.00139, 'vega': 9.05, 'theta': -1.32}
put 6100 {'medio': 108.8, 'real': 109.3, 'horquilla': 0.5, 'maximo': 5989.7, 'minimo': -109.3, 'muertos': [5990.7]}
    {'delta': -0.475, 'gamma': 0.00139, 'vega': 9.05, 'theta': -0.91}

The long call costs 119.8 at the ask (half a point more than at mid) and that is its maximum loss; its breakeven point is the strike plus what was paid, 6,219.8, 2.1% above the index. The put costs 109.3 and its breakeven point is at 5,990.7; its maximum gain is not unlimited but the strike minus the premium if the index went to zero (the grid starts at 1 point, hence the 5,989.7). Both have the same gamma and the same vega (that is parity: call − put is linear in S) and deltas that differ by exactly e−qTe^{-qT}: 0.523 − (−0.475) = 0.998. The theta of the put (−0.91) is smaller in absolute value than that of the call (−1.32) because with r > q the financing term works in its favor.

The P&L before expiration

The payoff at expiration is a kinked line; before it, the structure is worth the payoff plus the time value, and the curve is smooth. The header figure shows the three curves of the bull call spread from the next section; for the call alone it shows up in a table:

IN [06]python
coste = apertura(call)[1]
S = np.array([5700.0, 5900, 6100, 6300, 6500])
antes = pd.DataFrame({
    "today": valor(call, S, HOY) - coste,
    "in 25 days": valor(call, S, HOY + pd.Timedelta(days=25))
    - coste,
    "expiration": valor(call, S, VTO) - coste}, index=S)
antes.index.name = "ORX"
antes.round(1)
OUT [06]The long call before expiration
        today  in 25 days  expiration
ORX
5700.0 -108.9      -118.0      -119.8
5900.0  -75.0      -101.0      -119.8
6100.0    5.2       -32.7      -119.8
6300.0  138.5       107.7        80.2
6500.0  311.0       292.8       280.2

With the index at 6,100 the call would gain 5.2 points today (it is 10 points from S0 and its delta is 0.52), would lose 32.7 if mid-January arrives at the same level and would lose the whole premium at expiration. That difference is the accumulated theta: in 25 days 38 points are lost at a constant price, somewhat more than 25 × 1.32 suggests because the theta of an at-the-money option grows as expiration approaches (lesson 1.2.5). At 6,300 the call gains 138.5 today and 80.2 at expiration: rising fast is worth more than rising late.

Covered call, protective put and collar

With the index in the portfolio there are three classic structures. Strikes are chosen by delta (the traders' convention: a “25 delta” call, 25D in the tables), with a function that adds the BSM delta of each row to the chain. With 100 points between strikes, the delta obtained is the closest to the target, not the exact one:

IN [07]python
def con_delta(fecha=HOY, vto=VTO):
    """The chain of one expiration with the BSM delta of each row."""
    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()])


hoy = con_delta()
for t, obj in [("C", 0.25), ("P", -0.25), ("P", -0.10)]:
    k = strike_delta(hoy, t, obj)
    fila = hoy[(hoy.tipo == t) & (hoy.strike == k)].iloc[0]
    print(t, obj, "->", k, f"delta {fila.delta:.3f}",
          f"bid {fila.bid} ask {fila.ask}")
OUT [07]Strikes by delta
C 0.25 -> 6300.0 delta 0.214 bid 27.1 ask 27.7
P -0.25 -> 5900.0 delta -0.261 bid 56.5 ask 57.3
P -0.1 -> 5600.0 delta -0.105 bid 22.5 ask 22.9

The 25D call is the 6,300 (delta 0.214, 3.5% from the index) and the 25D put is the 5,900 (−0.261, 3.1% away). The two wings are not at the same distance even though they have the same delta: the index smile has skew (the volatility of the 5,900 put is higher than that of the 6,300 call), and the r − q drift moves the forward upward.

IN [08]python
cubierta = [pata("S", 0, 1), pata("C", 6300, -1)]
protegida = [pata("S", 0, 1), pata("P", 5900, 1)]
collar = [pata("S", 0, 1), pata("P", 5900, 1), pata("C", 6300, -1)]
for nombre, e in [("covered call", cubierta),
                  ("protective put", protegida), ("collar", collar)]:
    res = resumen(e)
    res["real"] = round(float(res["real"] - S0), 2)  # without S0
    res["medio"] = round(float(res["medio"] - S0), 2)
    print(nombre, res)
    print("   ", griegas(e))
OUT [08]Covered call, protective put and collar
covered call {'medio': -27.4, 'real': -27.1, 'horquilla': 0.3, 'maximo': 237.65, 'minimo': -6061.35, 'muertos': [6062.3]}
    {'delta': 0.786, 'gamma': -0.00127, 'vega': -6.63, 'theta': 0.74}
protective put {'medio': 56.9, 'real': 57.3, 'horquilla': 0.4, 'maximo': 'unlimited', 'minimo': -246.75, 'muertos': [6146.8]}
    {'delta': 0.739, 'gamma': 0.00093, 'vega': 7.39, 'theta': -1.0}
collar {'medio': 29.5, 'real': 30.2, 'horquilla': 0.7, 'maximo': 180.35, 'minimo': -219.65, 'muertos': [6119.6]}
    {'delta': 0.525, 'gamma': -0.00034, 'vega': 0.76, 'theta': -0.26}
Fig. — Index alone, 6,300 covered call, 5,900 protective put and 5,900/6,300 collar for February 2026: P&L at expiry with the opening at the bid and ask
  • 6,300 covered call. It collects 27.1 points (at the bid). The gain is capped at 237.65 (the 210.55 points up to the strike plus the premium); below that, it loses the same as the index minus 27.1. The breakeven point drops from 6,089.45 to 6,062.3, 0.4%. Its Greeks say it all: delta 0.786 (less than the index alone), negative gamma and vega and positive theta (+0.74 points a day). It is a short volatility position.
  • 5,900 protective put. It pays 57.3. Below 5,900 the loss stays at a floor of 246.75 points (4.1% of the index: the 189.45 down to the strike plus the premium); above, it gains like the index minus 57.3. It is a long volatility position: positive gamma and vega, theta of −1.0.
  • 5,900/6,300 collar. Both at once. It costs 30.2 points (57.3 − 27.1), loses at most 219.65 and gains at most 180.35. Vega almost disappears (0.76): the long put and the short call offset each other.

Put-call parity ties the three together. Since C−P=e−qTS−e−rTKC - P = e^{-qT}S - e^{-rT}K, the index plus a short call is, with the same strike, a short put plus a bond: the covered call is a put sale under another name. Likewise, the index plus a long put is a long call plus a bond, and the collar is a bull spread plus a bond:

Covered callShort put + bondProtective putLong call + bondCollarVertical + bond
Mermaid source
flowchart LR
  CC[Covered call] --- PV[Short put + bond]
  PP[Protective put] --- CCo[Long call + bond]
  CO[Collar] --- BS[Vertical + bond]

A zero-cost collar is the one that picks the call so that its bid pays for the ask of the put:

IN [09]python
puts = hoy[(hoy.tipo == "P") & (hoy.strike == 5900)].iloc[0]
calls = hoy[hoy.tipo == "C"].set_index("strike")
print("ask of the 5900 put:", puts.ask)
calls.loc[6200:6600, ["bid", "ask", "delta"]].round(3)
OUT [09]A zero-cost collar
ask of the 5900 put: 57.3
          bid    ask  delta
strike
6200.0  63.60  64.50  0.372
6300.0  27.10  27.70  0.214
6400.0   9.30   9.70  0.096
6600.0   0.85   0.95  0.013

The 5,900 put costs 57.3. The 6,300 call only pays 27.1; the 6,200 pays 63.6. A 5,900/6,200 collar is opened for a credit of 6.3 points: it protects below a 3.1% fall in exchange for giving up everything beyond a 1.8% rise. Skew is what makes protection expensive: at the same distance, the put costs more than the call, and to pay for it you have to sell a closer call.

Verticals: debit and credit

A bullish vertical can be built with calls (buy the low strike, sell the high strike: you pay) or with puts (buy the low strike, sell the high strike: you collect). With the same strikes, both pay the same at expiration except for a constant: the bull put is the bull call minus the width, K2−K1K_2 - K_1.

IN [10]python
bull_call = [pata("C", 6100, 1), pata("C", 6300, -1)]
bull_put = [pata("P", 6100, 1), pata("P", 6300, -1)]
for nombre, e in [("bull call 6100/6300", bull_call),
                  ("bull put 6100/6300", bull_put)]:
    print(nombre, resumen(e))
    print("   ", griegas(e))
ancho = 200 * np.exp(-r * dias / 365)
m_call, m_put = apertura(bull_call)[0], apertura(bull_put)[0]
print(f"parity: debit {m_call:.2f}"
      f"  discounted width − credit {ancho + m_put:.2f}")
OUT [10]Bullish verticals: debit and credit
bull call 6100/6300 {'medio': 91.9, 'real': 92.7, 'horquilla': 0.8, 'maximo': 107.3, 'minimo': -92.7, 'muertos': [6192.7]}
    {'delta': 0.309, 'gamma': 0.00012, 'vega': 2.42, 'theta': -0.58}
bull put 6100/6300 {'medio': -106.9, 'real': -105.7, 'horquilla': 1.2, 'maximo': 105.7, 'minimo': -94.3, 'muertos': [6194.3]}
    {'delta': 0.309, 'gamma': 0.00012, 'vega': 2.42, 'theta': -0.6}
parity: debit 91.90  discounted width − credit 92.08

The two 6,100/6,300 verticals have the same Greeks (delta 0.309, vega 2.42; theta differs by 0.02 because of the financing of the in-the-money put) and almost the same profile: the call version costs 92.7 and gains at most 107.3; the put version collects 105.7, gains at most that and loses at most 94.3. Parity holds at mid: the debit of the calls (91.90) is the discounted width minus the credit of the puts (92.08), with a difference of 0.18 points, within the bid-ask spreads. That one “collects” and the other “pays” is accounting: whoever sells the bull put does not receive a gift, they receive a premium they have to give back with high probability. The real difference is in the bid-ask spread: the bull put loses 1.2 points crossing it and the bull call 0.8, because the 6,300 put is in the money and its spread is wider in points.

The header figure shows the bull call spread: at expiration, the kinked line between −92.7 and +107.3 with the breakeven point at 6,192.7 (the low strike plus the debit); today, a smooth, flattened curve, because the two legs offset each other. The Greeks of a vertical are small compared with those of each leg: the vega of the structure is 2.42 versus 9.05 for the long call. It is a position on direction, with little volatility in it.

Bearish verticals are the mirror image, and the usual one in practice is the out-of-the-money credit vertical: sell a 25D put and buy a 10D put to cap the loss.

IN [11]python
bear_put = [pata("P", 6100, 1), pata("P", 5900, -1)]
bear_call = [pata("C", 5900, -1), pata("C", 6100, 1)]
put_credito = [pata("P", 5900, -1), pata("P", 5600, 1)]
for nombre, e in [("bear put 6100/5900", bear_put),
                  ("bear call 5900/6100", bear_call),
                  ("bull put 5900/5600", put_credito)]:
    print(nombre, resumen(e))
    print("   ", griegas(e))
OUT [11]Bearish verticals and out-of-the-money credit
bear put 6100/5900 {'medio': 51.9, 'real': 52.8, 'horquilla': 0.9, 'maximo': 147.2, 'minimo': -52.8, 'muertos': [6047.2]}
    {'delta': -0.214, 'gamma': 0.00046, 'vega': 1.66, 'theta': 0.09}
bear call 5900/6100 {'medio': -147.35, 'real': -146.0, 'horquilla': 1.35, 'maximo': 146.0, 'minimo': -54.0, 'muertos': [6046.0]}
    {'delta': -0.214, 'gamma': 0.00046, 'vega': 1.66, 'theta': 0.07}
bull put 5900/5600 {'medio': -34.2, 'real': -33.6, 'horquilla': 0.6, 'maximo': 33.6, 'minimo': -266.4, 'muertos': [5866.4]}
    {'delta': 0.156, 'gamma': -0.00051, 'vega': -3.25, 'theta': 0.27}

The 6,100/5,900 bear put costs 52.8 and gains up to 147.2; the 5,900/6,100 bear call is its credit twin (it collects 146.0 and loses at most 54.0). The 5,900/5,600 bull put is a different animal: it collects 33.6 and can lose 266.4, almost eight times more. It wins if the index does not fall more than 3.7% in 51 days (breakeven point 5,866.4). Its Greeks are those of a capped volatility sale: negative gamma and vega (−3.25), positive theta (+0.27).

What crossing the bid-ask spread costs

Each leg is opened by crossing its bid-ask spread and, if it is closed before expiration, the spread is crossed again. On the index, with spreads of 1-3% of the mid near the money, a leg costs little; in a multi-leg structure with a small net premium, the relative cost multiplies:

IN [12]python
estructuras = {"call 6100": call, "put 6100": put,
               "covered call": cubierta, "collar": collar,
               "bull call 6100/6300": bull_call,
               "bull put 6100/6300": bull_put,
               "bull put 5900/5600": put_credito}
filas = []
for nombre, e in estructuras.items():
    opciones = [p for p in e if p["tipo"] != "S"]
    medio, real = apertura(opciones)
    tope = resumen(e)["maximo"]
    tope = np.nan if tope == "unlimited" or tope > 1000 else tope
    filas.append({"estructura": nombre, "patas": len(opciones),
                  "prima_medio": medio, "horquilla": real - medio,
                  "usd_contrato": 100 * (real - medio),
                  "pct_prima": 100 * (real - medio) / abs(medio),
                  "pct_max_ganancia": 100 * (real - medio) / tope})
pd.DataFrame(filas).set_index("estructura").round(2)
OUT [12]What crossing the bid-ask spread costs
                     patas  prima_medio  horquilla  usd_contrato  pct_prima  pct_max_ganancia
estructura
call 6100                1        119.3        0.5          50.0       0.42               NaN
put 6100                 1        108.8        0.5          50.0       0.46               NaN
covered call             1        -27.4        0.3          30.0       1.09              0.13
collar                   2         29.5        0.7          70.0       2.37              0.39
bull call 6100/6300      2         91.9        0.8          80.0       0.87              0.75
bull put 6100/6300       2       -106.9        1.2         120.0       1.12              1.14
bull put 5900/5600       2        -34.2        0.6          60.0       1.75              1.79

The at-the-money call loses 0.42% of its premium on opening; the collar, which pays the difference between two premiums, 2.4%; the out-of-the-money credit vertical, 1.75% of what it collects and 1.79% of what it can gain. These are figures from December 31, 2025, a quiet day. Under stress the bid-ask spreads in the data widen (by a factor of 1 + 2.5 × stress in the generator's formula) and in the four-leg structures of lesson 3.5.2 the relative cost is higher. Opening and closing before expiration doubles it; a spread order at mid (combinations trade as a single instrument in real markets) recovers part of it, with no guarantee of execution.

Twelve years of each structure

How do these structures behave if they are opened every month? The history is as simple as possible: on the session after each monthly expiration the structure is opened with the next expiration (about 28-35 days), with strikes chosen by delta, buys at the ask and sells at the bid; it is held to expiration and settled at the index close. P&L is measured in % of the index on the entry day, without interest on the premium. The index legs use the total return index, with dividends.

IN [13]python
vtos = np.sort(cadena.vencimiento.unique())
sesiones = indice.index
por_dia = dict(tuple(cadena.groupby(["fecha", "vencimiento"])))


entradas = []                  # (session, expiration, chain)
for ant, vto in zip(vtos[:-1], vtos[1:]):
    if vto > sesiones[-1]:
        break
    f = sesiones[sesiones.searchsorted(ant, side="right")]
    if (f, vto) in por_dia:
        entradas.append((f, vto, con_delta(f, vto)))


def historia(receta):
    """Opens the structure on the session after each monthly
    expiration with the next expiration, at bid/ask, and
    settles it at the expiration close. P&L in % of the index."""
    filas = []
    for f, vto, d in entradas:
        S, ST = indice.loc[f, "cierre"], indice.loc[vto, "cierre"]
        crece = (indice.loc[vto, "cierre_rt"]
                 / indice.loc[f, "cierre_rt"])
        coste = pago = 0.0
        for tipo, K, n in receta(d):
            if tipo == "S":                # index with dividends
                coste, pago = coste + n * S, pago + n * S * crece
                continue
            o = d[(d.tipo == tipo) & (d.strike == K)].iloc[0]
            coste += n * (o.ask if n > 0 else o.bid)
            pago += n * (max(ST - K, 0) if tipo == "C"
                         else max(K - ST, 0))
        filas.append({"entrada": f, "vto": vto,
                      "pnl": 100 * (pago - coste) / S})
    return pd.DataFrame(filas).set_index("entrada")


def atm(d):
    return strike_delta(d, "C", 0.50)


recetas = {
    "index": lambda d: [("S", 0, 1)],
    "call ATM": lambda d: [("C", atm(d), 1)],
    "put ATM": lambda d: [("P", atm(d), 1)],
    "covered call 25D": lambda d: [
        ("S", 0, 1), ("C", strike_delta(d, "C", 0.25), -1)],
    "protective put 25D": lambda d: [
        ("S", 0, 1), ("P", strike_delta(d, "P", -0.25), 1)],
    "collar 25D": lambda d: [
        ("S", 0, 1), ("P", strike_delta(d, "P", -0.25), 1),
        ("C", strike_delta(d, "C", 0.25), -1)],
    "bull call ATM/25D": lambda d: [
        ("C", atm(d), 1), ("C", strike_delta(d, "C", 0.25), -1)],
    "bull put, same strikes": lambda d: [
        ("P", atm(d), 1), ("P", strike_delta(d, "C", 0.25), -1)],
    "bull put 25D/10D": lambda d: [
        ("P", strike_delta(d, "P", -0.25), -1),
        ("P", strike_delta(d, "P", -0.10), 1)],
}
hist = pd.DataFrame({k: historia(v)["pnl"]
                     for k, v in recetas.items()})
print(len(hist), "entries", hist.index[0].date(), "to",
      hist.index[-1].date())
OUT [13]Twelve years of monthly entries
143 entries 2014-01-21 to 2025-11-24
IN [14]python
tabla = 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()})
tabla.round(2)
OUT [14]Result by structure and worst episode
                        media  mediana  pct_pos  desv   peor  fecha_peor  mejor
index                    1.05     1.33    63.64  5.91 -28.13  2020-02-24  30.06
call ATM                 0.51    -0.44    46.15  3.58  -5.05  2020-05-18  22.43
put ATM                 -0.40    -1.38    27.27  3.47  -7.60  2020-03-23  26.49
covered call 25D         0.65     1.72    68.53  4.38 -27.41  2020-02-24  13.57
protective put 25D       0.67     0.56    56.64  4.69 -11.46  2020-04-20  26.86
collar 25D               0.27     1.07    62.24  2.84 -10.02  2020-04-20  10.37
bull call ATM/25D        0.11     0.06    50.35  1.42  -3.52  2020-05-18   5.94
bull put, same strikes   0.09     0.05    50.35  1.41  -3.58  2020-04-20   5.85
bull put 25D/10D         0.23     0.42    88.11  1.08  -8.50  2020-04-20   1.97
Fig. — 143 monthly trades from 2014 to 2025 (opened at the bid and ask, settled at expiry): mean and worst trade of each structure, in % of the index

With 143 entries, and remembering that this is synthetic data with a planted volatility premium:

  • Buying options loses on average what is paid for volatility. The ATM put loses 0.40% of the index per entry and only wins in 27% of the months. The ATM call gains 0.51% on average because the index rises (1.05% a month with dividends), but its median is negative (−0.44%): it gains a little often and a lot now and then.
  • The covered call trades the right tail for frequency. It wins in 69% of the months (the index, in 64%), with an average somewhat lower than the index (0.65% versus 1.05%) and a best entry of +13.6% versus +30.1%. Its worst entry, that of February 24, 2020, loses 27.4%: almost the same as the index (−28.1%). This is parity in action: a short put protects against nothing.
  • The protective put and the collar do cut the tail, to −11.5% and −10.0%, but their worst entry is not the one of the March crash but the one of April 20, 2020: after the rebound, with volatility still very high, the 25D put was far from the index and the 19% fall to the May expiration landed almost entirely in the unhedged zone.
  • The bull call and the bull put with the same strikes are the same position: averages of 0.11% and 0.09%, the same percentage of positive months (50.35%) and worst entries of −3.5% and −3.6%. The small difference in the average is the bid-ask spread of the in-the-money put.
  • The 25D/10D credit vertical is premium selling with a net. It wins in 88% of the entries, 0.23% on average, and its worst entry loses 8.5%: almost forty months of average gain in one. Its best entry does not exceed 1.97%.

The worst entry of the credit vertical teaches something more than the tail. With strikes chosen by delta, the width between the two puts depends on the day's volatility:

IN [15]python
for dia in ["2020-02-24", "2020-04-20"]:
    f, vto, d = next(e for e in entradas
                     if e[0] == pd.Timestamp(dia))
    k1 = strike_delta(d, "P", -0.25)
    k2 = strike_delta(d, "P", -0.10)
    x = d[d.tipo == "P"].set_index("strike")
    S = indice.loc[f, "cierre"]
    iv_atm = d.iv_modelo[d.strike == atm(d)].iloc[0]
    credito = x.loc[k1, "bid"] - x.loc[k2, "ask"]
    print(f.date(), f"ORX {S:.0f}  ATM iv {iv_atm:.3f}",
          f"strikes {k1:.0f}/{k2:.0f}",
          f"width {100 * (k1 - k2) / S:.1f} %",
          f"credit {credito:.2f}",
          f"at exp {indice.loc[vto, 'cierre']:.0f}")
OUT [15]Two entries of the 25D/10D bull put in 2020
2020-02-24 ORX 2960  ATM iv 0.147 strikes 2850/2750 width 3.4 % credit 9.60 at exp 2124
2020-04-20 ORX 2667  ATM iv 0.489 strikes 2425/2125 width 11.2 % credit 39.00 at exp 2159

On February 24, 2020, with ATM volatility at 0.147, the 25D and 10D puts were 100 points apart (3.4% of the index) and the vertical collected 9.60. The index fell to 2,124 and the maximum was lost: 90.4 points, 3.1%. On April 20, with volatility at 0.489, the same deltas were 300 points apart (11.2%) and the vertical collected 39.00; the index ended at 2,159, below both, and the loss was 227 points, 8.5%. A “defined” maximum loss is as large as the width you choose, and a width set by delta widens precisely when the market is most dangerous. Fixing the width in points or in % of the index, or sizing by the maximum loss and not by the number of contracts, are the usual answers; lesson 3.6.1 covers the sizing of systematic premium selling by margin and by stress.

Common mistakes

  • Valuing at mid. The backtest that opens at mid earns the bid-ask spread it would never collect. Opening at the ask and at the bid is the minimum; closing before expiration costs another spread.
  • Confusing credit with low risk. With the same strikes, debit and credit are the same position. What matters is the maximum loss and the probability of reaching it.
  • Believing that the covered call protects. It cushions with the premium, but the fall of the index stays whole.
  • Forgetting the multiplier and the units. A theta of −1.32 is per unit and calendar day: 132 USD per contract and day, Saturday and Sunday included. A vega of 9.05 is per volatility point.
  • Choosing strikes by delta without looking at the width. The same delta is a different distance when volatility changes; the risk of a vertical is measured in points, not in deltas.
  • Ignoring dividends and the rate. The February forward is 0.35% above the index; with r and q set wrong, the deltas and parity come out wrong (lesson 3.4.1).
  • Stock options are American. A short in-the-money call before a dividend, or a short deep in-the-money put, can be assigned before expiration (lesson 3.5.5); ORX options are European.

Exercises

  1. Build an out-of-the-money bear call credit spread with the 6,300 (short) and 6,400 (long) calls of the February expiration. How much does it collect, how much can it lose, where is its breakeven point and what Greeks does it have?
Solution
IN [16]python
bear_call_otm = [pata("C", 6300, -1), pata("C", 6400, 1)]
print(resumen(bear_call_otm))
griegas(bear_call_otm)
OUT [16]Bear call 6300/6400
{'medio': -17.9, 'real': -17.4, 'horquilla': 0.5, 'maximo': 17.4, 'minimo': -82.6, 'muertos': [6317.4]}
{'delta': -0.118, 'gamma': -0.00047, 'vega': -2.75, 'theta': 0.35}

It collects 17.4 points (27.1 for the 6,300 at the bid minus 9.7 for the 6,400 at the ask; at mid it would be 17.9) and can lose 82.6: the width of 100 minus the credit, 4.7 times what it gains. It wins as long as the index does not rise more than 3.7% up to the breakeven point, 6,317.4. It is a bearish, short volatility position: delta −0.118, negative gamma and vega (−2.75) and positive theta, +0.35 points a day (35 USD per contract). The ratio between what it collects and what it risks is that of any out-of-the-money credit vertical: likely and small against unlikely and large.

  1. Repeat the covered call history with 10D, 25D and 40D calls. How do the average, the percentage of positive months and the worst entry change?
Solution
IN [17]python
cc = {f"cc {int(100 * x)}D": historia(
          lambda d, x=x: [("S", 0, 1),
                          ("C", strike_delta(d, "C", x), -1)])["pnl"]
      for x in (0.10, 0.25, 0.40)}
cc = pd.DataFrame(cc)
pd.DataFrame({"media": cc.mean(), "pct_pos": 100 * (cc > 0).mean(),
              "peor": cc.min(), "mejor": cc.max()}).round(2)
OUT [17]Covered call with three deltas
        media  pct_pos   peor  mejor
cc 10D   0.78    65.03 -27.91  20.83
cc 25D   0.65    68.53 -27.41  13.57
cc 40D   0.51    70.63 -27.41   9.45

The closer to the money the call is sold (40D), the more positive months (70.6%) and the lower the average (0.51%): more premium is collected, but the upside is given up sooner, and in a rising index that weighs more. The 10D one resembles the index alone (average 0.78%, best entry +20.8%). The worst entry barely changes with delta: −27.9%, −27.4% and −27.4%. No call premium covers a 28% fall: the strike of the short call decides how much upside is given away, not how much downside is avoided.

  1. A colleague says their covered call “cannot lose much because it already collected the premium” and that the protective put “is money thrown away because it is almost never used”. With the results of the lesson, what would you answer?
Solution

That the covered call is, by parity, a short put: its loss when the index falls is that of the index minus the premium, and the premium of a 25D call is 0.4% of the index. Its worst month in the data loses 27.4%, seven tenths less than the index. The protective put, on the other hand, costs some return on average (it is a volatility purchase, which carries a premium against it) but cuts the worst entry to −11.5%. Neither is “better”: one sells the right tail and gets paid for it, the other buys protection of the left tail and pays for it. What cannot be said is that the covered call protects. And the protection of a 25D put depends on the day's volatility: after the 2020 crash it was so far away that the worst entry came with the hedge in place.

Each answer is folded under its question

Self-assessment

  1. A 6,100/6,300 bull call spread is opened paying 119.8 for the long call and collecting 27.1 for the short one. What is its maximum loss per contract with a multiplier of 100?

    • A9,270 USD, the net debit
    • B11,980 USD, the premium of the long call
    • C20,000 USD, the width between strikes
    • DUnlimited, because there is a short call
    Show answer

    Answer A. 9,270 USD, the net debit

    The maximum loss of a debit vertical is what was paid: 119.8 − 27.1 = 92.7 points, 9,270 USD per contract. The short call is covered by the long call with the lower strike, so there is no unlimited risk; the maximum gain is the width minus the debit, 107.3 points.

  2. With the same 6,100 and 6,300 strikes, the bull call spread is a debit spread and the bull put spread a credit spread. What really sets them apart?

    • AThe credit one is safer because it collects at opening
    • BAlmost nothing: by put-call parity they have the same payoff except for the discounted width; what changes is the bid-ask spread paid and the financing of the premium
    • CThe debit one cannot lose more than what was paid and the credit one can
    • DThe credit one has positive vega
    Show answer

    Answer B. Almost nothing: by put-call parity they have the same payoff except for the discounted width; what changes is the bid-ask spread paid and the financing of the premium

    Bull put = bull call − (K2 − K1), and the discounted width (200·e^(−rT)) equates the premiums: at mid, 91.90 versus 92.08. Greeks and risk are the same. In the data, the put version costs more bid-ask spread (1.2 versus 0.8 points) because its short put is in the money.

  3. In the 2014-2025 history, the worst entry of the 25D covered call loses 27.4% and that of the index only 28.1%. Why are they so similar?

    • ABecause the short call was exercised
    • BBecause the bid-ask spread of the call is very wide in crises
    • CBecause a covered call is, by parity, a short put: it keeps the full fall of the index and only cushions it with the premium collected
    • DBecause of an error in the March 2020 data
    Show answer

    Answer C. Because a covered call is, by parity, a short put: it keeps the full fall of the index and only cushions it with the premium collected

    Selling a call caps the upside, not the downside. The premium of a 25D call is small (27 points in the example, 0.4% of the index), so in a 28% fall it barely shows. Capping the downside requires a long put: the protective put and the collar stay at −11.5% and −10.0%.

  4. The 25D/10D bull put spread opened on February 24, 2020 loses 3.1% of the index and the one from April 20, 2020, 8.5%. What explains the difference?

    • AThat the index fell more in April
    • BThat the bid-ask spread was wider in April
    • CThat in February the long put could not be exercised
    • DThat with strikes chosen by delta the width between them grows with volatility: 3.4% of the index with an ATM IV of 0.147 and 11.2% with 0.489
    Show answer

    Answer D. That with strikes chosen by delta the width between them grows with volatility: 3.4% of the index with an ATM IV of 0.147 and 11.2% with 0.489

    Delta depends on the distance to the strike measured in standard deviations over the period (volatility times the square root of T). With volatility three times higher, the 25D and 10D puts end up three times farther apart, and the maximum loss (width − credit) grows with them. “Defined risk” does not mean “small risk”.

  5. The 6,100 call has a theta of −1.32 with the units of the lesson. What does it mean?

    • AThat, all else equal, it loses 1.32 points per calendar day: 132 USD per contract
    • BThat it loses 1.32 USD a year
    • CThat it loses 1.32% a day
    • DThat it loses 1.32 points per trading session
    Show answer

    Answer A. That, all else equal, it loses 1.32 points per calendar day: 132 USD per contract

    Theta is expressed here per calendar day (the annual one divided by 365, like QuantLib's thetaPerDay with Actual/365) and per index unit. Per contract it is multiplied by 100. A weekend counts as two calendar days even though there is no session.

Key points

  • A structure is a list of legs; its cost, payoff, P&L and Greeks are the sum of those of each leg with its sign and quantity.
  • The real cost is that of crossing the bid-ask spread: buys at the ask and sells at the bid. In the example chain it ranges from 0.4% of the premium in an at-the-money call to 1.8% in an out-of-the-money credit vertical.
  • By put-call parity, a covered call is equivalent to a short put and a protective put to a long call: selling a call does not protect against the fall, as its worst month in the history showed, −27.4% versus −28.1% for the index.
  • Debit or credit is accounting: with the same strikes, the bull call and the bull put have the same payoff except for the discounted width, and the same Greeks.
  • Credit verticals win often and lose all at once: the 25D/10D is positive in 88% of entries and its worst entry loses 37 times its average gain; with strikes set by delta, the maximum loss grows with volatility.

Resources