Le mélange le plus sûr
Combiner des actifs réduit le risque, mais de combien dépend des poids. Le portefeuille de variance minimale est le choix de poids, de somme 1, qui a la plus petite variance possible. Il n'exige aucune prévision de rendement, seulement la matrice de covariance, ce qui le rend populaire : les rendements espérés sont très difficiles à estimer, les covariances bien moins. Pour deux actifs, la réponse est une formule ; pour beaucoup, c'est la solution d'un système linéaire.
# Two assets: volatility 20% and 10%, correlation 0.3
def variance(w, vol1, vol2, rho):
return w * w * vol1 ** 2 + (1 - w) ** 2 * vol2 ** 2 + 2 * w * (1 - w) * rho * vol1 * vol2
vol1, vol2, rho = 0.20, 0.10, 0.3
w = (vol2 ** 2 - rho * vol1 * vol2) / (vol1 ** 2 + vol2 ** 2 - 2 * rho * vol1 * vol2)
print(round(w, 4), round(variance(w, vol1, vol2, rho) ** 0.5, 4)) # 0.1053 0.0979
print(round(variance(0.0, vol1, vol2, rho) ** 0.5, 4)) # 0.1 all in the safe asset: riskier than the mixBeaucoup d'actifs : un système linéaire
Pour minimiser w @ cov @ w sous la contrainte sum(w) = 1, on annule la dérivée du lagrangien. Le résultat est w = cov^-1 * 1 / (1' cov^-1 1) : on résout le système cov @ x = ones, puis on met x à l'échelle pour que les poids somment à 1. La variance minimale elle-même vaut 1 / (1' cov^-1 1). En code, on utilise np.linalg.solve(cov, ones) et non np.linalg.inv(cov) @ ones : c'est plus rapide et plus précis, car l'inverse n'est jamais formée.
# Minimum-variance weights of three assets
import numpy as np
cov = np.array([[0.0400, 0.0060, 0.0100],
[0.0060, 0.0100, 0.0020],
[0.0100, 0.0020, 0.0225]])
x = np.linalg.solve(cov, np.ones(3))
weights = x / x.sum()
print(np.round(weights, 4), round(float(weights.sum()), 4)) # [0.0192 0.7082 0.2726] 1.0
print(round(float(weights @ cov @ weights), 6), round(1 / float(x.sum()), 6)) # 0.007742 twice: the same number# No other mix with weights summing to 1 does better: try 5000 random ones
import numpy as np
cov = np.array([[0.0400, 0.0060, 0.0100],
[0.0060, 0.0100, 0.0020],
[0.0100, 0.0020, 0.0225]])
x = np.linalg.solve(cov, np.ones(3))
best = x / x.sum()
rng = np.random.default_rng(0)
trial = rng.normal(size=(5000, 3))
trial = trial / trial.sum(axis=1, keepdims=True) # each row sums to 1
variances = np.einsum("ij,jk,ik->i", trial, cov, trial) # w @ cov @ w for every row
print(round(float(variances.min()), 6), round(float(best @ cov @ best), 6))
print(bool(variances.min() >= best @ cov @ best)) # TrueVentes à découvert et contraintes
La formule autorise les poids négatifs : elle peut vendre un actif à découvert pour en couvrir un autre. Beaucoup d'investisseurs ne le peuvent pas ou ne le veulent pas, et veulent des poids long uniquement. Ramener à zéro les poids négatifs puis remettre à l'échelle est un correctif rapide qui garde des poids valides, mais ce n'est pas le vrai optimum sans vente à découvert, qui exige un solveur numérique (programmation quadratique) car la contrainte w >= 0 rend le problème sous contraintes d'inégalité. Le portefeuille tronqué est au moins détenable et facile à comparer au portefeuille libre.
# The unconstrained solution shorts an asset; clip it and compare the risks
import numpy as np
cov = np.array([[0.04, 0.0342, 0.0],
[0.0342, 0.0324, 0.0],
[0.0, 0.0, 0.0225]]) # assets 1 and 2: volatility 20% and 18%, correlation 0.95
x = np.linalg.solve(cov, np.ones(3))
free = x / x.sum()
clipped = np.clip(free, 0, None)
clipped = clipped / clipped.sum()
print(np.round(free, 4), round(float(free @ cov @ free), 6))
print(np.round(clipped, 4), round(float(clipped @ cov @ clipped), 6))- La variance minimale n'exige que la matrice de covariance : pour deux actifs,
(vol2**2 - rho * vol1 * vol2) / (vol1**2 + vol2**2 - 2 * rho * vol1 * vol2)dans l'actif 1. - En général,
w = solve(cov, ones) / sum(...)et la variance minimale vaut1 / (1' cov^-1 1); utiliseznp.linalg.solve, pas l'inverse. - La réponse libre peut vendre des actifs à découvert ; tronquer à zéro puis remettre à l'échelle est un correctif rapide, pas le vrai optimum, et les poids sont sensibles aux erreurs d'estimation.
À suivre : l'optimisation moyenne-variance, ajouter les rendements espérés pour trouver le meilleur compromis entre rendement et risque.
Lab : ce qui est noté
- Mélanger deux actifs pour un risque minimal avec
two_asset_weight25 pts - Résoudre les poids les plus sûrs avec
min_variance_weights25 pts - Donner le risque le plus bas avec
min_variance25 pts - Écarter les ventes à découvert avec
long_only_weights25 pts