Skip to content
Course contents
From an edge to a portfolio

Building the portfolio

Turning signals into positions is portfolio construction, and it rests on a risk model, an estimate of how assets move together. This chapter covers the covariance idea, the common weighting schemes from equal-weight to risk parity to mean-variance, and the honest warning that mean-variance optimisation is dangerously sensitive to its inputs.

9 min readChapter 20 of 26
What you will learn
  • Explain a covariance-based risk model in plain terms
  • Compare equal-weight, risk-parity, and mean-variance construction
  • Recognise why mean-variance optimisation is fragile and why simpler schemes often win

You have several edges you want to hold together. How much of each? That is portfolio construction, and it rests on a risk model: an estimate of how the pieces move together. This chapter covers the risk model, the main ways to weight a portfolio, and a hard-won warning that the most sophisticated method is often the worst, because it fits the past too precisely.

The risk model: covariance

Turning signals into positions rests on a risk model of how assets move together; an optimizer sets the weights, and mean-variance is dangerously sensitive to its inputs.
Turning signals into positions rests on a risk model of how assets move together; an optimizer sets the weights, and mean-variance is dangerously sensitive to its inputs.

A risk model estimates how the parts of a portfolio co-move, captured in the covariance matrix: each holding's variance on the diagonal, and the covariance between every pair off it. It is what lets you compute the risk of the whole from the parts, and it is the input to every weighting scheme that follows. But it is estimated from limited, noisy data, and the covariances between assets are especially hard to pin down. That estimation noise is the villain of this chapter, because a method that leans hard on the covariance matrix is really leaning on its errors.

Three ways to weight

Here are the three most common schemes, fitted on one half of the data and judged on the other.

ExampleEqual, risk-parity, and mean-variance weights, judged out-of-samplech20/construction.py
# Turning signals into positions is portfolio construction, and it rests on a risk
# model: how the pieces move together (the covariance). We compare three weighting
# schemes fitted on the in-sample half, then judge them out-of-sample. The fancy
# one (mean-variance) takes extreme bets and is fragile. Synthetic data.
import numpy as np
import pandas as pd
from factor_data import make_market, long_short, annual_sharpe

returns, static = make_market()
T = len(returns)


def broadcast(col):
    return pd.DataFrame([static[col].values] * T, columns=returns.columns, index=returns.index)


factors = pd.DataFrame({
    "value":    long_short(broadcast("earnings_yield"), returns),
    "quality":  long_short(broadcast("roe"), returns),
    "momentum": long_short((1 + returns).rolling(12).apply(np.prod, raw=True).shift(1) - 1, returns),
    "lowvol":   long_short(-returns.rolling(12).std().shift(1), returns),
}).dropna()

half = len(factors) // 2
ins, oos = factors.iloc[:half], factors.iloc[half:]
n = factors.shape[1]

w_equal = np.repeat(1 / n, n)                          # equal weight
inv_vol = 1 / ins.std()
w_riskparity = (inv_vol / inv_vol.sum()).values        # risk parity (inverse vol)
w_mv = np.linalg.solve(ins.cov().values, ins.mean().values)   # mean-variance (max Sharpe)
w_mv = w_mv / np.abs(w_mv).sum()

print("Weights fitted on the in-sample half:")
for name, w in [("equal", w_equal), ("risk-parity", w_riskparity), ("mean-variance", w_mv)]:
    parts = "  ".join(f"{col}={x:+.2f}" for col, x in zip(factors.columns, w))
    print(f"  {name:13s} {parts}")

print("\nOut-of-sample Sharpe of each scheme:")
for name, w in [("equal", w_equal), ("risk-parity", w_riskparity), ("mean-variance", w_mv)]:
    oos_ret = (oos * w).sum(axis=1)
    print(f"  {name:13s} {annual_sharpe(oos_ret):.2f}")

print("\nMean-variance takes extreme, concentrated bets fitted to the past's noise,")
print("and it is fragile out-of-sample. The simple schemes are usually sturdier.")
print("A risk model helps, but do not trust an optimiser to size your bets precisely.")
Output
Weights fitted on the in-sample half:
  equal         value=+0.25  quality=+0.25  momentum=+0.25  lowvol=+0.25
  risk-parity   value=+0.25  quality=+0.27  momentum=+0.20  lowvol=+0.28
  mean-variance value=+0.59  quality=+0.26  momentum=+0.11  lowvol=-0.03

Out-of-sample Sharpe of each scheme:
  equal         2.72
  risk-parity   2.74
  mean-variance 2.22

Mean-variance takes extreme, concentrated bets fitted to the past's noise,
and it is fragile out-of-sample. The simple schemes are usually sturdier.
A risk model helps, but do not trust an optimiser to size your bets precisely.

Equal weight gives each edge the same share: crude, stable, and hard to beat. Risk parity weights by inverse volatility so each contributes roughly equal risk: a little smarter, still stable. Mean-variance optimisation, the famous method, chooses the weights that would have maximised the Sharpe on the in-sample data: mathematically optimal, in theory. Now read the result. The mean-variance weights are concentrated and extreme, piling into value at 0.59 and even shorting low volatility, fitted to the quirks of the past. And out-of-sample, it is the worst of the three, a Sharpe of 2.22 against 2.72 for equal weight and 2.74 for risk parity. The optimal method lost to the naive ones.

Simpler is sturdier

This is not a fluke; it is famous. Mean-variance optimisation is exquisitely sensitive to its inputs: a tiny change in an estimated return or covariance swings the weights wildly, and because those estimates are mostly noise, the optimiser cheerfully optimises the noise, producing concentrated bets that fall apart on new data. Equal weight and risk parity barely use the fragile covariances, so they have little noise to overfit, and they win out-of-sample far more often than theory would suggest. This is the portfolio version of the whole course's theme. The past is mostly noise, and a method that fits it too precisely fits the noise. Use the risk model to understand your risk, not to chase a false precision, and prefer simple, stable weights.

What to carry forward

Portfolio construction weights your edges, and it rests on a risk model, the covariance matrix, estimated from noisy data. The three main schemes are equal weight, risk parity (inverse volatility), and mean-variance optimisation, and the last, though mathematically optimal in-sample, is fragile: it overfits its noisy inputs, takes concentrated bets, and lost to both simple schemes out-of-sample here. The lesson is the course's theme again: fitting the noisy past too precisely fails, so prefer simple, stable weights. With the portfolio weighted, one decision remains and it is as important as the edge: how much total capital to put at risk, which is position sizing and the Kelly criterion.