Lesson 1.2.5 · 60 min
Options: calls, puts, intrinsic value and time value
An option is a right with an expiry date, and its price splits into what exercising it is worth today and what waiting is worth. The lesson measures this on one day's full option chain of the fictitious FIX index: 584 contracts, four expiries.
- Module
- 1.2 · Asset classes
- Duration
- 60 min
- Prerequisites
- 1.1.3 · Order book: bid, ask, spread and depth, 1.2.3 · Futures: specifications, margin, roll and contango
- Author
- Jesús Cuesta
- Updated
Level 1 lessons
1.1 · How markets work
1.2 · Asset classes
- 1.2.1 Stocks: splits, dividends and corporate actions
- 1.2.2 ETFs: replication, tracking error, leveraged ETFs and UCITS
- 1.2.3 Futures: specifications, margin, roll and contango
- 1.2.4 Forex: pairs, pips, lots, rollover and CFDs
- 1.2.5 Options: calls, puts, intrinsic value and time value
- 1.2.6 Crypto: spot, perpetuals, funding and custody
- 1.2.7 Fixed income and interest rates; CFDs and leveraged products
1.3 · Market data sources
- 1.3.1 Types of market data: EOD, bars, ticks, order book and reference
- 1.3.2 Free market data sources
- 1.3.3 Retail and prosumer market data APIs
- 1.3.4 Professional market data providers
- 1.3.5 Crypto and on-chain data
- 1.3.6 Brokers as market data sources
- 1.3.7 Market data licensing and terms of use
- 1.3.8 How to choose a market data provider
1.4 · Financial data engineering
- 1.4.1 Downloading over REST and websockets: pagination, rate limits and retries
- 1.4.2 Data cleaning: missing values, bad ticks, gaps and time zones
- 1.4.3 Adjusting for splits and dividends: adjusted and unadjusted prices
- 1.4.4 Universes without survivorship bias: delistings, mergers and symbol changes
- 1.4.5 Building bars: time, tick, volume, dollar and imbalance
- 1.4.6 Continuous futures series: roll rules, difference adjustment and ratio adjustment
- 1.4.7 Storage: CSV, Parquet, partitioning, DuckDB, TimescaleDB and ArcticDB
- 1.4.8 Reproducible pipeline: download, validation, incremental updates and quality tests
1.5 · Fundamental and macro data
1.6 · Financial exploratory analysis
1.7 · Programmed technical analysis
A right with an expiry date
An option is a contract that gives its buyer the right, but not the obligation, to buy or sell an asset (the underlying) at a price fixed in advance (the strike or exercise price) until a date (the expiry). There are two types:
- A call gives the right to buy the underlying at the strike. It suits someone who expects it to rise.
- A put gives the right to sell the underlying at the strike. It suits someone who expects it to fall or wants protection against a drop.
For that right, the buyer pays a premium to the seller at the time of purchase. The seller (also called the writer) receives the premium and takes on the opposite obligation: if the buyer exercises, the seller has to sell (for the call) or buy (for the put) at the strike. The asymmetry is the key to everything that follows: the buyer risks at most the premium; the seller collects a small, certain amount in exchange for a risk that can be large.
Mermaid source
flowchart TB
C[Buyer] -- premium --> V[Seller]
C --> X{Ends in the money?}
X -- yes --> E[Exercises]
X -- no --> N[Expires worthless]
E --> L[Seller pays]At expiry, the buyer exercises only if it pays to do so: a call if the underlying is above the strike, a put if it is below. Settlement can be physical (the shares or the underlying future are delivered) or cash (the difference between the settlement price and the strike is paid). Index options, like the ones in this lesson, are cash-settled: an index cannot be delivered.
Each contract has a multiplier: how many units of money one point of premium is worth. In this lesson's options it is 100 USD per point, as in Cboe's SPX options on the S&P 5001Note 1 · SPX option specifications on the Cboe page linked in the resources: 100 USD multiplier, European style and cash settlement.: a premium of 174.05 points costs 17,405 USD per contract. Forgetting the multiplier is the cheapest mistake to make in a notebook and the most expensive in a real account.
The FIX chain
The data are a snapshot of the option chain of a fictitious index, FIX, at the close of June 2, 2025: all the
listed calls and puts, for four expiries and dozens of strikes, with their best bid and ask, the day's volume and the
open interest (outstanding contracts). They are European options with cash settlement. The index closed that day
at 6,359.10 (fix_indice_diario.csv, the same index as the futures in lesson 1.2.3).
import numpy as np
import pandas as pd
cad = pd.read_csv("opciones_cadena_fix.csv",
parse_dates=["fecha", "vencimiento"])
indice = pd.read_csv("fix_indice_diario.csv", parse_dates=["fecha"])
S = indice.set_index("fecha").loc["2025-06-02", "indice"]
print(f"{len(cad)} options; FIX at the close: {S}")
cad.groupby(["vencimiento", "tipo"]).agg(
strikes=("strike", "size"), desde=("strike", "min"),
hasta=("strike", "max"), interes=("interes_abierto", "sum"))584 options; FIX at the close: 6359.1
strikes desde hasta interes
vencimiento tipo
2025-06-20 call 50 5675 6900 412880
put 50 5675 6900 621931
2025-07-18 call 82 5275 7300 574545
put 82 5275 7300 863427
2025-09-19 call 66 4700 7950 414879
put 66 4700 7950 622741
2025-12-19 call 94 4150 8800 497606
put 94 4150 8800 746209Four expiries, the third Friday of June, July, September and December, and in each one the same number of calls and puts: every strike has its pair. The longer the expiry, the wider the range of strikes, because the index can move further away. The open interest of the puts is larger than that of the calls: on a stock index, puts are the insurance of portfolios, and there is more demand for insurance than for bets on a rise.
A listing like that is hard to read. The usual way to look at a chain is the one broker platforms use: one row per strike, calls on the left and puts on the right.
cad["medio"] = (cad["bid"] + cad["ask"]) / 2
jul = cad[cad["vencimiento"] == "2025-07-18"]
tabla = jul.pivot(index="strike", columns="tipo",
values=["bid", "ask"])
tabla.columns = [f"{t}_{c}" for c, t in tabla.columns]
tabla = tabla[["call_bid", "call_ask", "put_bid", "put_ask"]]
tabla.loc[6200:6500:2] call_bid call_ask put_bid put_ask
strike
6200 274.0 275.2 92.1 92.6
6250 238.4 239.5 106.1 106.7
6300 204.9 205.8 122.3 122.9
6350 173.6 174.5 140.7 141.4
6400 144.9 145.6 161.5 162.3
6450 118.8 119.4 185.1 185.9
6500 95.5 96.0 211.4 212.4The figure at the top draws the same chain as a strike ladder: each rung is a strike, with the bid and ask of its call on the left and those of its put on the right, and the amber line marks where the index is. The shaded areas are the in-the-money options: calls with a strike below the index (top left) and puts with a strike above it (bottom right). Going down the ladder, toward higher strikes, calls get cheaper and puts get more expensive. Each 50-point rung changes the call's mid price by less than 50 points: about 41 between 6,050 and 6,100, about 29 between 6,350 and 6,400 and about 15 between 6,600 and 6,650. That sensitivity of the premium to the strike is related to the probability of ending in the money (level 3).
What each position pays at expiry
At expiry, an option no longer has any uncertainty: it is worth what exercising it yields. With the underlying at expiry and the strike:
That is the payoff. The position's result (P&L) subtracts the premium paid, or adds the premium received if the option was sold. The four combinations —call or put, bought or sold— give the four basic positions, and every option strategy (spreads, straddles, condors) is a sum of them. They are computed with the July at-the-money options, strike 6,350, bought or sold at the mid price:
def pnl_vencimiento(s_t, strike, prima, tipo, lado=1):
"""P&L per unit of underlying; lado 1 buy, -1 sell."""
s_t = np.asarray(s_t, dtype=float)
if tipo == "call":
payoff = np.maximum(s_t - strike, 0)
else:
payoff = np.maximum(strike - s_t, 0)
return lado * (payoff - prima)
K = 6350
prima = jul[jul["strike"] == K].set_index("tipo")["medio"]
print(prima.to_dict())
escenarios = [6000, 6200, 6350, 6500, 6700]
pd.DataFrame({
"long call": pnl_vencimiento(escenarios, K,
prima["call"], "call"),
"short call": pnl_vencimiento(escenarios, K,
prima["call"], "call", -1),
"long put": pnl_vencimiento(escenarios, K,
prima["put"], "put"),
"short put": pnl_vencimiento(escenarios, K,
prima["put"], "put", -1),
}, index=pd.Index(escenarios, name="FIX at expiry")){'call': 174.05, 'put': 141.05}
long call short call long put short put
FIX at expiry
6000 -174.05 174.05 208.95 -208.95
6200 -174.05 174.05 8.95 -8.95
6350 -174.05 174.05 -141.05 141.05
6500 -24.05 24.05 -141.05 141.05
6700 175.95 -175.95 -141.05 141.05Each column is the mirror image of its neighbor: what the buyer gains the seller loses, point for point. It is worth recognizing the four shapes at a glance:
| Position | View | Maximum gain | Maximum loss | Breakeven |
|---|---|---|---|---|
| Long call | Bullish | Unlimited | The premium | |
| Short call | Not bullish | The premium | Unlimited | |
| Long put | Bearish | The premium | ||
| Short put | Not bearish | The premium |
mult = 100 # USD per point of premium
print(f"Call breakeven: {K + prima['call']:.2f}")
print(f"Put breakeven: {K - prima['put']:.2f}")
print(f"One call costs {prima['call'] * mult:,.0f} USD")
subida = (K + prima["call"]) / S - 1
print(f"The call makes money if FIX rises more than {subida:.2%}")Call breakeven: 6524.05
Put breakeven: 6208.95
One call costs 17,405 USD
The call makes money if FIX rises more than 2.59%Buying the at-the-money call only makes a profit if the index rises more than 2.59% in the six weeks left until expiry. That is the other side of the limited loss: the premium has to be recovered before anything is gained. The call seller makes money as long as the index does not go above 6,524.05: in most scenarios it gains a little, at most the premium; in a few it loses much more than it could have gained.
Intrinsic value and extrinsic value
Before expiry, an option's premium is greater than its immediate payoff. It splits into two parts:
- Intrinsic value: what exercising it now would yield, for a call and for a put. It is never negative.
- Extrinsic value or time value: the rest, premium minus intrinsic value. It is what is paid for the possibility that the underlying moves favorably before expiry.
The relation between strike and underlying is called moneyness and gives three categories:
| Category | Call | Put | Intrinsic value |
|---|---|---|---|
| In the money (ITM) | Positive | ||
| At the money (ATM) | Almost zero | ||
| Out of the money (OTM) | Zero |
def moneyness(tipo, strike, s, banda=0.005):
"""ITM, ATM (within 0.5% of the index) or OTM."""
dist = (s - strike) / s if tipo == "call" else (strike - s) / s
if abs(dist) < banda:
return "ATM"
return "ITM" if dist > 0 else "OTM"
es_call = cad["tipo"] == "call"
cad["intrinseco"] = np.where(es_call, S - cad["strike"],
cad["strike"] - S).clip(min=0)
cad["extrinseco"] = cad["medio"] - cad["intrinseco"]
cad["moneyness"] = [moneyness(t, k, S) for t, k in
zip(cad["tipo"], cad["strike"])]
cols = ["tipo", "strike", "medio", "intrinseco", "extrinseco",
"moneyness"]
jul = cad[cad["vencimiento"] == "2025-07-18"]
jul[jul["strike"].isin([6000, 6350, 6700])][cols].round(2) tipo strike medio intrinseco extrinseco moneyness
158 call 6000 433.75 359.1 74.65 ITM
159 put 6000 52.65 0.0 52.65 OTM
186 call 6350 174.05 9.1 164.95 ATM
187 put 6350 141.05 0.0 141.05 ATM
214 call 6700 31.15 0.0 31.15 OTM
215 put 6700 346.20 340.9 5.30 ITMThe six rows sum up the idea. The 6,000 call is in the money: of its premium of 433.75 points, 359.10 are intrinsic value and 74.65, time value. The 6,700 call is out of the money: all its premium is time value. The two at the 6,350 strike are at the money, with an intrinsic value of 9.10 points for the call and zero for the put, and they are the ones with the most time value of the six.
The figure extends this to all the July calls. The gray area is the intrinsic value, the straight line that starts at the strike equal to the index; the line, the premium. What is left between the two is the extrinsic value, and it is bell-shaped, centered on the index. Deep in the money the call behaves almost like the index itself; deep out of the money, it is worth almost nothing; near the money, the final outcome is more uncertain, and the uncertainty is what is paid for.
idx = cad.groupby(["vencimiento", "tipo"])["extrinseco"].idxmax()
maximos = cad.loc[idx].set_index(["vencimiento", "tipo"])
maximos[["strike", "extrinseco"]].round(2) strike extrinseco
vencimiento tipo
2025-06-20 call 6350 94.40
put 6350 85.05
2025-07-18 call 6350 164.95
put 6350 141.05
2025-09-19 call 6350 280.35
put 6350 223.95
2025-12-19 call 6350 414.10
put 6350 311.30In all four expiries and for both types, the maximum falls at the 6,350 strike, the closest to the index. Calls and puts with the same strike do not have the same extrinsic value: in July, 164.95 points for the call and 141.05 for the put. The difference comes from the forward, which is above the index (see the parity section), but the bell shape is the same for both.
Time value decays faster and faster
Extrinsic value depends on time to expiry. More time means more room for the underlying to move, and therefore more value. But the relation is not linear: with a price model of independent random moves, the dispersion of the underlying at expiry grows with the square root of time (lesson 0.7.1), and so, approximately, does the time value of an at-the-money option.
To measure it without the distortion of the forward, take the average of the extrinsic value of the call and the put at the 6,350 strike: the call has more extrinsic value than the put because the forward is above the index (the call is already in the money relative to the forward), and averaging the two offsets that effect.
del_dinero = cad[cad["strike"] == 6350]
atm = del_dinero.pivot(index="vencimiento", columns="tipo",
values="extrinseco")
atm["medio"] = (atm["call"] + atm["put"]) / 2
atm["dias"] = (atm.index - pd.Timestamp("2025-06-02")).days
atm["por_raiz_dia"] = atm["medio"] / np.sqrt(atm["dias"])
atm.round(2)tipo call put medio dias por_raiz_dia
vencimiento
2025-06-20 94.40 85.05 89.72 18 21.15
2025-07-18 164.95 141.05 153.00 46 22.56
2025-09-19 280.35 223.95 252.15 109 24.15
2025-12-19 414.10 311.30 362.70 200 25.65If time value were proportional to time to expiry, the last column would be multiplied by 3.3 from the first expiry to the last (); instead, it goes from 21 to 26 points per square-root day. That it rises somewhat with time to expiry is because this chain's implied volatility increases with expiry, something studied in level 3. With a single constant fitted to the four points, the curve shows how the time value of a 200-day at-the-money option is used up if nothing else changes:
a = (atm["medio"] * np.sqrt(atm["dias"])).sum() / atm["dias"].sum()
print(f"a = {a:.2f} points per square-root day")
inicial = a * np.sqrt(200)
for desde, hasta in [(200, 170), (90, 60), (60, 30), (30, 0)]:
perdida = a * (np.sqrt(desde) - np.sqrt(hasta))
print(f"from {desde:>3} to {hasta:>3} days: loses {perdida:6.1f}"
f" points ({perdida / inicial:.0%} of the initial value)")a = 24.61 points per square-root day
from 200 to 170 days: loses 27.2 points (8% of the initial value)
from 90 to 60 days: loses 42.8 points (12% of the initial value)
from 60 to 30 days: loses 55.8 points (16% of the initial value)
from 30 to 0 days: loses 134.8 points (39% of the initial value)With the curve fitted to the chain, a 200-day at-the-money option loses 27 points in its first month, 8% of its time value; in its last month it loses the 135 points it has left, 39% of the initial value. That daily erosion of time value has a name, theta, and it is one of the “Greeks”, the sensitivities of the premium to the underlying, volatility, time and the interest rate. The Greeks and implied volatility are covered in level 3; here the intuition is enough: the buyer of an option pays for the passage of time and the seller collects it.
Put-call parity
A long call and a short put with the same strike and expiry pay, at expiry, in every scenario: the same as a future bought at . If two portfolios pay the same in the future, they have to cost the same today, and that gives the put-call parity of European options:
with the interest rate, the index dividend yield (both as continuous rates) and the forward, the theoretical price of a future with the same expiry (lesson 1.2.3). For the date of the data, = 4.354% and = 1.350% (data README).
On a real chain it has to be checked with executable prices. Buying the call and selling the put costs ; selling the call and buying the put brings in . If the theoretical value falls between the two, no arbitrage is possible after paying the spreads:
r, q = 0.04354, 0.01350
ancho = cad.pivot_table(index=["vencimiento", "strike"],
columns="tipo", values=["bid", "ask"])
ancho.columns = [f"{t}_{c}" for c, t in ancho.columns]
venc = ancho.index.get_level_values("vencimiento")
k = ancho.index.get_level_values("strike")
T = (venc - pd.Timestamp("2025-06-02")).days / 365
F = S * np.exp((r - q) * T)
ancho["teorico"] = np.exp(-r * T) * (F - k)
ancho["minimo"] = ancho["call_bid"] - ancho["put_ask"]
ancho["maximo"] = ancho["call_ask"] - ancho["put_bid"]
dentro = ancho["teorico"].between(ancho["minimo"], ancho["maximo"])
print(f"Strikes that satisfy parity: {dentro.sum()} of "
f"{len(ancho)}")
ancho.loc[("2025-07-18", 6350),
["minimo", "teorico", "maximo"]].round(2)Strikes that satisfy parity: 292 of 292
minimo 32.20
teorico 33.04
maximo 33.80
Name: (2025-07-18 00:00:00, 6350), dtype: float64All 292 pairs satisfy parity within the spread. At the July at-the-money strike, the theoretical value of is 33.04 points and the executable interval, from 32.20 to 33.80, is 1.6 wide. Parity also works the other way: from the mid prices of the call and the put you can infer the forward the market is pricing, without needing a future. It is a common way to estimate the implied dividend or to detect faulty data:
ancho["medio_cp"] = ((ancho["call_bid"] + ancho["call_ask"])
- (ancho["put_bid"] + ancho["put_ask"])) / 2
ancho["F_implicito"] = k + ancho["medio_cp"] * np.exp(r * T)
resumen = ancho.groupby(level="vencimiento")["F_implicito"].median()
pd.DataFrame({"implied F": resumen,
"theoretical F": S * np.exp((r - q) *
(resumen.index - pd.Timestamp("2025-06-02"))
.days.values / 365)}).round(2) implied F theoretical F
vencimiento
2025-06-20 6368.52 6368.53
2025-07-18 6383.22 6383.22
2025-09-19 6416.42 6416.40
2025-12-19 6464.65 6464.64The implied forward matches the theoretical one to within 0.02 points in all four expiries, and it is above the index because the interest rate exceeds the dividend yield: the same contango as the FIX futures.
American and European
The difference is when they can be exercised:
- A European option is only exercised at expiry. European options include, for example, SPX options on the S&P 500, the ones in this lesson and many index options.
- An American option can be exercised any day until expiry. As a general rule, options on individual stocks and on ETFs listed in the US are American, and when exercised they settle by delivering the shares2Note 2 · Cboe, on the SPX specifications page: index options (European) are only exercised at expiry; options on stocks and ETFs (American) can be exercised at any time before expiry. In its comparison, ETF options settle by delivery of the shares..
Being able to exercise early never subtracts value, so an American option is worth at least as much as the equivalent European one. Above all, an American option can never be worth less than its intrinsic value: if it cost less, you could simply buy it and exercise it on the spot. A European one can, and the FIX chain shows it:
bajo = cad[(cad["tipo"] == "put") & (cad["extrinseco"] < 0)]
print(f"Puts with a premium below intrinsic value: {len(bajo)}")
print(bajo.groupby("vencimiento")["strike"].min())
peores = bajo.nsmallest(3, "extrinseco").set_index("vencimiento")
peores[["strike", "medio", "intrinseco", "extrinseco"]].round(2)Puts with a premium below intrinsic value: 93
vencimiento
2025-06-20 6625
2025-07-18 6750
2025-09-19 6900
2025-12-19 7050
Name: strike, dtype: int64
strike medio intrinseco extrinseco
vencimiento
2025-12-19 8800 2280.35 2440.9 -160.55
2025-12-19 8750 2231.55 2390.9 -159.35
2025-12-19 8700 2182.75 2340.9 -158.15The 93 affected puts are all deep in the money: from the 6,625 strike in June, 4% above the index, and from 7,050 in December, 11% above. Longer expiries need to be deeper in the money because more time value is left to offset. The reason for the phenomenon is the interest rate. A deep in-the-money put will almost certainly be exercised, and what it pays then is : the strike is received at expiry, not today. Discounted at a rate of 4.35%, that future payment is worth less than its face amount. The December put with an 8,800 strike is worth about 160 points less than if it could be exercised today.
With an American put, that discount is a reason to exercise early: receiving the strike today and investing it at the interest rate is worth more than waiting. That is why early exercise makes sense mainly in two cases:
Mermaid source
flowchart TB
A[American option] --> T{Type?}
T -- put --> P{Deep in the money?}
P -- yes --> PI[Exercise now]
P -- no --> NO[Wait]
T -- call --> D{Dividend before expiry?}
D -- yes --> CD[Exercise the day before]
D -- no --> NO- Deep in-the-money put: the interest on the strike received today exceeds the remaining time value.
- Call just before a dividend: the call holder does not receive dividends. If the dividend is larger than the time value, it pays to exercise the day before the ex date to hold the shares and receive it (lesson 1.2.1). Without dividends, exercising an American call early never pays: selling it yields more than exercising it.
For a system that sells American options, this is an operational risk: the counterparty can exercise against the position at any time (assignment), and the seller ends up with shares bought or sold that it did not expect. For a backtest, it means an American option cannot be valued with a European option's formula.
What to watch for in options data
A chain has hundreds of contracts per underlying and day, and most of them barely trade. Liquidity is concentrated in a few:
jul = cad[cad["vencimiento"] == "2025-07-18"].copy()
jul["redondo"] = np.where(jul["strike"] % 100 == 0,
"multiple of 100", "other")
jul["horquilla_pct"] = 100 * (jul["ask"] - jul["bid"]) / jul["medio"]
print(jul.groupby("redondo")["interes_abierto"].mean().round(0))
jul[jul["tipo"] == "call"].set_index("strike").loc[
[6000, 6400, 6800, 7000, 7200],
["bid", "ask", "horquilla_pct"]].round(2)redondo
multiple of 100 17177.0
other 5873.0
Name: interes_abierto, dtype: float64
bid ask horquilla_pct
strike
6000 432.80 434.70 0.44
6400 144.90 145.60 0.48
6800 14.40 14.70 2.06
7000 1.75 1.90 8.22
7200 0.00 0.15 200.00Strikes that are multiples of 100 hold about three times more open interest than the ones in between: that is where trading is concentrated. And the spread, measured as a percentage of the premium, shoots up far from the money: on the 7,200 call the bid is zero and the ask, 0.15. On an option like that the “mid price” means nothing, and a backtest that buys and sells at the mid completely underestimates costs.
The most common mistakes when working with options data:
- Using the last price. A thinly traded option may have had its last trade days ago, with the underlying somewhere else. To value it, use the bid and ask from the same instant.
- Mixing times. The index at the close and the options at 15:45 are not simultaneous. In parity, one minute of difference with the underlying moving breaks the equality.
- Forgetting the multiplier or the style. 100 USD per point for most US options, other values in other markets; European or American; cash or physical delivery. It is all in the contract specification.
- Contract survivorship bias. Historical chains only have the strikes that existed; those added after a strong move were not there before. And expired contracts disappear from many free APIs.
For real chains, yfinance gives the current snapshot of the chain of a US underlying, with no history (lesson 1.3.2). It is not run here because it needs a network connection:
import yfinance as yf
spy = yf.Ticker("SPY")
print(spy.options[:4]) # available expiries
cadena = spy.option_chain(spy.options[0])
calls, puts = cadena.calls, cadena.puts
calls[["strike", "bid", "ask", "lastPrice", "volume",
"openInterest", "impliedVolatility", "inTheMoney"]]option_chain returns two tables, calls and puts, with one row per strike: price, volume, open interest and an
implied volatility computed by Yahoo. SPY options are American and on an ETF that pays dividends, so neither exact
parity nor the check for puts below intrinsic value applies as is.
Exercises
- A long straddle is a call and a put bought with the same strike and expiry. Using the mid prices of the July 6,350 strike, compute the total premium and the two breakevens. How much does the index have to move, in percent, for the position to make money?
Solution
coste = prima["call"] + prima["put"]
arriba, abajo = K + coste, K - coste
print(f"Total premium: {coste:.2f} points "
f"({coste * mult:,.0f} USD)")
print(f"Breakevens: {abajo:.2f} and {arriba:.2f}")
print(f"Move needed: {arriba / S - 1:+.2%} "
f"or {abajo / S - 1:+.2%}")Total premium: 315.10 points (31,510 USD)
Breakevens: 6034.90 and 6665.10
Move needed: +4.81% or -5.10%The straddle pays off if the index moves a lot in either direction, and loses the entire premium if it ends exactly at the strike. The index has to rise more than 4.81% or fall more than 5.10% in six weeks. It is a bet that realized volatility will be higher than what the market has priced into the premiums.
- Use put-call parity to infer, for each expiry, which dividend yield the chain is pricing (assume = 4.354% is known). Hint: with the implied forward, .
Solution
dias = (resumen.index - pd.Timestamp("2025-06-02")).days.values
t = dias / 365
q_impl = r - np.log(resumen.values / S) / t
pd.Series(100 * q_impl, index=resumen.index,
name="implied q (%)").round(3)vencimiento
2025-06-20 1.353
2025-07-18 1.350
2025-09-19 1.349
2025-12-19 1.350
Name: implied q (%), dtype: float64All four expiries give the 1.35% with which the data were generated, with differences of thousandths that come from rounding the premiums to the tick (0.05 or 0.10 points) and that weigh more when is small, as in June. With real data, the implied dividend varies by expiry because dividends are not paid out evenly through the year.
- A colleague computes the extrinsic value of the options in a chain using the last traded price and finds several out-of-the-money calls with negative extrinsic value. What explanations would you give before thinking of an arbitrage?
Solution
An out-of-the-money call has zero intrinsic value, so a negative extrinsic value means a negative price, which is impossible: the error is in the data or in the calculation. The usual causes: the last price comes from an old trade (or from another contract with the same symbol after an adjustment), the underlying used to compute the intrinsic value is not from the same instant, the strike has been adjusted for a corporate event, or calls and puts have been mixed up. With a simultaneous bid and ask from the same instant as the underlying, the check is repeated and the problem usually disappears.
Footnotes
-
SPX option specifications on the Cboe page linked in the resources: 100 USD multiplier, European style and cash settlement. ↩
-
Cboe, on the SPX specifications page: index options (European) are only exercised at expiry; options on stocks and ETFs (American) can be exercised at any time before expiry. In its comparison, ETF options settle by delivery of the shares. ↩
Each answer is folded under its question
Self-assessment
The index is at 6,359.10. A call with a 6,300 strike costs 205.35 points. How much of that premium is extrinsic value?
- A205.35 points: the whole premium
- B59.10 points
- C146.25 points
- DZero, because it is in the money
Show answer
Answer C. 146.25 points
The call's intrinsic value is what exercising it now would yield: 6,359.10 − 6,300 = 59.10. The rest of the premium, 205.35 − 59.10 = 146.25 points, is extrinsic value: what the market pays for the possibility that the index rises further before expiry.
Which position has an unlimited maximum loss at expiry?
- ALong call
- BLong put
- CShort call
- DShort put
Show answer
Answer C. Short call
The seller of a call is obliged to pay
S − Kif the underlying ends above the strike, and the underlying has no ceiling. The short put can also lose a lot, but at mostKminus the premium, because the price cannot fall below zero.Where is the extrinsic value of options with the same expiry highest?
- AIn the deepest in-the-money ones
- BIn the deepest out-of-the-money ones
- CIt is the same at every strike
- DNear the money, with the strike close to the underlying
Show answer
Answer D. Near the money, with the strike close to the underlying
Far from the strike, the outcome is almost decided: deep in the money the option behaves like the underlying and deep out of the money it is worth almost nothing. The uncertainty about whether it will end in or out of the money, which is what is paid as extrinsic value, is greatest when the strike is close to the current price.
In the FIX chain, several deep in-the-money European puts trade below their intrinsic value. Why is there no arbitrage?
- ABecause the data are wrong
- BBecause a European option is only exercised at expiry: the strike is received later and has to be discounted at the interest rate
- CBecause puts have no intrinsic value
- DBecause the multiplier is 100
Show answer
Answer B. Because a European option is only exercised at expiry: the strike is received later and has to be discounted at the interest rate
The value of a deep in-the-money European put approaches
K·e^(−rT) − S·e^(−qT), notK − S. With rates of 4.35%, receiving the strike in a few months is worth less than receiving it today. An American put could be exercised right away, and that is why it is never worth less than its intrinsic value.To check put-call parity on a real chain, which prices should you use?
- ABid and ask:
C_bid − P_askandC_ask − P_bidbound what can actually be done - BThe last traded price of the call and the put
- COnly the call's mid price
- DThe index closing price
Show answer
Answer A. Bid and ask:
C_bid − P_askandC_ask − P_bidbound what can actually be doneThe last price can be hours old and not simultaneous for the call and the put. What is executable is the bid and the ask: buying the call and selling the put costs
C_ask − P_bid; the opposite brings inC_bid − P_ask. If the theoretical value falls between the two, there is no arbitrage after spreads.- ABid and ask:
Key points
- A call gives the right to buy and a put the right to sell, at a fixed price (the strike) until a date. The buyer pays the premium; the seller receives it and takes on the obligation.
- The premium splits into intrinsic value (what exercising now would yield) and extrinsic value (what waiting is worth). Extrinsic value peaks near the money and grows with time to expiry.
- Time value is not lost at a constant pace: it grows roughly with the square root of time to expiry, so the last month takes the most.
- In a European chain,
C − P = e^(−rT)(F − K): put-call parity links calls and puts with the same strike and is used to validate the data and to infer the forward. - An American option can be exercised any day and is never worth less than its intrinsic value; a European one, only at expiry, and it can be worth less.
- Options data mislead easily: stale last prices, wide spreads far from the money, multipliers and exercise styles that differ from contract to contract.