Der Aufstieg des automatisierten Handels: Maschinen, die den S&P 500 handeln

Veröffentlicht: 2022-03-11

Heutzutage werden mehr als 60 Prozent der Handelsaktivitäten mit verschiedenen Vermögenswerten (wie Aktien, Index-Futures, Rohstoffe) nicht mehr von „menschlichen“ Händlern durchgeführt, sondern auf automatisierten Handel angewiesen. Es gibt spezialisierte Programme, die auf bestimmten Algorithmen basieren, die automatisch Vermögenswerte über verschiedene Märkte kaufen und verkaufen, um langfristig eine positive Rendite zu erzielen.

In diesem Artikel werde ich Ihnen zeigen, wie Sie mit guter Genauigkeit vorhersagen können, wie der nächste Trade platziert werden sollte, um einen positiven Gewinn zu erzielen. Für dieses Beispiel habe ich als Basiswert für den Handel den S&P 500-Index ausgewählt, den gewichteten Durchschnitt von 500 US-Unternehmen mit größerer Kapitalisierung. Eine sehr einfach zu implementierende Strategie besteht darin, den S&P 500-Index um 9:30 Uhr zu kaufen, wenn die Wall Street Exchange mit dem Handel beginnt, und ihn bei der Schlusssitzung um 16:00 Uhr Eastern Time zu verkaufen. Liegt der Schlusskurs des Index über dem Eröffnungskurs, ergibt sich ein positiver Gewinn, während ein negativer Gewinn erzielt würde, wenn der Schlusskurs unter dem Eröffnungskurs liegt. Die Frage ist also: Woher wissen wir, ob die Handelssitzung mit einem Schlusskurs enden wird, der höher ist als der Eröffnungskurs? Maschinelles Lernen ist ein leistungsstarkes Werkzeug, um eine so komplexe Aufgabe zu bewältigen, und es kann ein nützliches Werkzeug sein, um uns bei der Handelsentscheidung zu unterstützen.

Maschinelles Lernen ist die neue Grenze vieler nützlicher realer Anwendungen. Der Finanzhandel ist einer davon und wird in diesem Sektor sehr häufig eingesetzt. Ein wichtiges Konzept des maschinellen Lernens ist, dass wir nicht für jede Art von möglichen Regeln, wie z. B. Mustererkennung, Code schreiben müssen. Dies liegt daran, dass jedes mit maschinellem Lernen verbundene Modell aus den Daten selbst lernt und später verwendet werden kann, um unsichtbare neue Daten vorherzusagen.

Haftungsausschluss : Der Zweck dieses Artikels ist es, zu zeigen, wie Machine Learning-Methoden trainiert werden, und in den bereitgestellten Codebeispielen wird nicht jede Funktion erklärt. Dieser Artikel soll nicht dazu dienen, den gesamten Code zu kopieren und einzufügen und die gleichen bereitgestellten Tests auszuführen, da einige Details fehlen, die den Umfang des Artikels sprengen würden. Außerdem sind Grundkenntnisse in Python erforderlich. Die Hauptabsicht des Artikels besteht darin, ein Beispiel dafür zu zeigen, wie maschinelles Lernen effektiv sein kann, um Käufe und Verkäufe im Finanzsektor vorherzusagen. Der Handel mit echtem Geld bedeutet jedoch, viele andere Fähigkeiten zu besitzen, wie z. B. Geldmanagement und Risikomanagement. Dieser Artikel ist nur ein kleiner Teil des „großen Ganzen“.

Erstellen Sie Ihr erstes automatisiertes Handelsprogramm für Finanzdaten

Sie möchten also Ihr erstes Programm erstellen, um Finanzdaten zu analysieren und den richtigen Trade vorherzusagen? Lassen Sie mich Ihnen zeigen, wie. Ich werde Python für Machine Learning-Code verwenden, und wir werden historische Daten vom Yahoo Finance-Dienst verwenden. Wie bereits erwähnt, sind historische Daten erforderlich, um das Modell zu trainieren, bevor wir unsere Vorhersagen treffen.

Zu Beginn müssen wir Folgendes installieren:

  • Python, und insbesondere empfehle ich die Verwendung des IPython-Notebooks.
  • Yahoo Finance Python-Paket (der genaue Name ist yahoo-finance ) über den Terminalbefehl: pip install yahoo-finance .
  • Eine kostenlose Testversion des Pakets für maschinelles Lernen namens GraphLab. Fühlen Sie sich frei, die nützliche Dokumentation dieser Bibliothek zu überprüfen.

Beachten Sie, dass nur ein Teil von GraphLab Open Source ist, der SFrame. Um die gesamte Bibliothek zu verwenden, benötigen wir also eine Lizenz. Es gibt eine kostenlose 30-Tage-Lizenz und eine nicht-kommerzielle Lizenz für Studenten oder Teilnehmer an Kaggle-Wettbewerben. Aus meiner Sicht ist GraphLab Create eine sehr intuitive und einfach zu bedienende Bibliothek, um Daten zu analysieren und Modelle für maschinelles Lernen zu trainieren.

Graben im Python-Code

Sehen wir uns etwas Python-Code an, um zu sehen, wie man Finanzdaten aus dem Internet herunterlädt. Ich schlage vor, das IPython-Notebook zu verwenden, um den folgenden Code zu testen, da IPython im Vergleich zu einer herkömmlichen IDE viele Vorteile hat, insbesondere wenn wir Quellcode, Ausführungscode, Tabellendaten und Diagramme im selben Dokument kombinieren müssen. Eine kurze Erklärung zur Verwendung von IPython Notebook finden Sie im Artikel Introduction to IPython Notebook.

Lassen Sie uns also ein neues IPython-Notebook erstellen und Code schreiben, um historische Preise des S&P 500-Index herunterzuladen. Beachten Sie, dass Sie, wenn Sie lieber andere Tools verwenden, mit einem neuen Python-Projekt in Ihrer bevorzugten IDE beginnen können.

 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'}

Hier ist hist_quotes eine Liste von Wörterbüchern und jedes Wörterbuchobjekt ist ein Handelstag mit den Werten Open , High , Low , Close , Adj_close , Volume , Symbol und Date . Während jedes Handelstages ändert sich der Preis normalerweise vom Eröffnungspreis Open zum Schlusspreis Close und erreicht einen maximalen und einen minimalen Wert High und Low . Wir müssen es durchlesen und Listen mit den wichtigsten Daten erstellen. Außerdem müssen die Daten zunächst nach den neuesten Werten geordnet werden, also müssen wir sie umkehren:

 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']))

Wir können alle heruntergeladenen Kurse in ein SFrame -Objekt packen, das ein hochgradig skalierbarer, spaltenbasierter Datenrahmen ist, und es ist komprimiert. Einer der Vorteile ist, dass es auch größer als die Menge an RAM sein kann, da es plattengestützt ist. Weitere Informationen zu SFrame finden Sie in der Dokumentation.

Lassen Sie uns also die historischen Daten speichern und dann überprüfen:

 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)
nah dran Terminzeit hoch niedrig offen Volumen
1283.27 2001-01-02 00:00:00 1320.28 1276.05 1320.28 1129400000
1347.56 03.01.2001 00:00:00 1347.76 1274.62 1283.27 1880700000
1333.34 04.01.2001 00:00:00 1350.24 1329.14 1347.56 2131000000

Jetzt können wir Daten mit der SFrame Methode save wie folgt auf der Festplatte speichern:

 qq.save(“SP500_daily.bin”) # once data is saved, we can use the following instruction to retrieve it qq = gl.SFrame(“SP500_daily.bin/”)

Mal sehen, wie der S&P 500 aussieht

Um zu sehen, wie die geladenen S&P 500-Daten aussehen werden, können wir den folgenden Code verwenden:

 import matplotlib.pyplot as plt %matplotlib inline # only for those who are using IPython notebook plt.plot(qq['close'])

Die Ausgabe des Codes ist das folgende Diagramm:

S&P 500-Grafik, die im Allgemeinen im Laufe der Zeit wächst, wie durch den obigen Python-Code gerendert.

Trainieren einiger Machine-Learning-Modelle

Ergebnis hinzufügen

Wie ich im einleitenden Teil dieses Artikels erwähnt habe, besteht das Ziel jedes Modells darin, vorherzusagen, ob der Schlusskurs höher als der Eröffnungskurs sein wird. Daher können wir in diesem Fall beim Kauf des Basiswerts eine positive Rendite erzielen. Also müssen wir unseren Daten eine outcome hinzufügen, die die target oder predicted Variable sein wird. Jede Zeile dieser neuen Spalte wird sein:

  • +1 für einen Up-Tag mit einem Closing , der höher ist als der Opening .
  • -1 für einen Down-Tag mit einem Closing unter dem 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']

Da wir einige Tage vor dem letzten Handelstag bewerten müssen, müssen wir die Daten um einen oder mehrere Tage nacheilen . Für diese Art von Verzögerungsoperation benötigen wir ein weiteres Objekt aus dem GraphLab-Paket namens TimeSeries . TimeSeries verfügt über eine Methodenverschiebung shift die Daten um eine bestimmte Anzahl von Zeilen verzögert.

 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

Prädiktoren hinzufügen

Prädiktoren sind eine Reihe von Merkmalsvariablen, die ausgewählt werden müssen, um das Modell zu trainieren und unser Ergebnis vorherzusagen. Daher ist die Wahl des Prognosefaktors eine entscheidende, wenn nicht die wichtigste Komponente des Prognostikers.

Um nur einige Beispiele zu nennen, könnte ein zu berücksichtigender Faktor sein, ob der heutige Schlusskurs höher ist als der gestrige Schlusskurs, und dies könnte um den Schlusskurs von zwei vorangegangenen Tagen erweitert werden usw. Eine ähnliche Auswahl kann mit dem folgenden Code übersetzt werden:

 ts['feat1'] = ts['close'] > ts_1['close'] ts['feat2'] = ts['close'] > ts_2['close']

Wie oben gezeigt, habe ich zwei neue Feature-Spalten hinzugefügt, feat1 und feat2 in unserem Datensatz ( ts ), die 1 enthalten, wenn der Vergleich wahr ist, und 0 andernfalls.

Dieser Artikel soll ein Beispiel für maschinelles Lernen geben, das auf den Finanzsektor angewendet wird. Ich konzentriere mich lieber darauf, wie Modelle des maschinellen Lernens mit Finanzdaten verwendet werden können, und wir werden nicht im Detail darauf eingehen, wie die richtigen Faktoren zum Trainieren der Modelle ausgewählt werden. Es ist zu erschöpfend zu erklären, warum bestimmte Faktoren in Bezug auf andere verwendet werden, da die Komplexität erheblich zunimmt. Meine Jobforschung besteht darin, viele Hypothesen zur Auswahl von Faktoren zu untersuchen, um einen guten Prädiktor zu erstellen. Daher schlage ich vor, dass Sie zunächst mit vielen verschiedenen Kombinationen von Faktoren experimentieren, um zu sehen, ob sie die Genauigkeit des Modells erhöhen können.

 # 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):]

Trainieren eines Entscheidungsbaummodells

GraphLab Create hat eine sehr übersichtliche Oberfläche zur Implementierung von Modellen für maschinelles Lernen. Jedes Modell verfügt über eine Methode create die verwendet wird, um das Modell mit einem Trainingsdatensatz auszustatten. Typische Parameter sind:

  • training - es ist ein Trainingssatz, der Feature-Spalten und eine Zielspalte enthält.
  • target - das ist der Name der Spalte, die die Zielvariable enthält.
  • validation_set – es ist ein Datensatz zur Überwachung der Generalisierungsleistung des Modells. In unserem Fall haben wir kein validation_set .
  • features - Dies ist eine Liste von Spaltennamen von Features, die zum Trainieren des Modells verwendet werden.
  • verbose - wenn true , Fortschrittsinformationen während des Trainings ausgeben.

Während andere Parameter typisch für das Modell selbst sind, wie zum Beispiel:

  • max_depth - es ist die maximale Tiefe eines Baums.

Mit folgendem Code bauen wir unseren Entscheidungsbaum auf:

 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)

Messen der Leistung des angepassten Modells

Genauigkeit ist eine wichtige Metrik, um die Güte des Prognostikers zu bewerten. Es ist die Anzahl der richtigen Vorhersagen dividiert durch die Anzahl der gesamten Datenpunkte. Da das Modell mit Trainingsdaten ausgestattet ist, ist die mit dem Trainingsset bewertete Genauigkeit besser als die mit einem Testset erhaltene.

Präzision ist der Anteil positiver Vorhersagen, die positiv sind. Wir brauchen Präzision, um eine Zahl näher an 1 zu sein, um eine „perfekte“ Gewinnrate zu erreichen. Unser decision_tree , als ein weiterer Klassifikator aus dem GraphLab Create-Paket, hat seine Methode evaluate , um viele wichtige Metriken des angepassten Modells zu erhalten.

Recall quantifiziert die Fähigkeit eines Klassifikators, positive Beispiele vorherzusagen. Die Erinnerung kann als die Wahrscheinlichkeit interpretiert werden, dass ein zufällig ausgewähltes positives Beispiel vom Klassifikator richtig identifiziert wird. Wir brauchen, dass die Genauigkeit eine Zahl näher an 1 ist, um eine „perfekte“ Gewinnrate zu erreichen.

Der folgende Code zeigt die Genauigkeit des angepassten Modells sowohl mit Trainingsset als auch mit Testset:

 decision_tree.evaluate(training)['accuracy'], decision_tree.evaluate(testing)['accuracy'] (0.6077348066298343, 0.577373211963589)

Wie oben gezeigt, liegt die Genauigkeit des Modells mit dem Testset bei etwa 57 Prozent, was irgendwie besser ist als ein Münzwurf (50 Prozent).

Daten vorhersagen

GraphLab Create hat die gleiche Schnittstelle, um Daten aus verschiedenen angepassten Modellen vorherzusagen. Wir werden die predict verwenden, die einen Testsatz benötigt, um die Zielvariable vorherzusagen, in unserem Fall outcome . Jetzt können wir Daten aus dem Testsatz vorhersagen:

 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)
Terminzeit Ergebnis Vorhersagen
2013-04-05 00:00:00 -1 -1
08.04.2013 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

Falsch positive Ergebnisse sind Fälle, in denen das Modell ein positives Ergebnis vorhersagt, während das tatsächliche Ergebnis des Testsatzes negativ ist. Umgekehrt sind False Negatives Fälle, in denen das Modell ein negatives Ergebnis vorhersagt, obwohl das tatsächliche Ergebnis des Testsatzes positiv ist.

Unsere Handelsstrategie wartet auf ein positiv prognostiziertes Ergebnis, um den S&P 500 zum Opening zu kaufen und zum Closing zu verkaufen. Daher hoffen wir, die niedrigste Falsch-Positiv-Rate zu haben, um Verluste zu vermeiden. Mit anderen Worten, wir erwarten, dass unser Modell die höchste Genauigkeitsrate aufweist.

Wie wir sehen können, gibt es zwei falsch negative Ergebnisse (am 10.04.2013 und 11.04.2013) und zwei falsche positive Ergebnisse (am 15.04.2013 und 18.04.2013) innerhalb der ersten zehn vorhergesagten Werte der Testset.

Mit einer einfachen Berechnung unter Berücksichtigung dieser kleinen Menge von zehn Vorhersagen:

  • Genauigkeit = 6/10 = 0,6 oder 60 %
  • Genauigkeit =3/5 = 0,6 oder 60 %
  • Erinnerung = 3/5 = 0,6 oder 60 %

Beachten Sie, dass sich die obigen Zahlen normalerweise voneinander unterscheiden, aber in diesem Fall sind sie gleich.

Backtesting des Modells

Wir simulieren nun, wie das Modell mit seinen vorhergesagten Werten handeln würde. Wenn das vorhergesagte Ergebnis gleich +1 ist, bedeutet dies, dass wir einen Up-Tag erwarten. Bei einem Aufwärtstag kaufen wir den Index zu Beginn der Sitzung und verkaufen den Index am Ende der Sitzung am selben Tag. Umgekehrt, wenn das vorhergesagte Ergebnis gleich -1 ist, erwarten wir einen Abwärtstag , also werden wir an diesem Tag nicht handeln.

Der Gewinn und Verlust ( pnl ) für einen kompletten Tageshandel , auch Roundturn genannt, ergibt sich in diesem Beispiel aus:

  • pnl = Close - Open (für jeden Handelstag)

Mit dem unten gezeigten Code rufe ich die plot_equity_chart auf, um ein Diagramm mit der Kurve der kumulativen Gewinne (Equity Curve) zu erstellen. Ohne zu tief zu gehen, erhält es einfach eine Reihe von Gewinn- und Verlustwerten und berechnet die Reihe der kumulativen Summen, die dargestellt werden sollen.

 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') 

Entscheidungsbaummodell, das im Allgemeinen nach oben und rechts verläuft, wie durch den obigen Python-Code gerendert.

 Mean of PnL is 1.843504 Sharpe is 1.972835 Round turns 511

Hier ist Sharpe die Annual Sharpe Ratio, ein wichtiger Indikator für die Güte des Handelsmodells.

Die Sharpe Ratio ist gleich der Quadratwurzel von 252, dann multipliziert mit dem Mittelwert von pnl dividiert durch die Standardabweichung von pnl.

Unter Berücksichtigung von Trades, die Tag für Tag ausgedrückt werden, wobei der mean der Mittelwert der Gewinn- und Verlustliste ist und sd die Standardabweichung ist. Der Einfachheit halber habe ich in der oben abgebildeten Formel eine risikofreie Rendite gleich 0 betrachtet.

Einige Grundlagen zum Trading

Der Handel mit dem Index erfordert den Kauf eines Vermögenswerts, der direkt vom Index abgeleitet ist. Viele Broker replizieren den S&P 500-Index mit einem derivativen Produkt namens CFD (Contract for Difference), bei dem es sich um eine Vereinbarung zwischen zwei Parteien handelt, die Differenz zwischen dem Eröffnungspreis und dem Schlusspreis eines Kontrakts auszutauschen.

Beispiel : Kaufen Sie 1 CFD S&P 500 bei Open (Wert ist 2000), verkaufen Sie ihn bei Close (Wert ist 2020). Die Differenz, also der Gewinn, beträgt 20 Punkte. Wenn jeder Punkt einen Wert von $25 hat:

  • Der Bruttogewinn beträgt 20 points x $25 = $500 mit 1 CFD-Kontrakt.

Nehmen wir an, dass der Broker eine Slippage von 0,6 Punkten für seine eigenen Einnahmen behält:

  • Der Nettogewinn beträgt (20 - 0.6) points x $25 = $485 .

Ein weiterer wichtiger Aspekt ist die Vermeidung erheblicher Verluste innerhalb eines Trades. Sie können immer dann eintreten, wenn das vorhergesagte Ergebnis +1 ist, der tatsächliche Ergebniswert jedoch -1 ist, also ein falsch positives Ergebnis ist. In diesem Fall stellt sich heraus, dass die Endsitzung ein Down-Tag mit einem Schlusskurs ist, der niedriger ist als der Eröffnungskurs, und wir erleiden einen Verlust.

Eine Stop-Loss-Order muss platziert werden, um sich vor einem maximalen Verlust zu schützen, den wir innerhalb eines Trades tolerieren würden, und eine solche Order wird ausgelöst, wenn der Preis des Vermögenswerts unter einen von uns zuvor festgelegten festen Wert fällt.

Wenn wir uns die Zeitreihen ansehen, die zu Beginn dieses Artikels von Yahoo Finance heruntergeladen wurden, hat jeder Tag einen Low Preis, der der niedrigste Preis ist, der an diesem Tag erreicht wurde. Wenn wir ein Stop-Level von -3 Punkten weit vom Opening setzen und Low - Open = -5 , wird die Stop-Order ausgelöst und die geöffnete Position wird mit einem Verlust von -3 Punkten statt -5 geschlossen. Dies ist eine einfache Methode, um das Risiko zu reduzieren. Der folgende Code stellt meine Hilfsfunktion dar, um einen Trade mit einem Stop-Level zu simulieren:

 # 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']

Handelskosten

Transaktionskosten sind Kosten, die beim Kauf oder Verkauf von Wertpapieren anfallen. Zu den Transaktionskosten gehören Maklerprovisionen und Spreads (die Differenz zwischen dem Preis, den der Händler für ein Wertpapier bezahlt hat, und dem Preis, den der Käufer zahlt), und sie müssen berücksichtigt werden, wenn wir unsere Strategie ähnlich wie in einem realen Szenario einem Backtest unterziehen wollen. Slippage beim Handel mit Aktien tritt häufig auf, wenn sich der Spread ändert. In diesem Beispiel und für die nächsten laufenden Simulationen sind die Handelskosten wie folgt festgelegt:

  • Schlupf = 0,6 Punkte
  • Provision = 1 $ für jeden Handel (eine Runde kostet 2 $)

Nur um einige Zahlen aufzuschreiben: Wenn unser Bruttogewinn 10 Punkte wäre, 1 Punkt = 25 $, also 250 $ inklusive Handelskosten, wäre unser Nettogewinn (10 - 0.6)*$25 - 2 = $233 .

Der folgende Code zeigt eine Simulation der vorherigen Handelsstrategie mit einem Stop-Loss von -3 Punkten. Die blaue Kurve ist die Kurve der kumulierten Renditen. Die einzigen berücksichtigten Kosten sind Slippage (0,6 Punkte), und das Ergebnis wird in Basispunkten ausgedrückt (dieselbe Basiseinheit der S&P 500-Werte, die von Yahoo Finance heruntergeladen wurden).

 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)) 

Entscheidungsbaummodell, das im Allgemeinen nach oben und rechts verläuft, jedoch mit weniger ausgeprägten Spitzen, wie durch den obigen Python-Code gerendert.

 Mean of PnL is 2.162171 Sharpe is 3.502897 Round turns 511 Slippage is 0.6 STOP level at -3

Der folgende Code wird verwendet, um Vorhersagen auf etwas andere Weise zu treffen. Bitte achten Sie auf die Methode „ predict “, die mit einem zusätzlichen Parameter output_type = “probability” wird. Dieser Parameter wird verwendet, um Wahrscheinlichkeiten von vorhergesagten Werten anstelle ihrer Klassenvorhersage zurückzugeben ( +1 für ein positiv vorhergesagtes Ergebnis, -1 für ein negativ vorhergesagtes Ergebnis). Eine Wahrscheinlichkeit größer oder gleich 0.5 ist einem Vorhersagewert +1 zugeordnet, und ein Wahrscheinlichkeitswert kleiner als 0.5 ist einem Vorhersagewert von -1 zugeordnet. Je höher diese Wahrscheinlichkeit ist, desto größer ist die Wahrscheinlichkeit, dass wir einen echten Up Day vorhersagen können.

 predictions_prob = decision_tree.predict(testing, output_type = 'probability') # predictions_prob will contain probabilities instead of the predicted class (-1 or +1)

Jetzt testen wir das Modell mit einer Hilfsfunktion namens backtest_ml_model , die die Reihe der kumulativen Renditen einschließlich Slippage und Provisionen berechnet und ihre Werte grafisch darstellt. Der Kürze halber, ohne die Funktion backtest_ml_model gründlich zu erklären, ist das wichtige Detail hervorzuheben, dass wir, anstatt die Tage mit einem vorhergesagten outcome = 1 zu filtern, wie wir es im vorherigen Beispiel getan haben, jetzt diese predictions_prob gleich oder größer als ein threshold = 0.5 filtern. wie folgt:

 trades = testing[predictions_prob>=0.5][('datetime', 'gain', 'ho', 'lo', 'open', 'close')]

Denken Sie daran, dass der Nettogewinn an jedem Handelstag wie folgt ist: Net gain = (Gross gain - SLIPPAGE) * MULT - 2 * COMMISSION .

Eine weitere wichtige Metrik, die verwendet wird, um die Güte einer Handelsstrategie zu bewerten, ist der maximale Drawdown . Im Allgemeinen misst es den größten einzelnen Rückgang des Werts eines investierten Portfolios vom Höchststand bis zum Tiefststand. In unserem Fall ist es der bedeutendste Rückgang vom Hoch zum Tief der Aktienkurve (wir haben nur einen Vermögenswert in unserem Portfolio, S&P 500). Bei einem SArray von Gewinn und Verlust pnl berechnen wir den Drawdown also wie folgt:

 drawdown = pnl - pnl.cumulative_max() max_drawdown = min(drawdown)

Innerhalb der Hilfsfunktion wird backtest_summary berechnet:

  • Maximaler Drawdown (in Dollar) wie oben gezeigt.
  • Genauigkeit, mit Graphlab.evaluation Methode.
  • Präzision, mit Graphlab.evaluation Methode.
  • Rückruf, mit Graphlab.evaluation Methode.

Alles zusammengenommen zeigt das folgende Beispiel die Eigenkapitalkurve, die die kumulierten Renditen der Modellstrategie darstellt, wobei alle Werte in Dollar ausgedrückt sind.

 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) 

DecisionTree-Diagramm mit der Y-Achse, die mit „Dollars“ bezeichnet ist und bis zu 30.000 reicht, und der X-Achse, die mit „# of roundturns“ bezeichnet und bis 600 reicht, wie durch den obigen Python-Code gerendert. Die graphisch dargestellten Daten selbst sind identisch mit dem vorherigen Rendering.

 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

Um die Genauigkeit der prognostizierten Werte zu erhöhen, wählen wir anstelle einer Standardwahrscheinlichkeit von 0.5 (50 Prozent) einen höheren Schwellenwert, um sicherer zu sein, dass das Modell einen Up-Tag vorhersagt .

 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) 

DecisionTree-Diagramm mit der Y-Achse, die mit „Dollars“ bezeichnet ist und bis zu 30.000 reicht, und der X-Achse, die mit „# of roundturns“ bezeichnet ist und sich diesmal nur auf 250 erstreckt, wie durch den obigen Python-Code gerendert. Die grafisch dargestellten Daten selbst ähneln dem vorherigen Rendering, sind jedoch noch stärker geglättet.

 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

Wie wir der obigen Grafik entnehmen können, ist die Equity-Kurve viel besser als zuvor (Sharpe liegt bei 6,5 statt 3,5), auch mit weniger Roundturns.

Ab diesem Zeitpunkt betrachten wir alle nächsten Modelle mit einem Schwellenwert, der höher als ein Standardwert ist.

Ausbildung eines logistischen Klassifikators

Wir können unsere Forschung, wie wir es zuvor mit dem Entscheidungsbaum getan haben, in einem logistischen Klassifikatormodell anwenden. GraphLab Create hat dieselbe Schnittstelle wie das Logistic Classifier-Objekt, und wir rufen die create -Methode auf, um unser Modell mit derselben Parameterliste zu erstellen. Darüber hinaus bevorzugen wir die Vorhersage des Wahrscheinlichkeitsvektors anstelle des vorhergesagten Klassenvektors (zusammengesetzt aus +1 für ein positives Ergebnis und -1 für ein negatives Ergebnis), sodass wir einen Schwellenwert von mehr als 0.5 hätten, um eine bessere Genauigkeit in unserem zu erreichen Prognose.

 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) 

LogisticClassifier-Diagramm mit der Y-Achse, die mit „Dollars“ beschriftet ist und dieses Mal bis zu 50.000 reicht, und der X-Achse, die mit „# of roundturns“ beschriftet ist und sich jetzt auf 450 erstreckt, wie durch den obigen Python-Code gerendert. Die grafisch dargestellten Daten selbst ähneln in ihrem Gesamttrend dem vorherigen Rendering.

 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

In diesem Fall gibt es eine Zusammenfassung, die dem Entscheidungsbaum sehr ähnlich ist. Schließlich sind beide Modelle Klassifikatoren, sie sagen nur eine Klasse binärer Ergebnisse ( +1 , -1 ) voraus.

Trainieren eines linearen Regressionsmodells

Der Hauptunterschied dieses Modells besteht darin, dass es, wie bereits erwähnt, mit kontinuierlichen Werten statt mit binären Klassen umgeht. Wir müssen das Modell nicht mit einer Zielvariablen trainieren, die +1 für Up-Tage und -1 für Down-Tage ist, unser Ziel muss eine kontinuierliche Variable sein. Da wir einen positiven Gewinn vorhersagen wollen, oder anders gesagt einen Schlusskurs , der höher ist als der Eröffnungskurs , muss das Ziel jetzt die Gewinnspalte unseres Trainingssatzes sein. Außerdem muss die Liste der Features aus kontinuierlichen Werten bestehen, wie z. B. dem vorherigen Open , Close usw.

Der Kürze halber werde ich nicht auf die Details der Auswahl der richtigen Funktionen eingehen, da dies den Rahmen dieses Artikels sprengen würde, der eher zeigen soll, wie wir verschiedene Machine Learning-Modelle auf einen Datensatz anwenden sollten. Die Liste der an die create-Methode übergebenen Parameter lautet:

  • training - es ist ein Trainingssatz, der Feature-Spalten und eine Zielspalte enthält.
  • target - das ist der Name der Spalte, die die Zielvariable enthält.
  • validation_set – es ist ein Datensatz zur Überwachung der Generalisierungsleistung des Modells. In unserem Fall haben wir kein validation_set .
  • features - Es ist eine Liste von Spaltennamen von Features, die zum Trainieren des Modells verwendet werden. Für dieses Modell verwenden wir einen anderen Satz in Bezug auf die Classifier-Modelle.
  • verbose - wenn true , werden Fortschrittsinformationen während des Trainings ausgegeben.
  • max_iterations - dies ist die maximale Anzahl zulässiger Durchgänge durch die Daten. Mehr Durchgänge über die Daten können zu einem genauer trainierten Modell führen.
 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)

Bisher haben wir Vorhersagen, die SArray vorhergesagter Gewinne sind, während predictions_prob SArray mit normalisierten predictions ist. Um eine gute Genauigkeit und eine gewisse Anzahl an Runden, vergleichbar mit früheren Modellen, zu haben, habe ich einen Schwellwert von 0.4 gewählt. Bei einer predictions_prob von weniger als 0.4 wird die backtest_linear_model keinen Trade eröffnen, da ein Down-Tag erwartet wird. Andernfalls wird ein Trade eröffnet.

 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) 

LinearRegression-Diagramm mit der Y-Achse, die mit „Dollars“ bezeichnet ist und bis zu 45.000 reicht, und der X-Achse, die mit „# of roundturns“ bezeichnet und bis 350 reicht, wie durch den obigen Python-Code gerendert. Die graphisch dargestellten Daten selbst sind wieder ähnlich, aber nicht genau identisch mit dem vorherigen Rendering.

 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

Einen geboosteten Baum trainieren

Wie wir zuvor einen Entscheidungsbaum trainiert haben, werden wir jetzt einen verstärkten Baumklassifikator mit den gleichen Parametern trainieren, die für andere Klassifikatormodelle verwendet werden. Außerdem setzen wir die Anzahl der max_iterations = 12 , um die maximale Anzahl der Iterationen für das Boosten zu erhöhen. Jede Iteration führt zur Erstellung eines zusätzlichen Baums. Wir legen auch einen höheren Schwellenwert als 0.5 fest, um die Genauigkeit zu erhöhen.

 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) 

BoostedTreesClassifier-Diagramm mit der Y-Achse, die mit „Dollars“ bezeichnet ist und bis zu 25.000 reicht, und der X-Achse, die mit „# of roundturns“ bezeichnet und bis 250 reicht, wie durch den obigen Python-Code gerendert. Die graphisch dargestellten Daten selbst ähneln wieder dem vorherigen Rendering, mit einem steileren Anstieg um 175 auf der X-Achse.

 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

Trainieren eines Random Forest

Dies ist unser letztes trainiertes Modell, ein Random Forest Classifier, das aus einem Ensemble von Entscheidungsbäumen besteht. Die maximale Anzahl der im Modell zu verwendenden Bäume ist auf num_trees = 10 festgelegt, um zu viel Komplexität und Überanpassung zu vermeiden.

 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) 

RandomForestClassifier-Diagramm mit der Y-Achse, die mit „Dollars“ bezeichnet ist und bis zu 40.000 reicht, und der X-Achse, die mit „# of roundturns“ bezeichnet und bis 350 reicht, wie durch den obigen Python-Code gerendert. Die graphisch dargestellten Daten selbst ähneln wiederum dem vorherigen Rendering.

 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.

Name Richtigkeit 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%.

Fazit

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.

Siehe auch: Eine Einführung in die Theorie des maschinellen Lernens und ihre Anwendungen: Ein visuelles Tutorial mit Beispielen

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.