ProPython
Backtrader - Introduction
11 Apr, 2021

Backtrader - Introduction

Backtrader est un framework open-source en Python qui va intéresser ceux qui aiment le trading algorithmique, ou ceux qui souhaiteraient trouver un moyen de tester leurs stratégies à l'aide d'un outil informatique.

Pourquoi devrais-je utiliser Backtrader ?

Utiliser Backtrader peut vous permettre d'économiser un temps fou si vous souhaitez vous intéresser au trading algorithmique, ou tester des stratégies. En effet, Backtrader vous évite de tout tester à la main, et vous permet de réutiliser vos algorithmes sur différentes données de façon automatisée.

De plus, ce framework est très bien documenté, et une grosse communauté l'utilise, donc en cas de souci lors du développement il y aura toujours des gens pour vous aider à résoudre votre problème (moi y compris ;)).

Voici les fonctionnalités principales intégrées dans Backtrader :

Backtesting : évidemment, ce framework porte bien son nom. Le process est cependant simplifié avec Backtrader (et je sais de quoi je parler, j'ai déjà essayé de créer mon propre backtester et c'est une galère sans nom).

Optimizing : c'est à dire optimiser vos stratégies afin de trouver les paramètres les plus adaptés, et passer éventuellement d'une stratégie non profitable à une stratégie profitable.

Affichage graphique : vous connaissez peut-être Matplotlib ? Si c'est le cas, vous savez à quel point il peut-être difficile d'afficher un graphique correct. Backtrader vous permet d'afficher des graphiques complexes en une ligne de code.

Développement d'indicateurs : vous souhaitez développer vos propres indicateurs de trading ? Backtrader vous permet de réaliser ceci simplement, en plus la plupart des indicateurs que vous connaissez sont déjà intégrés dans Backtrader donc vous pouvez simplement les utiliser.

Développement de stratégies : des plus simples aux plus compliquées, Backtrader vous permet de développer des stratégies de toute complexité, avec plusieurs données en entrées, ou plusieurs intervalles de temps, etc...

Open Source : vous avez pleinement accès au code et vous pouvez le modifier si vous le souhaitez. Et tous les autres avantages d'utiliser des solutions open-source.

Trading en live : vous pouvez connecter Backtrader à certains brokers afin de trader en live avec les stratégies que vous aurez développé et testé au préalable.

En développement continu : Backtrader est régulièrement amélioré et mis à jour.

Comment fonctionne Backtrader ?

Backtrader vous permet de savoir comment performe une stratégie que vous aurez développé en utilisant des données passées (c'est le principe du backtesting en gros..).

Pour cela, une fonctionnalité de base du framework est d'itérer à travers des données historiques pour simuler l'exécution de trades, dont les signaux sont donnés par la stratégie que vous aurez développé.

Backtrader vous offre également la possibilité d'obtenir des analyses détaillées de la performance de votre stratégie en utilisant des objets appelés des "Analyzers". Par exemple, vous pouvez utiliser un analyzer pour obtenir le ratio de Calmar de votre stratégie sur une période donnée.

Afin d'optimiser la vitesse de calcul, Backtrader intègre la possibilité d'utiliser plusieurs coeurs de votre processeur.

Finalement, ce framework utilise matplotlib afin de réaliser de beaux graphiques, et vous n'avez rien à configurer, la librairie se charge de tout.

Installer Backtrader

Pour installer backtrader, le plus simple est d'utiliser pip comme pour n'importe quelle autre librairie.

pip install backtrader

Si vous souhaitez utiliser l'affichage graphique, vous devez également installer matplotlib.

pip install matplotlib

Choix de l'IDE à utiliser

Certaines fonctionnalités de Backtrader peuvent ne pas être disponibles en fonction de l'IDE que vous utilisez.

Personnellement, j'utilise PyCharm et tout fonctionne à merveille. Avant, j'étais sous Spyder et l'affichage graphique ne fonctionnait pas vraiment. J'ai aussi testé la librairie sous Jupyter et tout fonctionne sauf l'utilisation de plusieurs processeurs, ou l'affichage graphique.

A vous de voir quel IDE vous souhaitez utiliser en fonction de l'utilisation que vous comptez faire de la librairie.

Débuter avec Backtrader

Bien, normalement tout est en place, nous allons pouvoir entrer dans le vif du sujet. Nous allons donc voir comment créer une stratégie de base, et la tester sur des données passées.

Extraction de données

La première étape est d'obtenir nos données. Pour cela, Backtrader nous fournit un objet appelé un Datafeed. C'est en fait un flux de données qui va servir à nos stratégies pour itérer dessus et prendre des décisions en conséquence. Il y a plusieurs types de Datafeed utilisables par Backtrader correspondant à des flux de données de différentes sources.

Pour charger un Datafeed en données, on a donc plusieurs possibilités. La première façon est d'utiliser un fichier CSV stocké en local contenant les informations dont nous avons besoin. La deuxième est d'utiliser une API afin d'extraire des données en ligne. Nous allons voir ces deux méthodes.

Via un fichier CSV

Si vous souhaitez un fichier d'exemple pour vos tests, cliquez ici afin de télécharger le dataset CSV que je vous mets à disposition. C'est simplement un fichier contenant dans la première colonne des informations sur le temps (en format unix timestamp), dans la seconde des prix d'ouverture, dans la troisième les prix les plus élevés, dans la quatrième les prix les plus bas et dans la dernière le volume échangé.

L'objet à initialiser pour obtenir un Datafeed à partir d'un fichier CSV est un objet de type GenericCSVData, contenu dans backtrader.feeds. On l'initialise en passant en argument "dataname" le chemin vers notre fichier (absolu ou relatif). Dans le cas d'un fichier placé à la racine de notre projet, l'initialisation se fera donc comme ceci :

import backtrader.feeds as btfeeds

datafeed = btfeeds.GenericCSVData(dataname="dataset_ohlcv.csv")

Si vous essayez ce code avec le fichier donné en exemple, vous obtiendrez une erreur. En effet, les dates sont au format unix dans l'exemple, ce qui n'est pas traité naturellement par Backtrader. Avant de voir comment régler ce problème, voyons les paramètres optionels utilisables lors de l'initialisation de notre GenericCSVData.

  • fromdate : datetime indiquant la date de début d'extraction données (si vous ne savez pas ce qu'est une datetime, l'article sur cette librairie arrive prochainement).
  • todate : datetime indiquant la date de fin d'extraction.
  • headers : booléen indiquant si le fichier CSV contient une rangée de titres.
  • separator : caractère séparant les colonnes dans le fichier CSV.
  • datetime : colonne contenant l'information de temps (défaut : 0).
  • open : colonne contenant l'information sur le prix d'ouverture (défaut 1).
  • high : ......... (défaut 2).
  • low : ........ (défaut 3).
  • close : .......... (défaut 4).
  • volume : ............ (défaut 5).
  • openinterest : ......... (défaut 6). Si n'est pas présente, on assigne la valeur -1.
  • dtformat : format du champ datetime (défaut : %Y-%m-%d %H:%M:%S).

Revenons à notre problème. On aimerait formater notre champ datetime de sorte que ce soit un format interprétable par Backtrader. Pour cela, je vous propose de charger notre fichier CSV avec Pandas, de modifier ce champ, et de charger notre fichier dans un Datafeed ensuite (si vous ne savez pas ce qu'est Pandas, retrouvez mon article sur cette librairie ici : Pandas - La référence pour la manipulation de données). Voici comment formater notre fichier.

import pandas as pd

data = pd.read_csv("dataset_ohlcv.csv", names="Date Open High Low Close Volume".split(" "))
data["Date"] = pd.to_datetime(data["Date"], unit="ms")
data.to_csv("dataset_ohlcv_formatted.csv", index=False)

On peut maintenant le charger dans un Datafeed.

import backtrader.feeds as btfeeds
datafeed = btfeeds.GenericCSVData(dataname="dataset_ohlcv_formatted.csv", openinterest=-1)
Remarquez qu'on initialise openinterest à -1. En effet, notre fichier CSV ne contient pas cette colonne, donc on met la valeur -1.

Pour l'instant, aucun moyen de vérifier que nos données se sont bien chargées, nous verrons ça après.

Via une API en ligne

On peut également utiliser une API afin d'extraire des données en ligne, qu'on peut soit traiter puis enregistrer sous format CSV, soit convertir en PandasData qui est un Datafeed utilisé pour les DataFrame Pandas.

On va utiliser par exemple Yahoo Finance. Cette API dispose d'une intégration en Python, on va donc l'installer :

pip install yfinance

Ensuite, on peut utiliser yfinance pour charger un Ticker, qui correspond à un élément boursier dont on peut obtenir des informations, par exemple la paire BTC-EUR est un ticker, l'action Microsoft MSFT est un Ticker, etc...

import yfinance as yf

btc_eur = yf.Ticker("BTC-EUR")
msft = yf.Ticker("MSFT")

Une fois qu'on a un Ticker, on peut obtenir ses informations OHLCV passées en utilisant la méthode history. Cela nous les stocke dans un Dataframe Pandas dans notre cas, mais cela peut varier en fonction des API, à vous de vous adapter !

data = btc_eur.history()
print(data.head(3))

"""
                    Open          High  ...  Dividends  Stock Splits
Date                                    ...                         
2021-12-21  41575.058594  43688.652344  ...          0             0
2021-12-22  43355.234375  43932.410156  ...          0             0
2021-12-23  42931.710938  45285.339844  ...          0             0
"""

On peut maintenant stocker ceci dans notre Datafeed. On utilise donc l'objet PandasData. Les arguments sont similaires à ceux vus pour le Datafeed au format CSV. Plus d'infos sur la doc : PandasData.

pandas_data = btfeeds.PandasData(dataname=data)

Développer une stratégie

Bien, maintenant que nous disposons de notre Datafeed, il faut pouvoir l'exploiter. Pour cela, il faut développer une stratégie. Mais avant cela, il faut comprendre un concept de base de Backtrader.

Notre Datafeed est un ensemble de données sur lesquelles on va itérer. Pour chaque itération, notre stratégie va étudier les données reçues et voir s'il faut ouvrir, fermer une position, ou ne rien faire. Voyez notre Datafeed comme une liste de données, qu'on munit d'un curseur qui se déplace à chaque itération. A la première itération, le curseur est en position 0, ensuite en position 1, etc... jusqu'à arriver à la fin des données. Pour accéder à des données, vous y accédez relativement à ce curseur. Par exemple, si mes données sont stockées dans data, que mon curseur est en position 287 (donc je suis à la 287ème itération), et que je souhaite accéder aux données en position 285, j'utilise data[-2] (j'accède aux données 2 périodes avant le curseur). Une itération plus tard, mon curseur sera en position 288, donc pour accéder aux données en position 285 je devrai écrire data[-3]. Pour accéder aux données futures, j'utilise data[1] (1 période plus tard), data[2] (2 périodes plus tard), etc... Cependant essayez d'accéder aux données futures le moins souvent, non seulement cette approche n'est pas logique (sur les marchés on ne connait pas le futur), mais en plus elle génère souvent des bugs.

Si vous n'avez pas compris ce qui est au dessus, vous comprendrez peut-être mieux une fois que nous aurons codé une première stratégie. En effet, le concept ci-dessus est contre intuitif avec le Python, par exemple lorsque l'on accède à l'index -1 d'une liste, on accède au dernier élément, et non celui d'avant. C'est pour cela que les Datafeed n'ont finalement rien à voir avec les listes, ce sont des objets complètement différents.

Notre première stratégie

La logique de Backtrader consiste à nous faire développer des stratégies sous forme de classes, de sorte qu'elles soient réutilisables. Backtrader nous fournit donc une classe de base pour réaliser nos stratégies : la classe Strategy. Nous pouvons donc commencer à créer une stratégie en la faisant hériter de cette classe :

import backtrader as bt

class MyStrategy(bt.Strategy):

Mais que met-on dedans ? Pour l'instant, nous n'allons pas modifier le constructeur de base. En revanche, nous allons modifier la méthode next. Cette méthode s'exécute à chaque itération, c'est la méthode au coeur d'une stratégie permettant de gérer la prise de décision. Pour commencer, nous allons simplement créer une stratégie qui nous indique le prix de fermeture à chaque itération.

Pour accéder à nos Datafeed (je dis "nos" car il peut y en avoir plusieurs en même temps), on utilise self.datas[index] dans notre stratégie (index étant l'index du Datafeed auquel on souhaite accéder) Pour connaitre l'index d'un Datafeed, il suffit de connaitre son ordre d'ajout (nous verrons cela après, si on n'utilise qu'un seul Datafeed ce sera donc l'index 0). Ensuite, on peut accéder aux attributs de ce Datafeed. Ceux qui nous intéressent sont :

  • datetime
  • open
  • high
  • low
  • close
  • volume

datetime est la date, open le prix d'ouverture, high le plus haut prix atteint pendant la période, etc... ce sont les informations OHLCV classiques.

Ensuite, pour accéder aux valeurs de ces attributs, on utilise le concept de toute à l'heure. Ainsi, pour accéder au prix de fermeture actuel, j'utilise self.datas[0].close[0]. Pour accéder au prix de fermeture précédent, j'utilise self.datas[0].close[-1]. Etc...

Maintenant nous avons tout ce qu'il nous faut pour finir notre stratégie :

class MyFirstStrat(bt.Strategy):

    def next(self):
        print(self.datas[0].close[0])

Cette stratégie affiche à chaque itération le prix de fermeture actuel.

Testons notre stratégie

Maintenant que nous avons nos données et notre stratégie, on peut mélanger tout ça et faire notre premier programme. Pour cela, on a besoin d'un objet appelé le Cerebro. C'est en fait le cerveau de notre programme, c'est lui qui nous permet de mettre tous nos éléments en commun et de prendre les décisions pour exécuter les ordres.

On instantie un Cerebro de cette façon :

import backtrader as bt

cerebro = bt.Cerebro()

Ensuite, on lui ajoute un Datafeed, par exemple en reprenant l'exemple de toute à l'heure :

import yfinance as yf

btc_eur = yf.Ticker("BTC-EUR")
data = btc_eur.history()
pandas_data = btfeeds.PandasData(dataname=data)

cerebro.adddata(pandas_data)

Puis on lui ajoute la stratégie qu'on vient de développer de cette façon :

cerebro.addstrategy(MyFirstStrat)
Remarquez bien qu'on ne lui ajoute pas notre classe instantiée, mais simplement notre classe. Mettre MyFirstStrat() à la place de MyFirstStrat vous générera une erreur.

Finalement, on n'a plus qu'à lancer le moteur :

cerebro.run()
#
43354.8046875
42933.625
44849.703125
44886.01171875
...

On obtient les prix de fermeture à chaque itération, donc les prix de chaque ligne de notre Datafeed.

Le mot de la fin

C'est déjà la fin de cet article, qui consistait en une introduction à Backtrader, un magnifique framework pour les amateurs de trading algorithmique et de backtesting.

Nous irons plus loin dans un prochain article, il s'agissait ici d'acquérir les bases et de bien comprendre les quelques concepts importants de Backtrader.

S'il y a des choses que vous n'avez pas compris ou que vous avez besoin de précisions, n'hésitez pas à laisser un commentaire ou à me contacter, par mail ou via le site, je me ferai un plaisir de vous répondre :)

A la prochaine pour la suite de votre apprentissage du Python !

Laisser un commentaire

Premium - 15€/mois

L'accès à des articles inédits, à une multitude de ressources, à de nouveaux projets, mais également à des vidéos explicatives, découvrez ici pourquoi passer premium.

Articles liés

Catégories

Ressources

Retrouvez une collection de ressources (des scripts, des fiches résumé, des images...) liées aux articles du blog ou au Python.
Voir

Contact

contact@propython.fr
Se connecter pour envoyer un message directement depuis le site.

Navigation

AccueilSe connecterCréer un compteRessourcesPremium

Catégories

Pages légales

Politique de confidentialitéMentions légalesConditions générales de vente