Analyse financière et Python : une analyse orientée données de la corrélation existant entre cryptomonnaies et bitcoin

Analyse financière et Python : une analyse orientée données de la corrélation existant entre cryptomonnaies et bitcoin

Malgré l’écroulement récent des marchés de cryptomonnaies, celles-ci restent un sujet brûlant. Comment se comportent donc ces marchés ? Quelles sont les causes de ces brusques pics et de ces vallées dans leur cours ? Ces différents marchés sont-ils intriqués ou indépendant ? Et est-il possible de prévoir l’avenir d’un cours ?

 Cet article ne constitue pas un une proposition pour un investissement ou un avis financier. Il ne s’agit que d’un article technique explorant les possibilités offertes par Python dans le domaine de l’analyse financière. Je ne peux pas être tenu pour responsable des conséquences qui découleraient de l’utilisation des scripts présentés !

La majorité des articles traitant de cryptomonnaies font la part égale entre la spéculation et les bons conseils d’experts auto-proclamés défendant la nouvelle prédiction qu’ils viennent de produire. Peu d’articles se fondent sur une analyse objective des données et des statistiques pour justifier celle-ci.

L’objectif de cet article est d’introduire de manière la plus simple possible des méthodes d’analyse des cryptomarchés en utilisant Python. Nous utiliserons ce langage puissant afin de récupérer, d’analyser, et de visualiser des tendances intéressantes de ces marchés fort volatiles.

Ce tutoriel est destiné au programmeur enthousisate disposant d’une connaissance de base de Python et de la commande en ligne (dans notre cas: Terminal, puisque nous travaillons sous MacOS). Nous utiliserons Python3.

Je vais tenter de répondre à la question: le cours des crytpomonnaies est-il corrélé significativement à celui de la parité bitcoin/dollars ?

1. Premiére étape: installer les dépendances nécessaires

Les librairies Python suivantes vous seront nécessaires:

os
numpy
pandas
pickle
ccxt
datetime
matplotlib

os ,date time et  pickle  font partie de l’installation par défaut de Python. Pour les autres, le plus simple est d’utiliser pip3 dans le terminal:

MacBook-Pro-of-Pyt0f:~ pip3 install numpy pandas ccxt matplotlib

Et ensuite importons les librairies dans notre script:

import os
import numpy as np
import pandas as pd
import pickle
import ccxt
import matplotlib.pyplot as plt 
from datetime import datetime

2. Récupérer les données financières de Bittrex

J’ai fait le choix d’utiliser les données de Bittrex, car c’est la platteforme d’échange qui propose le plus de cryptomonnaies à l’heure actuelle, ce qui permet de ne pas être limité par ce nombre dans nos analyses. Evidemment, libre à vous d’utiliser d’autres platteformes comme Kraken, Cryptopia ou HitBTC…

2.1. Définir une fonction d’aide pour la récupération des données

Pour faciliter la récupération et la manipulation des données, cette fonction nous permettra de faire appel à  ccxt  et de mettre les données en cache en les sérialisant.

def getCCXTData(market):
    '''télécharge et met les données de CCXT en cache'''
    cachePath = '{}.pkl'.format(market).replace('/', '-')
    if os.path.isfile(cachePath):
        dataFile = open(cachePath, 'rb')
        downloadFile = pickle.load(dataFile)
        print('{} est chargé du cache'.format(market))
    else:
        dataFile = open(cachePath, 'wb')
        print('Charge de {} de CCXT'.format(market))
        exchange = ccxt.bittrex()
        downloadFile = exchange.fetch_ohlcv(market, timeframe = '1m') # 1 minute
        dataFrame = pd.DataFrame(data=downloadFile, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
        dataFrame.to_pickle(cachePath)
        print('{} en cache dans {}'.format(market, cachePath))
    return downloadFile

pickle est utilisé afin de sérialiser et mettre en cache les données que nous recevons de ccxt , afin de ne pas recharger celles-ci à chaque fois que nous utilisons le script. La fonction retourne un dataframe via  panda

Les dataframes sont une mise en format “feuille de calcul” d’une série de données, ce qui en facilite la lecture et la manipulation.

Remarquez que, comme nous nous intéressons à un marché extrêmement volatile, nous nous sommes intéressés par les valeurs des cours pour un délais de une minute.

2.2. Chargeons l’historique du cours du marché BTC/USD de Bittrex

Testons cette première fonction…

def loadData():
    '''télécharge les données de Bittrex'''
    getCCXTData('BTC/USD')

if __name__ == '__main__':
    loadData()
Charge de BTC/USD de CCXT
BTC/USD en cache dans BTC-USD.pkl

Comme prévu, lors du premier lancement, la fonction nous annonce le chargement des données demandées, et leur mise en cache.

Tous les appels suivants donneront le résultat:

 <br> <br> <br> <br> timestamp      open      high       low     close     volume<br> 0     1546442940000  3825.040  3825.040  3825.000  3825.000   3.271725<br> 1     1546443000000  3825.000  3825.000  3824.720  3824.720   1.516976<br> 2     1546443060000  3818.960  3826.020  3818.960  3826.020   0.135811<br> 3     1546443120000  3829.000  3829.000  3829.000  3829.000   0.000718<br> 4     1546443240000  3829.000  3829.000  3829.000  3829.000   0.048068<br> 5     1546443480000  3829.000  3829.000  3829.000  3829.000   0.063025<br> 6     1546443540000  3828.980  3828.980  3828.980  3828.980   0.003917<br> 7     1546443660000  3820.002  3820.002  3820.002  3820.002   0.438515<br> 8     1546444020000  3821.325  3821.325  3810.000  3810.000   0.192905<br> 9     1546444080000  3810.000  3810.000  3810.000  3810.000   0.001316<br> 10    1546444140000  3813.683  3821.150  3803.028  3803.028  10.752267<br> 11    1546444200000  3821.150  3821.150  3816.380  3816.380   0.221177<br> 12    1546444260000  3815.467  3815.467  3815.467  3815.467   0.023245<br> 13    1546444320000  3815.467  3815.467  3815.467  3815.467   0.035437<br> 14    1546444380000  3807.793  3810.622  3807.793  3810.622   0.526225<br> 15    1546444440000  3814.200  3814.200  3814.200  3814.200   0.012319<br> 16    1546444620000  3817.020  3817.020  3816.444  3816.444   0.362134<br> 17    1546444680000  3819.320  3819.320  3812.533  3819.320   2.550677<br> 18    1546444740000  3815.856  3815.856  3812.530  3812.530   0.553482<br> 19    1546444800000  3817.837  3819.320  3817.837  3819.320   1.813975<br> 20    1546444860000  3819.693  3819.693  3819.693  3819.693   0.194963<br> 21    1546444980000  3819.857  3819.857  3819.857  3819.857   0.196342<br> 22    1546445040000  3814.108  3814.108  3814.108  3814.108   0.130000<br> 23    1546445220000  3818.350  3818.350  3818.350  3818.350   0.109049<br> 24    1546445280000  3818.291  3818.291  3814.000  3814.000   0.228191<br> 25    1546445460000  3822.155  3822.155  3822.155  3822.155   0.144071<br> 26    1546445520000  3822.000  3822.000  3822.000  3822.000   0.632746<br> 27    1546445580000  3823.185  3823.185  3823.185  3823.185   0.998608<br> 28    1546445640000  3824.850  3826.000  3824.850  3826.000   0.010784<br> 29    1546445700000  3826.000  3827.747  3826.000  3827.747   0.535021<br> ...             ...       ...       ...       ...       ...        ...<br> 8035  1547301900000  3630.213  3630.213  3630.213  3630.213   0.082809<br> 8036  1547301960000  3624.130  3624.130  3624.130  3624.130   1.168241<br> 8037  1547302020000  3622.470  3622.470  3622.470  3622.470   0.158570<br> 8038  1547302080000  3622.470  3622.470  3622.470  3622.470   0.050364<br> 8039  1547302200000  3627.280  3627.280  3627.280  3627.280   0.098796<br> 8040  1547302260000  3627.169  3627.169  3627.169  3627.169   0.170932<br> 8041  1547302440000  3627.166  3627.166  3627.166  3627.166   0.342779<br> 8042  1547302500000  3627.163  3627.163  3627.163  3627.163   0.004675<br> 8043  1547303040000  3625.997  3625.997  3625.997  3625.997   0.002831<br> 8044  1547303460000  3619.140  3625.994  3619.140  3625.994   0.035951<br> 8045  1547303640000  3619.320  3619.320  3619.320  3619.320   0.003791<br> 8046  1547303760000  3625.994  3625.994  3625.994  3625.994   0.082529<br> 8047  1547304000000  3626.000  3626.000  3626.000  3626.000   0.013353<br> 8048  1547304240000  3629.492  3629.492  3629.492  3629.492   0.054966<br> 8049  1547304300000  3619.340  3619.340  3619.340  3619.340   0.085000<br> 8050  1547304420000  3628.574  3628.574  3628.574  3628.574   0.084360<br> 8051  1547304660000  3628.571  3628.571  3628.571  3628.571   0.879685<br> 8052  1547304720000  3629.041  3629.041  3629.041  3629.041   0.000964<br> 8053  1547305320000  3623.128  3623.128  3623.128  3623.128   0.457837<br> 8054  1547305380000  3620.105  3620.105  3620.105  3620.105   1.191792<br> 8055  1547305440000  3623.222  3623.537  3623.222  3623.537   1.106750<br> 8056  1547305560000  3622.000  3622.000  3622.000  3622.000   0.363555<br> 8057  1547305740000  3616.950  3616.950  3616.950  3616.950   0.424445<br> 8058  1547305800000  3616.950  3616.950  3614.177  3614.177   2.849890<br> 8059  1547305860000  3616.962  3616.962  3616.962  3616.962   0.004657<br> 8060  1547306100000  3618.690  3618.690  3618.690  3618.690   0.016483<br> 8061  1547306400000  3625.977  3625.977  3618.273  3618.273   2.131880<br> 8062  1547306520000  3618.270  3618.270  3618.270  3618.270   0.092911<br> 8063  1547306640000  3625.977  3625.980  3625.977  3625.980   1.105909<br> 8064  1547306700000  3618.760  3618.760  3618.760  3618.760   0.011500 

Ce qui donne, à ceux qui ne connaissaient pas les dataframes, une idée de la puissance de l’outil !

Nous disposons à présent des données nécessaires à notre analyse.

2.3. Visualisation des données

Nous allons maintenant générer un simple graphique  afin de vérifier si, visuellement, nos données semblent correctes.

Rajoutons à notre code les commandes nécessaires.

def main():
    data = getCCXTData('BTC/USD')
    print(data)
    data[['close']].plot(grid=True,figsize=(8,5))
    plt.show()

if __name__ == '__main__':
        main()

Et voici ce que  matplotlib nous génère:

Comparons avec les graphiques présentés par Bittrex:

Nous constatons que le test visuel est positif: les données téléchargées et mises en graphique correspondent à celles de Bittrex.

3. Téléchargement et agrégation des données d’autres cryptomonnaies

3.1. Téléchargement et cache

Maintenant que nous disposons des données concernant la parité Bitcoin/DollarsUS, recherchons les mêmes données concernant d’autres cryptomonnaies.

La grosse majorité des cryptomonnaies ne peuvent pas être achetées en dollars, mais nécessitent pour cela des Bitcoins. Pour disposer d’une base de comparaison valide, nous devrons donc convertir le taux d’échange de nos cryptomonnaies par-rapport au Bitcoin en taux relatif aux dollars.

Quelles cryptomonnaies allons-nous utiliser pour notre comparaison ? Un petit tour vers coincap.io nous apprend que le top 10 est constitué de Ethereum, Ripple, Bitcoin Cash, EOS, Litecoin, Stellar, Cardano, TRON, IOTA et DASH. Créons une fonction  loadData() en généralisant le code utilisé dans notre fonction  main() :

altcoinsData = {}
def loadData():
    altcoins = ['BTC/USD', 'ETH/BTC','XRP/BTC','BCH/BTC','LTC/BTC','XLM/BTC','ADA/BTC','TRX/BTC','XMR/BTC','NEO/BTC']
    for market in altcoins:
        altcoinsData[market] = getCCXTData(market)
        print(altcoinsData[market])

if __name__ == '__main__':
    loadData()

Et limitons-nous, pour des raisons pratiques, au 500 premières valeurs retournées en modifiant cette ligne dans la fonction  getCCXTData() :

dataFrame = pd.DataFrame(data=dataFile, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']).head(500)

Nous constatons que les données sont correctement chargées et mises en cache. Nous disposons à présent d’un dictionnaire (dans le vocabulaire Python) chargé des cours de 10 cryptomonnaies.

3.2. Conversion vers le dollars

Maintenant nous sommes en mesure de convertir chaque paire de cryptomonnaie/bitcoin vers un taux de change en dollars.

Pour ce faire, nous rajoutons à chaque dataframe de cryptomonnaie, une colonne reprenant le cours USD/BTC à la minute correspondante, et nous multiplions le montant par le cours de fermeture. Nous avons alors calculé le prix correspondant en dollars.

Créons une nouvelle fonction computeDollarsRate() qui va calculer le taux de change de chaque cryptomonnaie et retourner un dataframe contenant une colonne par marché :

def computeDollarsRate():     
	altcoinsInDollars = {}     
	for market in altcoinsData:         
		if market != 'BTC/USD':             
			altcoinsData[market]['rateBTCUSD'] = altcoinsData['BTC/USD']['close']             
			altcoinsData[market]['rateUSD'] = altcoinsData[market]['close'] * altcoinsData[market]['rateBTCUSD']          
			coin = market.split('/')[0]         
		elif market == 'BTC/USD':             
			altcoinsData[market]['rateUSD'] = altcoinsData['BTC/USD']['close'] - 5000             
			coin = 'BTC'         
			altcoinsInDollars[coin] = altcoinsData[market]['rateUSD']     
		return pd.DataFrame(altcoinsInDollars) 

Le dataframe retourné est :

      BTC         ETH        XRP       ...       TRX       XMR          NEO 
0    1531.694  498.956105  0.545723    ...      0.044350  124.373251  39.059530
1    1520.000  497.046593  0.544355    ...      0.044271  123.880000  39.092746
2    1545.000  498.952446  0.546835    ...      0.044179  124.626683  39.008265
3    1550.781  499.393156  0.550069    ...      0.044152  125.021262  39.245663
4    1560.000  500.095958  0.548088    ...      0.044280  125.137838  39.097666
5    1516.851  494.954833  0.544483    ...      0.043924  124.309910  38.840497
..        ...         ...       ...    ...           ...         ...        ...
470  1208.597  472.322990  0.514382    ...      0.041225  119.200654  36.320913
495   908.603  448.965140  0.491478    ...      0.039174  112.501101  34.722615
496   916.170  448.094502  0.492107    ...      0.039224  113.679680  34.767083
497   916.234  448.099409  0.492112    ...      0.039225  113.874844  34.767577
498   936.694  449.649062  0.495002    ...      0.039360  114.218609  34.997524
499   900.000  448.891175  0.489995    ...      0.039176  113.480128  34.803038

Remarquez que nous répliquons dans le dataframe ‘BTC/USD’ la colonne ‘close’ sous l’étiquette ‘rateUSD’: le but est ici d’harmoniser la référence aux données, ce qui nous sera utile pour la prochaine étape.

3.3. Visualisation des données

Créons une fonction pour visualiser nos données:

def plotData(dataFrame):
    for market in dataFrame:
        dataFrame[market]['rateUSD'].plot(grid=True,figsize=(8,5),label=market)
    plt.legend()
    plt.show()
    
if __name__ == '__main__':
    loadData()
    df = computeDollarsRate()
    plotData(df)

Ce qui nous donne:

Nous pouvons constater que la différence de taux entre BTC (> 6000 $) et les autres monnaies (< 1000 $) font les mouvements de la courbe sont difficilement observables. Rapprochons le taux du BTC en diminuant son taux de 5000$ par cette modification de la fonction  computeDollarsRate()  :

altcoinsData[market]['rateUSD'] = altcoinsData['BTC/USD']['close'] - 5000

Exécutons notre script avec ce nouveau paramètre …

Visuellement, nous pouvons constater une certaine corrélation dans les variations du cours du BTC et dans celles des cryptomonnaies qui lui sont liées:

Cette analyse visuelle manque évidement de précision, mais moins que les pures spéculations avancées par des soit-disant experts présents sur le Net. Essayons néanmoins de confirmer nos conclusions provisoires par l’usage des statistiques, et plus particulièrement par l’utilisation de la fonction  corr() proposée par Pandas. Cette fonction calcule la corrélation de Pearson pour chaque colonne du dataframe, par-rapport aux autres colonnes.

En statistiques, étudier la corrélation entre deux ou plusieurs variables aléatoires ou statistiques numériques, c’est étudier l’intensité de la liaison qui peut exister entre ces variables. Ce coefficient r est égal à 1 dans le cas où l’une des variables est une fonction affine croissante de l’autre variable, à -1 dans le cas où une variable est une fonction affine et décroissante. Les valeurs intermédiaires renseignent sur le degré de dépendance linéaire entre les deux variables. Plus le coefficient est proche des valeurs extrêmes -1 et 1, plus la corrélation linéaire entre les variables est forte ; on emploie simplement l’expression “fortement corrélées” pour qualifier les deux variables. Une corrélation égale à 0 signifie que les variables ne sont pas corrélées linéairement.

Créons et testons une nouvelle fonction  computeCorrelation()

def computeCorrelation(dataFrame):
    return dataFrame.pct_change().corr(method='pearson')
    
if __name__ == '__main__':
    loadData()
    df = computeDollarsRate()
    print(df)
    df = computeCorrelation(df)
    print(df)
    plotData(df)

La matrice de corrélation suivante est retournée:

          BTC       ETH       XRP       BCH       LTC       XLM       ADA  \
BTC  1.000000  0.931594  0.909206  0.799207  0.868990  0.832845  0.890406   
ETH  0.931594  1.000000  0.875255  0.765099  0.818872  0.795341  0.855965   
XRP  0.909206  0.875255  1.000000  0.733991  0.819642  0.767050  0.812354   
BCH  0.799207  0.765099  0.733991  1.000000  0.734319  0.662926  0.738466   
LTC  0.868990  0.818872  0.819642  0.734319  1.000000  0.726015  0.784301   
XLM  0.832845  0.795341  0.767050  0.662926  0.726015  1.000000  0.751741   
ADA  0.890406  0.855965  0.812354  0.738466  0.784301  0.751741  1.000000   
TRX  0.873375  0.832670  0.815812  0.723629  0.781478  0.740716  0.788595   
XMR  0.713503  0.688553  0.691728  0.556210  0.628500  0.619054  0.645045   
NEO  0.855761  0.821927  0.791748  0.714918  0.744257  0.729556  0.774456   

          TRX       XMR       NEO  
BTC  0.873375  0.713503  0.855761  
ETH  0.832670  0.688553  0.821927  
XRP  0.815812  0.691728  0.791748  
BCH  0.723629  0.556210  0.714918  
LTC  0.781478  0.628500  0.744257  
XLM  0.740716  0.619054  0.729556  
ADA  0.788595  0.645045  0.774456  
TRX  1.000000  0.601881  0.753009  
XMR  0.601881  1.000000  0.632402  
NEO  0.753009  0.632402  1.000000

et notre fonction  plotData() génère le graphique.

Ce qui est peu parlant… Nous pouvons cependant déjà constater l’absence totale de corrélation négative: la montée d’un cours n’engendre jamais la descente d’un autre !

Utilisons à présent une “heatmap” afin de mieux visualiser le résultat de nos calculs. Ajoutons une ligne à notre fonction  plotData() :

plt.imshow(dataFrame, cmap='hot', interpolation='nearest')
plt.show()

Cette carte s’interprète de la manière suivante: au plus la couleur est claire, au plus la corrélation est forte (proche de 1). La corrélation la plus forte est évidement la série de cases blanches, où les cryptomonnaies sont corrélées avec elles-mêmes !

La carte étant symétrique par rapport à l’axe transversal, la bande verticale de gauche suffit à apporter une réponse à la question initiale de cet article: oui, il existe une forte corrélation entre les fluctuations du BTC et celles du top 10 des cryptomonnaies, sur le marché “1 minute” !

En d’autre terme, observer les tendances haussières au baissières du BTC peut suffire à prévoir celles des autres monnaies… Intéressant pour des techniques d’investissement comme le scalping

Cette conclusion peut-elle également tirée pour d’autre marchés, comme celui du day-trading ?

Adaptons une ligne de code de notre fonction getCCXTData() en remplaçant ‘1m’ par ‘1d’:

dataFile = exchange.fetch_ohlcv(market, timeframe = '1d')

Et exécutons le script (sans avoir oublié de vider les caches, bien entendu !). Vo

La corrélation que nous avions observée sur le marché ‘1m’ n’existe plus ! Nous y retrouvons même parfois des corrélations négatives…

4. Conclusion

Outre le fait que Python démontre sa puissance pour ce genre de démonstration, nous pouvons conclure que le taux du cryptomonnaies appartenant au top 10 et lié à celui du Bitcoin par-rapport au dollars américain sur le marché à très court terme (1m), mais pas sur celui du court terme (1d).

Fort de cette information, nous savons maintenant qu’une stratégie d’investissement efficace sur le Bitcoin le devient de facto sur les cryptomonnaies corrélées !

5. Le code

Ce code a été écrit parallèlement avec la composition de cette article. L’accent a été mis non pas sur la pérennité du code – il reste de nombreux points à optimiser – mais sur l’obtention des résultats recherchés.

Pour ceux qui sont plus à l’aise dans un simple copier/coller du code plutôt qu’assembler les pièces de scripts présentées, voici l’intégralité du code :

#!/usr/bin/env python3
# encoding: utf-8

# cryptoanalyse.py
# Created by pyT0f on 24/06/2018.
    
import os
import numpy as np
import pandas as pd
import pickle
import ccxt
import matplotlib.pyplot as plt 
from datetime import datetime

pd.set_option('display.max_columns', 11)
altcoinsData = {}


def getCCXTData(market):
    '''télécharge et met les données de CCXT en cache'''
    cachePath = '{}.pkl'.format(market).replace('/', '-')
    if os.path.isfile(cachePath):
        cacheFile = open(cachePath, 'rb')
        dataFile = pickle.load(cacheFile)
        print('{} est chargé du cache'.format(market))
    else:
        dataFile = open(cachePath, 'wb')
        print('Charge de {} de CCXT'.format(market))
        exchange = ccxt.bittrex()
        dataFile = exchange.fetch_ohlcv(market, timeframe = '1m')
        dataFrame = pd.DataFrame(data=dataFile, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']).head(500)
        dataFrame.to_pickle(cachePath)		
        print('{} en cache dans {}'.format(market, cachePath))
    return dataFile

def loadData():
    '''charge les données historiques des cryptomonnaies choisies dans un dictionnaire'''
    altcoins = ['BTC/USD', 'ETH/BTC','XRP/BTC','BCH/BTC','LTC/BTC','XLM/BTC','ADA/BTC','TRX/BTC','XMR/BTC','NEO/BTC']
    for market in altcoins:
        altcoinsData[market] = getCCXTData(market)
        #print(altcoinsData[market])
        
def computeDollarsRate():
    '''effectue la conversion du taux crypto/BTC vers crypto/USD
    RETURN: dataframe'''
    altcoinsInDollars = {}
    for market in altcoinsData:
        if market != 'BTC/USD':
            altcoinsData[market]['rateBTCUSD'] = altcoinsData['BTC/USD']['close']
            altcoinsData[market]['rateUSD'] = altcoinsData[market]['close'] * altcoinsData[market]['rateBTCUSD']
            coin = market.split('/')[0]
        elif market == 'BTC/USD':
            altcoinsData[market]['rateUSD'] = altcoinsData['BTC/USD']['close']
            coin = 'BTC'
        altcoinsInDollars[coin] = altcoinsData[market]['rateUSD']
    return pd.DataFrame(altcoinsInDollars)
        
def plotData(dataFrame):
    '''compose un graphique sur base d'un dataframe'''
    for market in dataFrame:
        dataFrame[market].plot(grid=True,figsize=(8,5),label=market)
        
    plt.legend()
    plt.show()
    fig, ax = plt.subplots()
    plt.imshow(dataFrame, cmap='hot', interpolation='nearest')
    plt.colorbar()
    plt.show()

def computeCorrelation(dataFrame):
    return dataFrame.pct_change().corr(method='pearson')
    
if __name__ == '__main__':
    loadData()
    df = computeDollarsRate()
    print(df)
    df = computeCorrelation(df)
    print(df)
    plotData(df)

Des commentaires, des questions ? N’hésitez pas à me contacter via la page “Contact

Close Menu