You are currently viewing Analyse financière et python: calcul de la MACD (Moving Average Convergence/Divergence) et analyse prévisionelle des cours des cryptomonnaies

Analyse financière et python: calcul de la MACD (Moving Average Convergence/Divergence) et analyse prévisionelle des cours des cryptomonnaies

Tout investisseur, que ce soit en cryptomonnaies ou en fiats rêve d’être en mesure d’en prévoir le cours: « buy low, sell high » est le crédo de l’investisseur !

Dans cet article, nous allons utiliser la puissance du langage de script Python afin, dans un premier temps, de calculer la valeur du MACD d’une cryptomonnaie donnée et d’en étudier la relation avec les fluctuation du cours et, dans un second temps, d’exploiter cette connaissance afin de vérifier si nous pouvons utiliser ce MACD pour établir une analyse prévisionnelle des cours gagnante.

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 !

Qu’est-ce que le MACD ?

Cet indicateur qui signifie en anglais « Moving Average Convergence/Divergence » est utilisé lors de l’analyse technique qui a pour objectif de prévoir la tendance d’un cours. La MACD permet donc de visualiser des signaux d’achat ou de vente. Un histogramme sont associés afin de travailler sur les divergences.

Comment calculer le MACD ?

Pour calculer la MACD il faut faire la différence entre deux moyennes mobiles exponentielles (MME) ayant des périodes différentes. De manière générale, il est convenu de faire la différence entre une moyenne mobile exceptionnelle à 12 jours et une autre à 26 jours. On l’écrit donc MACD(e12,e26). Une fois calculée et représentée graphiquement, on peut parler de ligne de MACD.

Comment utiliser le MACD ?

Visuellement parlant, il est aisé de prévoir une tendance:

  • chaque croisement des lignes des moyennes indique un changement de tendance
  • si la moyenne sur 12 jours (e12) dépasse la moyenne sur 24 jours, cela signifier que nous avons une tendance haussière
  • si la moyenne sur 24 jours (e24) dépasse la moyenne sur 12 jours, cela signifier que nous avons une tendance baissière

Programatiquement, nous utiliserons le calcul e12 – e24 en observant son trajet autour de 0

Au boulot…

Première étape. Installer les dépendances nécessaires

Nous aurons besoin de diverses bibliothèques de fonctions:

  • ccxt qui nous donne accès aux données financières des différents exchanges négociant les cryptomonnaies: voir GitHub;
  • pandas qui nous fourni la conversion de nos données en format dictionnaire vers un format dataframe : il s’agit d’un format semblable à celui d’une feuille de calcul EXCEL, ce qui facilite la manipulation des données;
  • matplotlib qui nous permet facilement de visualiser graphiquement les résultats de nos calculs et le cours des cryptomonnaies. Nous utiliserons la classe matplotlib.pyplot

Pour ceux qui débutent en Python: voyez la documention de ‘pip’ afin d’installer localement ces différentes bibliothèques de fonctions.

Commençons notre script par:

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

# article-MACD
# Created by xtof on 20/07/2018.

import pandas as pd
import ccxt
import matplotlib.pyplot as plt 

if __name__ == '__main__':
    pass

Deuxième étape. Télécharger les données financière d’un marché

Utilisons à présent l’excellent ccxt afin de télécharger les données du cours d’un marché, disons par exemple : ETH/BTC (Ethereum / Bitcoin) sur l’exchange BITTREX et pour le cours journalier (timeframe = ‘1d’). Pour ce faire, créons une fonction getCCXTData(market). Celle-ci prend un marché en paramètre et nous retourne un dataframe avec les données de cours :

def getCCXTData(market):

    exchange = ccxt.bittrex()
    dataFile = exchange.fetch_ohlcv(market, timeframe = '1d')
    dataFrame = pd.DataFrame(data=dataFile, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
    return dataFrame

et appelons cette fonction au lancement du script afin de voir son résultat:

if __name__ == '__main__':
    df = getCCXTData('ETH/BTC')
    print(df)

Un dataframe des cours de ‘ETH/BTC’ nous est retourné:

          timestamp      open      high       low     close        volume
0     1439510400000  0.006900  0.080000  0.006667  0.007000   3123.423352
1     1439596800000  0.006740  0.007240  0.005950  0.006954   3060.474329
2     1439683200000  0.006501  0.006900  0.004120  0.006051   8554.037732
3     1439769600000  0.006139  0.006139  0.004655  0.004798   6059.488321
...             ...       ...       ...       ...       ...           ...
1069  1531872000000  0.068245  0.068741  0.064352  0.065201   8439.720603
1070  1531958400000  0.065050  0.065201  0.062175  0.062610   7261.326607
1071  1532044800000  0.062610  0.062900  0.060770  0.061226   4563.440498

[1072 rows x 6 columns]

Troisième étape. Calcul du MACD

Exploitons à présent ces données afin de calculer le MACD. Une petite fonction va nous y aider:

def createMACD(df):
    
    df['e26'] = pd.Series.ewm(df['close'], span=26).mean()
    df['e12'] = pd.Series.ewm(df['close'], span=12).mean()
    df['MACD'] = df['e12'] - df['e26']
    return df

Cette fonction prend en argument le dataframe contenant les données de cours et y rajoute trois colonnes calculées: e12, e26 et MACD avant de le retourner. Testons la fonction:

if __name__ == '__main__':
    df = getCCXTData('ETH/BTC')
    df = createMACD(df)
    print(df)

Nous obtenons dataframe modifié:

          timestamp      open      high    ...          e26       e12      MACD
0     1439510400000  0.006900  0.080000    ...     0.007000  0.007000  0.000000
1     1439596800000  0.006740  0.007240    ...     0.006976  0.006975 -0.000001
2     1439683200000  0.006501  0.006900    ...     0.006644  0.006614 -0.000029
3     1439769600000  0.006139  0.006139    ...     0.006128  0.006041 -0.000087
...             ...       ...       ...    ...          ...       ...       ...
1069  1531872000000  0.068245  0.068741    ...     0.071088  0.069392 -0.001696
1070  1531958400000  0.065050  0.065201    ...     0.070460  0.068349 -0.002112
1071  1532044800000  0.062610  0.062900    ...     0.069779  0.067259 -0.002520

[1072 rows x 9 columns]

Quatrième étape. Visualisation des données

Ecrivons une fonction qui va nous permettre de visualiser nos données:

def plotData(df, columns):
    df[columns].plot()
    plt.grid(True)
    plt.show()

Cette fonction prend comme argument notre dataframe et l’intitulé des colonnes que nous souhaitons visualiser.

Testons la fonction:

if __name__ == '__main__':
    df = getCCXTData('ETH/BTC')
    df = createMACD(df)
    plotData(df, ['close', 'e26', 'e12'])

La fonction génère un graphique avec trois lignes: une par colonne demandée.

Effectuons un zoom sur une partie significative pour le propos: entre les lignes 400 et 520.

Nous pouvons ici constater comment la visualisation de e12 et e26 permet de prévoir la tendance d’un cours. Entre 400 et 420, e12 est situé au-dessus de e26 : la tendance est haussière. e12 croise e26 aux environs de 400 : changement de tendance, nous repartons vers la baisse. Vers 507, e1 remonte à la rencontre de e26: la tendance est haussière et est confirmée vers 511 !

Un autre exemple significatif:

Vers 978, e12 dépasse e26: le moment est venu d’acheter ! A 1018, e12 plonge en-dessous de e26: il est temps de vendre ! Nous avons acheté à 0.063 BTC, nous revendons à 0.078 BTC avec une marge de 0.013 BTC. Si nous y ajoutons les frais d’achat et de revente (0.025%) imposé par Bittrex, nous pouvons calculer notre profit net (avec le 1 BTC à 6000 $) :

achat: 0.063 + 0.025% =0,06301575 (378 $)

vente: 0.078 – 0.025% = 0,0779805 (467,8 $)

profit: 0,01496475 BTC (89,8 $)

Pas mal pour une seule opération !

Fort de ce constat, pouvons nous envisager une stratégie à terme qui nous garantirait un revenu constant, avec des risques minimisés ? Le rêve de l’investisseur…

Peut-être venons-nous de découvrir le Graal tant convoité ! Avant de commander la Lamborghini, il serait sage de procéder à une petite simulation pour vérifier nos conclusions… 😉

Cinquième étape. Simulation

Nous allons à présent simuler l’application notre stratégie sur les données dont nous disposons, soit 1072 jours (une rangée par jour).

Notre stratégie est la suivante:

  • e12 passe au-dessus de e26 (MACD négative), nous achetons 1 ETH
  • e12 passe en-dessous de e26 (MACD positive), nous vendons 1 ETH

Après chaque vente, nous calculons le profit (ou la perte) réalisé et nous le cumulons dans une variable ‘budget’.

Définissons quelques conditions de base nécessaires à notre simulation :

  • nous supposons disposer d’un budget initial qui est suffisant pour un achat d’au moins 1 ETH (au cours actuel de 0,08 BTC)
  • le premier mouvement effectué devra être un achat
  • le dernier mouvement effectué devra être une vente
  • nous considérons que nos demandes de vente et d’achat sont toutes acceptées et complètement honorée par l’exchange

Nous allons procéder en deux temps:

  1. nous allons parcourir chaque ligne de notre dataframe qui contient les données financières et la colonne de notre MACD, et rajouter dans une colonne supplémentaire ‘position’ ce que nous décidons d’effectuer: achat ‘buy’, vente ‘sell’ ou maintenir ‘hold’.
  2. ensuite nous allons parcourir chaque rangée en réalisant la ‘position’ calculée

A l’attaque !

Définissons notre fonction computeStrategy(dataframe).Celle-ci prend un argument notre dataframe et retournera le profit (ou la perte) réalisé en fin d’exercice.

def computeStrategy(df):
    # initialisation de quelques variables
    profit = 0
    move = 'buy' # drapeau qui servira à signaler le prochain mouvement 'buy' > 'sell' > 'buy' etc...
    
    # crée des colonnes vides dans laquelle nous placerons notre strategie et notre budget
    df['position'] = None
    df['budget'] = 0

Pour faciliter la lecture du dataframe, supprimons les colonnes superflues: seul la colonne ‘close’ (le prix de fermeture) nous intéresse.

# enlève les colonnes superflues
del df['open']
del df['high']
del df['low']
del df['volume']

Parcourons les rangée du dataframe et appliquons notre théorie:

  • si le MACD de la rangée est négatif et que le MACD de la rangée précédente est positif, il s’agit d’un signal d’achat. Effectuons celui-ci et soustrayons de notre budget le prix de l’achat.
  • si le MACD de la rangée est positif et que le MACD de la rangée précédente est négatif, il s’agit d’un signal de vente. Effectuons celle-ci et rajoutons à notre budget le prix de la vente.
for row in range (len(df)): 
    
    # conditions pour un achat
    if df['MACD'].iloc[row] < 0 and df['MACD'].iloc[row-1] > 0 and move == 'buy':
        df['position'].iloc[row] = 'buy'
        move = 'sell'
        df['budget'].iloc[row] = df['budget'].iloc[row-1] - df['close'].iloc[row]
    # conditions pour une vente
    elif df['MACD'].iloc[row] > 0 and df['MACD'].iloc[row-1] < 0 and move == 'sell':
        df['position'].iloc[row] = 'sell'
        move = 'buy'
        df['budget'].iloc[row] = df['budget'].iloc[row-1] + df['close'].iloc[row]
    # ni vente ni achat, nous tenons la position
    else:
        df['position'].iloc[row] = 'hold'
        df['budget'].iloc[row] = df['budget'].iloc[row-1]

Remarquez l’utilisation du drapeau ‘move’ qui oblige le script à effectuer une vente après un achat et un achat après une vente, pour éviter de cumuler les achats successifs, dans le cadre de cette simulation.

Testons notre script:

if __name__ == '__main__':
    df = getCCXTData('ETH/BTC')
    df = createMACD(df)
    profit = computeStrategy(df)
    plotData(df, ['close', 'e26', 'e12', 'budget'])

Nous constatons:

  • que le dernier mouvement est un achat, qu’il convient d’annuler: en effet, dans le cadre de cette simulation celui-ci n’a pas de raison d’être puisque nous n’auront pas l’occasion de revendre l’ETH acheté. Après annulation notre profit est de -0.047215
  • que même en ignorant ce dernier achat, notre stratégie est perdante, car notre profit est négatif !

Que s’est-il passé ? Pourtant nous avons suivi au pied de la lettre les indications du MACD !

Oui, mais MACD est un indicateur de tendance, pas un générateur de profit ! MACD ignore si le taux existant au moment d’un signal de vente est supérieur ou inférieur à celui de l’achat…

Effectuons un zoom sur la partie 680-760 :

Nous effectuons un achat d’1 ETH au taux de 0.101950 (rangée 691), car e26 passe au-dessus de e12

690   1499126400000  0.102931  0.113130  0.113371  2.408214e-04     hold  0.002642
691   1499212800000  0.101950  0.112302  0.111614 -6.881184e-04      buy -0.099308

et nous le revendons plus tard car e12 repasse au dessus de e26, mais au taux de 0.083790: donc à perte !

746   1503964800000  0.081438  0.077967  0.077566 -4.007387e-04     hold -0.099308
747   1504051200000  0.083790  0.078398  0.078524  1.254276e-04     sell -0.015518

Conclusion temporaire: ne jamais suivre aveuglement les signaux d’un indicateur !

Introduisons un garde-fou afin de nous prémunir de cette situation: nous ne vendons que si le prix de vente est supérieur au prix d’achat !

Utilisons pour cela une variable  lastClose afin de retenir le dernier prix d’achat et intégrons-là dans une condition supplémentaire pour la vente:

for row in range (len(df)): 

    # conditions pour un achat
    if df['MACD'].iloc[row] < 0 and df['MACD'].iloc[row-1] > 0 and move == 'buy':
        df['position'].iloc[row] = 'buy'
        move = 'sell'
        lastClose = df['close'].iloc[row]
        df['budget'].iloc[row] = df['budget'].iloc[row-1] - df['close'].iloc[row]
    # conditions pour une vente
    elif df['MACD'].iloc[row] > 0 and df['MACD'].iloc[row-1] < 0 and move == 'sell' and df['close'].iloc[row] > lastClose:
        df['position'].iloc[row] = 'sell'
        move = 'buy'
        df['budget'].iloc[row] = df['budget'].iloc[row-1] + df['close'].iloc[row]
    # ni vente ni achat, nous tenons la position
    else:
        df['position'].iloc[row] = 'hold'
        df['budget'].iloc[row] = df['budget'].iloc[row-1]

En ignorant le dernier achat, qui doit être être annulé, nous avons effectué un profit de 0.099261 BTC sur un budget initial qui doit correspondre un montant du premier achat, soit  0.002332 BTC !

En d’autre termes, j’ai récupéré 42 fois ma mise de départ ! J’ai investit 17$ pour récupérer 728$ (au taux de change à l’écriture de cet article) sur une période 1072 jours (une rangée par jour dans le dataframe), soit presque trois ans…

Perspectives supplémentaires

Evidement, ce calcul ne prend pas en compte les frais d’achat et de revente, les crash boursiers, les exchanges qui se font hacker et voler leurs bitcoins…

La manière la plus sûre d’engranger des profits en investissant dans les cryptomonnaies et de viser le terme le plus court afin d’éviter les désagréments cités: prendre un (petit) profit et se retirer prudemment, puis recommencer…

Nous avons effectué cette simulation sur le marché ‘1d’ de la journée. Qu’en est-il du marché ‘1m’, celui de la minute ? Recommençons notre simulation en adaptant notre script:

dataFile = exchange.fetch_ohlcv(market, timeframe = '1m')
           timestamp     close       e26       e12          MACD position    budget
0      1531315260000  0.069364  0.069364  0.069364  0.000000e+00     hold  0.000000
1      1531315320000  0.069365  0.069365  0.069365  2.266026e-08     hold  0.000000
2      1531315380000  0.069368  0.069366  0.069366  1.222136e-07     hold  0.000000
3      1531315440000  0.069556  0.069419  0.069426  6.955776e-06     hold  0.000000
(...)
13967  1532178900000  0.062614  0.062550  0.062529 -2.181685e-05     hold -0.029076
13968  1532179020000  0.062436  0.062542  0.062514 -2.758565e-05     hold -0.029076
13969  1532179080000  0.062436  0.062534  0.062502 -3.179100e-05     hold -0.029076

Avec une mise initiale de 0.069383 BTC (509$) et en ignorant le dernier achat, nous obtenons donc après 10 jours un profit de 0.0422 BTC (309$).

Cependant, il s’agit ici d’un profit purement théorique, car si nous effectuons la simulation en tenant compte des frais d’achat et de vente retenus par Bittrex (0.25%), nous n’arrivons jamais à trouver un prix de vente qui couvre le prix d’achat et les frais qui vont avec ! En effet, sur le marché de la minute, les fluctuations sont minimales, et la probabilité de rencontrer un prix de vente qui couvre les frais est faible…

Conclusion

Oui, il est possible de gagner pas mal d’argent en suivant une stratégie appropriée et en suivant un principe simple: ‘buy low, sell high fast’. Les investissements à terme qui sont effectuées par la plupart des traders ne tient que peu compte de la réalité des marchés de cryptomonnaies, à savoir, volatilité, insécurité, manque de contrôle et j’en passe. Il convient donc de choisir un ou plusieurs indicateurs et, par simulation interposée, vérifier son application sur le marché choisit…

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