The fund's variance, from the rules
The data are case study 3.2.5's, invented but typical, in annual decimals. The expected returns are and the volatilities 0.12, 0.18 and 0.25. The correlations are , and , and the weights .
Before the year starts, its returns are random. Write them as a random vector , in capitals; case study 3.1.4 wrote one year's values as .
Its mean is , and its covariance matrix has entries (lesson 5.5.2). It is the matrix case study 3.2.5 built from the volatilities and correlations.
The fund's return is (case study 3.1.4). Its mean follows from linearity (lesson 5.3.2): . Its variance takes three steps.
- 1
- 2
- 3
Rule: Mean and variance of a portfolio
is the random vector of the assets' returns over one period, with mean and covariance matrix . It is lesson 5.5.3's rule with the single row .
The double sum has nine terms. The three on the diagonal, each asset with itself, add up to 0.009016; the six off it, each pair twice, add 0.005244. The total is 0.01426, a volatility of 11.94%: case study 3.2.5's numbers, now derived.
Two assets: case study 1.2.4. With weight in A and in B, lesson 5.5.2's rule for gives
with . That is the formula case study 1.2.4 took as given.
Its factor 2 counts and . With that case study's , and , the minimum-variance weight 0.7358 still gives a variance of 0.0326.
What the formula needs, and what it does not
The steps used only expectations and covariances. The formula needs no normal distribution and no independence: any returns with this give a variance of 0.01426.
A simulation shows it. Draw 200,000 years as , with the Cholesky factor of (case study 3.7.4) and normal.
Then do it again with uniform on , which also has mean 0 and variance 1. By lesson 5.5.3's rule both have covariance matrix .
In this seeded run both give a sample variance close to 0.01426. Their chances of a loss beyond 10% differ, though: 7.03% with normal draws and 7.55% with uniform ones. The variance comes from alone; a probability needs the whole distribution.
With jointly normal returns, (lesson 5.5.3). So , matching the normal draws.
Try it
The first lines print the nine terms , their diagonal and off-diagonal totals, and .
Then 200,000 seeded years, from normal and from uniform , both mixed by U @ L.T. Each line prints the sample mean, variance and volatility of , and the share of years below . The last line is the exact normal chance.
From months to a year: when √12 works
Log returns add across time: a year's log return is the sum of its twelve monthly ones (case study 1.4.7). Lesson 5.5.2's rule for a sum of many variables then gives
with the log return of month . If the months are uncorrelated, every covariance is 0 and only the twelve variances are left. Independence is enough for that, since independent variables are uncorrelated (lesson 5.5.2).
Rule: Scaling risk over time
For uncorrelated periods, each with variance . The mean grows times and the volatility only times. Without the zero covariances, the rule fails.
This is what case studies 3.2.5 and 3.6.5 assumed.
- Months to a year. The fund's monthly volatility is 3.4472%, from ; times it is the annual 11.94% (case study 3.2.5).
- Days to a year. The level component of case study 3.6.5's yield changes has a daily volatility of 10.52 bp. Yield changes add up exactly over days, so with uncorrelated days it is bp a year.
When neighbouring months are correlated
Suppose each month's return has correlation with the month before, and none with months further apart: a lag-one correlation. Twelve months have eleven neighbouring pairs, each counted twice, so the variance becomes .
| 0 | 0.2 | ||
|---|---|---|---|
| 7.6 | 12 | 16.4 | |
| annual volatility | 9.50% | 11.94% | 13.96% |
With , the rule reports 11.94% when the risk is 13.96%: 14.5% too low. Positive lag-one correlation is typical of smoothed or stale prices, such as appraisal values of property and thinly traded assets. A negative one, as in a series that tends to reverse its moves, makes overstate the risk.
Volatility of the total over months. With uncorrelated months it grows like ; a positive lag-one correlation makes it grow faster, a negative one slower. The dots mark a year.
Try it
The first lines give the monthly volatility and the two scalings, the second from case study 3.6.5's own data and seed.
Then 20,000 seeded years of 12 months with a lag-one correlation of 0.2, made as with independent normal . In this run the sample lag-one correlation is 0.199 and the annual volatility 14.0%, against the formula's 13.96%.
What B's return says about C's
Now take two assets, B and C, and treat their returns as jointly normal (lesson 5.5.3). Their means are 8% and 11%, their volatilities 18% and 25%, and . Build them from independent standard normals and with the Cholesky factor, B first, as case study 4.7.5 built A and B:
Learning that fixes . It says nothing about , which is independent of , so is still standard normal (lesson 5.5.1). Given , then, is a known number plus .
Rule: Conditional mean of a bivariate normal
Given , is normal with this mean and standard deviation. The mean is a straight line in through , with slope . The spread does not depend on .
Here the slope is , and the conditional volatility is , against 25% before anything is known.
| B's year, | 8% | 20% | 40% | ||
|---|---|---|---|---|---|
| 11% | 19.33% | 33.22% | |||
| minus one conditional sd | 11.57% | ||||
| plus one conditional sd | 6.26% | 20.15% | 32.65% | 40.98% | 54.87% |
At , B's mean, the line gives C's mean, 11%. Each point that B's return lies above or below 8% moves the expected C by 0.6944 points the same way.
Probabilities shift too. Unconditionally, C loses money in of years. Given that B lost 10%, the chance is .
The hedge ratio again
The slope is case study 3.5.5's hedge ratio for C hedged with B alone. That case study found it as the coefficient of a projection, with .
Probability adds a reading. Selling 0.6944 of B per dollar of C leaves , which is plus a constant.
That depends only on , while depends only on , so the two are independent (lesson 5.5.1). What the hedge leaves, 21.65% a year, is a risk that B's moves cannot explain at all.
The same rule works for any pair. A's return given B's has slope .
The line does not reverse. Given C's return instead, has slope , not . Both lines pass through the two means, but they differ unless .
Estimated from simulated years
In the explorer, and stand for B's and C's returns, each measured from its mean in units of B's volatility, 18%. Set the standard deviation of to 1.4, close to . The dashed line of conditional means then has slope 0.7.
Turn on the fitted line and cut the number of draws to 20. Press "Draw again" a few times: a slope estimated from a short sample wanders.
Explore
Each dot is one draw of the pair (X, Y). Move ρ and watch the cloud stretch along a line. The dashed line is E[Y | X = x], the average of Y for each x; turn on the fitted line to see least squares estimate it from the dots.
- Correlation
- true ρ = 0.5; sample r = 0.522
- Slope of E[Y | X]
- ρσ_Y/σ_X = 0.5 × 1 / 1 = 0.5
- Explained
- r² = 0.272: the share of the variance of Y that the line accounts for
With many draws the estimate is close. In 200,000 simulated years, the years with within 2 points of each in the table have average close to the line. The sample slope, (lesson 5.5.2), is 0.696.
Ten years of monthly returns are another matter. Case study 3.2.5's 120 months give a slope of 0.5109. Across 1,000 such ten-year samples, the middle 90% of the estimates runs from 0.5169 to 0.881.
Try it
The first lines compute the line's slope, the conditional volatility, the table and the two chances of a loss.
Then 200,000 seeded years of and . For each , the cell averages over the years with within 0.02 of . In this run every average is within 0.007 of the line.
The sample slope is 0.696 and the residuals have standard deviation 0.2164.
The last lines estimate the slope from case study 3.2.5's 120 months, then from 1,000 ten-year samples.
When the line is not straight
The straight line is a property of the normal distribution. For other joint distributions, can bend: lesson 5.5.1's density had , a curve.
The slope then still gives the best straight line through the cloud, the least-squares line of lesson 3.5.3. But it is only an approximation to the conditional mean. Module 5.10 studies such lines as regressions.
On the desk
Conditional means and scaled volatility
Write two functions, with NumPy.
conditional_normal(mu, cov, target, given, value)returns(mean, sd)for givenvalue. Here is jointly normal with meanmuand covariance matrixcov.targetandgivenare indices, such as 2 and 1 for C given B.annual_vol(period_vol, periods, lag1=0.0)returns the volatility of a sum ofperiodsreturns, each with volatilityperiod_vol. Each has correlationlag1with the one before and no other correlations, so the answer is .
In conditional_normal, raise ValueError if cov is not square and symmetric or an index is out of range. Raise it too if target == given, or if a variance is not positive.
In annual_vol, raise it if periods is below 1 or period_vol is negative. Raise it too if lag1 is outside , or if the variance would be negative.
The tests use the three assets, the hook's and the reverse slope 0.36. They also use case study 3.2.5's monthly volatility, case study 3.6.5's 10.52 bp a day, and each bad input.
See the tests
import math
import numpy as np
MU = np.array([0.06, 0.08, 0.11])
VOLS = np.array([0.12, 0.18, 0.25])
CORR = np.array([[1.0, 0.3, 0.1], [0.3, 1.0, 0.5], [0.1, 0.5, 1.0]])
SIGMA = np.diag(VOLS) @ CORR @ np.diag(VOLS)
def _raises(f, *args, what=""):
try:
f(*args)
except ValueError:
return
raise AssertionError(f"expected ValueError: {what}")
def test_hook():
"""If B returns −10%, C's conditional mean is −1.5% with standard deviation about 21.65%"""
expect_close(conditional_normal(MU, SIGMA, 2, 1, -0.10), (-0.015, 0.25 * math.sqrt(0.75)), what="conditional_normal(C given B = -0.10)")
def test_at_the_mean():
"""Given B at its mean of 8%, C's conditional mean is its own mean, 11%"""
expect_close(conditional_normal(MU, SIGMA, 2, 1, 0.08)[0], 0.11, what="mean of C given B = 0.08")
def test_slope():
"""The conditional mean of C moves 0.6944 points per point of B"""
up = conditional_normal(MU, SIGMA, 2, 1, 0.20)[0]
down = conditional_normal(MU, SIGMA, 2, 1, 0.10)[0]
expect_close((up - down) / 0.10, 0.5 * 0.25 / 0.18, what="slope of C on B")
def test_reverse():
"""B given C has slope 0.36, not 1 / 0.6944, and standard deviation 18% × √0.75"""
up = conditional_normal(MU, SIGMA, 1, 2, 0.21)
down = conditional_normal(MU, SIGMA, 1, 2, 0.11)
expect_close((up[0] - down[0]) / 0.10, 0.36, what="slope of B on C")
expect_close(up[1], 0.18 * math.sqrt(0.75), what="standard deviation of B given C")
def test_a_given_b():
"""A given B = 20% has mean 8.4%: slope 0.2"""
expect_close(conditional_normal(MU, SIGMA, 0, 1, 0.20)[0], 0.084, what="mean of A given B = 0.20")
def test_bad_cov():
"""A non-symmetric covariance matrix, a bad index or target equal to given raises ValueError"""
bad = SIGMA.copy()
bad[0, 1] = 0.0648
_raises(conditional_normal, MU, bad, 0, 1, 0.1, what="a non-symmetric covariance matrix")
_raises(conditional_normal, MU, SIGMA, 3, 1, 0.1, what="index 3 with three assets")
_raises(conditional_normal, MU, SIGMA, 1, 1, 0.1, what="target equal to given")
_raises(conditional_normal, MU, SIGMA[:2, :2], 0, 1, 0.1, what="a 2 x 2 matrix with three means")
def test_months():
"""The fund's monthly volatility 3.4472%, over 12 uncorrelated months, is 11.94% a year"""
monthly = math.sqrt(np.array([0.5, 0.3, 0.2]) @ (SIGMA / 12) @ np.array([0.5, 0.3, 0.2]))
expect_close(annual_vol(monthly, 12), 0.11941524190239804, what="annual_vol(monthly, 12)")
def test_days():
"""10.52 bp a day over 252 uncorrelated days is about 167.0 bp"""
expect_close(annual_vol(10.52, 252), 10.52 * math.sqrt(252), what="annual_vol(10.52, 252)")
def test_lag_one():
"""A lag-one correlation of 0.2 raises 12 months of 3.4472% to √16.4 times as much; −0.2 lowers it to √7.6 times"""
expect_close(annual_vol(0.034472, 12, 0.2), 0.034472 * math.sqrt(16.4), what="annual_vol(0.034472, 12, 0.2)")
expect_close(annual_vol(0.034472, 12, -0.2), 0.034472 * math.sqrt(7.6), what="annual_vol(0.034472, 12, -0.2)")
def test_one_period():
"""One period is the period's own volatility, whatever lag1 is"""
expect_close(annual_vol(0.02, 1, 0.5), 0.02, what="annual_vol(0.02, 1, 0.5)")
def test_bad_inputs():
"""No periods, a negative volatility, a correlation above 1 or a negative variance raises ValueError"""
_raises(annual_vol, 0.01, 0, what="periods = 0")
_raises(annual_vol, -0.01, 12, what="a negative volatility")
_raises(annual_vol, 0.01, 12, 1.5, what="lag1 = 1.5")
_raises(annual_vol, 0.01, 12, -0.6, what="lag1 = -0.6, which gives 12 - 13.2 < 0")
Summary card
- A portfolio's return has mean and variance , derived from bilinearity: . For the fund, a mean of 7.6% and a volatility of 11.94%; with two assets, case study 1.2.4's formula.
- The variance needs only , with no normality or independence; probabilities need the distribution.
- Over uncorrelated periods (independent is enough), variance scales by and volatility by . So 3.4472% a month gives 11.94% a year, and 10.52 bp a day gives 167.0 bp. With a lag-one correlation , the variance of twelve months is .
- For jointly normal returns, , with spread . The slope is case study 3.5.5's hedge ratio, 0.6944 for C on B; the reverse slope is 0.36.
- Slopes estimated from ten years of data are rough. Outside the normal model, the conditional mean need not be a straight line.