---
title: "1.2.5 Options: calls, puts, premium and chains"
description: "What calls and puts are, how the premium splits into intrinsic and extrinsic value, American versus European options and how to read a chain."
url: "https://pythonparatrading.com/en/python-trading-course/level-1/options-calls-puts-and-time-value/"
lang: "en"
image: "https://pythonparatrading.com/og/en/python-trading-course/level-1/options-calls-puts-and-time-value.png"
---

# 1.2.5 · 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.

- Course: [Level 1: Financial data, markets and analysis](https://pythonparatrading.com/en/python-trading-course/level-1/)
- Module: 1.2 · Asset classes
- Duration: 60 min
- Prerequisites: [1.1.3 · Order book: bid, ask, spread and depth](https://pythonparatrading.com/en/python-trading-course/level-1/order-book-bid-ask-spread-depth/), [1.2.3 · Futures: specifications, margin, roll and contango](https://pythonparatrading.com/en/python-trading-course/level-1/futures-contract-specs-margin-and-roll/)

## By the end of this lesson

- Explain what each side of a call and a put buys and sells, and draw its payoff at expiry.
- Split the premium of each option in a chain into intrinsic and extrinsic value, and classify it by its relation to the underlying (in, at or out of the money).
- Read an option chain as a strike ladder and check put-call parity within the bid-ask spread with pandas.
- Distinguish American and European options and recognize in the data a European put trading below its intrinsic value.

## 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
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 500[^1]: 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.

[^1]: SPX option specifications on the Cboe page linked in the resources: 100 USD multiplier, European style and cash
settlement.

## 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).

```python
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"))
```
```output
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   746209
```

Four 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.

```python
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]
```
```output
        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.4
```

The 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 $S_T$ the underlying
at expiry and $K$ the strike:

$$
\text{call: } \max(S_T - K,\ 0) \qquad \text{put: } \max(K - S_T,\ 0)
$$

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:

```python
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"))
```
```output
{'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.05
```

*Fig. — Expiry P&L of the four basic positions with the July 6,350-strike options, at the 2 June 2025 mid price (solid line, long; dashed, short).* Two panels. Left, 6,350-strike call with a premium of 174.05: the long call loses the premium up to 6,350 and makes money above the break-even of 6,524.05, highlighted; the short call is its mirror image. Right, 6,350-strike put with a premium of 141.05: the long put makes money below the break-even of 6,208.95 and loses the premium above 6,350; the short put, the opposite.

Each 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 | $K + \text{premium}$ |
| Short call | Not bullish | The premium | Unlimited | $K + \text{premium}$ |
| Long put | Bearish | $K - \text{premium}$ | The premium | $K - \text{premium}$ |
| Short put | Not bearish | The premium | $K - \text{premium}$ | $K - \text{premium}$ |

```python
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%}")
```
```output
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.

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

Selling uncovered options (without holding the underlying or an option that limits the loss) yields small, frequent
gains and large, rare losses. A short backtest usually shows only the former. Brokers require high margins for these
positions and, in a sharp drop, can close them at any price. The loss on a short call has no ceiling.

</aside>

## 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, $\max(S - K, 0)$ for a call and $\max(K - S, 0)$ 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) | $K < S$ | $K > S$ | Positive |
| At the money (ATM) | $K \approx S$ | $K \approx S$ | Almost zero |
| Out of the money (OTM) | $K > S$ | $K < S$ | Zero |

```python
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)
```
```output
     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       ITM
```

The 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.

*Fig. — Premium of the July FIX calls against their intrinsic value, by strike, at the close of 2 June 2025. The gap between them is the extrinsic value.* Line of the call premium, from 5,700 to 7,100, over the intrinsic value area, which falls in a straight line from 659.10 at the 5,700 strike to zero at the index, 6,359.10. Far from the index the line hugs the area or zero; close to it the gap widens: at the 6,350 strike, highlighted, the premium is 174.05 and the extrinsic value 164.95.

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.

```python
idx = cad.groupby(["vencimiento", "tipo"])["extrinseco"].idxmax()
maximos = cad.loc[idx].set_index(["vencimiento", "tipo"])
maximos[["strike", "extrinseco"]].round(2)
```
```output
                  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.30
```

In 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.

```python
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)
```
```output
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.65
```

If time value were proportional to time to expiry, the last column would be multiplied by 3.3 from the first expiry to
the last ($\sqrt{200/18}$); 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 $V = a\sqrt{\text{days}}$ shows how the time value of a 200-day
at-the-money option is used up if nothing else changes:

```python
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)")
```
```output
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)
```

*Fig. — Time value of the FIX 6,350-strike options (average of call and put) in the four expiries of the 2 June 2025 chain, with the a·√days curve fitted to the four points.* Four points with the time value by days to expiry, with the days axis running from 200 to 0 so time moves to the right: 362.70 at 200 days, 252.15 at 109, 153.00 at 46 and 89.72 at 18. The square-root curve through them falls slowly at first and fast at the end; highlighted, the last month, which takes 39% of the starting 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, $S_T - K$ in every scenario: the same as a
future bought at $K$. 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:

$$
C - P = e^{-rT}\,(F - K), \qquad F = S\,e^{(r - q)T}
$$

with $r$ the interest rate, $q$ the index dividend yield (both as continuous rates) and $F$ the forward, the
theoretical price of a future with the same expiry (lesson 1.2.3). For the date of the data, $r$ = 4.354% and
$q$ = 1.350% (data README).

On a real chain it has to be checked with executable prices. Buying the call and selling the put costs
$C_{ask} - P_{bid}$; selling the call and buying the put brings in $C_{bid} - P_{ask}$. If the theoretical value falls
between the two, no arbitrage is possible after paying the spreads:

```python
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)
```
```output
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: float64
```

All 292 pairs satisfy parity within the spread. At the July at-the-money strike, the theoretical value of $C - P$ 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:

```python
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)
```
```output
             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.64
```

The 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.

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

If many strikes violate parity when you check it on real data, the first suspect is the data, not an arbitrage: call
and put prices taken at different moments, a last price instead of the bid and ask, a misassigned expiry or a dividend
that has not been accounted for. For American options on stocks, parity is only an inequality, because early exercise
has value.

</aside>

## 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 shares[^2].

[^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:

```python
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)
```
```output
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.15
```

The 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 $K - S_T$: 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
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:

```python
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)
```
```output
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.00
```

Strikes 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:

```python skip
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

1. 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.**

```python
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%}")
```
```output
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.

2. Use put-call parity to infer, for each expiry, which dividend yield $q$ the chain is pricing (assume $r$ = 4.354%
   is known). Hint: with the implied forward, $q = r - \ln(F/S)/T$.

**Solution.**

```python
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)
```
```output
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: float64
```

All 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 $T$ is small, as in June. With real
data, the implied dividend varies by expiry because dividends are not paid out evenly through the year.

3. 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.

## Self-assessment

1. 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?

   - A. 205.35 points: the whole premium
   - B. 59.10 points
   - C. 146.25 points
   - D. Zero, because it is in the money

   *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.

2. Which position has an unlimited maximum loss at expiry?

   - A. Long call
   - B. Long put
   - C. Short call
   - D. Short put

   *Answer C: Short call.* The seller of a call is obliged to pay `S − K` if the underlying ends above the strike, and the underlying has no ceiling. The short put can also lose a lot, but at most `K` minus the premium, because the price cannot fall below zero.

3. Where is the extrinsic value of options with the same expiry highest?

   - A. In the deepest in-the-money ones
   - B. In the deepest out-of-the-money ones
   - C. It is the same at every strike
   - D. Near the money, with the strike close to the underlying

   *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.

4. In the FIX chain, several deep in-the-money European puts trade below their intrinsic value. Why is there no arbitrage?

   - A. Because the data are wrong
   - B. Because a European option is only exercised at expiry: the strike is received later and has to be discounted at the interest rate
   - C. Because puts have no intrinsic value
   - D. Because the multiplier is 100

   *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)`, not `K − 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.

5. To check put-call parity on a real chain, which prices should you use?

   - A. Bid and ask: `C_bid − P_ask` and `C_ask − P_bid` bound what can actually be done
   - B. The last traded price of the call and the put
   - C. Only the call's mid price
   - D. The index closing price

   *Answer A: Bid and ask: `C_bid − P_ask` and `C_ask − P_bid` bound what can actually be done.* The 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 in `C_bid − P_ask`. If the theoretical value falls between the two, there is no arbitrage after spreads.

## 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.

## Resources

### Research

- [Uses of Volatility and Current Volatility - VIX1D](https://pythonparatrading.com/en/uses-of-volatility/) · Research-0003 · 5 min

### Documentation

- [Cboe: SPX option specifications (European, cash-settled)](https://www.cboe.com/tradable_products/sp_500/spx_options/specifications/) · cboe.com

### Books

- [John C. Hull, Options, Futures, and Other Derivatives (Pearson, 11th ed., 2021)](https://www.pearson.com/en-us/subject-catalog/p/options-futures-and-other-derivatives/P200000005938) · pearson.com

### Data

- [Level 1 data: FIX option chain on June 2, 2025 (CSV)](https://pythonparatrading.com/curso/datos/nivel-1/opciones_cadena_fix.csv) · Download · opciones_cadena_fix.csv

Previous lesson: [1.2.4 · Forex: pairs, pips, lots, rollover and CFDs](https://pythonparatrading.com/en/python-trading-course/level-1/forex-pairs-pips-lots-and-rollover/)

Next lesson: [1.2.6 · Crypto: spot, perpetuals, funding and custody](https://pythonparatrading.com/en/python-trading-course/level-1/crypto-spot-perpetuals-and-funding/)
