L'essor du trading automatisé : les machines négociant le S&P 500
Publié: 2022-03-11De nos jours, plus de 60% des activités de trading avec différents actifs (tels que les actions, les contrats à terme sur indices, les matières premières) ne sont plus réalisées par des commerçants "humains", mais s'appuient plutôt sur le trading automatisé. Il existe des programmes spécialisés basés sur des algorithmes particuliers qui achètent et vendent automatiquement des actifs sur différents marchés, destinés à obtenir un rendement positif à long terme.
Dans cet article, je vais vous montrer comment prédire, avec une bonne précision, comment le prochain trade doit être placé pour obtenir un gain positif. Pour cet exemple, comme actif sous-jacent à négocier, j'ai sélectionné l'indice S&P 500, la moyenne pondérée de 500 sociétés américaines à plus grande capitalisation. Une stratégie très simple à mettre en œuvre consiste à acheter l'indice S&P 500 lorsque Wall Street Exchange commence à se négocier, à 9h30, et à le vendre à la séance de clôture à 16h00, heure de l'Est. Si le cours de clôture de l'indice est supérieur au cours d'ouverture, il y a un gain positif, alors qu'un gain négatif serait réalisé si le cours de clôture est inférieur au cours d'ouverture. La question est donc : comment savoir si la séance de trading se terminera avec un cours de clôture supérieur au cours d'ouverture ? L'apprentissage automatique est un outil puissant pour accomplir une tâche aussi complexe, et il peut être un outil utile pour nous aider à prendre une décision commerciale.
L'apprentissage automatique est la nouvelle frontière de nombreuses applications utiles de la vie réelle. Le trading financier en fait partie, et il est très souvent utilisé dans ce secteur. Un concept important concernant l'apprentissage automatique est que nous n'avons pas besoin d'écrire de code pour tous les types de règles possibles, telles que la reconnaissance de formes. En effet, chaque modèle associé à l'apprentissage automatique apprend à partir des données elles-mêmes, puis peut être utilisé ultérieurement pour prédire de nouvelles données invisibles.
Clause de non- responsabilité : le but de cet article est de montrer comment entraîner les méthodes d'apprentissage automatique, et dans les exemples de code fournis, toutes les fonctions ne sont pas expliquées. Cet article n'est pas destiné à laisser un copier-coller tout le code et exécuter les mêmes tests fournis, car il manque certains détails qui n'entraient pas dans le cadre de l'article. De plus, une connaissance de base de Python est requise. L'intention principale de l'article est de montrer un exemple de la façon dont l'apprentissage automatique peut être efficace pour prédire les achats et les ventes dans le secteur financier. Cependant, trader avec de l'argent réel signifie avoir de nombreuses autres compétences, telles que la gestion de l'argent et la gestion des risques. Cet article n'est qu'une petite partie de la "grande image".
Construire votre premier programme de trading automatisé de données financières
Alors, vous voulez créer votre premier programme pour analyser les données financières et prédire le bon trade ? Laisse moi te montrer comment. J'utiliserai le code Python pour l'apprentissage automatique et nous utiliserons les données historiques du service Yahoo Finance. Comme mentionné précédemment, les données historiques sont nécessaires pour former le modèle avant de faire nos prédictions.
Pour commencer, nous devons installer :
- Python, et en particulier je suggère d'utiliser le bloc-notes IPython.
- Package Yahoo Finance Python (le nom exact est
yahoo-finance
) via la commande du terminal :pip install yahoo-finance
. - Une version d'essai gratuite du package Machine Learning appelée GraphLab. N'hésitez pas à consulter la documentation utile de cette bibliothèque.
Notez que seule une partie de GraphLab est open source, le SFrame, donc pour utiliser toute la bibliothèque nous avons besoin d'une licence. Il existe une licence gratuite de 30 jours et une licence non commerciale pour les étudiants ou ceux qui participent aux compétitions Kaggle. De mon point de vue, GraphLab Create est une bibliothèque très intuitive et facile à utiliser pour analyser des données et entraîner des modèles de Machine Learning.
Creuser dans le code Python
Examinons un peu de code Python pour voir comment télécharger des données financières à partir d'Internet. Je suggère d'utiliser IPython notebook pour tester le code suivant, car IPython présente de nombreux avantages par rapport à un IDE traditionnel, en particulier lorsque nous devons combiner le code source, le code d'exécution, les données de table et les graphiques sur le même document. Pour une brève explication sur l'utilisation du bloc-notes IPython, veuillez consulter l'article Introduction au bloc-notes IPython.
Alors, créons un nouveau bloc-notes IPython et écrivons du code pour télécharger les prix historiques de l'indice S&P 500. Notez que si vous préférez utiliser d'autres outils, vous pouvez commencer avec un nouveau projet Python dans votre IDE préféré.
import graphlab as gl from __future__ import division from datetime import datetime from yahoo_finance import Share # download historical prices of S&P 500 index today = datetime.strftime(datetime.today(), "%Y-%m-%d") stock = Share('^GSPC') # ^GSPC is the Yahoo finance symbol to refer S&P 500 index # we gather historical quotes from 2001-01-01 up to today hist_quotes = stock.get_historical('2001-01-01', today) # here is how a row looks like hist_quotes[0] {'Adj_Close': '2091.580078', 'Close': '2091.580078', 'Date': '2016-04-22', 'High': '2094.320068', 'Low': '2081.199951', 'Open': '2091.48999', 'Symbol': '%5eGSPC', 'Volume': '3790580000'}
Ici, hist_quotes
est une liste de dictionnaires, et chaque objet du dictionnaire est un jour de trading avec les valeurs Open
, High
, Low
, Close
, Adj_close
, Volume
, Symbol
et Date
. Au cours de chaque jour de bourse, le prix change généralement à partir du cours d'ouverture Open
jusqu'au cours de clôture Close
, et atteint une valeur maximale et minimale High
et Low
. Nous devons le lire et créer des listes de chacune des données les plus pertinentes. De plus, les données doivent d'abord être triées par les valeurs les plus récentes, nous devons donc l'inverser :
l_date = [] l_open = [] l_high = [] l_low = [] l_close = [] l_volume = [] # reverse the list hist_quotes.reverse() for quotes in hist_quotes: l_date.append(quotes['Date']) l_open.append(float(quotes['Open'])) l_high.append(float(quotes['High'])) l_low.append(float(quotes['Low'])) l_close.append(float(quotes['Close'])) l_volume.append(int(quotes['Volume']))
Nous pouvons regrouper toutes les citations téléchargées dans un objet SFrame
, qui est une trame de données basée sur des colonnes hautement évolutive, et elle est compressée. L'un des avantages est qu'il peut également être plus grand que la quantité de RAM car il est sauvegardé sur disque. Vous pouvez consulter la documentation pour en savoir plus sur SFrame.
Alors, stockons puis vérifions les données historiques :
qq = gl.SFrame({'datetime' : l_date, 'open' : l_open, 'high' : l_high, 'low' : l_low, 'close' : l_close, 'volume' : l_volume}) # datetime is a string, so convert into datetime object qq['datetime'] = qq['datetime'].apply(lambda x:datetime.strptime(x, '%Y-%m-%d')) # just to check if data is sorted in ascending mode qq.head(3)
Fermer | date-heure | haut | meugler | ouvrir | le volume |
1283.27 | 2001-01-02 00:00:00 | 1320.28 | 1276.05 | 1320.28 | 1129400000 |
1347.56 | 2001-01-03 00:00:00 | 1347.76 | 1274.62 | 1283.27 | 1880700000 |
1333.34 | 2001-01-04 00:00:00 | 1350.24 | 1329.14 | 1347.56 | 2131000000 |
Nous pouvons maintenant enregistrer les données sur le disque avec la méthode SFrame
save
, comme suit :
qq.save(“SP500_daily.bin”) # once data is saved, we can use the following instruction to retrieve it qq = gl.SFrame(“SP500_daily.bin/”)
Voyons à quoi ressemble le S&P 500
Pour voir à quoi ressembleront les données S&P 500 chargées, nous pouvons utiliser le code suivant :
import matplotlib.pyplot as plt %matplotlib inline # only for those who are using IPython notebook plt.plot(qq['close'])
La sortie du code est le graphique suivant :
Formation de certains modèles d'apprentissage automatique
Ajouter un résultat
Comme je l'ai indiqué dans la partie introductive de cet article, l'objectif de chaque modèle est de prédire si le cours de clôture sera supérieur au cours d'ouverture. Par conséquent, dans ce cas, nous pouvons obtenir un rendement positif lors de l'achat de l'actif sous-jacent. Nous devons donc ajouter une colonne de outcome
sur nos données qui sera la target
ou la variable predicted
. Chaque ligne de cette nouvelle colonne sera :
-
+1
pour une journée Up avec un cours deClosing
supérieur au cours d'Opening
. -
-1
pour un jour bas avec un cours deClosing
inférieur au cours d'Opening
.
# add the outcome variable, 1 if the trading session was positive (close>open), 0 otherwise qq['outcome'] = qq.apply(lambda x: 1 if x['close'] > x['open'] else -1) # we also need to add three new columns 'ho' 'lo' and 'gain' # they will be useful to backtest the model, later qq['ho'] = qq['high'] - qq['open'] # distance between Highest and Opening price qq['lo'] = qq['low'] - qq['open'] # distance between Lowest and Opening price qq['gain'] = qq['close'] - qq['open']
Étant donné que nous devons évaluer quelques jours avant le dernier jour de négociation, nous devons décaler les données d'un ou plusieurs jours. Pour ce type d'opération retardée, nous avons besoin d'un autre objet du package GraphLab appelé TimeSeries
. TimeSeries
a un shift
de méthode qui décale les données d'un certain nombre de lignes.
ts = gl.TimeSeries(qq, index='datetime') # add the outcome variable, 1 if the bar was positive (close>open), 0 otherwise ts['outcome'] = ts.apply(lambda x: 1 if x['close'] > x['open'] else -1) # GENERATE SOME LAGGED TIMESERIES ts_1 = ts.shift(1) # by 1 day ts_2 = ts.shift(2) # by 2 days # ...etc.... # it's an arbitrary decision how many days of lag are needed to create a good forecaster, so # everyone can experiment by his own decision
Ajout de prédicteurs
Les prédicteurs sont un ensemble de variables de caractéristiques qui doivent être choisies pour former le modèle et prédire notre résultat . Ainsi, le choix des facteurs de prévision est un élément crucial, sinon le plus important, du prévisionniste.
Pour ne citer que quelques exemples, un facteur à considérer peut être si la clôture d'aujourd'hui est supérieure à la clôture d'hier, et cela peut être prolongé avec la clôture de deux jours précédents, etc. Un choix similaire peut être traduit avec le code suivant :
ts['feat1'] = ts['close'] > ts_1['close'] ts['feat2'] = ts['close'] > ts_2['close']
Comme indiqué ci-dessus, j'ai ajouté deux nouvelles colonnes de fonctionnalités, feat1
et feat2
sur notre ensemble de données ( ts
) contenant 1
si la comparaison est vraie et 0
sinon.
Cet article a pour but de donner un exemple de Machine Learning appliqué au secteur financier. Je préfère me concentrer sur la façon dont les modèles d'apprentissage automatique peuvent être utilisés avec des données financières, et nous n'entrerons pas dans les détails sur la façon de choisir les bons facteurs pour former les modèles. Il est trop exhaustif d'expliquer pourquoi certains facteurs sont utilisés par rapport à d'autres, en raison d'une augmentation considérable de la complexité. Ma recherche professionnelle consiste à étudier de nombreuses hypothèses de choix de facteurs pour créer un bon prédicteur. Donc, pour commencer, je vous suggère d'expérimenter avec de nombreuses combinaisons de facteurs différentes, pour voir si elles peuvent augmenter la précision du modèle.
# add_features is a helper function, which is out of the scope of this article, # and it returns a tuple with: # ts: a timeseries object with, in addition to the already included columns, also lagged columns # as well as some features added to train the model, as shown above with feat1 and feat2 examples # l_features: a list with all features used to train Classifier models # l_lr_features: a list all features used to train Linear Regression models ts, l_features, l_lr_features = add_features(ts) # add the gain column, for trading operations with LONG only positions. # The gain is the difference between Closing price - Opening price ts['gain'] = ts['close'] - ts['open'] ratio = 0.8 # 80% of training set and 20% of testing set training = ts.to_sframe()[0:round(len(ts)*ratio)] testing = ts.to_sframe()[round(len(ts)*ratio):]
Formation d'un modèle d'arbre de décision
GraphLab Create a une interface très propre pour implémenter des modèles d'apprentissage automatique. Chaque modèle a une méthode de create
utilisée pour ajuster le modèle avec un ensemble de données d'apprentissage. Les paramètres typiques sont :
-
training
- il s'agit d'un ensemble de formation contenant des colonnes de fonctionnalités et une colonne cible. -
target
- c'est le nom de la colonne contenant la variable cible. -
validation_set
- il s'agit d'un ensemble de données permettant de surveiller les performances de généralisation du modèle. Dans notre cas, nous n'avons pas devalidation_set
. -
features
- il s'agit d'une liste de noms de colonnes de fonctionnalités utilisées pour la formation du modèle. -
verbose
- sitrue
, affiche les informations de progression pendant la formation.
Alors que d'autres paramètres sont typiques du modèle lui-même, tels que :
-
max_depth
- c'est la profondeur maximale d'un arbre.
Avec le code suivant, nous construisons notre arbre de décision :
max_tree_depth = 6 decision_tree = gl.decision_tree_classifier.create(training, validation_set=None, target='outcome', features=l_features, max_depth=max_tree_depth, verbose=False)
Mesure des performances du modèle ajusté
La précision est une mesure importante pour évaluer la qualité du prévisionniste. C'est le nombre de prédictions correctes divisé par le nombre total de points de données. Comme le modèle est équipé de données d'apprentissage, la précision évaluée avec l'ensemble d'apprentissage est meilleure que celle obtenue avec un ensemble de test.
La précision est la fraction des prédictions positives qui sont positives. Nous avons besoin de précision pour être un nombre plus proche de 1
, afin d'obtenir un taux de victoire "parfait". Notre decision_tree
, en tant qu'autre classificateur du package GraphLab Create, a sa méthode evaluate
pour obtenir de nombreuses métriques importantes du modèle ajusté.
Le rappel quantifie la capacité d'un classificateur à prédire des exemples positifs. Le rappel peut être interprété comme la probabilité qu'un exemple positif sélectionné au hasard soit correctement identifié par le classifieur. Nous avons besoin que la précision soit un nombre plus proche de 1
, pour obtenir un taux de victoire "parfait".
Le code suivant montrera la précision du modèle ajusté à la fois avec l'ensemble d'apprentissage et l'ensemble de test :
decision_tree.evaluate(training)['accuracy'], decision_tree.evaluate(testing)['accuracy'] (0.6077348066298343, 0.577373211963589)
Comme indiqué ci-dessus, la précision du modèle avec l'ensemble de test est d'environ 57 %, ce qui est en quelque sorte meilleur que de lancer une pièce de monnaie (50 %).
Prédire les données
GraphLab Create a la même interface pour prédire les données de différents modèles ajustés. Nous utiliserons la méthode de predict
, qui nécessite un ensemble de tests pour prédire la variable cible, dans notre cas outcome
. Maintenant, nous pouvons prédire les données de l'ensemble de test :
predictions = decision_tree.predict(testing) # and we add the predictions column in testing set testing['predictions'] = predictions # let's see the first 10 predictions, compared to real values (outcome column) testing[['datetime', 'outcome', 'predictions']].head(10)
date-heure | résultat | prédictions |
2013-04-05 00:00:00 | -1 | -1 |
2013-04-08 00:00:00 | 1 | 1 |
2013-04-09 00:00:00 | 1 | 1 |
2013-04-10 00:00:00 | 1 | -1 |
2013-04-11 00:00:00 | 1 | -1 |
2013-04-12 00:00:00 | -1 | -1 |
2013-04-15 00:00:00 | -1 | 1 |
2013-04-16 00:00:00 | 1 | 1 |
2013-04-17 00:00:00 | -1 | -1 |
2013-04-18 00:00:00 | -1 | 1 |
Les faux positifs sont des cas où le modèle prédit un résultat positif alors que le résultat réel de l'ensemble de tests est négatif. Vice versa, les faux négatifs sont des cas où le modèle prédit un résultat négatif alors que le résultat réel de l'ensemble de tests est positif.
Notre stratégie de trading attend un résultat prédit positivement pour acheter le S&P 500 au prix Opening
et le vendre au prix de Closing
, donc notre espoir est d'avoir le taux de faux positifs le plus bas pour éviter les pertes. En d'autres termes, nous nous attendons à ce que notre modèle ait le taux de précision le plus élevé.
Comme on peut le voir, il y a deux faux négatifs (au 2013-04-10 et 2013-04-11) et deux faux positifs (au 2013-04-15 et 2013-04-18) dans les dix premières valeurs prédites du ensemble de test.
Avec un calcul simple, en considérant ce petit ensemble de dix prédictions :
- précision = 6/10 = 0,6 ou 60 %
- précision =3/5 = 0,6 ou 60%
- rappel = 3/5 = 0,6 ou 60 %
Notez que les nombres ci-dessus sont généralement différents les uns des autres, mais dans ce cas, ils sont identiques.
Backtester le modèle
Nous simulons maintenant comment le modèle se négocierait en utilisant ses valeurs prédites. Si le résultat prédit est égal à +1
, cela signifie que nous nous attendons à un Up day . Avec un jour Up, nous achetons l'indice au début de la session et vendons l'indice à la fin de la session au cours de la même journée. Inversement, si le résultat prédit est égal à -1
, nous nous attendons à un jour bas , nous n'échangerons donc pas ce jour-là.
Le profit et la perte ( pnl
) pour un échange quotidien complet, également appelé tour de table , dans cet exemple est donné par :
-
pnl = Close - Open
(pour chaque jour de bourse)
Avec le code ci-dessous, j'appelle la fonction d'assistance plot_equity_chart
pour créer un graphique avec la courbe des gains cumulés (courbe d'équité). Sans aller trop loin, il obtient simplement une série de valeurs de profits et pertes et calcule la série de sommes cumulées à tracer.
pnl = testing[testing['predictions'] == 1]['gain'] # the gain column contains (Close - Open) values # I have written a simple helper function to plot the result of all the trades applied to the # testing set and represent the total return expressed by the index basis points # (not expressed in dollars $) plot_equity_chart(pnl,'Decision tree model')
Mean of PnL is 1.843504 Sharpe is 1.972835 Round turns 511
Ici, Sharpe est le ratio annuel de Sharpe, un indicateur important de la qualité du modèle de trading.
Considérant les métiers exprimés au jour le jour alors que la mean
est la moyenne de la liste des profits et pertes, et sd
est l'écart type. Pour simplifier la formule décrite ci-dessus, j'ai considéré un rendement sans risque égal à 0.
Quelques notions de base sur le trading
La négociation de l'indice nécessite l'achat d'un actif directement dérivé de l'indice. De nombreux courtiers répliquent l'indice S&P 500 avec un produit dérivé appelé CFD (Contract for difference), qui est un accord entre deux parties pour échanger la différence entre le cours d'ouverture et le cours de clôture d'un contrat.
Exemple : Achetez 1 CFD S&P 500 à Open
(la valeur est 2000), vendez-le à la Close
de la journée (la valeur est 2020). La différence, donc le gain, est de 20 points. Si chaque point a une valeur de 25 $ :

- Le gain brut est
20 points x $25 = $500
avec 1 contrat CFD.
Disons que le broker garde un slippage de 0,6 points pour ses propres revenus :
- Le gain net est de
(20 - 0.6) points x $25 = $485
.
Un autre aspect important à considérer est d'éviter des pertes importantes au sein d'un commerce. Ils peuvent se produire chaque fois que le résultat prédit est +1
mais que la valeur réelle du résultat est -1
, il s'agit donc d'un faux positif . Dans ce cas, la session de fin s'avère être un jour bas avec un cours de clôture inférieur à l'ouverture, et nous obtenons une perte.
Un ordre stop loss doit être placé pour se protéger contre une perte maximale que nous tolérerions dans une transaction, et un tel ordre est déclenché chaque fois que le prix de l'actif descend en dessous d'une valeur fixe que nous avons fixée auparavant.
Si nous regardons la série chronologique téléchargée depuis Yahoo Finance au début de cet article, chaque jour a un prix Low
qui est le prix le plus bas atteint au cours de cette journée. Si nous fixons un niveau stop de -3
points loin du cours d' Opening
, et Low - Open = -5
l'ordre stop sera déclenché, et la position ouverte sera fermée avec une perte de -3
points au lieu de -5
. C'est une méthode simple pour réduire le risque. Le code suivant représente ma fonction d'assistance pour simuler une transaction avec un niveau d'arrêt :
# This is a helper function to trade 1 bar (for example 1 day) with a Buy order at opening session # and a Sell order at closing session. To protect against adverse movements of the price, a STOP order # will limit the loss to the stop level (stop parameter must be a negative number) # each bar must contains the following attributes: # Open, High, Low, Close prices as well as gain = Close - Open and lo = Low - Open def trade_with_stop(bar, slippage = 0, stop=None): """ Given a bar, with a gain obtained by the closing price - opening price it applies a stop limit order to limit a negative loss If stop is equal to None, then it returns bar['gain'] """ bar['gain'] = bar['gain'] - slippage if stop<>None: real_stop = stop - slippage if bar['lo']<=stop: return real_stop # stop == None return bar['gain']
Coûts commerciaux
Les frais de transaction sont les dépenses engagées lors de l'achat ou de la vente de titres. Les coûts de transaction incluent les commissions et les spreads des courtiers (la différence entre le prix que le courtier a payé pour un titre et le prix que l'acheteur paie), et ils doivent être pris en compte si nous voulons tester notre stratégie, comme dans un scénario réel. Le glissement dans le trading des actions se produit souvent lorsqu'il y a un changement de spread. Dans cet exemple et pour les prochaines simulations en cours, les frais de transaction sont fixés comme suit :
- Glissement = 0,6 point
- Commission = 1 $ pour chaque transaction (un tour coûtera 2 $)
Juste pour écrire quelques chiffres, si notre gain brut était de 10 points, 1 point = 25 $, donc 250 $, y compris les frais de négociation, notre gain net serait de (10 - 0.6)*$25 - 2 = $233
.
Le code suivant montre une simulation de la stratégie de trading précédente avec un stop loss de -3 points. La courbe bleue est la courbe des rendements cumulés. Les seuls coûts pris en compte sont le glissement (0,6 point), et le résultat est exprimé en points de base (la même unité de base des valeurs S&P 500 téléchargées depuis Yahoo Finance).
SLIPPAGE = 0.6 STOP = -3 trades = testing[testing['predictions'] == 1][('datetime', 'gain', 'ho', 'lo', 'open', 'close')] trades['pnl'] = trades.apply(lambda x: trade_with_stop(x, slippage=SLIPPAGE, stop=STOP)) plot_equity_chart(trades['pnl'],'Decision tree model') print("Slippage is %s, STOP level at %s" % (SLIPPAGE, STOP))
Mean of PnL is 2.162171 Sharpe is 3.502897 Round turns 511 Slippage is 0.6 STOP level at -3
Le code suivant est utilisé pour faire des prédictions d'une manière légèrement différente. Veuillez prêter attention à la méthode de predict
qui est appelée avec un paramètre supplémentaire output_type = “probability”
. Ce paramètre est utilisé pour renvoyer les probabilités des valeurs prédites au lieu de leur prédiction de classe ( +1
pour un résultat prédit positivement, -1
pour un résultat prédit négativement). Une probabilité supérieure ou égale à 0.5
est associée à une valeur prédite +1
et une valeur de probabilité inférieure à 0.5
est liée à une valeur prédite de -1
. Plus cette probabilité est élevée, plus nous avons de chance de prédire un vrai Up Day .
predictions_prob = decision_tree.predict(testing, output_type = 'probability') # predictions_prob will contain probabilities instead of the predicted class (-1 or +1)
Maintenant, nous testons le modèle avec une fonction d'assistance appelée backtest_ml_model
qui calcule la série de rendements cumulés, y compris le glissement et les commissions, et trace leurs valeurs. Par souci de brièveté, sans expliquer en détail la fonction backtest_ml_model
, le détail important à souligner est qu'au lieu de filtrer ces jours avec un outcome = 1
comme nous l'avons fait dans l'exemple précédent, nous filtrons maintenant ces predictions_prob
égales ou supérieures à un threshold = 0.5
, comme suit:
trades = testing[predictions_prob>=0.5][('datetime', 'gain', 'ho', 'lo', 'open', 'close')]
N'oubliez pas que le gain net de chaque jour de bourse est : Net gain = (Gross gain - SLIPPAGE) * MULT - 2 * COMMISSION
.
Une autre mesure importante utilisée pour évaluer la qualité d'une stratégie de trading est le Maximum Drawdown . En général, il mesure la plus grande chute du sommet au creux de la valeur d'un portefeuille investi. Dans notre cas, il s'agit de la baisse la plus importante du sommet au bas de la courbe des actions (nous n'avons qu'un seul actif dans notre portefeuille, le S&P 500). Donc, étant donné un SArray
de profits et pertes pnl
, nous calculons le drawdown comme suit :
drawdown = pnl - pnl.cumulative_max() max_drawdown = min(drawdown)
À l'intérieur de la fonction d'assistance, backtest_summary
est calculé :
- Prélèvement maximal (en dollars) comme indiqué ci-dessus.
- Précision, avec la méthode d'
Graphlab.evaluation
. - Précision, avec la méthode d'
Graphlab.evaluation
. - Rappel, avec la méthode
Graphlab.evaluation
.
En mettant tout cela ensemble, l'exemple suivant montre la courbe d'équité représentant les rendements cumulés de la stratégie modèle, avec toutes les valeurs exprimées en dollars.
model = decision_tree predictions_prob = model.predict(testing, output_type="probability") THRESHOLD = 0.5 bt_1_1 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, MULT=25, SLIPPAGE=0.6, COMMISSION=1, plot_title='DecisionTree') backtest_summary(bt_1_1)
Mean of PnL is 54.054286 Sharpe is 3.502897 Round turns 511 Name: DecisionTree Accuracy: 0.577373211964 Precision: 0.587084148728 Recall: 0.724637681159 Max Drawdown: -1769.00025
Pour augmenter la précision des valeurs prévues, au lieu d'une probabilité standard de 0.5
(50 %), nous choisissons une valeur de seuil plus élevée, pour être plus sûr que le modèle prédit un Up day .
THRESHOLD = 0.55 # it's the minimum threshold to predict an Up day so hopefully a good day to trade bt_1_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, MULT=25, SLIPPAGE=0.6, COMMISSION=1, plot_title='DecisionTree') backtest_summary(bt_1_2)
Mean of PnL is 118.244689 Sharpe is 6.523478 Round turns 234 Name: DecisionTree Accuracy: 0.560468140442 Precision: 0.662393162393 Recall: 0.374396135266 Max Drawdown: -1769.00025
Comme on peut le voir sur le graphique ci-dessus, la courbe d'équité est bien meilleure qu'avant (Sharpe est de 6,5 au lieu de 3,5), même avec moins de tours de table.
À partir de ce moment, nous considérerons tous les modèles suivants avec un seuil supérieur à une valeur standard.
Formation d'un classificateur logistique
Nous pouvons appliquer nos recherches, comme nous l'avons fait précédemment avec l'arbre de décision, dans un modèle de classificateur logistique. GraphLab Create a la même interface avec l'objet Logistic Classifier, et nous appellerons la méthode create
pour construire notre modèle avec la même liste de paramètres. De plus, nous préférons prédire le vecteur de probabilité au lieu du vecteur de classe prédit (composé de +1
pour un résultat positif et de -1
pour un résultat négatif), nous aurions donc un seuil supérieur à 0.5
pour obtenir une meilleure précision dans notre prévision.
model = gl.logistic_classifier.create(training, target='outcome', features=l_features, validation_set=None, verbose=False) predictions_prob = model.predict(testing, 'probability') THRESHOLD = 0.6 bt_2_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name()) backtest_summary(bt_2_2)
Mean of PnL is 112.704215 Sharpe is 6.447859 Round turns 426 Name: LogisticClassifier Accuracy: 0.638491547464 Precision: 0.659624413146 Recall: 0.678743961353 Max Drawdown: -1769.00025
Dans ce cas, il y a un résumé très similaire à l'arbre de décision. Après tout, les deux modèles sont des classificateurs, ils ne prédisent qu'une classe de résultats binaires ( +1
, -1
).
Entraînement d'un modèle de régression linéaire
La principale différence de ce modèle est qu'il traite des valeurs continues au lieu de classes binaires, comme mentionné précédemment. Nous n'avons pas besoin d'entraîner le modèle avec une variable cible égale à +1
pour Up days et -1
pour Down days , notre cible doit être une variable continue. Puisque nous voulons prédire un gain positif, ou en d'autres termes un cours de clôture supérieur au cours d' ouverture , la cible doit maintenant être la colonne de gain de notre ensemble d'entraînement. De plus, la liste des fonctionnalités doit être composée de valeurs continues, telles que les précédentes Open
, Close
, etc.
Par souci de concision, je n'entrerai pas dans les détails de la sélection des bonnes fonctionnalités, car cela dépasse le cadre de cet article, qui est plus enclin à montrer comment nous devrions appliquer différents modèles de Machine Learning sur un ensemble de données. La liste des paramètres passés à la méthode create est :
-
training
- il s'agit d'un ensemble de formation contenant des colonnes de fonctionnalités et une colonne cible. -
target
- c'est le nom de la colonne contenant la variable cible. -
validation_set
- il s'agit d'un ensemble de données permettant de surveiller les performances de généralisation du modèle. Dans notre cas, nous n'avons pas devalidation_set
. -
features
- il s'agit d'une liste de noms de colonnes de fonctionnalités utilisées pour la formation du modèle, pour ce modèle, nous utiliserons un autre ensemble de respect pour les modèles Classifier. -
verbose
- sitrue
, il imprimera les informations de progression pendant la formation. -
max_iterations
- c'est le nombre maximum de passages autorisés à travers les données. Plus de passages sur les données peuvent entraîner un modèle formé plus précisément.
model = gl.linear_regression.create(training, target='gain', features = l_lr_features, validation_set=None, verbose=False, max_iterations=100) predictions = model.predict(testing) # a linear regression model, predict continuous values, so we need to make an estimation of their # probabilities of success and normalize all values in order to have a vector of probabilities predictions_max, predictions_min = max(predictions), min(predictions) predictions_prob = (predictions - predictions_min)/(predictions_max - predictions_min)
Jusqu'à présent, nous avons des prédictions qui sont SArray
de gains prédits, alors que predictions_prob
est SArray
avec des valeurs de predictions
normalisées. Pour avoir une bonne précision et un certain nombre de tours, comparable aux modèles précédents, j'ai choisi une valeur seuil de 0.4
. Pour un predictions_prob
inférieur à 0.4
, la fonction d'assistance backtest_linear_model
n'ouvrira pas de transaction car un jour Down est attendu. Sinon, une transaction sera ouverte.
THRESHOLD = 0.4 bt_3_2 = backtest_linear_model(testing, predictions_prob, target='gain', threshold=THRESHOLD, STOP = -3, plot_title=model.name()) backtest_summary(bt_3_2)
Mean of PnL is 138.868280 Sharpe is 7.650187 Round turns 319 Name: LinearRegression Accuracy: 0.631989596879 Precision: 0.705329153605 Recall: 0.54347826087 Max Drawdown: -1769.00025
Former un arbre boosté
Comme nous avions précédemment entraîné un arbre de décision, nous allons maintenant entraîner un classificateur d'arbre boosté avec les mêmes paramètres que ceux utilisés pour les autres modèles de classificateur. De plus, nous fixons le nombre de max_iterations = 12
afin d'augmenter le nombre maximum d'itérations pour le boosting. Chaque itération entraîne la création d'un arbre supplémentaire. Nous avons également défini une valeur de seuil supérieure à 0.5
pour augmenter la précision.
model = gl.boosted_trees_classifier.create(training, target='outcome', features=l_features, validation_set=None, max_iterations=12, verbose=False) predictions_prob = model.predict(testing, 'probability') THRESHOLD = 0.7 bt_4_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name()) backtest_summary(bt_4_2)
Mean of PnL is 112.002338 Sharpe is 6.341981 Round turns 214 Name: BoostedTreesClassifier Accuracy: 0.563068920676 Precision: 0.682242990654 Recall: 0.352657004831 Max Drawdown: -1769.00025
Formation d'une forêt aléatoire
Il s'agit de notre dernier modèle formé, un classificateur de forêt aléatoire, composé d'un ensemble d'arbres de décision. Le nombre maximum d'arbres à utiliser dans le modèle est fixé à num_trees = 10
, pour éviter trop de complexité et de surajustement.
model = gl.random_forest_classifier.create(training, target='outcome', features=l_features, validation_set=None, verbose=False, num_trees = 10) predictions_prob = model.predict(testing, 'probability') THRESHOLD = 0.6 bt_5_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name()) backtest_summary(bt_5_2)
Mean of PnL is 114.786962 sharpe is 6.384243 Round turns 311 Name: RandomForestClassifier Accuracy: 0.598179453836 Precision: 0.668810289389 Recall: 0.502415458937 Max Drawdown: -1769.00025
Collecting All the Models Together
Now we can join all the strategies together and see the overall result. It's interesting to see the summary of all Machine Learning models, sorted by their precision.
Nom | accuracy | precision | round turns | sharpe |
LinearRegression | 0.63 | 0.71 | 319 | 7.65 |
BoostedTreesClassifier | 0,56 | 0,68 | 214 | 6.34 |
RandomForestClassifier | 0,60 | 0,67 | 311 | 6.38 |
DecisionTree | 0,56 | 0,66 | 234 | 6.52 |
LogisticClassifier | 0,64 | 0,66 | 426 | 6.45 |
If we collect all the profit and loss for each one of the previous models in the array pnl
, the following chart depicts the equity curve obtained by the sum of each profit and loss, day by day.
Mean of PnL is 119.446463 Sharpe is 6.685744 Round turns 1504 First trading day 2013-04-09 Last trading day 2016-04-22 Total return 179647
Just to give some numbers, with about 3 years of trading, all models have a total gain of about 180,000 dollars. The maximum exposition is 5 CFD contracts in the market, but to reduce the risk they all are closed at the end of each day, so overnight positions are not allowed.
Statistics of the Aggregation of All Models Together
Since each model can open a trade, but we added 5 concurrent models together, during the same day there could be from 1 contract up to 5 CFD contracts. If all models agree to open trades during the same day, there is a high chance to have an Up day predicted. Moreover, we can group by the number of models that open a trade at the same time during the opening session of the day. Then we evaluate precision as a function of the number of concurrent models.
As we can see by the chart depicted above, the precision gets better as the number of models do agree to open a trade. The more models agree, the more precision we get. For instance, with 5 models triggered the same day, the chance to predict an Up day is greater than 85%.
Conclusion
Even in the financial world, Machine Learning is welcomed as a powerful instrument to learn from data and give us great forecasting tools. Each model shows different values of accuracy and precision, but in general, all models can be aggregated to achieve a better result than each one of them taken singularly. GraphLab Create is a great library, easy to use, scalable and able to manage Big Data very quickly. It implements different scientific and forecasting models, and there is a free license for students and Kaggle competitions.
Additional disclosure: This article has been prepared solely for information purposes, and is not an offer to buy or sell or a solicitation of an offer to buy or sell any security or instrument or to participate in any particular trading strategy. Examples presented on these sites are for educational purposes only. Past results are not necessarily indicative of future results.