The data
The returns below are invented for illustration, not market data. They are 60 monthly returns made with a fixed seed. Each month the index moves at random, and the stock moves 1.2 times the index plus a move of its own.
Write for the stock's 60 returns and for the future's, two vectors in as in case study 3.1.4. A future's return here means its gain per dollar of index it covers; the cost of holding it is left out.
The stock's monthly volatility is 6.26%, or 21.69% a year after multiplying by (case study 3.2.5). The future's is 4.63% a month, and the correlation of the two is 0.852.
Try it
rng.normal(mean, size, 60) draws 60 random numbers around mean, of typical size size (Level 5 explains it). The returns are rounded to 0.01%.
The cell prints the first three months of each, then the two monthly volatilities, the stock's annual volatility and the correlation.
The variance of a hedged position
Hold $1 of stock and sell dollars of futures. Each month the position returns , and is called the hedge ratio. Its variance is case study 1.2.4's two-asset formula with weights 1 and , or with (case study 3.2.5):
These are sample variances and covariances, dividing by . Here , and , all monthly.
The variance is a parabola in that opens upwards. Its lowest point, the vertex of lesson 1.2.2, is at . (The rounded numbers above give 1.152.)
| 0 | 0.5 | 1 | 1.1518 | 1.5 | 2 | |
|---|---|---|---|---|---|---|
| volatility, a month | 6.26% | 4.46% | 3.35% | 3.28% | 3.65% | 5.12% |
| volatility, a year | 21.69% | 15.44% | 11.62% | 11.36% | 12.66% | 17.73% |
Selling one dollar of futures per dollar of stock already does well, 11.62%. Selling two does worse than selling half: beyond the extra futures are a bet against the index.
Rule: Minimum-variance hedge ratio
holds the returns of the position to hedge and those of the hedge. Selling dollars of the hedge per dollar of position leaves the least variance. With correlation and volatilities and , .
Try it
The first line prints the three numbers of the parabola, then .
The loop is the table: the volatility of for each , a month and a year.
The hedge is a projection
Centre both vectors, as in case study 3.1.4: and . Then and . The two cancel:
That is the coefficient of the projection of onto the line through (lesson 3.5.2). It is also the slope of the least-squares line of the stock's returns against the future's (lesson 3.5.3). Desks call it the stock's beta to the index.
The centred hedged return, , is the residual of that projection. So it is orthogonal to : the hedged position is uncorrelated with the future. No other amount of the future removes more of its risk.
What is left
The projection and the residual are the legs of a right-angled triangle with hypotenuse . By Pythagoras, the sums of squares split: . Divided by 59, the variance 0.003921 splits into 0.002846 removed and 0.001075 left.
The angle between and has , so (case study 3.1.4). The residual's length is , so the hedge leaves of the volatility: .
Rule: Risk left by the best hedge
is the share of the variance the hedge removes, here . The volatility left is .
The hedged volatility against the hedge ratio, and the same numbers as a projection. The best hedge removes the part of the stock's centred returns along the future's. The residual at right angles is the risk left, of the original.
Hedges need high correlations. At , the best hedge leaves of the volatility. Even at it leaves 0.4359.
Try it
The cell centres both vectors and computes as a projection coefficient. The residual's dot product with is rounding noise, so the cell tests it against .
Then Pythagoras, , , and the slope from lstsq with a column of ones (lesson 3.5.3).
Several hedges
With instruments, sell of them per dollar of the position. Put their centred returns in the columns of a matrix , one row per month. The centred hedged return is , and making its variance smallest is least squares (lesson 3.5.3).
The normal equations are . Divided by , each entry becomes a covariance: is the instruments' covariance matrix, and holds their covariances with the position.
Rule: The minimum-variance hedge
is the covariance matrix of the hedging instruments, and the vector of their covariances with the position, whose variance is . Entry of is how much of instrument to sell per unit of the position. With one instrument, this is .
Hedging asset C with A and B
Take the three assets of case study 3.2.5 and hedge a holding of C with A and B. The blocks of are
Solving gives . The variance left is 0.046703, a volatility of 21.61% against C's 25%, so . B alone, with , already gives , which is .
The ratio for A is negative although A and C are positively correlated. Selling 0.7173 of B also sells some of A's moves, since A and B move together, and buying a little A takes that back.
On a sample the same equations hold with sample covariances. Case study 3.2.5's 120 invented months give , both from the sample covariance matrix and from least squares on the centred returns. The sample is 0.1789, against the model's 0.2527: ten years of data pin a hedge down no better than they pin down .
Try it
The first block solves for the model, then gives the variance left, its volatility and , and B alone.
The second block uses case study 3.2.5's sample, with the same seed. lstsq on the centred returns and solve with the sample covariances agree, and the residual is orthogonal to both hedges.
Equal volatilities: case study 3.4.3's system
Case study 3.4.3 hedged a stock with two futures on the same index and took the system on trust. Here is where it comes from. Give the stock and every hedge the same volatility .
Then every covariance is times a correlation: , with the hedges' correlation matrix, and , with their correlations with the stock. Dividing by leaves .
For futures with correlation 0.999 and , this gives case study 3.4.3's , with . One future alone gives and . The second future adds 0.0004, and, as case study 3.4.3 showed, positions that swing with every small change in .
With unequal volatilities, each ratio is scaled by , where is hedge 's volatility. For a stock with volatility 0.25 and futures with 0.20 and 0.21, the same correlations give .
Try it
The cell solves the system both ways, with and and with and for . They agree.
The last lines are for two futures and for one, and the hedge with unequal volatilities.
The closest copy of the call
Case study 3.3.4 found that the bond and the stock alone cannot copy the call: their payoff matrix has rank 2, below the three states. Least squares finds the closest copy instead. Here holds units bought, as in case study 3.3.4: a negative entry is a short position.
The normal equations give : sell 50 bonds and buy 0.5738 shares. The copy pays , 7.3770 and 21.7213 in the down, middle and up states.
| down | middle | up | sum of squares | |
|---|---|---|---|---|
| call | 0 | 0 | 25 | |
| closest copy | 7.3770 | 21.7213 | ||
| leftover | 4.0984 | 3.2787 | 81.9672 |
The lines of case study 3.3.4's figure did worse. met two states and missed the down state by 20, a sum of squares of 400. The line through the down and up states missed the middle by 11.11, a sum of 123.4568.
What the leftover means
The leftover payoff is orthogonal to both columns of . So it adds up to 0 across the three states, and it is orthogonal to the stock's payoffs too. Counting the states as equally likely, it has mean 0 and no covariance with the stock.
So the closest copy is the minimum-variance hedge of the call with the stock, with the bond taking care of the mean. The 0.5738 shares are over the three states, the hedge ratio of the first half of this case study.
The copy costs 8.1598 today: selling 50 bonds brings in 48.50, and 0.5738 shares cost 56.6598. The call trades at 6.75.
That is not an arbitrage: the copy does not pay what the call pays. The difference, , is the price of the leftover at the market's state prices.
The bond and the stock do not fix those state prices. Along case study 3.3.4's line of state prices, the copy always costs 8.1598, but the leftover costs anything from to 3.5902. The whole range of call prices that case study left open, 1.75 to 11.75, sits in the leftover.
Least squares counts every state equally. A desk that thinks some states more likely would weight their misses more, and the holdings would change.
Try it
lstsq finds the closest copy; the leftover is checked against both columns.
The loop prices the copy and the leftover along case study 3.3.4's line of state prices. The last line is the hedge ratio over the three states.
Try it
The loop estimates on each run of 24 months; the next line uses the two halves. Then the percent mix-up and the number of contracts.
The minimum-variance hedge and the closest copy
Write two functions, with NumPy.
min_variance_hedge(target, hedges)returns(h, r2).targetholds returns of the position, andhedgesis either returns of one instrument or an array with one column per instrument. Centre every series and find by least squares. Return it as an array of length , with , where is the centred hedged return.closest_copy(D, payoff)returns(h, leftover): the holdings whose payoffsD @ hare closest topayoffin the least-squares sense, andpayoff - D @ h.
Raise ValueError when the sizes do not match or the target is constant. Raise it too when the hedges, or the columns of D, are dependent: a constant hedge, or one repeated.
The tests use this case study's data and the sample of case study 3.2.5. They also use the call and the put of case study 3.3.4, and each bad input.
See the tests
import numpy as np
def _case_study_data():
"""The 60 invented months of case study 3.5.5 (seed 355): the stock x and the index future f."""
rng = np.random.default_rng(355)
index = rng.normal(0.006, 0.045, 60)
stock = 0.003 + 1.2 * index + rng.normal(0, 0.035, 60)
return np.round(stock, 4), np.round(index, 4)
def _sample_325():
"""Case study 3.2.5's 120 invented months of A, B and C (seed 7)."""
sigma = np.array([0.12, 0.18, 0.25])
R = np.array([[1.0, 0.3, 0.1], [0.3, 1.0, 0.5], [0.1, 0.5, 1.0]])
Sigma = np.diag(sigma) @ R @ np.diag(sigma)
rng = np.random.default_rng(7)
return rng.multivariate_normal(np.array([0.06, 0.08, 0.11]) / 12, Sigma / 12, size=120, method="cholesky")
def _raises_value_error(fn, *args):
try:
fn(*args)
except ValueError:
return True
return False
BOND_STOCK = [[1, 80], [1, 100], [1, 125]]
CALL = [0, 0, 25]
def test_one_hedge():
"""The stock hedged with the future: h = Cov / Var = 1.1518, returned as an array of length 1"""
x, f = _case_study_data()
h, _ = min_variance_hedge(x, f)
expect_equal(np.shape(h), (1,), what="shape of h for one hedge")
expect_close(h[0], np.cov(x, f)[0, 1] / np.var(f, ddof=1), what="h for the stock and the future")
expect_close(h[0], 1.1518, rel=1e-4, what="h, to 4 decimals")
def test_r2_is_rho_squared():
"""With one hedge, R² is the squared correlation, 0.7259"""
x, f = _case_study_data()
_, r2 = min_variance_hedge(x, f)
expect_close(r2, np.corrcoef(x, f)[0, 1] ** 2, what="R² for one hedge")
expect_close(r2, 0.7259, rel=1e-4, what="R², to 4 decimals")
def test_small_example():
"""Target (0.02, −0.01, 0.03, 0) hedged with (0.01, −0.01, 0.02, 0): h = 1.4"""
x = [0.02, -0.01, 0.03, 0.0]
f = [0.01, -0.01, 0.02, 0.0]
h, _ = min_variance_hedge(x, f)
expect_close(h[0], np.cov(x, f)[0, 1] / np.var(f, ddof=1), what="h for the small example")
def test_negative_hedge():
"""Hedging with the opposite of the future gives h = −1.1518: buy it instead of selling"""
x, f = _case_study_data()
h, r2 = min_variance_hedge(x, -f)
expect_close(h[0], -np.cov(x, f)[0, 1] / np.var(f, ddof=1), what="h for the reversed hedge")
expect_close(r2, 0.7259, rel=1e-4, what="R² for the reversed hedge")
def test_two_hedges():
"""Case study 3.2.5's sample: C hedged with A and B gives h = (0.1061, 0.4895) and R² = 0.1789"""
X = _sample_325()
h, r2 = min_variance_hedge(X[:, 2], X[:, :2])
S = np.cov(X, rowvar=False)
expect_close(h, np.linalg.solve(S[:2, :2], S[:2, 2]), what="h from the sample covariance matrix")
expect_close(r2, 0.1789, rel=1e-3, what="R² for C hedged with A and B")
def test_units():
"""The stock in percent and the future in decimals make h 100 times bigger: 115.18"""
x, f = _case_study_data()
h, r2 = min_variance_hedge(100 * x, f)
expect_close(h[0], 100 * np.cov(x, f)[0, 1] / np.var(f, ddof=1), what="h with the stock in percent")
expect_close(r2, 0.7259, rel=1e-4, what="R² with the stock in percent")
def test_constant_hedge():
"""A hedge that returns 0.3% every month cannot offset anything: ValueError"""
x, _ = _case_study_data()
if not _raises_value_error(min_variance_hedge, x, np.full(60, 0.003)):
raise AssertionError("expected ValueError for a constant hedge")
def test_repeated_hedge():
"""The same future twice is a dependent pair of hedges: ValueError"""
x, f = _case_study_data()
if not _raises_value_error(min_variance_hedge, x, np.column_stack([f, f])):
raise AssertionError("expected ValueError for a repeated hedge")
def test_constant_target():
"""A target that never moves has no risk to hedge: ValueError"""
_, f = _case_study_data()
if not _raises_value_error(min_variance_hedge, np.full(60, 0.01), f):
raise AssertionError("expected ValueError for a constant target")
def test_lengths_differ():
"""60 months of the stock against 59 of the future: ValueError"""
x, f = _case_study_data()
if not _raises_value_error(min_variance_hedge, x, f[:59]):
raise AssertionError("expected ValueError when the lengths differ")
def test_closest_copy_of_the_call():
"""The bond and the stock copy the call best with −50 bonds and 35/61 ≈ 0.5738 shares"""
h, leftover = closest_copy(BOND_STOCK, CALL)
expect_close(h, [-50, 35 / 61], what="holdings of the closest copy")
expect_close(leftover, [250 / 61, -450 / 61, 200 / 61], what="leftover payoff")
def test_leftover_is_orthogonal():
"""The leftover adds up to 0 across the states and is orthogonal to the stock's payoffs"""
_, leftover = closest_copy(BOND_STOCK, CALL)
expect_close(np.array(BOND_STOCK, dtype=float).T @ leftover, [0, 0], abs_tol=1e-9, what="D.T @ leftover")
def test_complete_market():
"""With the call added, the put is copied exactly: 100 bonds, −1 share, 1 call, leftover 0"""
h, leftover = closest_copy([[1, 80, 0], [1, 100, 0], [1, 125, 25]], [20, 0, 0])
expect_close(h, [100, -1, 1], what="holdings that copy the put")
expect_close(leftover, [0, 0, 0], abs_tol=1e-9, what="leftover for the put")
def test_dependent_columns():
"""The bond, the stock and case study 3.4.3's fund are dependent: ValueError"""
D = [[1, 80, 90], [1, 100, 100], [1, 125, 112.5]]
if not _raises_value_error(closest_copy, D, CALL):
raise AssertionError("expected ValueError: the fund is 50 bonds and half a share")
def test_states_differ():
"""Three states in D but a payoff for two: ValueError"""
if not _raises_value_error(closest_copy, BOND_STOCK, [0, 25]):
raise AssertionError("expected ValueError when the payoff has the wrong length")
Summary card
- Hedging by selling of leaves the variance , smallest at ; here 1.1518, cutting 21.69% to 11.36% a year.
- is the coefficient of the projection of the centred onto ; the hedged return is the residual, uncorrelated with the hedge.
- The best hedge leaves of the volatility and removes of the variance.
- With several hedges, : the normal equations of least squares on centred returns. With equal volatilities it becomes case study 3.4.3's .
- The closest copy of the call by the bond and the stock is bonds and 0.5738 shares. The leftover is the part of the call whose price they cannot fix.
- Hedge ratios move with the data window; keep signs and units straight, and avoid near-copies.