Rozwój handlu zautomatyzowanego: maszyny handlujące S&P 500

Opublikowany: 2022-03-11

Obecnie ponad 60 procent działań handlowych z różnymi aktywami (takimi jak akcje, kontrakty futures na indeksy, towary) nie jest już dokonywanych przez traderów „ludzi”, zamiast tego polega na handlu automatycznym. Istnieją wyspecjalizowane programy oparte na określonych algorytmach, które automatycznie kupują i sprzedają aktywa na różnych rynkach, aby osiągnąć dodatni zwrot w długim okresie.

W tym artykule pokażę, jak z dużą dokładnością przewidzieć, jak należy ułożyć następną transakcję, aby uzyskać pozytywny zysk. W tym przykładzie jako aktywa bazowe do handlu wybrałem indeks S&P 500, średnią ważoną 500 amerykańskich spółek o większej kapitalizacji. Bardzo prostą strategią do wdrożenia jest kupno indeksu S&P 500, gdy Wall Street Exchange rozpocznie handel o godzinie 9:30 i sprzedaż na sesji zamykającej o godzinie 16:00 czasu wschodniego. Jeżeli cena zamknięcia indeksu jest wyższa niż cena otwarcia, zyskuje się dodatni, natomiast zysk ujemny zostanie osiągnięty, jeśli cena zamknięcia jest niższa niż cena otwarcia. Pytanie brzmi więc: skąd wiemy, czy sesja handlowa zakończy się ceną zamknięcia wyższą niż cena otwarcia? Uczenie maszynowe jest potężnym narzędziem do realizacji tak złożonego zadania i może być przydatnym narzędziem wspierającym nas w podejmowaniu decyzji handlowych.

Uczenie maszynowe to nowa granica wielu użytecznych, rzeczywistych aplikacji. Jednym z nich jest handel finansowy, który jest bardzo często stosowany w tym sektorze. Ważną koncepcją dotyczącą uczenia maszynowego jest to, że nie musimy pisać kodu dla każdego rodzaju możliwych reguł, takich jak rozpoznawanie wzorców. Dzieje się tak, ponieważ każdy model związany z uczeniem maszynowym uczy się na podstawie samych danych, a następnie może być później wykorzystany do przewidywania niewidocznych nowych danych.

Zastrzeżenie : Celem tego artykułu jest pokazanie, jak trenować metody uczenia maszynowego, a w dostarczonych przykładach kodu nie wyjaśniono każdej funkcji. Ten artykuł nie ma na celu umożliwienia kopiowania i wklejania całego kodu oraz przeprowadzania tych samych dostarczonych testów, ponieważ brakuje niektórych szczegółów, które wykraczały poza zakres artykułu. Wymagana jest również podstawowa znajomość Pythona. Główną intencją artykułu jest pokazanie przykładu, jak uczenie maszynowe może być skuteczne w przewidywaniu kupna i sprzedaży w sektorze finansowym. Jednak handel prawdziwymi pieniędzmi oznacza posiadanie wielu innych umiejętności, takich jak zarządzanie pieniędzmi i zarządzanie ryzykiem. Ten artykuł to tylko mały wycinek „dużego obrazu”.

Budowanie pierwszego zautomatyzowanego programu handlu danymi finansowymi

Chcesz więc stworzyć swój pierwszy program do analizy danych finansowych i przewidywania właściwej transakcji? Pokażę ci jak. Będę używał Pythona do kodu uczenia maszynowego, a my będziemy korzystać z danych historycznych z usługi Yahoo Finance. Jak wspomniano wcześniej, dane historyczne są niezbędne do trenowania modelu przed dokonaniem naszych prognoz.

Na początek musimy zainstalować:

  • Python, aw szczególności sugeruję korzystanie z notatnika IPython.
  • Pakiet Yahoo Finance Python (dokładna nazwa to yahoo-finance ) za pomocą polecenia terminala: pip install yahoo-finance .
  • Bezpłatna wersja próbna pakietu Machine Learning o nazwie GraphLab. Zapraszam do zapoznania się z przydatną dokumentacją tej biblioteki.

Zauważ, że tylko część GraphLab jest open source, SFrame, więc aby korzystać z całej biblioteki potrzebujemy licencji. Dla studentów lub osób biorących udział w zawodach Kaggle obowiązuje 30-dniowa licencja bezpłatna oraz licencja niekomercyjna. Z mojego punktu widzenia GraphLab Create to bardzo intuicyjna i łatwa w użyciu biblioteka do analizy danych i trenowania modeli uczenia maszynowego.

Kopanie w kodzie Pythona

Zagłębmy się w kod Pythona, aby zobaczyć, jak pobrać dane finansowe z Internetu. Proponuję użyć notatnika IPython do przetestowania poniższego kodu, ponieważ IPython ma wiele zalet w porównaniu z tradycyjnym IDE, zwłaszcza gdy musimy połączyć kod źródłowy, kod wykonawczy, dane tabeli i wykresy w tym samym dokumencie. Aby uzyskać krótkie wyjaśnienie dotyczące korzystania z notebooka IPython, zapoznaj się z artykułem Wprowadzenie do notebooka IPython.

Stwórzmy więc nowy notatnik IPython i napiszmy kod, aby pobrać historyczne ceny indeksu S&P 500. Uwaga, jeśli wolisz używać innych narzędzi, możesz zacząć od nowego projektu Pythona w preferowanym IDE.

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

W tym hist_quotes to lista słowników, a każdy obiekt słownika to dzień handlowy z wartościami Open , High , Low , Close , Adj_close , Volume , Symbol i Date . Podczas każdego dnia handlowego cena zwykle zmienia się od ceny otwarcia Open do ceny zamknięcia Close i osiągając maksymalną i minimalną wartość High i Low . Musimy się z nim zapoznać i stworzyć listy wszystkich najistotniejszych danych. Ponadto dane muszą być najpierw uporządkowane według najnowszych wartości, więc musimy je odwrócić:

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

Możemy spakować wszystkie pobrane cytaty do obiektu SFrame , który jest wysoce skalowalną ramką danych opartą na kolumnach i jest skompresowany. Jedną z zalet jest to, że może być również większy niż ilość pamięci RAM, ponieważ jest oparty na dysku. Możesz sprawdzić dokumentację, aby dowiedzieć się więcej o SFrame.

Zapiszmy więc, a następnie sprawdźmy dane historyczne:

 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)
blisko data i godzina wysoka Niska otwarty Tom
1283,27 2001-01-02 00:00:00 1320,28 1276,05 1320,28 112940000
1347,56 2001-01-03 00:00:00 1347,76 1274,62 1283,27 188070000
1333,34 2001-01-04 00:00:00 1350,24 1329,14 1347,56 2131000000

Teraz możemy zapisać dane na dysku metodą SFrame save , w następujący sposób:

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

Zobaczmy, jak wygląda S&P 500

Aby zobaczyć, jak będą wyglądały wczytane dane S&P 500, możemy użyć następującego kodu:

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

Wynikiem kodu jest następujący wykres:

Wykres S&P 500, generalnie rosnący w czasie, jak jest renderowany przez powyższy kod Pythona.

Szkolenie niektórych modeli uczenia maszynowego

Dodawanie wyniku

Jak wspomniałem we wstępnej części tego artykułu, celem każdego modelu jest przewidzenie, czy cena zamknięcia będzie wyższa niż cena otwarcia. Dlatego w takim przypadku możemy osiągnąć dodatni zwrot przy zakupie instrumentu bazowego. Dlatego musimy dodać kolumnę outcome do naszych danych, która będzie zmienną target lub predicted . Każdy wiersz tej nowej kolumny będzie:

  • +1 za dzień Up z ceną Closing wyższą niż cena Opening .
  • -1 za dzień Down z ceną Closing niższą niż cena 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']

Ponieważ musimy ocenić kilka dni przed ostatnim dniem handlu, musimy opóźnić dane o jeden lub więcej dni. Do tego rodzaju operacji z opóźnieniem potrzebujemy innego obiektu z pakietu GraphLab o nazwie TimeSeries . TimeSeries ma shift metody, które opóźnia dane o określoną liczbę wierszy.

 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

Dodawanie predyktorów

Predyktory to zestaw zmiennych cech, które należy wybrać, aby trenować model i przewidywać nasz wynik . Tak więc wybór czynnika prognostycznego jest kluczowy, jeśli nie najważniejszy, komponent prognostyka.

Aby wymienić tylko kilka przykładów, czynnikiem do rozważenia może być to, czy dzisiejsze zamknięcie jest wyższe niż wczorajsze zamknięcie, które może zostać rozszerzone o zamknięcie z dwóch poprzednich dni itp. Podobny wybór można przetłumaczyć za pomocą następującego kodu:

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

Jak pokazano powyżej, dodałem dwie nowe kolumny funkcji, feat1 i feat2 w naszym zestawie danych ( ts ) zawierającym 1 , jeśli porównanie jest prawdziwe, a 0 w przeciwnym razie.

Ten artykuł ma na celu przedstawienie przykładu uczenia maszynowego stosowanego w sektorze finansowym. Wolę skupić się na tym, jak modele uczenia maszynowego mogą być używane z danymi finansowymi, i nie będziemy wchodzić w szczegóły dotyczące wyboru właściwych czynników do trenowania modeli. Wyjaśnienie, dlaczego niektóre czynniki są stosowane w odniesieniu do innych, jest zbyt wyczerpujące, ze względu na znaczny wzrost złożoności. Moja praca badawcza polega na badaniu wielu hipotez dotyczących wyboru czynników, aby stworzyć dobry predyktor. Na początek proponuję poeksperymentować z wieloma różnymi kombinacjami czynników, aby sprawdzić, czy mogą one zwiększyć dokładność modelu.

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

Trening modelu drzewa decyzyjnego

GraphLab Create ma bardzo przejrzysty interfejs do implementacji modeli uczenia maszynowego. Każdy model ma metodę create służącą do dopasowania modelu do zestawu danych uczących. Typowe parametry to:

  • training - jest to zestaw treningowy zawierający kolumny cech oraz kolumnę docelową.
  • target - jest to nazwa kolumny zawierającej zmienną docelową.
  • validation_set - jest to zbiór danych do monitorowania wydajności uogólniania modelu. W naszym przypadku nie mamy validation_set .
  • features — jest to lista kolumn z nazwami cech używanych do uczenia modelu.
  • verbose - jeśli true , drukuj informacje o postępie podczas treningu.

Natomiast inne parametry są typowe dla samego modelu, takie jak:

  • max_depth - to maksymalna głębokość drzewa.

Za pomocą poniższego kodu budujemy nasze drzewo decyzyjne:

 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)

Pomiar wydajności dopasowanego modelu

Dokładność jest ważnym miernikiem oceny dobroci prognosty. Jest to liczba poprawnych prognoz podzielona przez liczbę wszystkich punktów danych. Ponieważ model jest wyposażony w dane uczące, dokładność oceniana za pomocą zestawu uczącego jest lepsza niż dokładność uzyskana za pomocą zestawu testowego.

Precyzja to ułamek pozytywnych prognoz, które są pozytywne. Potrzebujemy precyzji, aby była liczbą bliższą 1 , aby osiągnąć „doskonały” współczynnik wygranych. Nasze decision_tree , jako kolejny klasyfikator z pakietu GraphLab Create, ma swoją metodę evaluate , aby uzyskać wiele ważnych metryk dopasowanego modelu.

Recall określa ilościowo zdolność klasyfikatora do przewidywania pozytywnych przykładów. Przypomnienie można interpretować jako prawdopodobieństwo, że losowo wybrany pozytywny przykład zostanie poprawnie zidentyfikowany przez klasyfikator. Potrzebujemy, aby precyzja była liczbą bliższą 1 , aby osiągnąć „doskonały” współczynnik wygranych.

Poniższy kod pokaże dokładność dopasowanego modelu zarówno z zestawem uczącym, jak i zestawem testowym:

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

Jak pokazano powyżej, dokładność modelu z zestawem testowym wynosi około 57 procent, co jest jakoś lepiej niż rzucanie monetą (50 procent).

Przewidywanie danych

GraphLab Create ma ten sam interfejs do przewidywania danych z różnych dopasowanych modeli. Użyjemy metody predict , która wymaga zestawu testowego do przewidzenia zmiennej docelowej, w naszym przypadku outcome . Teraz możemy przewidzieć dane ze zbioru testowego:

 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)
data i godzina wynik prognozy
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

Fałszywie pozytywne to przypadki, w których model przewiduje pozytywny wynik, podczas gdy rzeczywisty wynik zestawu testowego jest negatywny. Odwrotnie, wyniki fałszywie ujemne to przypadki, w których model przewiduje wynik negatywny, gdy rzeczywisty wynik zestawu testowego jest pozytywny.

Nasza strategia handlowa czeka na pozytywnie przewidywany wynik, aby kupić S&P 500 po cenie Opening i sprzedać go po cenie Closing , więc mamy nadzieję, że uzyskamy najniższy kurs fałszywych trafień, aby uniknąć strat. Innymi słowy, oczekujemy, że nasz model będzie miał najwyższą precyzję .

Jak widać, w ciągu pierwszych dziesięciu przewidywanych wartości zestaw testowy.

Za pomocą prostego obliczenia, biorąc pod uwagę ten mały zestaw dziesięciu prognoz:

  • dokładność = 6/10 = 0,6 lub 60%
  • precyzja =3/5 = 0,6 lub 60%
  • przypomnienie = 3/5 = 0,6 lub 60%

Zauważ, że zwykle powyższe liczby różnią się od siebie, ale w tym przypadku są takie same.

Testowanie wsteczne modelu

Teraz symulujemy, w jaki sposób model będzie handlował, używając swoich przewidywanych wartości. Jeśli przewidywany wynik jest równy +1 , oznacza to, że oczekujemy dnia Up . W dniu Up kupujemy indeks na początku sesji i sprzedajemy go na koniec sesji w tym samym dniu. I odwrotnie, jeśli przewidywany wynik jest równy -1 , spodziewamy się dnia Down , więc nie będziemy handlować w tym dniu.

Zysk i strata ( pnl ) dla kompletnej dziennej transakcji, zwanej również round turn , w tym przykładzie jest wyrażona wzorem:

  • pnl = Close - Open (dla każdego dnia handlowego)

Za pomocą kodu pokazanego poniżej wywołuję funkcję pomocniczą plot_equity_chart , aby utworzyć wykres z krzywą skumulowanych zysków (krzywa equity). Nie wchodząc zbyt głęboko, po prostu pobiera serię wartości zysków i strat i oblicza serię skumulowanych sum do wykreślenia.

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

Model drzewa decyzyjnego, zwykle w górę iw prawo, tak jak jest renderowany przez powyższy kod Pythona.

 Mean of PnL is 1.843504 Sharpe is 1.972835 Round turns 511

Tutaj Sharpe to roczny wskaźnik Sharpe'a, ważny wskaźnik dobroci modelu handlowego.

Współczynnik Sharpe'a jest równy pierwiastkowi kwadratowemu z 252, następnie pomnożonemu przez średnią pnl podzieloną przez odchylenie standardowe pnl.

Biorąc pod uwagę transakcje wyrażone dzień po dniu, podczas gdy mean jest średnią listy zysków i strat, a sd jest odchyleniem standardowym. Dla uproszczenia powyższego wzoru rozważyłem zwrot wolny od ryzyka równy 0.

Kilka podstawowych informacji o handlu

Obrót indeksem wymaga zakupu aktywa, które pochodzi bezpośrednio z indeksu. Wielu brokerów replikuje indeks S&P 500 za pomocą produktu pochodnego o nazwie CFD (Kontrakt na różnicę), który jest umową między dwiema stronami w celu wymiany różnicy między ceną otwarcia a ceną zamknięcia kontraktu.

Przykład : Kup 1 CFD S&P 500 po Open (wartość 2000), sprzedaj po Close dnia (wartość 2020). Różnica, a więc zysk, wynosi 20 punktów. Jeśli każdy punkt ma wartość 25 USD:

  • Zysk brutto to 20 points x $25 = $500 z 1 kontraktem CFD.

Załóżmy, że broker zachowuje poślizg 0,6 punktu dla własnych dochodów:

  • Zysk netto wynosi (20 - 0.6) points x $25 = $485 .

Innym ważnym aspektem do rozważenia jest uniknięcie znacznych strat w handlu. Mogą się zdarzyć, gdy przewidywany wynik wynosi +1 , ale rzeczywista wartość wyniku wynosi -1 , więc jest to fałszywy alarm . W takim przypadku końcowa sesja okazuje się dniem Downa z ceną zamknięcia niższą od otwarcia i otrzymujemy stratę.

Zlecenie stop loss musi zostać złożone w celu ochrony przed maksymalną stratą, jaką byśmy tolerowali w ramach transakcji, a takie zlecenie jest uruchamiane za każdym razem, gdy cena aktywa spadnie poniżej ustalonej wartości, którą ustaliliśmy wcześniej.

Jeśli spojrzymy na szeregi czasowe pobrane z Yahoo Finance na początku tego artykułu, każdy dzień ma Low cenę, która jest najniższą ceną osiągniętą w tym dniu. Jeśli ustawimy poziom stop na -3 punkty daleko od ceny Opening , a Low - Open = -5 , zlecenie stop zostanie uruchomione, a otwarta pozycja zostanie zamknięta ze stratą -3 punktów zamiast -5 . To prosta metoda na zmniejszenie ryzyka. Poniższy kod reprezentuje moją funkcję pomocniczą do symulacji transakcji z poziomem stopu:

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

Koszty handlu

Koszty transakcyjne to wydatki poniesione przy kupnie lub sprzedaży papierów wartościowych. Koszty transakcyjne obejmują prowizje brokerów i spready (różnicę między ceną, którą dealer zapłacił za papier wartościowy, a ceną, którą płaci kupujący) i należy je wziąć pod uwagę, jeśli chcemy zweryfikować naszą strategię, podobnie jak w prawdziwym scenariuszu. Poślizg w handlu akcjami często występuje, gdy następuje zmiana spreadu. W tym przykładzie i dla następnych trwających symulacji koszty handlowe są ustalone jako:

  • Poślizg = 0,6 punktu
  • Prowizja = 1$ za każdą transakcję (jedna runda będzie kosztować 2$)

Wystarczy napisać kilka liczb, jeśli nasz zysk brutto wynosiłby 10 punktów, 1 punkt = 25 USD, więc 250 USD z uwzględnieniem kosztów transakcyjnych, nasz zysk netto wyniósłby (10 - 0.6)*$25 - 2 = $233 .

Poniższy kod pokazuje symulację poprzedniej strategii handlowej ze stop lossem -3 punktów. Niebieska krzywa to krzywa skumulowanych zwrotów. Jedyne uwzględnione koszty to poślizg (0,6 punktu), a wynik jest wyrażony w punktach bazowych (ta sama jednostka bazowa wartości S&P 500 pobrana z 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)) 

Model drzewa decyzyjnego, zwykle idący w górę iw prawo, ale z mniej wyraźnymi skokami, tak jak w powyższym kodzie Pythona.

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

Poniższy kod służy do tworzenia prognoz w nieco inny sposób. Proszę zwrócić uwagę na metodę predict , która jest wywoływana z dodatkowym parametrem output_type = “probability” . Ten parametr służy do zwracania prawdopodobieństw przewidywanych wartości zamiast przewidywania ich klasy ( +1 dla wyniku przewidywanego pozytywnie, -1 dla wyniku przewidywanego negatywnie). Prawdopodobieństwo większe lub równe 0.5 jest związane z przewidywaną wartością +1 , a wartość prawdopodobieństwa mniejsza niż 0.5 jest związana z przewidywaną wartością -1 . Im wyższe to prawdopodobieństwo, tym większa szansa na przewidzenie prawdziwego Up Day .

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

Teraz testujemy model za pomocą funkcji pomocniczej o nazwie backtest_ml_model , która oblicza serię skumulowanych zwrotów, w tym poślizg i prowizje, oraz wykreśla ich wartości. Dla zwięzłości, bez dokładnego wyjaśniania funkcji backtest_ml_model , ważnym szczegółem do podkreślenia jest to, że zamiast filtrować te dni z przewidywanym outcome = 1 , jak to zrobiliśmy w poprzednim przykładzie, teraz filtrujemy te predictions_prob równe lub większe niż threshold = 0.5 , w następujący sposób:

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

Pamiętaj, że zysk netto każdego dnia handlowego wynosi: Net gain = (Gross gain - SLIPPAGE) * MULT - 2 * COMMISSION .

Inną ważną miarą używaną do oceny wartości strategii handlowej jest Maximum Drawdown . Ogólnie rzecz biorąc, mierzy największy pojedynczy spadek od szczytu do dna wartości zainwestowanego portfela. W naszym przypadku jest to najistotniejszy spadek od szczytu do dołu krzywej kapitałowej (mamy tylko jeden aktyw w naszym portfelu, S&P 500). Mając zatem SArray zysków i strat pnl , obliczamy wypłatę jako:

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

Wewnątrz funkcji pomocniczej backtest_summary jest obliczany:

  • Maksymalna wypłata (w dolarach), jak pokazano powyżej.
  • Dokładność metodą Graphlab.evaluation .
  • Precyzja, metodą Graphlab.evaluation .
  • Przypomnijmy, za pomocą metody Graphlab.evaluation .

Podsumowując, poniższy przykład pokazuje krzywą kapitału reprezentującą skumulowane zwroty strategii modelu, z wszystkimi wartościami wyrażonymi w dolarach.

 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) 

Wykres DecisionTree, z osią Y oznaczoną „dolarami” i dochodzącą do 30 000 oraz osią X oznaczoną „# of roundturns” i rozciągającą się do 600, zgodnie z powyższym kodem Pythona. Same dane na wykresie są identyczne z poprzednim renderowaniem.

 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

Aby zwiększyć precyzję prognozowanych wartości, zamiast standardowego prawdopodobieństwa 0.5 (50 procent) wybieramy wyższą wartość progową, aby mieć większą pewność, że model przewiduje dzień Up .

 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) 

Wykres Drzewa decyzyjnego, z osią Y oznaczoną „dolarami” i dochodzącą do 30 000 oraz osią X oznaczoną „# of roundturns” i tym razem sięgającą tylko 250, co jest renderowane przez powyższy kod Pythona. Same dane na wykresie są podobne do poprzedniego renderowania, ale jeszcze bardziej wygładzone.

 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

Jak widać na powyższym wykresie, krzywa equity jest znacznie lepsza niż wcześniej (Sharpe to 6,5 zamiast 3,5), nawet przy mniejszej liczbie rund.

Od tego momentu rozważymy wszystkie kolejne modele z progiem wyższym niż wartość standardowa.

Szkolenie klasyfikatora logistycznego

Możemy zastosować nasze badania, tak jak to zrobiliśmy wcześniej z drzewem decyzyjnym, w modelu klasyfikatora logistycznego. GraphLab Create ma ten sam interfejs co obiekt Logistic Classifier i wywołamy metodę create , aby zbudować nasz model z tą samą listą parametrów. Co więcej, wolimy przewidywać wektor prawdopodobieństwa zamiast przewidywanego wektora klasy (składającego się z +1 dla pozytywnego wyniku i -1 dla negatywnego wyniku), więc mielibyśmy próg większy niż 0.5 , aby osiągnąć lepszą precyzję w naszym prognozowanie.

 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) 

Wykres LogisticClassifier, z osią Y oznaczoną „dolarami” i tym razem dochodzącą do 50 000, oraz osią X oznaczoną „# of roundturns” i rozciągającą się teraz do 450, zgodnie z powyższym kodem Pythona. Same dane na wykresie są podobne do poprzedniego renderowania pod względem ogólnego trendu.

 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

W tym przypadku istnieje podsumowanie bardzo podobne do drzewa decyzyjnego. W końcu oba modele są klasyfikatorami, przewidują tylko klasę wyników binarnych ( +1 , -1 ).

Trening modelu regresji liniowej

Główną różnicą tego modelu jest to, że zajmuje się wartościami ciągłymi, a nie klasami binarnymi, jak wspomniano wcześniej. Nie musimy trenować modelu ze zmienną docelową równą +1 dla dni Up i -1 dla dni Down , nasz cel musi być zmienną ciągłą. Ponieważ chcemy przewidzieć dodatni zysk lub innymi słowy cenę zamknięcia wyższą niż cena otwarcia , teraz celem musi być kolumna wzmocnienia naszego zestawu treningowego. Ponadto lista funkcji musi składać się z wartości ciągłych, takich jak poprzednie Open , Close , itp.

Dla zwięzłości nie będę omawiał szczegółów wyboru właściwych funkcji, ponieważ wykracza to poza zakres tego artykułu, który jest bardziej skłonny pokazać, jak powinniśmy zastosować różne modele uczenia maszynowego w zestawie danych. Lista parametrów przekazywanych do metody create to:

  • training - jest to zestaw treningowy zawierający kolumny cech oraz kolumnę docelową.
  • target - jest to nazwa kolumny zawierającej zmienną docelową.
  • validation_set - jest to zbiór danych do monitorowania wydajności uogólniania modelu. W naszym przypadku nie mamy validation_set .
  • features - jest to lista nazw kolumn cech używanych do uczenia modelu, dla tego modelu użyjemy innego zestawu względem modeli klasyfikatora.
  • verbose - jeśli true , podczas szkolenia zostaną wydrukowane informacje o postępach.
  • max_iterations - jest to maksymalna dozwolona liczba przejść przez dane. Więcej przejść nad danymi może skutkować dokładniejszym wytrenowaniem modelu.
 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)

Do tej pory mamy predykcje, które są SArray przewidywanych korzyści, podczas gdy predictions_prob to SArray ze znormalizowanymi wartościami predictions . Aby mieć dobrą celność i określoną liczbę obrotów, porównywalną z poprzednimi modelami, wybrałem wartość progową 0.4 . W przypadku predictions_prob mniejszej niż 0.4 funkcja pomocnicza backtest_linear_model nie otworzy transakcji, ponieważ oczekiwany jest dzień Down . W przeciwnym razie transakcja zostanie otwarta.

 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) 

Wykres regresji liniowej, z osią Y oznaczoną „dolarami” i dochodzącą do 45 000 oraz osią X oznaczoną „# of roundturns” i rozciągającą się do 350, zgodnie z powyższym kodem Pythona. Same dane na wykresie są ponownie podobne, ale nie identyczne z poprzednim renderowaniem.

 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

Trening wzmocnionego drzewa

Tak jak poprzednio trenowaliśmy drzewo decyzyjne, teraz będziemy trenować klasyfikator drzewa wzmocnionego z tymi samymi parametrami, które są używane w innych modelach klasyfikatorów. Dodatkowo ustawiamy liczbę max_iterations = 12 w celu zwiększenia maksymalnej liczby iteracji dla wzmocnienia. Każda iteracja skutkuje utworzeniem dodatkowego drzewa. Ustawiliśmy również wyższą wartość progu niż 0.5 , aby zwiększyć precyzję.

 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) 

Wykres BoostedTreesClassifier, z osią Y oznaczoną „dolarami” i dochodzącą do 25 000 oraz osią X oznaczoną „# of roundturns” i rozciągającą się do 250, zgodnie z powyższym kodem Pythona. Same dane na wykresie są ponownie podobne do poprzedniego renderowania, z ostrzejszym wzrostem około 175 na osi X.

 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

Trening losowego lasu

To jest nasz ostatni wytrenowany model, losowy klasyfikator lasu, złożony z zestawu drzew decyzyjnych. Maksymalna liczba drzew do użycia w modelu jest ustawiona na num_trees = 10 , aby uniknąć zbytniej złożoności i przesadnego dopasowania.

 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) 

Wykres RandomForestClassifier z osią Y oznaczoną jako „dolary” i dochodzącą do 40 000 oraz osią X oznaczoną „# of roundturns” i rozciągającą się do 350, zgodnie z powyższym kodem Pythona. Same dane na wykresie są ponownie podobne do poprzedniego renderowania.

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

Wniosek

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.

Powiązane: Wprowadzenie do teorii uczenia maszynowego i jej zastosowań: wizualny samouczek z przykładami

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.