Splitting a change among its inputs
Case study 4.1.4 wrote the call's value as a function of three inputs, . They are the stock price , the volatility and the date , in years from today.
The strike is , expiry is years away and the rate is . Today and , and the call is worth $6.3710.
The Greeks are its partial derivatives. Delta , vega (0.2777 per vol point) and theta per year, or per calendar day.
Case study 4.1.4 used them to estimate a change from given moves. P&L attribution runs the other way. The profit or loss (P&L) has happened, and the desk wants to know how much of it each input caused.
Rule: P&L attribution, first order
This is the total differential of lesson 4.2.2, one term per input. Each part is a Greek, taken at the start of the day, times the move in its own input. is the actual change, found by repricing.
As in case study 2.7.4, on its own is delta, while means the change in . For the night of the hook, per option:
| input | move | Greek | part |
|---|---|---|---|
| stock | |||
| volatility | vega | 0.2777 | |
| time | |||
| explained | |||
| actual, by repricing | |||
| unexplained | 0.0546 |
Times 100 options, these are the hook's numbers. The stock caused the loss, volatility gave back about a quarter of it, and time cost about 2 cents an option. The unexplained part is 6.6% of the loss.
Try it
V is case study 4.1.4's value, with the date as an input, and greeks returns delta, gamma, vega and theta per year. The cell splits the night's change into its three parts.
The last line is the same split for 100 options.
What the unexplained part is
Lesson 4.2.2 found that the total differential misses by about the square of the steps. Here the large step is the stock's, and its square brings in gamma. Case study 2.7.4's gamma term is .
That is almost all of the unexplained 0.0546. With a gamma line added, the gap shrinks to , case study 4.1.4's error.
What is left mixes the moves: vega itself changes when moves, and case study 4.3.4 measures that. So desks report four lines, delta, gamma, vega and theta, and the unexplained remainder.
Big moves
The unexplained part of the first-order split grows like the square of the stock move. Here is the same night, volatility up a point and one day passing, with other stock moves:
| stock move | first-order unexplained | gamma line | left after gamma |
|---|---|---|---|
| 1.38 | 1.3886 | ||
| 0.344 | 0.3472 | ||
| 0.0546 | 0.0555 | ||
| 0.0134 | 0.0139 | ||
| 0.0117 | 0.0139 | ||
| 0.0499 | 0.0555 | ||
| 0.3126 | 0.3472 | ||
| 1.1884 | 1.3886 |
Doubling the move, from $1 to $2 or $5 to $10, multiplies it by about 4. It is positive both ways: a long call's value bends up, so the straight-line estimate always comes out low (case study 2.7.4). After the gamma line, the rest grows faster still, and reaches 20 cents on a $10 rise.
Close to expiry
Gamma grows as expiry nears (case study 2.7.4), and the unexplained part with it. Take a one-week call at the same strike, worth $1.1352, with . On a night with a $5 fall and volatility up a point, the first-order split leaves 1.49 unexplained: more than the option is worth.
The gamma line, 1.80, now overshoots, and is still left. For short-dated options near the strike, desks reprice rather than trust the lines.
The P&L that delta, vega and theta leave unexplained, as the stock move grows. It grows like the square of the move, and much faster with a week to expiry, where gamma is about five times larger. A gamma line removes most of it at six months but not at one week.
Volatility moves leave much less unexplained for this call, because its value is almost a straight line in (case study 2.4.6). A jump of ten vol points with the stock unchanged leaves only .
Try it
split returns the first-order unexplained part and the gamma line for a stock move dS, with volatility up a point and one day passing. T is the time to expiry today.
The first loop prints the table. Then the one-week call, and a ten-point volatility jump on its own.
Attribution by repricing
Many risk systems split the P&L a second way: they reprice with one input moved at a time, the others left at yesterday's values. This is case study 2.8.4's bump-and-reprice, with the day's actual moves as the bumps.
| moved alone | stock | volatility | time | sum | actual |
|---|---|---|---|---|---|
| change in value | 0.2778 |
The stock's line now includes the bend: it is the delta part plus 0.0563, close to the gamma line. What is left, , is only the effect of the moves together.
Some systems instead move the inputs one after another, each from where the last left off. Then the parts add up exactly to the actual change, but the split depends on the order:
- Stock first, then volatility: the stock , volatility 0.2763.
- Volatility first, then stock: volatility 0.2778, the stock .
The difference, 0.0015, is the mixed effect once more. Volatility moved after the stock fell, when vega was only 0.2763 per point. No order is the right one, so a desk picks one and keeps it, and its reports stay comparable from day to day.
Try it
The first line moves each input alone and adds the three changes. The next two lines move them in turn, in the two orders; in both, time moves last.
The last line is vega per vol point with the stock at $98.
A small book
A book of options is attributed the same way, line by line. Take two positions, each option on one share:
- long 100 of the six-month calls;
- short 100 one-month calls with the same strike, 100.
| option | value | delta | gamma | vega per point | theta per day |
|---|---|---|---|---|---|
| six-month | 6.3710 | 0.5702 | 0.0278 | 0.2777 | |
| one-month | 2.4271 | 0.5288 | 0.0689 | 0.1149 |
A book's Greek is the sum of quantity times Greek, because the derivative of a sum is the sum of the derivatives (lesson 2.3.1). A short position enters with quantity . So the book has delta 4.14, gamma , vega 16.29 per point and theta 2.25 per day.
The book hardly notices small stock moves, gains when volatility rises, and earns time decay, since the one-month option decays faster. In exchange it is short gamma: large stock moves either way cost it money. This mix is called a calendar spread.
Here is its night, in dollars:
| line | long six-month | short one-month | book |
|---|---|---|---|
| delta | 105.75 | ||
| vega | 27.77 | 16.29 | |
| theta | 4.19 | 2.25 | |
| explained | 98.46 | 10.26 | |
| actual | 85.12 | 2.38 | |
| unexplained | 5.46 | ||
| gamma line | 5.55 |
The two delta lines almost cancel, and the first-order split says the book made $10.26, mostly from volatility. It made $2.38. The unexplained part, −$7.88, is more than three times the profit, and negative, because the book is short gamma. The gamma line, −$8.23, accounts for it.
On a bigger day the gap swamps the split. After a $5 fall with volatility up a point, the three lines say −$2.16, but the book loses $49.48; the gamma line is −$51.43. A short-gamma book needs its gamma line every day.
Try it
Each position is a quantity, a strike and a time to expiry. The first line is the book's Greeks: delta, gamma, vega per point and theta per day, each the sum of quantity times Greek.
Then each position's night, in the order of the table's lines, and the book's totals. The last line is the $5 fall.
Explain a day's P&L
Write pnl_explain(S0, S1, sigma0, sigma1, days, K, T, r, qty=1) for a position of qty Black–Scholes calls, each on one share. S0 and sigma0 are yesterday's stock price and volatility, S1 and sigma1 today's, and T is yesterday's time to expiry in years. Return a dictionary with:
"delta","gamma","vega"and"theta": the four lines , , and , timesqty. Use yesterday's Greeks, with per year anddays/ 365;"actual": the change in the position's value, by repricing with left;"unexplained": the actual change minus the four lines.
Raise ValueError if a price, a volatility, K or T is not positive, if days is negative, or if the option expires within days.
The tests use the night of this case study, the short one-month position, a weekend with nothing moving, and inputs that should be refused.
See the tests
import math
from scipy.stats import norm
def _call(S, K, tau, r, sigma):
d1 = (math.log(S / K) + (r + sigma**2 / 2) * tau) / (sigma * math.sqrt(tau))
return S * norm.cdf(d1) - K * math.exp(-r * tau) * norm.cdf(d1 - sigma * math.sqrt(tau))
def _raises_value_error(f, *args, **kwargs):
try:
f(*args, **kwargs)
except ValueError:
return True
return False
def _night(qty=1, T=0.5):
return pnl_explain(100, 98, 0.2, 0.21, 1, 100, T, 0.03, qty=qty)
def test_returns_all_lines():
"""The answer has the six keys delta, gamma, vega, theta, actual and unexplained"""
out = _night()
assert isinstance(out, dict), f"expected a dictionary, got {type(out).__name__}"
missing = {"delta", "gamma", "vega", "theta", "actual", "unexplained"} - set(out)
assert not missing, f"missing keys: {sorted(missing)}"
def test_night_lines():
"""On the night (stock 100 to 98, volatility 20% to 21%, one day) the lines are −1.1403, 0.0555, 0.2777 and −0.0194"""
out = _night()
expect_close(out["delta"], -1.140316, what="delta line", rel=1e-5)
expect_close(out["gamma"], 0.055544, what="gamma line", rel=1e-4)
expect_close(out["vega"], 0.277721, what="vega line", rel=1e-5)
expect_close(out["theta"], -0.019380, what="theta line", rel=1e-3)
def test_night_actual_and_unexplained():
"""On the night the call loses 0.8274 and about 0.0009 is left unexplained"""
out = _night()
expect_close(out["actual"], _call(98, 100, 0.5 - 1 / 365, 0.03, 0.21) - _call(100, 100, 0.5, 0.03, 0.2), what="actual change", rel=1e-9)
expect_close(out["unexplained"], -0.000934, what="unexplained", rel=1e-2)
def test_lines_add_up():
"""The four lines and the unexplained part add up to the actual change"""
out = pnl_explain(50, 53, 0.3, 0.27, 2, 55, 0.25, 0.01, qty=7)
total = out["delta"] + out["gamma"] + out["vega"] + out["theta"] + out["unexplained"]
expect_close(total, out["actual"], what="sum of the lines and the unexplained part", rel=1e-12, abs_tol=1e-12)
def test_short_position():
"""Short 100 one-month calls: the delta line is +105.75 and the gamma line −13.78 on the night"""
out = _night(qty=-100, T=1 / 12)
expect_close(out["delta"], 105.749, what="delta line", rel=1e-4)
expect_close(out["gamma"], -13.78, what="gamma line", rel=1e-3)
expect_close(out["vega"], -11.486, what="vega line", rel=1e-3)
expect_close(out["actual"], 100 * (_call(100, 100, 1 / 12, 0.03, 0.2) - _call(98, 100, 1 / 12 - 1 / 365, 0.03, 0.21)), what="actual change", rel=1e-9)
def test_weekend():
"""Over a weekend with nothing moving, only theta counts: three days of it, and almost nothing unexplained"""
out = pnl_explain(100, 100, 0.2, 0.2, 3, 100, 0.5, 0.03)
expect_close(out["delta"], 0.0, what="delta line", abs_tol=1e-12)
expect_close(out["vega"], 0.0, what="vega line", abs_tol=1e-12)
expect_close(out["theta"], 3 * -0.019380, what="theta line", rel=1e-3)
assert abs(out["unexplained"]) < 0.001, f"expected almost nothing unexplained, got {out['unexplained']}"
def test_expires_within_days():
"""An option with one week left cannot be explained over 8 days: ValueError"""
assert _raises_value_error(pnl_explain, 100, 98, 0.2, 0.21, 8, 100, 1 / 52, 0.03), "expected ValueError: the option expires within 8 days"
def test_bad_inputs():
"""A zero volatility, a negative price or negative days: ValueError"""
assert _raises_value_error(pnl_explain, 100, 98, 0.2, 0.0, 1, 100, 0.5, 0.03), "expected ValueError for sigma1 = 0"
assert _raises_value_error(pnl_explain, 100, -98, 0.2, 0.21, 1, 100, 0.5, 0.03), "expected ValueError for S1 = −98"
assert _raises_value_error(pnl_explain, 100, 98, 0.2, 0.21, -1, 100, 0.5, 0.03), "expected ValueError for days = −1"
Summary card
- P&L attribution splits an actual change in value among the inputs that moved. The total differential gives one line per input: , vega and , with the Greeks from the start of the day.
- The unexplained part is the actual change minus the lines. For the first-order split it is mostly , so desks add a gamma line; what remains mixes the moves.
- The unexplained part grows like the square of the move, and fast near expiry, where gamma is large. It has the sign of gamma: positive for a long option, negative for a short-gamma book.
- Repricing one input at a time also splits the P&L. Moving the inputs in turn makes the lines add up exactly, but the split depends on the order.
- A book's Greeks are the sums of quantity times Greek. Keep the conventions fixed: start-of-day Greeks, theta per calendar day, vega per vol point, signs for the holder.