Déverrouiller la puissance de Pandas et éviter les boucles

Pandas est une bibliothèque Python qui fournit des structures de données puissantes et simples à utiliser pour effectuer des analyses de données. Elle est particulièrement efficace pour travailler avec des données structurées ou tabulaires, comme les données financières.

L'un des plus grands avantages de l'utilisation de Pandas est sa vitesse. En utilisant des opérations vectorielles plutôt que des boucles, Pandas peut effectuer des opérations sur des ensembles de données entiers beaucoup plus rapidement que ne le permettrait le code traditionnel basé sur des boucles. Les opérations vectorielles sont également beaucoup plus lisibles et concises, ce qui rend le code plus facile à comprendre et à maintenir.

Dans cet épisode, nous allons vous montrer comment déverrouiller le plein potentiel de Pandas pour analyser les données des marchés de la crypto-monnaie. Nous nous concentrerons sur des exemples concrets pour illustrer les concepts, en espérant que vous pourrez ensuite les appliquer à vos propres problèmes d'analyse de données financières.

Importer et préparer les données

Avant de plonger dans les analyses, nous devons d'abord préparer nos données. Dans cette formation, nous allons utiliser des données de crypto-monnaie qui sont disponibles sur ce répéertoire GitHub https://github.com/CryptoRobotFr/python-pour-la-finance vous devriez pouvoir télécharger les 10 csv représentant le prix des différentes crypto-monnaies. Ces données sont organisées dans des fichiers CSV, chaque fichier contenant les données OHLCV (Open, High, Low, Close, Volume) pour une crypto-monnaie spécifique.

Chaque fichier CSV comprend une série de mesures, dont le prix d'ouverture, le prix le plus élevé, le prix le plus bas, le prix de clôture et le volume des transactions pour chaque période (période d’une heure).

Toutefois, afin d'analyser efficacement ces données, il serait plus pratique de les rassembler en un seul DataFrame. Cela nous permettra de travailler avec toutes les données de manière unifiée, plutôt que de manipuler chaque fichier séparément. De plus, en utilisant un seul DataFrame, nous pouvons facilement effectuer des analyses comparatives entre différentes crypto-monnaies.

Importation des données

La première étape est de télécharger tous les fichiers disponibles sur ce lien, puis les placer dans votre répertoire.

Ce code vous permettra pour chacune des crypto-monnaies de lire le fichier CSV, le transformer en DataFrame et de le stocker dans un dictionnaire que l’on appelle df_list qui a pour clé le nom des crypto-monnaies.

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

crypto_list = ["ADA", "BCH", "BNB", "BTC", "ETC", "ETH", "LINK", "LTC", "XLM", "XRP"]
df_list = {}
for crypto in crypto_list:
    df_list[crypto] = pd.read_csv(f"{crypto}-USDT.csv")
    df_list[crypto]["date"] = pd.to_datetime(df_list[crypto]["date"], unit="ms")
    df_list[crypto] = df_list[crypto].set_index("date")

Nous remarquons que les fichiers ont toujours le format {crypto}-USDT.csv, pour lire les fichiers, nous allons donc créer une liste avec le nom des crypto-monnaies que l’on va parcourir avec une boucle for afin de lire le fichier csv et créer un DataFrame associé. Nous allons ensuite transformer la colonne date en datetime pour que la doit soit utilisable puis la mettre en index.

Nettoyage des données

Pour vérifier la qualité des données, vous pouvez utiliser .describe()

df_list["ADA"].describe() # df_list["ADA"] nous permet d'accéder au DataFrame de ADA

PythonFinance7Image0.png

À première vue, nous ne remarquons pas de problème spécifique. Cependant, une bonne pratique lorsque l’on modifie les index est également de vérifier l’unicité (pas de doublons) de ces index (pour rappels, nous avons mis les dates en index). Pour cela, nous pouvons utiliser la méthode .nunique() pour compter le nombre de valeurs unique et le comparer avec le nombre de lignes dans notre DataFrame.

PythonFinance7Image1.png

Nous remarquons ici qu’il y a plus de lignes dans notre DataFrame que d’index uniques.

Nous pouvons vérifier s'il y a des index en double dans notre DataFrame en utilisant la méthode duplicated de Pandas. Le paramètre keep='last' signifie que nous gardons la dernière occurrence en cas de doublons.

print(df_list["ADA"].index.duplicated(keep='last'))

PythonFinance7Image2.png

Notre première ligne de code nous affiche un tableau de False et True qui représente chaque ligne qui sera un True si l’index est dupliqué.

Pour afficher les lignes concernées, il suffit de faire notre_DataFrame.loc[notre_filtre]. Ici notre_DataFrame est df_list[”ADA”] et notre_filtre est df_list["ADA].index.duplicated(keep='last').

À partir de là, nous pouvons supprimer toutes les lignes dupliquées, pour cela, on va commencer par stocker nos index dupliqués et sélectionner notre DataFrame sans les index dupliqués (pour faire une sélection inverse, vous pouvez utiliser le symbole ~)

duplicate_index = df_list["ADA"].index.duplicated(keep='last')
df_list["ADA"] = df_list["ADA"][~duplicate_index]

Pour finir, nous allons appliquer cette opération sur tous nos DataFrame dans la boucle for afin d’avoir des données “propres” :

crypto_list = ["ADA", "BCH", "BNB", "BTC", "ETC", "ETH", "LINK", "LTC", "XLM", "XRP"]
df_list = {}
for crypto in crypto_list:
    df_list[crypto] = pd.read_csv(f"{crypto}-USDT.csv")
    df_list[crypto]["date"] = pd.to_datetime(df_list[crypto]["date"], unit="ms")
    df_list[crypto] = df_list[crypto].set_index("date")
    duplicate_index = df_list[crypto].index.duplicated(keep='last')
    df_list[crypto] = df_list[crypto][~duplicate_index]

Création d'un DataFrame unique

Nous allons maintenant fusionner toutes ces données en un seul DataFrame, avec une colonne pour chaque crypto-monnaie. Pour ce faire, nous parcourons df_list et ajoutons à notre DataFrame la colonne close de chaque DataFrame individuel.

df = pd.DataFrame()
for crypto in df_list:
    df[crypto] = df_list[crypto]["close"]

Nous allons ensuite supprimer toutes les lignes qui contiennent des valeurs NaN (Not a Number) en utilisant la méthode dropna de Pandas.

df = df.dropna()

PythonFinance7Image3.png

Nous avons ici un DataFrame avec chaque colonne représentant l’historique d’une crypto-monnaie.

Visualisation des données

Nous pouvons ensuite visualiser les prix de fermeture de chaque crypto-monnaie en utilisant Matplotlib.

for col in df.columns:
    fig, ax = plt.subplots(figsize=(15, 7))
    df[col].plot(label=col)
    ax.set_ylabel(col) # Permet d'afficher le nom de la crypto sur l'axe Y
    plt.show()

PythonFinance7Image4.png

Analyse des corrélations

Nous pouvons ensuite calculer la matrice de corrélation de nos données en utilisant la méthode corr de Pandas.

La corrélation mesure le degré d'association entre deux variables. Cette association peut être positive (quand une variable augmente, l'autre aussi) ou négative (quand une variable augmente, l'autre diminue). La valeur de la corrélation varie de -1 à 1. Un 1 indique une corrélation positive parfaite, un -1 une corrélation négative parfaite, et un 0 indique qu'il n'y a pas de corrélation.

La matrice de corrélation permet donc d’afficher pour chacune de nos crypto-monnaies si elles sont corrélées entre elles.

df.corr()

PythonFinance7Image5.png

La bibliothèque de visualisation Seaborn propose une méthode pour visualiser des matrices comme une matrice de corrélation grâce à des graphiques de type “heatmap”.

f, ax = plt.subplots(figsize=(15, 8))
sns.heatmap(df.loc[:].corr(), annot=True)

PythonFinance7Image6.png

On peut remarquer avec ce graphique que la corrélation la plus élevée est celle entre LINK et XLM. Cependant ici, nous calculons la corrélation sur le prix directement, c'est-à-dire que deux actifs globalement haussiers seront très corrélés. Il est bien plus pertinent de calculer la corrélation sur l’évolution en pourcentage.

Nous allons donc calculer les pourcentages de changement pour chaque crypto-monnaie en utilisant la méthode pct_change de Pandas, et visualiser la matrice de corrélation de ces pourcentages de changement.

df_pct = df.pct_change()

PythonFinance7Image7.png

Nous affichons maintenant la matrice de corrélation seulement sur les données de 2023.

f, ax = plt.subplots(figsize=(15, 8))
sns.heatmap(df_pct.loc["2023"].corr(), annot=True)

PythonFinance7Image8.png

Sur 2023, la corrélation des évolutions de prix la plus importante est entre Bitcoin (BTC) et Ethereum (ETH) de 89%.

Comparaison de deux actifs

Nous allons ensuite comparer deux actifs en traçant leurs prix sur deux axes y différents mais partageant le même axe x. Cela va nous permettre d’observer graphiquement la corrélation.

Commençons avec les deux actifs les plus corrélés BTC et ETH (89%).

fig, ax = plt.subplots(figsize=(15, 8))

data = df.loc["2023"]
asset1 = "BTC"
asset2 = "ETH"
# Tracez le premier actif sur l'axe principal
ax.plot(data.index, data[asset1], color='blue')
ax.set_ylabel(asset1, color='blue')
ax.tick_params(axis='y', labelcolor='blue')

# Créez un deuxième axe partageant le même axe x
ax2 = ax.twinx()

# Tracez le deuxième actif sur le deuxième axe
ax2.plot(data.index, data[asset2], color='red')
ax2.set_ylabel(asset2, color='red')
ax2.tick_params(axis='y', labelcolor='red')

plt.show()

PythonFinance7Image9.png

On remarque que l’on a une corrélation quasi parfaite à l’inverse prenons les actifs les moins corrélés XRP et BCH (43%).

PythonFinance7Image10.png

Nous remarquons ici que l'on avait l’air d’avoir une belle corrélation jusqu’au mois de mars 2023, mais qu’après les deux actifs n’ont plus du tout été corrélés.

Analyse des rendements moyens et de la volatilité

Quelle est la meilleure crypto à avoir en portefeuille ? Il est difficile de répondre à cette question mais une réponse serait la crypto avec les meilleurs rendements et le risque le plus faible.

Nous allons donc essayer de répondre à cette question en affichant sur un graphique nos cryptos avec sur un axe les rendements et sur un autre axe les risques (ou plutôt la volatilité).

Pour ce faire, nous allons pour chacune des cryptos calculer le rendement comme étant la moyenne des rendements horaires, et le risque comme étant l’écart type des rendements horaires. Nous allons ensuite insérer ces valeurs dans des listes afin de pouvoir afficher un nuage de points.

mean_return_list = []
volatility_list = []

temp_data = df_pct.loc[:]

fig, ax = plt.subplots(figsize=(15, 8))

for col in temp_data.columns:
    mean_return = temp_data[col].mean() * 100
    volatility = temp_data[col].std() * 100
    mean_return_list.append(mean_return)
    volatility_list.append(volatility)

    ax.text(mean_return, volatility, col)
    ax.scatter(mean_return, volatility, label=col)

ax.grid(True, linestyle='--', alpha=0.6)
ax.axvline(0, color='black', linestyle='--')
ax.axhline(0, color='black', linestyle='--')

ax.set_xlabel('Rendement moyen')
ax.set_ylabel('Volatilité')

ax.legend()

plt.show()

PythonFinance7Image11.png

D’après le graphique, la crypto avec le meilleur rendement moyen est le BNB, la crypto la plus volatile est l’ETC.

Avec ce graphique, il est difficile par exemple de comparer BTC et ETH car BTC a une volatilité plus faible, mais ETH a des rendements plus élevés. Cependant, en se basant sur ce graphique, nous pouvons dire que sur le passé, il était préférable d’avoir par exemple de l’ETH plutôt que du LINK puisque ETH a à la fois un meilleur et une volatilité plus faible. Les 3 cryptos les plus intéressantes à avoir semble être BTC/ETH/BNB en fonction de votre aversion au risque.

Il peut être intéressant de réaliser ce graphique que sur l’année 2023.

temp_data = df_pct.loc["2023"]

PythonFinance7Image12.png

Sur l’année 2023, les meilleurs cryptos à avoir ont été le BTC et le BCH.

Analyse du ratio de Sharpe

Nous ne pouvons par parler du ratio rendement / risque sans parler du ratio de Sharpe. Le ratio de Sharpe est un ratio très connu en finance qui permet justement de calculer le couple rendement / risque. La formule est :

SharpeRatio=Moyenne[Rp]EcartType[Rp]×NSharpeRatio = \frac{Moyenne[R_p]}{{EcartType[R_p]}} \times \sqrt{N}

Rp: Rendement (en pourcentage)

N: Nombre de période par an

Cette formule nous donne un nombre (souvent compris autour de 1), si le résultat est inférieur à 0 c’est que les rendements sont mauvais, s’il est compris entre 0 et 1 cela signifie que les risques encourus sont trop important par rapport aux rendements espérés. Enfin, s'il est supérieur à 1, c'est que les rendements espérés compensent les risques encourus.

Nous pouvons commencer par afficher le Sharpe Ratio pour chacun des cryptos de notre liste :

temp_data = df_pct.loc[:]

for col in temp_data.columns:
    sharpe_ratio = (8760**0.5)*(temp_data[col].mean()/temp_data[col].std())
    print(col, sharpe_ratio)

PythonFinance7Image13.png

L’opérateur ** permet de mettre à la puissance. Pour faire la racine carré il faut mettre à la puissance 0.5

Nous multiplions par la racine carrée de 8760 car nous avons nos données en heures par heures et qu’il y a 8760 heures par an.

Nous allons ensuite visualiser ces ratios de Sharpe sur un graphique à barres.

sharpe_ratios = []

temp_data = df_pct.loc[:]

for col in temp_data.columns:
    sharpe_ratio = (8760**0.5)*(temp_data[col].mean()/temp_data[col].std())
    sharpe_ratios.append(sharpe_ratio)
    print(col, sharpe_ratio)

df_sharpe = pd.DataFrame(sharpe_ratios, index=temp_data.columns, columns=['Sharpe Ratio'])
df_sharpe = df_sharpe.sort_values(by='Sharpe Ratio')

plt.figure(figsize=(10, 6))
plt.barh(df_sharpe.index, df_sharpe['Sharpe Ratio'], color='lightgreen')
plt.xlabel('Sharpe Ratio')
plt.title('Sharpe Ratio par actif')
plt.show()

PythonFinance7Image14.png

Comme on aurait pu le prévoir avec le graphique de nuage de points, l’actif avec le meilleur Sharpe Ratio sur notre période d’analyse est le BNB.

Analyse des drawdowns

Un drawdown est une chute du prix d'un actif depuis son pic le plus élevé. C’est une métrique très importante car elle nous permet d’analyser les plus grandes chutes qu’un actif a connu par le passé. Si un actif a connu un drawdown maximum de -80% alors il faut prendre en compte que lorsque l’on achète cet actif il est possible qu’il perde 80% de sa valeur même si nous le rappellons le passé ne présage pas forcément le futur.

Pour calculer le drawdown au lieu de le calculer en heures par heures nous pouvons le calculer en jours par jours (cela sera plus lisible sur un graphique). Pour se faire il est très simple de transformer nos données horaires en données journalières grâce à la méthode .resample(). Egalement nous ne l’avons pas précisé mais nous allons réutiliser notre DataFrame df qui contient les prix et non leurs évolutions en pourcentage.

Afin de faire notre calcul, nous aurons besoin de la valeur maximum de notre actif au cours du temps. La méthode .cummax() (pour cumulative maximum) nous permet exactement de faire cela. Imaginons que l’on ai une série s qui contient 2, 4, 7, 5, 1, 8, 4 alors s.cummax() aura pour valeur 2, 4, 7, 7, 7, 8, 8 qui représente de manière roulante la valeur la plus haute atteinte. Enfin pour calculer notre drawdown il suffit de comparer notre valeur actuelle avec la valeur cumulative maximum et le transformer en pourcentage.

Dans ce code, nous calculons le drawdown journalier pour les actifs BTC et ETH et nous l’affichons sur un graphique.

import matplotlib.pyplot as plt

plt.figure(figsize=(15, 10))
temp_data = df.resample('D').last()
for col in ["BTC", "ETH"]:
    running_max = temp_data[col].cummax()
    drawdown = (temp_data[col] - running_max) / running_max
    plt.plot(drawdown, label=col)

plt.title('Drawdown des Actifs au cours du temps')
plt.xlabel('Temps')
plt.ylabel('Drawdown')
plt.legend()
plt.grid(True)
plt.show()

PythonFinance7Image15.png

On remarque ici que lors du crash covid en Mars 2020 le Bitcoin a eu un drawdown d’environ 50% et qu’Ethereum un peu plus de 60%. Enfin en juillet 2022 les 2 actifs ont connu un drawdown de plus de 70%. En effet, si l’on prend l’exemple du Bitcoin son plus haut a été de 68 000$ en Novembre 2021 et en juillet 2022 son prix était de 16 000$.

(1600068000)/68000=0.76(16000-68000)/68000 = -0.76

Ce qui fait une fait une baisse de 76%.

Exercice d’application et Conclusion

Cette séance était en quelque sorte un exercice d’application à elle seule. Cependant, si vous souhaitez pratiquer, nous vous invitons à réaliser par exemple le graphique de nuages de points que l’on a vu en remplaçant la volatilité par le drawdown maximum. Vous pouvez également essayer d’obtenir le nombre de drawdown supérieur à 10% par actif (nombre de fois où l’actif à baisser de plus de 10% par rapport à son plus haut). Un exemple de correction se trouve ici: https://github.com/RobotTraders/Python_For_Finance/blob/main/exercise_correction_chapter_7.ipynb.

Il est très important de comprendre ce qui a été fait lors de cette séance, si vous avez bien compris cela, vous devriez pouvoir appliquer ces méthodes afin de faire des analyses poussées selon vos envies des différents actifs financiers.

Voilà, vous savez maintenant comment utiliser efficacement Pandas pour analyser les données financières. L'important est de comprendre comment Pandas fonctionne et d'utiliser ses fonctionnalités à votre avantage, dans le but d'éviter d'écrire des boucles inutiles et de rendre votre code plus efficace. Dans le prochain épisode, nous allons nous concentrer sur la maîtrise des dates qui sont une composante important à maîtriser lorsque l’on travaille avec beaucoup de série temporelle comme en finance.