A portfolio is a vector of weights
Number the assets A, B and C as 1, 2 and 3. The weights are the fractions of the money in each, as decimals: here . Lesson 0.2.4 worked out weights from dollar holdings.
Over a period, each asset earns a simple return (case study 1.4.7). Collect the three in a vector ; here bold holds simple returns, not log returns.
Case study 1.4.7 found that the portfolio's return is the weighted sum of these. That is the dot product of lesson 3.1.2: .
Suppose that in one year A returns 4%, B loses 10% and C gains 25%, so . Then : the portfolio returns 4%.
The expected return
The same weighted sum of the assets' expected returns gives the portfolio's expected return. Write them as a vector , a bold mu.
| asset | weight | expected return | |
|---|---|---|---|
| A | 0.5 | 0.06 | 0.03 |
| B | 0.3 | 0.08 | 0.024 |
| C | 0.2 | 0.11 | 0.022 |
| total | 1 | 0.076 |
Rule: Portfolio return
means expected value, as in case study 1.2.4. Weights have no units, so each answer is a return over the same period as or : here, a year.
With dollar holdings in place of weights, the dot product is in dollars. The holdings are : $5 million, $3 million and $2 million. Then is the expected gain, and the gain in the year above.
Try it
w @ r and w @ mu are the two dot products above. w * mu shows the three contributions before they are added.
The last line uses the dollar holdings instead of the weights.
The budget:
Write for the vector of ones. Its dot product with any vector adds up the components, so , the total weight. A fully invested portfolio has : every dollar is placed somewhere.
Rule: Fully invested
The weights add up to 1. Each weight may be negative or above 1; only the total is fixed.
Case study 1.2.4 put a weight in its first asset and in its second. As a vector that is , whose components add up to 1 for every .
Its long-only portfolios run from to . At the least risky mix was , beyond that range: a short sale of the second asset.
Long and short weights
A negative weight is a short sale: the fund borrows an asset, sells it, and spends the cash on the others. A "130/30" portfolio is long 130% of its capital and short 30%. For example, is short A and long B and C.
Its weights still add up to 1, and its expected return is . The sizes of the weights add up to 1.6: 160% of the capital is at work, which is also more risk.
Trades that keep the budget
Changing the weights by a vector gives new weights (lesson 3.1.1). They still add up to 1 exactly when , that is, when is orthogonal to (lesson 3.1.2). Whatever is sold pays for what is bought.
Moving 10% from A into C is . The new weights are , and the expected return rises by , to 0.081.
A zero-cost portfolio has , for example long $1 million of C and short $1 million of A. Nothing is invested, so a return per dollar invested would divide by 0. Desks measure it in dollars instead: in the year above, .
Try it
np.ones(3) is the vector . The first line checks the budget of the original weights and of the 130/30 portfolio, then that portfolio's expected return.
The next lines make the trade , then price the zero-cost portfolio in dollars.
Correlation as an angle
A portfolio's risk depends on how its assets move together. The usual measure is the correlation (rho), a number from to 1 (case study 1.2.4). It is the cosine of an angle between two vectors.
Here are ten months of returns for two stocks, X and Y. They are invented: each month both follow a common market move plus a move of their own. The moves are drawn at random with a fixed seed and rounded to 0.1%.
Each stock's returns form a vector in , and , one component per month.
The means are and ( is read "x bar"). Centring subtracts each mean from every month:
This is a scaling and a subtraction from lesson 3.1.1, with now ten ones. is read "x tilde", and is made the same way. A centred vector's components add up to 0, so .
| month | (%) | (%) | (%) | (%) |
|---|---|---|---|---|
| 1 | ||||
| 2 | 5.5 | 2.3 | 4.13 | 1.11 |
| 3 | 8.7 | 3.8 | 7.33 | 2.61 |
| 4 | 2.5 | 3.9 | 1.13 | 2.71 |
| 5 | 1.7 | 0.51 | ||
| 6 | 1.0 | |||
| 7 | ||||
| 8 | 0.4 | |||
| 9 | 3.2 | 0.9 | 1.83 | |
| 10 | 7.5 | 3.2 | 6.13 | 2.01 |
The subscript numbers the months. In 8 of the 10, X and Y sit on the same side of their means, both above or both below. Each of those months adds a positive product to the dot product ; months 5 and 9 subtract a little.
Rule: Correlation is a cosine
is the angle between the centred return vectors. The Cauchy–Schwarz inequality of lesson 3.1.2 is why always lies between and 1.
Here , and , in decimals. So , and the angle is 52.32°. NumPy's np.corrcoef gives the same 0.6113.
Two vectors in still lie in one flat plane through the origin, so the angle between them can be drawn.
Ten months as points, and the same months as two centred vectors scaled to length 1. The ticks mark the angles for correlations 1, 0.5, 0, and . The vectors are 52.3° apart, and is the correlation.
Divide by , with the number of months, and you get the sample covariance, 0.000679. Divide each length by and you get the sample standard deviations, X's being 0.050284; lesson 5.5.2 defines both. The factors cancel in the cosine, and case study 3.2.5 gathers covariances into a matrix.
The level's correlations as angles
The three assets A, B and C have correlations , and ; case study 3.2.5 uses them. As angles:
| angle | where it appears | |
|---|---|---|
| 1 | 0° | |
| 0.5 | 60° | |
| 0.3 | 72.54° | |
| 0.2 | 78.46° | case study 1.2.4 |
| 0.1 | 84.26° | |
| 0 | 90° | |
| 120° | ||
| 180° |
Around 90°, where is near 0, the cosine changes fastest: a small turn moves the most. Near 0° and 180° it changes slowest: from 0° to 25.84°, only falls from 1 to 0.9.
Try it
The first lines invent the data: rng.normal(mean, size, 10) draws ten random numbers around mean, of typical size size (Level 5 explains it). Rounding to 3 decimals keeps 0.1%.
Then the cell centres both vectors and prints them in percent, with and the count of months on the same side. Next come the dot product and lengths, the cosine and its angle, and np.corrcoef for comparison. The last line is the table of angles above, with 0.9 added.
Correlations of 1, 0 and −1
The extreme correlations are the extreme angles of lesson 3.1.2.
- : 0°. A fund holding X with double exposure, less a fee of 0.5% a month, returns . Centring removes the fee, and scaling by 2 keeps the direction (lesson 3.1.1). So , the angle is 0° and .
- : 180°. A fund that returns every month has , pointing the opposite way.
- : 90°. The centred vectors are orthogonal.
A short position turns returns round, so it turns the vector round too. has correlation with , an angle of . Writing returns in percent scales both vectors by 100 and leaves at 0.6113.
Zero correlation, and why centring matters
Take four months with returns for one asset and for another, in percent. Centred, they are and , and their dot product is . So .
Yet the second asset depends on the first: it is high exactly when the first is extreme. Correlation measures only how closely two series follow a straight-line relation, not every link between them.
Skip the centring, and the cosine of the raw vectors is 0.866, an angle of 30°. Both series are positive every month, so the raw arrows point roughly the same way. For X and Y the raw cosine is 0.6472 rather than 0.6113, so always subtract the means first.
Try it
The first line checks the three extremes on the sample: double exposure, the opposite fund and the short position. The second shows that percent units change nothing.
The last lines are the four months: centred, they are orthogonal, while the raw cosine says 0.866.
Try it
The first line pairs each month of X with the previous month of Y. The second correlates X with a constant series; np.errstate hides NumPy's warning, as in case study 1.4.7.
The loop drops each month in turn and prints the ten estimates that are left.
Portfolio return and correlation
Write two functions. Both take NumPy arrays or lists; start with np.asarray(x, dtype=float).
portfolio_return(w, r)returns . RaiseValueErrorif the sizes differ, or if the weights do not add up to 1, allowing a rounding error of .correlation(x, y)returns , without callingnp.corrcoef. RaiseValueErrorif the sizes differ, if there are fewer than two values, or if either series is constant.
The tests compare your correlation with np.corrcoef on the ten months above. They also try the cases on this page: double exposure, a short position, percent units and the four months with .
A constant series of 0.01 centres to about , not exactly 0. So test whether every value is the same, not whether the length is 0.
See the tests
import numpy as np
def _sample():
"""The lesson's ten invented months for stocks X and Y."""
rng = np.random.default_rng(449)
market = rng.normal(0.008, 0.04, 10)
x = np.round(market + rng.normal(0.002, 0.025, 10), 3)
y = np.round(0.6 * market + rng.normal(0.003, 0.02, 10), 3)
return x, y
def _raises_value_error(f, *args):
try:
f(*args)
except ValueError:
return True
return False
def test_portfolio_return():
"""Weights (0.5, 0.3, 0.2) and returns (4%, −10%, 25%) give 4%"""
expect_close(portfolio_return(np.array([0.5, 0.3, 0.2]), np.array([0.04, -0.10, 0.25])), 0.04, what="w · r")
def test_expected_return():
"""With expected returns (6%, 8%, 11%) the portfolio expects 7.6%"""
expect_close(portfolio_return(np.array([0.5, 0.3, 0.2]), np.array([0.06, 0.08, 0.11])), 0.076, what="w · μ")
def test_short_weights():
"""A 130/30 portfolio, (−0.3, 0.6, 0.7), expects 10.7%: negative weights are allowed"""
expect_close(portfolio_return([-0.3, 0.6, 0.7], [0.06, 0.08, 0.11]), 0.107, what="w · μ for (−0.3, 0.6, 0.7)")
def test_weights_must_add_to_one():
"""Weights adding to 0.999, dollar holdings or percentages: ValueError"""
mu = np.array([0.06, 0.08, 0.11])
assert _raises_value_error(portfolio_return, np.array([0.333, 0.333, 0.333]), mu), "expected ValueError for weights adding to 0.999"
assert _raises_value_error(portfolio_return, np.array([5e6, 3e6, 2e6]), mu), "expected ValueError for dollar holdings"
assert _raises_value_error(portfolio_return, np.array([50, 30, 20]), mu), "expected ValueError for weights in percent"
def test_sizes_must_match():
"""Three weights and two returns: ValueError"""
assert _raises_value_error(portfolio_return, np.array([0.5, 0.3, 0.2]), np.array([0.06, 0.08])), "expected ValueError for sizes 3 and 2"
def test_correlation_sample():
"""The ten months of X and Y have correlation about 0.6113, as np.corrcoef says"""
x, y = _sample()
expect_close(correlation(x, y), np.corrcoef(x, y)[0, 1], what="correlation of X and Y")
def test_centring():
"""(1, 2, 3, 4) and (4, 2, 2, 4) have correlation 0: centre before taking the cosine"""
expect_close(correlation([1, 2, 3, 4], [4, 2, 2, 4]), 0.0, what="correlation of (1, 2, 3, 4) and (4, 2, 2, 4)", abs_tol=1e-12)
def test_scaling():
"""Percent units and double exposure leave the correlation alone; a short position flips its sign"""
x, y = _sample()
rho = np.corrcoef(x, y)[0, 1]
expect_close(correlation(100 * x, 100 * y), rho, what="correlation with returns in percent")
expect_close(correlation(x, 2 * x - 0.005), 1.0, what="correlation of X with 2X − 0.005")
expect_close(correlation(x, -y), -rho, what="correlation of X with −Y")
expect_close(correlation(x, -x), -1.0, what="correlation of X with −X")
def test_constant_series():
"""A constant series has no correlation: ValueError, even when centring leaves rounding dust"""
x, _ = _sample()
assert _raises_value_error(correlation, x, np.full(10, 0.003)), "expected ValueError for a series that is 0.003 every month"
assert _raises_value_error(correlation, x, np.full(10, 0.01)), (
"expected ValueError for a series that is 0.01 every month: centred, it is about 1e-18, not exactly 0"
)
def test_bad_lengths():
"""Series of different lengths, or a single month: ValueError"""
x, y = _sample()
assert _raises_value_error(correlation, x, y[:-1]), "expected ValueError for 10 and 9 months"
assert _raises_value_error(correlation, [0.01], [0.02]), "expected ValueError for a single month"
Summary card
- A portfolio's return is a dot product: for simple returns, and . With dollar holdings , is the gain in dollars.
- Fully invested means . Weights may be negative (short). A trade keeps the budget when , and changes the expected return by .
- Check the order of the assets, the units and before trusting .
- Correlation is the cosine of the angle between centred return vectors: . Cauchy–Schwarz keeps it in .
- , 0 and are angles of 0°, 90° and 180°. Positive scaling leaves alone; a short position flips its sign.
- Centre first, line up the dates, and treat a short sample's correlation as rough. A constant series has none.