Le risque pour chaque niveau de rendement
Le sujet précédent trouvait, pour un rendement cible, les poids de variance minimale. Faites-le pour chaque cible et placez la volatilité en abscisse et le rendement en ordonnée : les points forment une courbe, la frontière efficiente. Tout portefeuille de ces actifs se trouve sur elle ou à l'intérieur, à droite. Un portefeuille à l'intérieur est inefficace, puisqu'un autre offre le même rendement avec moins de risque, ou plus de rendement pour le même risque. L'idée de Markowitz : l'investisseur ne doit regarder que la courbe.
import numpy as np
mu = np.array([0.08, 0.05, 0.10])
cov = np.array([[0.0400, 0.0060, 0.0100],
[0.0060, 0.0100, 0.0020],
[0.0100, 0.0020, 0.0225]])
def target_return_weights(mu, cov, target):
ones = np.ones(len(mu))
s1, sm = np.linalg.solve(cov, ones), np.linalg.solve(cov, mu)
a, b, c = ones @ s1, ones @ sm, mu @ sm
d = a * c - b ** 2
return ((c - b * target) * s1 + (a * target - b) * sm) / d
for target in (0.05, 0.06, 0.07, 0.08, 0.09, 0.10):
w = target_return_weights(mu, cov, target)
print(target, round(float(np.sqrt(w @ cov @ w)), 4))Sa forme et sa pointe
La frontière est une parabole dans le plan (rendement, variance), et une hyperbole si l'on trace la volatilité. La variance pour un rendement cible t vaut (a * t**2 - 2 * b * t + c) / (a * c - b**2), avec les mêmes quatre nombres a, b, c qu'avant. Son point le plus bas est le portefeuille de variance minimale globale, de rendement b / a : la pointe de la courbe, qui est le portefeuille de variance minimale d'il y a deux sujets. Seule la partie de la pointe vers le haut est la frontière efficiente ; la branche basse est son image miroir, avec moins de rendement pour le même risque.
# The tip of the curve: the minimum-variance portfolio and its return
import numpy as np
mu = np.array([0.08, 0.05, 0.10])
cov = np.array([[0.0400, 0.0060, 0.0100],
[0.0060, 0.0100, 0.0020],
[0.0100, 0.0020, 0.0225]])
ones = np.ones(3)
x = np.linalg.solve(cov, ones)
w = x / x.sum()
print(np.round(w, 4), round(float(w @ mu), 4), round(float(np.sqrt(w @ cov @ w)), 4)) # the tip: return, volatility
def closed_form_vol(target):
s1, sm = x, np.linalg.solve(cov, mu)
a, b, c = ones @ s1, ones @ sm, mu @ sm
return float(np.sqrt((a * target ** 2 - 2 * b * target + c) / (a * c - b ** 2)))
for target in (0.05, 0.0642, 0.07):
print(target, round(closed_form_vol(target), 4))Points efficients et inefficaces
Le même risque apparaît pour deux rendements sur la courbe, un sur chaque branche, et seul le plus élevé vaut la peine d'être détenu. Un objectif est donc efficient exactement quand il est au moins égal au rendement de variance minimale. Le test est une simple comparaison, mais il décide quels objectifs figurent sur un graphique de choix. Chercher le meilleur compromis sur la frontière revient alors à choisir un point, et une réponse courante est celui qui rapporte le plus de rendement par unité de risque au-dessus du taux sans risque.
# The point with the highest Sharpe ratio among candidate targets
import numpy as np
mu = np.array([0.08, 0.05, 0.10])
cov = np.array([[0.0400, 0.0060, 0.0100],
[0.0060, 0.0100, 0.0020],
[0.0100, 0.0020, 0.0225]])
rf = 0.02
ones = np.ones(3)
s1, sm = np.linalg.solve(cov, ones), np.linalg.solve(cov, mu)
a, b, c = ones @ s1, ones @ sm, mu @ sm
d = a * c - b ** 2
targets = np.linspace(0.05, 0.10, 11)
vols = np.sqrt((a * targets ** 2 - 2 * b * targets + c) / d)
sharpe = (targets - rf) / vols
for t, v, s in zip(targets, vols, sharpe):
print(round(float(t), 3), round(float(v), 4), round(float(s), 4))
print("best target:", round(float(targets[np.argmax(sharpe)]), 3))# Efficient or not: compare a target with the return of the minimum-variance portfolio
import numpy as np
mu = np.array([0.08, 0.05, 0.10])
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))
tip = float((x / x.sum()) @ mu)
for target in (0.05, 0.06, 0.07, 0.08):
print(target, "efficient" if target >= tip else "inefficient")mu et cov, tous deux estimés, donc c'est elle-même une estimation, et une estimation flatteuse : l'optimiseur a choisi, parmi tous les portefeuilles, ceux dont les rendements estimés sont élevés et les risques faibles par hasard. Les résultats réels se situent à droite et en dessous de la ligne visible. Rééchantillonner les données, ou comparer la frontière de la première moitié d'un échantillon avec sa performance sur la seconde, montre la part de chance de la courbe. Voyez la frontière comme une carte des compromis, pas comme une promesse.- La frontière efficiente donne, pour chaque rendement cible, la volatilité minimale :
sqrt(w @ cov @ w)avecw = target_return_weights(...); les portefeuilles à l'intérieur sont inefficaces. - Sa pointe est le portefeuille de variance minimale globale, de rendement
b / a; seuls les objectifs au moins égaux à ce rendement sont efficients. - Choisissez un point selon un critère comme le ratio de Sharpe maximal
(target - rf) / volatility; rappelez-vous que la courbe vient d'estimations et est optimiste.
À suivre : un backtester vectorisé, tester une règle de trading sur des prix historiques.
Lab : ce qui est noté
- Tracer la frontière avec
frontier_vols25 pts - Trouver la pointe de la courbe avec
min_variance_return25 pts - Distinguer la bonne moitié de la mauvaise avec
is_efficient25 pts - Choisir le meilleur point avec
max_sharpe_target25 pts