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«