- Markowitz
- efficient frontier
- covariance matrix
- Sharpe ratio
- scipy.optimize
- diversification
- estimation error
Markowitz Mean-Variance Optimisation 💎
Monday, 9:02 am. You are the newest analyst at an asset manager. Your PM slides four tickers across the desk — AAPL, MSFT, JPM, GS — and says: "A client is wiring in €1,000,000 today. Give me the mix by lunch." The intern answer: put it all into whichever stock went up most last year. The Nobel-Prize answer: stop judging assets one by one — the magic is in the combination.
🧠 The mental model: the only free lunch in finance
A beach town has two businesses: an ice-cream stand (great on sunny days) and an umbrella stand (great on rainy days). Each alone is a rollercoaster — half its days are terrible. Own both, and someone is always selling: your combined income is far steadier, yet you gave up nothing in average income.
That is diversification — often called "the only free lunch in finance": mixing assets that do not move in lockstep cuts risk without cutting expected return. Harry Markowitz's 1952 insight was that this free lunch is computable: every risk interaction lives in one object, the covariance matrix, so finding the best mix becomes an optimisation problem.
Put numbers on it: two stocks, each 20% volatility, zero correlation, 50/50 mix. Portfolio volatility is not 20% — it is 20% / √2 ≈ 14%. Same expected return, nearly a third less risk. Lunch, served free.
The optimisation problem
We hunt for the weights w that maximise the Sharpe ratio — excess return earned per unit of risk:
maximise (mu · w - rf) / sqrt(w' Σ w)
subject to sum(w) = 1 (fully invested)
each w[i] >= 0 (long-only)
| Symbol | Meaning | In the code |
|---|---|---|
w | portfolio weights (what we solve for) | w |
mu (μ) | expected annualised returns | mu |
Σ | annualised covariance matrix | cov |
rf | risk-free rate | rf = 0.05 |
sum(w) = 1 | fully invested | constraints |
w[i] >= 0 | long-only, no short selling | bounds |
Coming from econometrics? Then w' Σ w is an old friend in disguise: it is the variance-of-a-linear-combination formula — Var(aX + bY) = a²·Var(X) + b²·Var(Y) + 2ab·Cov(X, Y) — written in matrix form for n assets. Low covariances are exactly where the free lunch is served.
One catch: scipy only minimises
scipy.optimize.minimize does what its name says. To maximise the Sharpe ratio we hand it the negative Sharpe ratio: minimising -f(w) is mathematically identical to maximising f(w). This sign-flip is the standard idiom whenever a library only ships a minimiser — you will meet it again in maximum likelihood estimation.
Python# maximise f(w) == minimise -f(w) result = minimize(neg_sharpe, w0, method='SLSQP', bounds=bounds, constraints=constraints)
We use the SLSQP solver (Sequential Least SQuares Programming) because it accepts both our equality constraint (weights sum to 1) and per-weight bounds (each between 0 and 1).
Reading the result: the tangency portfolio
Every possible mix of the four assets plots as a point in risk-return space. The upper edge of that cloud is the efficient frontier — for each level of risk, the highest achievable return. Now draw a straight line from the risk-free rate (cash at 5%) and tilt it upward until it just touches the frontier: that tangency point is the maximum Sharpe portfolio — the best risk-adjusted deal on the whole menu. That is what your code hunts for.
⚠️ Honest talk: why raw Markowitz is fragile
Before you put "portfolio optimisation" on your CV, know what practitioners know:
- ›Garbage in, garbage out. The optimiser treats
muas gospel, but expected returns estimated from one year of data are extremely noisy. In this very lesson, JPM's true simulated mean is 15% a year — yet this particular sample happens to come out near 4%, so the optimiser confidently allocates it 0%. It is not optimising the truth; it is optimising your estimation error. - ›Error maximisation. Richard Michaud famously called mean-variance optimisers "error maximisers": they pile the portfolio into whatever asset you accidentally over-estimated, and short-change whatever you under-estimated. Tiny changes in
mucan flip the weights violently. - ›The humbling 1/N result. DeMiguel, Garlappi & Uppal (2009, Review of Financial Studies) raced 14 sophisticated optimisation models out-of-sample against the dumbest possible rule — equal weights, 1/N. None won consistently. The theoretical gains were eaten alive by estimation error.
So why learn it? Because mean-variance is the language of institutional investing, and every real-world fix — shrinkage, Black-Litterman, robust optimisation — is a patch on exactly this framework. You cannot patch what you do not understand.
Your Task
The whole pipeline is built: simulated returns for the 4 assets, annualised mu and cov, constraints, bounds, and the SLSQP call. Only the heart is missing — the objective neg_sharpe(w). Right now it returns the constant 0.0: a perfectly flat landscape, so the optimiser never moves off its equal-weight starting point.
Fill in the body so it returns the negative Sharpe ratio of the weights w. The script then compares your optimised portfolio against the naive 1/N benchmark and prints Beats equal-weight: True/False.
Predict before you run: with the broken return 0.0, what weights will be reported — and will the last line say True or False? Then fix the objective and check by how much the optimiser beats 1/N. (DeMiguel would tell you to hold the applause.)