Portefeuille efficient de Markowitz
J'importe les bibliothèques nécéssaires
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
Je crée les variables qui contiennent les dates de début et de fin. Je crée un objet contenant l'ensemble des titres que je souhaite ajouter à mon portefeuille.
PS : Depuis 2017, l'API Yahoo Finance a été abandonnée. Il existe d'autres bibliothèques pour importer les cours historiques des titres financiers.
DATE_DEBUT = '2015-01-01'
DATE_FIN = '2022-12-31'
tickers = ['AAPL', 'MSFT', 'META', 'GOOG', 'AMZN', 'TSLA', 'NVDA', 'AMD', 'IBM', 'TM', 'DIS']
actif_sans_risque = 3.5/100
J'importe les cours historique à l'aide de la bibliothèque yfinance, je choisis de ne conserver pour chacun des titres que le cours de cloture ajusté.
stocks = yf.download(tickers, start=DATE_DEBUT, end=DATE_FIN, interval='1wk')
stocks = stocks['Adj Close']
stocks.head()
duree = len(stocks)
[*********************100%***********************] 11 of 11 completed
Je divise mon échantillon en deux parties, je me retrouve avec des données d'entrainements, qui correspondent aux premiers 50% des données initiales.
train_data, test_data = train_test_split(stocks, test_size=0.5, shuffle=False)
data = train_data
J'affiche un graphique de mes données
data.plot(figsize=(10,7))
plt.title('Cours des actions')
plt.legend(tickers)
plt.show()
Vous remarquerez ,sur le graphique ci-dessus, des données manquantes. Cela correspond au fait qu'au moment où ce document a été rédigé, l'API yfinance avait déjà été abandonnée.
Je calcule le rendement de mon portefeuille. cela consiste à calculer le taux de variation $$ TV = \frac{V_2 - V_1}{V_1} \times 100% $$
rendement = data.pct_change().dropna()
rendement.plot(figsize=(10,7), title="Rendement journalier")
plt.legend(tickers)
plt.show()
Je calcule ma Matrice de variances covariances sur les rendements $\Sigma$
cov_matrix = rendement.cov() * duree
cov_matrix
AAPL | AMD | AMZN | DIS | GOOG | IBM | META | MSFT | NVDA | TM | TSLA | |
---|---|---|---|---|---|---|---|---|---|---|---|
AAPL | 0.382149 | 0.227132 | 0.213258 | 0.099244 | 0.201026 | 0.086328 | 0.177000 | 0.183870 | 0.277617 | 0.082692 | 0.207835 |
AMD | 0.227132 | 3.075550 | 0.502631 | 0.238326 | 0.151108 | 0.378132 | 0.182866 | 0.275887 | 0.878926 | 0.244595 | 0.397604 |
AMZN | 0.213258 | 0.502631 | 0.699076 | 0.176782 | 0.326876 | 0.168063 | 0.326821 | 0.333101 | 0.379513 | 0.109111 | 0.386064 |
DIS | 0.099244 | 0.238326 | 0.176782 | 0.245055 | 0.115184 | 0.095683 | 0.118435 | 0.122804 | 0.164842 | 0.103824 | 0.166805 |
GOOG | 0.201026 | 0.151108 | 0.326876 | 0.115184 | 0.434032 | 0.106029 | 0.316756 | 0.271831 | 0.285169 | 0.131781 | 0.252305 |
IBM | 0.086328 | 0.378132 | 0.168063 | 0.095683 | 0.106029 | 0.292996 | 0.083873 | 0.142385 | 0.201307 | 0.107986 | 0.168277 |
META | 0.177000 | 0.182866 | 0.326821 | 0.118435 | 0.316756 | 0.083873 | 0.626485 | 0.230162 | 0.268226 | 0.098720 | 0.322378 |
MSFT | 0.183870 | 0.275887 | 0.333101 | 0.122804 | 0.271831 | 0.142385 | 0.230162 | 0.384167 | 0.304882 | 0.127990 | 0.264682 |
NVDA | 0.277617 | 0.878926 | 0.379513 | 0.164842 | 0.285169 | 0.201307 | 0.268226 | 0.304882 | 1.435761 | 0.170029 | 0.320615 |
TM | 0.082692 | 0.244595 | 0.109111 | 0.103824 | 0.131781 | 0.107986 | 0.098720 | 0.127990 | 0.170029 | 0.266494 | 0.092687 |
TSLA | 0.207835 | 0.397604 | 0.386064 | 0.166805 | 0.252305 | 0.168277 | 0.322378 | 0.264682 | 0.320615 | 0.092687 | 1.477749 |
Je calcule et affiche ma matrice des correlations.
ps: j'admets être surpris, dans la mesure ou l'essentiel des titre choisis appartiennent aux mêmes secteur d'activité, soit l'est entrprise du numerique ou de l'automobile. par exemple, la correlation entre le rendement de TESLA, ici noté TSLA et Toyota Motor TM n'est que de 15%.
corr_matrix = rendement.corr()
corr_matrix
sns.heatmap(corr_matrix,
annot=True,
cmap="YlGnBu",
linewidths=0.3,
annot_kws={"size": 8})
plt.xticks(rotation=90)
plt.yticks(rotation=0)
plt.show()
Je choisi un nombre de portefeuille à générer. j'ai choisi arbitrairement 5000. dans la variable all_titres je stockerais l'ensemble des portefeuille qui seront généré. mes autres varables stockeront les caracteristiques des 5000 portefeuilles généré, c'est à dire, le rendement espéré la volatilité et le ratio de sharpe.
nb_portefeuille = 5000
all_titres = np.zeros((nb_portefeuille,len(tickers)))
ret_arr = np.zeros(nb_portefeuille)
vol_arr = np.zeros(nb_portefeuille)
sharpe_arr = np.zeros(nb_portefeuille)
Je génère les portefeuilles en faisant varier les allocations de manière aléatoire et en suivant une marche aléatoire. Cependant, je fais attention à ne pas dépasser une allocation totale de 100% pour chaque portefeuille, afin de ne pas avoir recours aux opérations à découvert. Bien sûr, cette contrainte peut être supprimée si nécessaire.
Une fois les portefeuilles générés, je les stocke dans la variable all_titres, puis j'enregistre les caractéristiques de chaque portefeuille (rendement espéré, volatilité et ratio de Sharpe) dans les autres variables correspondantes.
for x in range(nb_portefeuille):
titres = np.array(np.random.random(len(tickers)))
titres /= np.sum(titres)
all_titres[x,:] = titres
ret_arr[x] = np.sum(rendement.mean() * titres) * duree
vol_arr[x] = np.sqrt(np.dot(titres.T, np.dot(rendement.cov() * duree , titres)))
sharpe_arr[x] = (ret_arr[x]-actif_sans_risque)/vol_arr[x]
Nous pouvons maintenant tracer l'ensemble de nos portefeuilles
plt.figure(figsize=(10,7))
plt.scatter(vol_arr, ret_arr, c=sharpe_arr, cmap='YlGnBu')
plt.xlabel('Volatilité')
plt.ylabel('rendement')
plt.colorbar(label='Ratio de Sharpe')
plt.show()
Je crée deux lots de varables qui serviront respectivement de coordonnées aux portefeuilles de marché et à variance minimal
max_sr_vol = vol_arr[sharpe_arr.argmax()]
max_sr_ret = ret_arr[sharpe_arr.argmax()]
min_vol_ret = ret_arr[vol_arr.argmin()]
min_vol_vol = vol_arr[vol_arr.argmin()]
Je trace à nouveau ma frontière efficiente avec en rouge le portefeuille de marché et en noir le portefeuille à variance minimal
plt.figure(figsize=(10,7))
plt.scatter(vol_arr, ret_arr, c=sharpe_arr, cmap='YlGnBu')
plt.xlabel('Volatilité')
plt.ylabel('Rendement')
plt.colorbar(label='Ratio de Sharpe')
plt.scatter(max_sr_vol, max_sr_ret,c='red', s=50, edgecolors='black')
plt.scatter(min_vol_vol, min_vol_ret,c='Black', s=50, edgecolors='black')
plt.show()
Je récupère l'allocation de mes deux portefeuilles puis je les affiche.
max_sr_titres = all_titres[sharpe_arr.argmax(),:]
min_vol_titres = all_titres[vol_arr.argmin(),:]
print('Portefeuille de marché\n')
print('Volatilité du portefeuille :', str(round(max_sr_vol*100, 2)) + ' %')
print('Rendement attendu du portefeuille :', str(round(max_sr_ret*100, 2)) + ' %')
print('Ratio Sharpe du portefeuille :', str(round(sharpe_arr[sharpe_arr.argmax()]*100, 2)) + ' %')
allocation_max = pd.DataFrame({'Titre': data.columns, 'Allocation': max_sr_titres})
allocation_max['Allocation'] = (allocation_max['Allocation']).apply(lambda x: "{:.2%}".format(x).replace('%', ' %'))
print('Allocation optimale :')
print(str(allocation_max))
Portefeuille de marché Volatilité du portefeuille : 65.74 % Rendement attendu du portefeuille : 310.59 % Ratio Sharpe du portefeuille : 467.12 % Allocation optimale : Titre Allocation 0 AAPL 4.03 % 1 AMD 13.33 % 2 AMZN 20.91 % 3 DIS 1.04 % 4 GOOG 5.88 % 5 IBM 0.52 % 6 META 22.80 % 7 MSFT 4.07 % 8 NVDA 19.15 % 9 TM 7.44 % 10 TSLA 0.82 %
print('Portefeuille à variance minimal\n')
print('Volatilité du portefeuille :', str(round(min_vol_vol*100, 2)) + ' %')
print('Rendement attendu du portefeuille :', str(round(min_vol_ret*100, 2)) + ' %')
print('Ratio Sharpe du portefeuille :', str(round(sharpe_arr[vol_arr.argmin()]*100, 2)) + ' %')
allocation_min = pd.DataFrame({'Titre': data.columns, 'Allocation': min_vol_titres})
allocation_min['Allocation'] = (allocation_min['Allocation']).apply(lambda x: "{:.2%}".format(x).replace('%', ' %'))
print('Allocation optimale :')
print(str(allocation_min))
Portefeuille à variance minimal Volatilité du portefeuille : 41.24 % Rendement attendu du portefeuille : 102.27 % Ratio Sharpe du portefeuille : 239.5 % Allocation optimale : Titre Allocation 0 AAPL 14.74 % 1 AMD 0.10 % 2 AMZN 1.20 % 3 DIS 14.20 % 4 GOOG 13.22 % 5 IBM 21.22 % 6 META 3.19 % 7 MSFT 15.97 % 8 NVDA 1.73 % 9 TM 13.16 % 10 TSLA 1.27 %
Je vais maintenant tester le rendement que l'on aurait obtenu en investissant sur notre portefeuille. Pour ce faire, je vais me servir de mes données de test. L'idée ici est donc de tester notre portefeuille sur des données qu'il n'a jamais vues.
Je conserve la première valeur du dataframe test_data comme cours d'achat et la dernière comme cours de vente. Je définis aussi un capital K.
K = 10000
achat = test_data.head(1).reset_index(drop=True)
vente = test_data.tail(1).reset_index(drop=True)
L'algorithme suivant simule l'achat des titres en suivant la repartition, puis en revandant à la date d'échéance. $\\ \\ montant\ investi = Allocation \times K\\$ $\ nombre\ parts = \frac{montant\ investi}{achat}\$ $\ montant\ obtenu = nombre\ parts \times vente\$
montant_investi = max_sr_titres * K
nombre_parts = montant_investi / achat
montant_obtenu = nombre_parts * vente
montant_obtenu
AAPL | AMD | AMZN | DIS | GOOG | IBM | META | MSFT | NVDA | TM | TSLA | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1400.303807 | 4376.777964 | 2086.387663 | 82.57006 | 965.758834 | 76.504239 | 1859.748869 | 967.4644 | 7577.328198 | 808.559269 | 437.51392 |
Je somme le vecteur les gains par titre contenu dans montant_obtenu
montant_final = montant_obtenu.sum(axis=1)
montant_final
0 25920.934328 dtype: float64
Pour un capital K de 10 000€ le rendement absolue est de 15 920€ la durée séparant la date d'achat et de vente est de 1447 jours soit près de quatre ans.
# 4. Gain total
gain = montant_obtenu.sum(axis=1) - K
print("Gain obtenu:", gain)
Gain obtenu: 0 15920.934328 dtype: float64
Le rendement est donc de 159% environs pour un placement d'une durée de quatre ans.
gain/K*100
0 159.209343 dtype: float64
quelques pistes d'ameliorations:
De façon plus général, il serait de bonne augure de faire evoluer ce model. Je pourrait utiliser le CAPM pour mesure le rendement attendu d'un titre, et ainsi améliorer la frontière efficiente. Je pourrais aussi utiliser un model garch pour mesurer le risque d'un titre. je pourrais aussi faire evoluer le model de markowitz en utilisant le portefeuille de Black-Litterman.