Lesson 3.6.1 · 70 min
Systematic premium selling: put-write, strangles and their tail risk
Selling options every month collects the volatility risk premium: implied volatility tends to sit above realized. This lesson replicates on the level's synthetic data a put-selling strategy like the Cboe PUT index and a 0.16 delta strangle, measures them by their worst episode and not only by their Sharpe, and computes how much notional the margin allows and how much the capital can withstand.
- Module
- 3.6 · Systematic volatility and options strategies
- Duration
- 70 min
- Prerequisites
- 3.4.6 · Realized vs implied volatility; volatility risk premium; VIX and VSTOXX, 3.5.2 · Volatility and range structures: straddles, strangles, condors and butterflies, 3.5.4 · A custom tool: payoff, aggregate Greeks and scenarios, 3.5.5 · Management: adjustments, rolls, early closes, early assignment and pin risk
- Author
- Jesús Cuesta
- Updated
Level 3 lessons
3.1 · Futures in depth
3.2 · Futures spreads
- 3.2.1 Calendar spreads, butterflies and condors
- 3.2.2 Intercommodity spreads: crack, spark and crush
- 3.2.3 Rate and index spreads: 2s10s, NOB, NQ/ES and Europe versus the U.S.
- 3.2.4 Hedge ratios: by notional, by DV01 and by volatility
- 3.2.5 Spread data and backtesting: listed, synthetic and legged
- 3.2.6 Seasonality of spreads: multi-year analysis, patterns and traps
3.3 · Advanced forex
3.4 · Option theory and pricing
- 3.4.1 Payoffs, put-call parity and arbitrage bounds
- 3.4.2 Binomial trees, Black-Scholes-Merton, Black-76 and Monte Carlo
- 3.4.3 Greeks: delta, gamma, vega, theta, rho, vanna, volga and charm
- 3.4.4 Implied volatility: Newton-Raphson, Brent, py_vollib and QuantLib
- 3.4.5 Smile, skew and term structure; volatility surface with SVI
- 3.4.6 Realized vs implied volatility; volatility risk premium; VIX and VSTOXX
- 3.4.7 Advanced models: Heston, SABR and local volatility
3.5 · Option structures in Python
- 3.5.1 Directional and vertical structures: call, put, covered call, collar and spreads
- 3.5.2 Volatility and range structures: straddles, strangles, condors and butterflies
- 3.5.3 Calendars, diagonals, ratios, backspreads, risk reversals and jade lizard
- 3.5.4 A custom tool: payoff, aggregate Greeks and scenarios
- 3.5.5 Management: adjustments, rolls, early closes, early assignment and pin risk
3.6 · Systematic volatility and options strategies
3.7 · Crypto derivatives and specific strategies
- 3.7.1 Perpetuals: funding mechanics, mark price and liquidations
- 3.7.2 Funding arbitrage, basis with futures and cross-exchange arbitrage
- 3.7.3 Crypto options, on-chain data and DeFi
- 3.7.4 ccxt for trading on multiple exchanges
- 3.7.5 MiCA: the EU framework for crypto-assets and their service providers (CASPs)
What is collected by selling premium
Lesson 3.4.6 measured the volatility risk premium: the implied volatility of index options tends to sit above the volatility that is later realized. Whoever sells options collects that difference systematically. What lesson 3.4.6 did not do is turn it into a strategy and see what happens to the capital. In the level's synthetic data, with the VIX-style index (VORX) of the ORX chain:
import numpy as np
import pandas as pd
from scipy.special import ndtr
AÑO = 365.0
ind = pd.read_csv("opciones3_indice.csv", parse_dates=["fecha"])
ind = ind.set_index("fecha")
S = ind["cierre"]
rf = ind["tipo_1m"] / 100
q = ind["rentabilidad_dividendo"] / 100
cad = pd.read_parquet("opciones3_cadena.parquet")
cad["medio"] = (cad["bid"] + cad["ask"]) / 2
por_dia = dict(tuple(cad.groupby("fecha")))
vencs = sorted(cad["vencimiento"].unique())
vol = pd.read_csv("vol3_indices.csv", parse_dates=["fecha"])
vorx = vol.set_index("fecha")["vorx"]
# volatility risk premium: VORX versus the realized volatility of
# the next 21 sessions (lesson 3.4.6)
r_log = np.log(S).diff()
realizada = r_log.rolling(21).std().shift(-21) * np.sqrt(252) * 100
vrp = (vorx - realizada).dropna()
print(f"VORX - realized: mean {vrp.mean():.1f}, median "
f"{vrp.median():.1f} points; realized exceeds VORX on "
f"{(vrp < 0).mean():.0%} of days; worst {vrp.min():.0f}")VORX - realized: mean 5.4, median 6.5 points; realized exceeds VORX on 12% of days; worst -78VORX exceeds the realized volatility of the next 21 sessions by 5.4 points on average (median 6.5). But the difference is not stable: on 12% of days realized volatility exceeds VORX, and in March 2020 it exceeds it by up to 78 points. That is the whole business of premium selling: collecting a few points almost always and paying dozens sometimes. A premium-selling strategy is insurance sold, and like any insurer it has to be measured by the claim, not by the premium.
The strategies in the lesson follow the same monthly cycle:
Mermaid source
flowchart LR V[Sell at the bid] --> M[Mark at mid] M --> L[Settle at expiry] L --> R[Reinvest cash] R --> V
Put-write: replicating the Cboe PUT index
The Cboe S&P 500 PutWrite index (PUT) is the public benchmark for systematic put selling. According to its methodology, every month, normally on the third Friday, it sells an at-the-money monthly S&P 500 put: the listed strike closest to the index without exceeding it. It sells it at the volume-weighted average price between 11:30 and 12:00 ET, holds it until expiration (it is settled with the special opening quotation, SOQ) and leaves the cash in one- and three-month Treasury bills. The number of puts is set so that the cash covers the maximum settlement loss: it is a cash-secured sale, without leverage.
The replication with the ORX data follows those rules with three differences imposed by daily data: it sells at the bid of the close on expiration day (instead of the mid-morning average price), settles at the close (ORX options expire at the close) and sets the number of puts with the cash before collecting the premium (somewhat more conservative than the Cboe formula, which counts the premium and the interest). The same function also sells strangles and each of their legs by delta, with the notional equal to the index:
def delta_bsm(tipo, S, K, T, iv, r, q):
d1 = (np.log(S / K) + (r - q + iv**2 / 2) * T) / (iv * np.sqrt(T))
w = 1.0 if tipo == "C" else -1.0
return w * np.exp(-q * T) * ndtr(w * d1)
PATAS = {"put": ("P",), "strangle": ("P", "C"), "put_d": ("P",),
"call_d": ("C",)}
def venta_prima(estructura, apal=1.0, cobro="bid", delta=0.16):
"""Equity curve (starts at 1) of selling premium every month.
“put”: at-the-money put (highest strike <= index) with a
notional of apal × capital in strikes, like the Cboe PUT index
when apal = 1. “strangle”, “put_d”, “call_d”: legs of
delta ±delta with notional apal × capital in index. Sale at the bid
(or at the mid) on the day of each expiration, of the next
expiration with 20 days or more; daily marking at the mid;
settlement at the expiration close; cash at the 1-month rate."""
caja, patas, venc, eq, ultimo = 1.0, [], None, {}, 1.0
for f in S.index:
g = por_dia.get(f) # None: day without a chain
if venc is not None and f == venc:
caja -= sum(n * max(k - S[f] if t == "P" else S[f] - k, 0)
for t, k, n in patas)
patas, venc = [], None
if caja <= 0: # ruin: the cash does not cover the settlement
eq[f] = ultimo = 0.0
continue
if venc is None and g is not None:
v = next(x for x in vencs if (x - f).days >= 20)
sub = g[g["vencimiento"] == v]
T = (v - f).days / AÑO
if estructura == "put":
pp = sub[(sub["tipo"] == "P") & (sub["strike"] <= S[f])]
sel = [pp.loc[pp["strike"].idxmax()]]
n = apal * caja / sel[0]["strike"]
else:
sel = []
for t in PATAS[estructura]:
st = sub[sub["tipo"] == t]
d = delta_bsm(t, S[f], st["strike"].to_numpy(),
T, st["iv_modelo"].to_numpy(),
rf[f], q[f])
sel.append(st.iloc[int(np.argmin(
np.abs(np.abs(d) - delta)))])
n = apal * caja / S[f]
caja += sum(n * fila[cobro] for fila in sel)
patas = [(fila["tipo"], fila["strike"], n) for fila in sel]
venc = v
marca = 0.0
if patas and g is not None:
m = g[g["vencimiento"] == venc].set_index(
["tipo", "strike"])["medio"]
marca = sum(n * m.get((t, k), np.nan) for t, k, n in patas)
if patas and (g is None or not np.isfinite(marca)):
eq[f] = ultimo # no quote: the previous mark
else:
eq[f] = ultimo = caja - marca
caja *= np.exp(rf[f] / 252)
return pd.Series(eq)The apal parameter multiplies the notional per unit of capital: with 1, each unit of capital backs one unit of
strike (or of index); with 3, three. If the cash is not enough to pay a settlement, the account is ruined and the
curve stays at zero.
Results with the tail in view
The standard metric, the Sharpe, is computed with the standard deviation of daily returns. In a strategy with its tail on the left, the normal months set the standard deviation, and the tail barely weighs on it. That is why each result comes with its maximum drawdown, its monthly skewness and its worst month:
def metricas(eq):
"""Sharpe of the daily excess over the 1-month rate, CAGR,
maximum drawdown, skewness and worst month (with its date)."""
r = eq.pct_change().dropna()
ex = r - rf.reindex(r.index) / 252
mes = eq.resample("ME").last().pct_change().dropna()
anos = (eq.index[-1] - eq.index[0]).days / 365.25
return {"sharpe": ex.mean() / ex.std() * np.sqrt(252),
"cagr": (eq.iloc[-1] / eq.iloc[0]) ** (1 / anos) - 1,
"max_caida": (eq / eq.cummax() - 1).min(),
"asim_mensual": mes.skew(), "peor_mes": mes.min(),
"fecha_peor": mes.idxmin().strftime("%Y-%m")}
curvas = {"TR index": ind["cierre_rt"] / ind["cierre_rt"].iloc[0],
"put-write": venta_prima("put"),
"put-write at mid": venta_prima("put", cobro="medio"),
"strangle 16Δ": venta_prima("strangle"),
"put 16Δ": venta_prima("put_d"),
"call 16Δ": venta_prima("call_d")}
res = pd.DataFrame([metricas(v) for v in curvas.values()],
index=list(curvas))
res.round(3) sharpe cagr max_caida asim_mensual peor_mes fecha_peor
TR index 0.656 0.125 -0.399 -0.793 -0.288 2020-05
put-write 0.520 0.068 -0.395 -3.731 -0.232 2020-03
put-write at mid 0.533 0.069 -0.394 -3.720 -0.232 2020-03
strangle 16Δ -0.165 0.002 -0.421 -5.480 -0.237 2020-03
put 16Δ 0.469 0.047 -0.274 -8.596 -0.223 2020-03
call 16Δ -0.850 -0.026 -0.384 -3.701 -0.119 2020-08The header figure compares the put-write with the total return index. The put-write has a Sharpe of 0.52 versus 0.66 for the index and half of its CAGR (6.8% versus 12.5%), but not half of its risk: its maximum drawdown, −39.5%, is practically that of the index (−39.9%), and its monthly skewness is −3.7 versus −0.8. Collecting at the mid instead of at the bid barely changes anything (0.53): in an at-the-money put on a liquid index the bid-ask spread is small relative to the premium. With real data, costs and execution weigh more in out-of-the-money options (lesson 3.6.4).
The figures in the data say nothing about what the strategy returns in real markets: they are synthetic, with the volatility risk premium planted by the generator (data README). What they do show is its shape:
mensual = pd.DataFrame({k: curvas[k].resample("ME").last().pct_change()
for k in ("TR index", "put-write")}).dropna()
mensual.index = mensual.index.strftime("%Y-%m")
peores = mensual.nsmallest(6, "put-write")
print(f"Months: {len(mensual)} · put-write positive in "
f"{(mensual['put-write'] > 0).mean():.0%}")
(100 * peores).round(1)Months: 143 · put-write positive in 78%
TR index put-write
2020-03 -19.5 -23.2
2020-05 -28.8 -21.2
2018-02 -7.8 -8.9
2018-10 -8.7 -7.1
2015-09 -6.9 -5.6
2022-05 -10.9 -4.5The put-write wins in 78% of months; 55 of the 143 fall between +1 and +2%. The two worst, March and May 2020 (−23.2 and −21.2%), add up to −44.4%: about thirty good months of +1.5%. In March 2020 the put-write loses more than the index (−23.2 versus −19.5%): the put sold on 21 February is settled on 20 March, near the low, and the index recovers somewhat in the last week of the month. In May, the second leg of the synthetic drop in the data, it loses less than the index (−21.2 versus −28.8%): the May put was sold on 17 April with an implied volatility of 49% and collected 5.46% of the strike, versus 1.14% for the put sold on 21 February.
The 0.16 delta strangle: why it loses in this sample
The short strangle adds an out-of-the-money call to the put. It collects two premiums and, on paper, “wins if the market does not move much”. In the table it has a Sharpe of −0.16, a CAGR of 0.2% and a maximum drawdown of −42.1%: worse than the put alone (0.47 and −27.4%). The reason lies in the legs:
def log_anual(eq):
return np.log(eq).diff().groupby(eq.index.year).sum()
patas = pd.DataFrame({"put 16Δ": log_anual(curvas["put 16Δ"]),
"call 16Δ": log_anual(curvas["call 16Δ"]),
"index": log_anual(curvas["TR index"])})
c = patas["call 16Δ"]
print(f"Call 16Δ: 2020-2021 {c.loc[[2020, 2021]].sum():.2f} in log; "
f"other years {c.drop([2020, 2021]).mean():.3f} on average")
(100 * patas).round(1).TCall 16Δ: 2020-2021 -0.43 in log; other years 0.012 on average
2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025
put 16Δ 5.2 4.0 5.3 6.5 -4.0 8.3 -18.0 7.8 8.7 11.7 10.8 8.6
call 16Δ -2.6 -4.3 -4.2 1.0 4.9 0.6 -25.7 -16.9 3.7 4.2 4.4 4.0
index 8.7 -0.0 22.6 12.4 -12.0 24.3 10.0 29.1 -21.1 20.4 33.2 13.7The −0.16 delta put wins every year except 2018 (−4.0%) and 2020 (−18.0%). The 0.16 delta call loses in 2014-2016 and, above all, in 2020 and 2021: −25.7% and −16.9% in log, −0.43 in total, when the index rebounds after the drop and rises 29.1% (in log) in 2021. In the other years it earns 1.2% on average: little, because the skew makes out-of-the-money calls cheap and the premium collected is small. The result is a call leg that loses 2.6% a year in the sample.
It is not a law: with another sample, without such a bullish market, the call leg could win. What it shows is that a strangle is two bets: one on the left tail, well paid by the skew, and another on the right tail, poorly paid. Each leg is measured separately before combining them.
Sizing by margin: what the broker allows
A naked short put does not need the whole strike in cash: the broker requires a margin. The Cboe SPX specifications set it for naked short index options at 100% of the premium plus 15% of the index value times the multiplier, minus the amount the option is out of the money, with a minimum for puts of the premium plus 10% of the strike times the multiplier. Applied to ORX two months before the drop:
def margen_put(prima, S, K, mult=100):
"""Margin per contract of a naked short index put
(Cboe SPX specifications): 100% of the premium + 15% of the
index notional - the out-of-the-money amount, with a
minimum of premium + 10% of the strike notional."""
fuera = max(S - K, 0)
return mult * max(prima + 0.15 * S - fuera, prima + 0.10 * K)
f0 = pd.Timestamp("2019-12-20")
g0 = por_dia[f0]
v0 = next(x for x in vencs if (x - f0).days >= 20)
filas = []
for nombre, d in (("ATM", 0.5), ("16Δ", 0.16)):
st = g0[(g0["vencimiento"] == v0) & (g0["tipo"] == "P")]
dl = delta_bsm("P", S[f0], st["strike"].to_numpy(), (v0 - f0).days
/ AÑO, st["iv_modelo"].to_numpy(), rf[f0], q[f0])
fila = st.iloc[int(np.argmin(np.abs(np.abs(dl) - d)))]
m = margen_put(fila["bid"], S[f0], fila["strike"])
filas.append({"put": nombre, "strike": fila["strike"],
"prima": fila["bid"], "margen_usd": m,
"nominal_usd": 100 * fila["strike"],
"apal_max": 100 * fila["strike"] / m})
print(f"ORX on {f0.date()}: {S[f0]}")
pd.DataFrame(filas).round(2)ORX on 2019-12-20: 2850.28
put strike prima margen_usd nominal_usd apal_max
0 ATM 2850.0 32.5 45976.2 285000.0 6.20
1 16Δ 2750.0 11.0 33826.2 275000.0 8.13An ATM put requires 45,976 USD of collateral for 285,000 of notional: the margin allows 6.2 times the capital in notional. The −0.16 delta one, further from the money, needs even less margin: 8.1 times. The margin is designed to cover the move of a few days with a cushion, not a crisis.
Leverage and ruin
The same put-write, with more notional per unit of capital:
apal = {}
for L in (1.0, 1.5, 2.0, 3.0, 4.0):
eq = venta_prima("put", apal=L)
m = metricas(eq)
m["ruina"] = bool((eq <= 0).any())
apal[L] = m
pd.DataFrame(apal).T.drop(columns="fecha_peor").astype(float).round(3) sharpe cagr max_caida asim_mensual peor_mes ruina
1.0 0.520 0.068 -0.395 -3.731 -0.232 0.0
1.5 0.482 0.086 -0.557 -3.609 -0.358 0.0
2.0 0.439 0.097 -0.696 -3.485 -0.491 0.0
3.0 0.336 0.078 -0.914 -3.211 -0.777 0.0
4.0 -0.609 -1.000 -1.217 -4.810 -1.000 1.0With 1.5 times the notional, the CAGR rises from 6.8 to 8.6% and the maximum drawdown goes from −39.5 to −55.7%. With 2 times, 9.7% and −69.6%. With 3 times the CAGR already falls (7.8%) and the drawdown is −91.4%: the account loses nine tenths in March 2020. With 4 times, the account goes negative on 16 March 2020 and does not survive the expiration: ruin, with a measured drawdown of −121.7% (the account owes money to the broker). The return grows little and stops growing; the drawdown grows almost proportionally: beyond a certain leverage, large losses eat up the compounded return.
All those leverages fit within the margin of the previous cell (6.2 times). The margin is not a risk limit: it is a minimum the broker sets to protect itself.
Stress-based sizing
A risk limit is set the other way around: you choose how much of the capital you accept losing in an extreme scenario and compute the notional that meets it. The tool from lesson 3.5.4 does the repricing; here the ATM put of 2019-12-20 is enough, repriced with Black-Scholes if the scenario happens the next day:
def bsm_put(S, K, T, iv, r, q):
d1 = (np.log(S / K) + (r - q + iv**2 / 2) * T) / (iv * np.sqrt(T))
d2 = d1 - iv * np.sqrt(T)
return (K * np.exp(-r * T) * ndtr(-d2)
- S * np.exp(-q * T) * ndtr(-d1))
# the ATM put of 2019-12-20 (previous cell), repriced if the
# scenario happens the next day
atm = filas[0]
K0, T0 = atm["strike"], (v0 - f0).days / AÑO - 1 / AÑO
escenarios = {"10% drop, vol 35%": (0.90, 0.35),
"20% drop, vol 60%": (0.80, 0.60),
"30% drop, vol 80%": (0.70, 0.80)}
filas_e = []
for nombre, (mov, iv) in escenarios.items():
valor = bsm_put(S[f0] * mov, K0, T0, iv, rf[f0], q[f0])
perdida = 100 * (valor - atm["prima"]) / K0 # % of notional
filas_e.append({"escenario": nombre,
"pérdida_%": perdida,
"apal_20%": 20 / perdida,
"con_apal_max_%": perdida * atm["apal_max"]})
peor_hist = -res.at["put-write", "peor_mes"] * 100
filas_e.append({"escenario": "worst month in the data",
"pérdida_%": peor_hist,
"apal_20%": 20 / peor_hist,
"con_apal_max_%": peor_hist * atm["apal_max"]})
print(f"Put {K0:.0f} sold at {atm['prima']}: "
f"{100 * atm['prima'] / K0:.2f}% of notional")
pd.DataFrame(filas_e).set_index("escenario").round(2)Put 2850 sold at 32.5: 1.14% of notional
pérdida_% apal_20% con_apal_max_%
escenario
10% drop, vol 35% 9.46 2.11 58.63
20% drop, vol 60% 19.41 1.03 120.32
30% drop, vol 80% 29.21 0.68 181.06
worst month in the data 23.24 0.86 144.09The put was sold at 32.5 points, 1.14% of its notional. With a 20% drop and volatility at 60% (a month like March 2020), it loses 19.41% of its notional: so as not to lose more than 20% of the capital, the notional cannot exceed 1.03 times the capital. With a 30% drop and volatility at 80%, 0.68 times. The worst month in the data at 1× (−23.24%) gives 0.86 times. With the leverage the margin allows, those scenarios cost between 59 and 181% of the capital.
Mermaid source
flowchart TB A[Capital] --> B[Required margin] A --> C[Stress loss] B --> D[Max leverage 6.2x] C --> E[Prudent leverage 1x] D --> F[Notional = minimum] E --> F
The notional traded is the smaller of the two. In premium selling, the stress one almost always rules. Three precautions about the scenarios: they must include the one-day gap, not only the cumulative drop (on 16 March 2020 the index falls 12.8% in one session); they must raise volatility more at the low strikes (lesson 3.5.4); and the worst episode in the sample is not the worst possible, only the worst seen.
What this lesson does not prove
The data are synthetic and the volatility risk premium is planted. The lesson teaches how to measure a premium sale, not that it works. Before trading one with real money you need real option data with their bid-ask spreads and their glitches (lesson 3.6.4), realistic execution costs at the opening and closing of each cycle, the opportunity cost of the margin and a plan for the worst month that does not depend on the next one being better. The methodology of the Cboe PUT index is a good starting point for comparing your own replication with a public series.
Exercises
- Further from the money, with more notional. Sell only the −0.16 delta put with 2 and 3 times the capital in notional and compare its CAGR, its maximum drawdown and its worst month with those of the ATM put-write at 1×. Which is riskier: a leveraged far put or an unleveraged near one?
Solution
The far put collects less premium, so more notional is needed to approach the put-write's return; with more notional, its worst month grows proportionally. With 2 times the capital it returns 6.6% a year (less than the put-write at 1×, 6.8%) with a maximum drawdown of −54.8% and a worst month of −45.6%; with 3 times, 6.2%, −82.2% and −69.9%. The leveraged far put is riskier and, in this sample, returns less: “far from the money” does not mean “low risk” when it is offset with notional.
filas = {"put-write 1x": metricas(curvas["put-write"])}
for L in (2.0, 3.0):
filas[f"put 16Δ {L:.0f}x"] = metricas(venta_prima("put_d", apal=L))
pd.DataFrame(filas).T[["sharpe", "cagr", "max_caida", "peor_mes"]
].astype(float).round(3) sharpe cagr max_caida peor_mes
put-write 1x 0.520 0.068 -0.395 -0.232
put 16Δ 2x 0.373 0.066 -0.548 -0.456
put 16Δ 3x 0.288 0.062 -0.822 -0.699- A put with a wing. Compute the loss in the three stress scenarios of a short put spread (2850 put sold and 2550 put bought, 10% below, same expiration) and the notional the 20% limit allows.
Solution
The bought wing costs 1.6 points: the net credit falls from 32.5 to 30.9 (1.08% of notional) and the maximum loss is 300 − 30.9 = 269.1 points, 9.44% of notional. In the scenarios, the spread loses 6.37, 7.99 and 8.70% of notional, versus 9.46, 19.41 and 29.21% for the put alone: the 20% limit allows between 2.3 and 3.1 times the capital. The wing costs 5% of the premium (1.6 of 32.5 points) and completely changes the tail.
st = g0[(g0["vencimiento"] == v0) & (g0["tipo"] == "P")]
ala = st.set_index("strike").loc[2550.0]
credito = atm["prima"] - ala["ask"]
print(f"Net credit: {credito:.1f} ({100 * credito / K0:.2f}%)")
filas_s = {}
for nombre, (mov, iv) in escenarios.items():
s1 = S[f0] * mov
valor = (bsm_put(s1, K0, T0, iv, rf[f0], q[f0])
- bsm_put(s1, 2550.0, T0, iv, rf[f0], q[f0]))
perdida = 100 * (valor - credito) / K0
filas_s[nombre] = {"pérdida_%": perdida, "apal_20%": 20 / perdida}
pd.DataFrame(filas_s).T.round(2)Net credit: 30.9 (1.08%)
pérdida_% apal_20%
10% drop, vol 35% 6.37 3.14
20% drop, vol 60% 7.99 2.50
30% drop, vol 80% 8.70 2.30- Margin or stress. A colleague proposes trading the put-write with the leverage the margin allows “because the Sharpe is the same”. What do you answer with the numbers from the lesson?
Solution
That the Sharpe barely measures the tail. With 6.2 times the capital, the worst month in the data (−23.2% at 1×) is a loss of 144% of the capital, and already at 4 times the account in the data goes negative on 16 March 2020. Besides, the Sharpe does change with leverage when there is ruin or near-total drawdowns: in the table it falls from 0.52 (1×) to 0.34 (3×). Leverage is decided with stress scenarios and with the worst known episode, and the result (around 1x the capital so as not to lose more than 20% in a 20% drop) is far below what the margin allows.
Each answer is folded under its question
Self-assessment
The put-write in the data has a Sharpe of 0.52 and a monthly skewness of −3.7. What best describes its profile?
- AMany small positive months (78%) and a few very bad months (−23.2% in March 2020), with a maximum drawdown of −39.5%, like that of the index
- BA stable income with low risk
- CA strategy uncorrelated with the index
- DA strategy with a higher return than the index
Show answer
Answer A. Many small positive months (78%) and a few very bad months (−23.2% in March 2020), with a maximum drawdown of −39.5%, like that of the index
Negative skewness says the distribution has its tail on the left. The put-write collects a small premium every month and gives back several years of premiums in a crisis. Its maximum drawdown is that of the index, with half the CAGR.
The ±0.16 delta short strangle loses in the sample (Sharpe −0.16) although the −0.16 delta put alone wins (0.47). Why?
- ABecause the put and the call cancel out
- BBecause strangles always lose
- CBecause the implied volatility of calls is higher
- DBecause the call leg loses 2.6% a year: the 2020 rebound and the 2021 rally (−0.43 in log over two years) wipe out what it earns in the other years
Show answer
Answer D. Because the call leg loses 2.6% a year: the 2020 rebound and the 2021 rally (−0.43 in log over two years) wipe out what it earns in the other years
A short call loses on strong rallies. In a sample with a very bullish market and a violent rebound, the premium of the calls (small: the skew makes them cheap) does not compensate. It is a result of the sample, not a law, but it is a reminder that each leg is a different bet.
With the Cboe margin rule, a short ATM put on ORX requires about 46,000 USD per contract of 285,000 notional. What leverage does it allow and what would have happened with it in the sample?
- A1×; nothing
- B15 times; a 15% drop
- CAbout 6 times the capital; with 4 times the account already goes negative on 2020-03-16
- DThe margin prevents losing more than the capital
Show answer
Answer C. About 6 times the capital; with 4 times the account already goes negative on 2020-03-16
The margin covers the risk of a few days with 15% of the index, not a crisis. With 285,000 / 45,976 ≈ 6.2 times the capital, the worst month in the data (−23.2% at 1×) becomes a loss of more than 140% of the capital. In the leverage cell, already at 4× the account does not survive March 2020.
What notional keeps the loss below 20% of the capital in a scenario of a 20% drop with volatility at 60%?
- A6.2 times the capital
- BAbout 1x the capital: in that scenario the ATM put loses 19.4% of its notional
- C0.2 times the capital
- DIt cannot be computed without the Sharpe
Show answer
Answer B. About 1x the capital: in that scenario the ATM put loses 19.4% of its notional
20 / 19.41 = 1.03. Stress-based sizing comes from repricing the position in the scenario (lesson 3.5.4) and dividing the tolerable loss by the loss per unit of notional. It is six times less than the margin allows.
Why does the lesson present each strategy with its worst month and its maximum drawdown and not only with the Sharpe?
- ABecause the Sharpe cannot be computed with options
- BBecause the Sharpe assumes the standard deviation summarizes the risk; with a skewness of −3.7, the normal months set the standard deviation and the tail barely weighs on it
- COut of habit
- DBecause the worst month is always equal to the maximum drawdown
Show answer
Answer B. Because the Sharpe assumes the standard deviation summarizes the risk; with a skewness of −3.7, the normal months set the standard deviation and the tail barely weighs on it
With very skewed distributions, two strategies with the same Sharpe can have very different tails. A premium sale without its worst episode in view looks like safe income, and it is not.
Key points
- Selling options collects the volatility risk premium (in the data, VORX exceeds realized volatility by 5.4 points on average), in exchange for large losses in crises: it is insurance sold, not income.
- The put-write in the data has a Sharpe of 0.52 with a maximum drawdown of −39.5%, that of the index, and a monthly skewness of −3.7. A premium-selling strategy is always presented with its worst episode.
- The 0.16 delta strangle loses in this sample because the call leg loses in the bull market: each leg is a different bet and is measured separately.
- The Cboe margin for a naked index put allows about 6 times the capital; at 4 times, the account in the data goes negative in March 2020. The margin measures the risk of a few days, not that of a crisis.
- Size is set with stress scenarios and with the worst known episode: in the data, a notional of about 1x the capital so as not to lose more than 20% in a 20% drop.