Creșterea tranzacționării automate: Mașinile care tranzacționează S&P 500
Publicat: 2022-03-11În zilele noastre, peste 60 la sută din activitățile de tranzacționare cu diferite active (cum ar fi acțiuni, futures pe index, mărfuri) nu mai sunt realizate de comercianți „ființe umane”, ci se bazează pe tranzacționare automată. Există programe specializate bazate pe algoritmi specifici care cumpără și vând automat active pe diferite piețe, menite să obțină un randament pozitiv pe termen lung.
În acest articol, vă voi arăta cum să preziceți, cu o bună acuratețe, cum ar trebui plasată următoarea tranzacție pentru a obține un câștig pozitiv. Pentru acest exemplu, ca activ de bază pentru tranzacționare, am selectat indicele S&P 500, media ponderată a 500 de companii din SUA cu capitalizare mai mare. O strategie foarte simplă de implementat este să cumpărați indicele S&P 500 atunci când Wall Street Exchange începe tranzacționarea, la 9:30 AM, și să îl vindeți în sesiunea de închidere la 4:00 PM, ora Estului. Dacă prețul de închidere al indicelui este mai mare decât prețul de deschidere, există un câștig pozitiv, în timp ce un câștig negativ s-ar obține dacă prețul de închidere este mai mic decât prețul de deschidere. Deci întrebarea este: de unde știm dacă sesiunea de tranzacționare se va termina cu un preț de închidere mai mare decât prețul de deschidere? Machine Learning este un instrument puternic pentru a realiza o sarcină atât de complexă și poate fi un instrument util pentru a ne sprijini în decizia de tranzacționare.
Machine Learning este noua frontieră a multor aplicații utile din viața reală. Tranzacționarea financiară este una dintre acestea și este folosită foarte des în acest sector. Un concept important despre Machine Learning este că nu trebuie să scriem cod pentru orice fel de reguli posibile, cum ar fi recunoașterea modelelor. Acest lucru se datorează faptului că fiecare model asociat cu Machine Learning învață din datele în sine și apoi poate fi folosit ulterior pentru a prezice date noi nevăzute.
Disclaimer : Scopul acestui articol este de a arăta cum să antrenați metodele de învățare automată, iar în exemplele de cod furnizate nu sunt explicate toate funcțiile. Acest articol nu are scopul de a permite unei singure să copieze și să lipească tot codul și să ruleze aceleași teste furnizate, deoarece lipsesc unele detalii care au fost în afara domeniului de aplicare al articolului. De asemenea, sunt necesare cunoștințe de bază despre Python. Intenția principală a articolului este de a arăta un exemplu despre modul în care învățarea automată poate fi eficientă pentru a prezice cumpărările și vânzările în sectorul financiar. Cu toate acestea, comerțul cu bani reali înseamnă să ai multe alte abilități, cum ar fi managementul banilor și managementul riscului. Acest articol este doar o mică parte din „imaginea de ansamblu”.
Creați primul program de tranzacționare automată a datelor financiare
Deci, doriți să vă creați primul program pentru a analiza datele financiare și a prezice tranzacția corectă? Lasă-mă să-ți arăt cum. Voi folosi codul Python pentru Machine Learning și vom folosi datele istorice de la serviciul Yahoo Finance. După cum am menționat anterior, datele istorice sunt necesare pentru a antrena modelul înainte de a face predicțiile noastre.
Pentru a începe, trebuie să instalăm:
- Python și, în special, sugerez să utilizați notebook-ul IPython.
- Pachetul Yahoo Finance Python (numele exact este
yahoo-finance
) prin comanda terminalului:pip install yahoo-finance
. - O versiune de probă gratuită a pachetului Machine Learning numit GraphLab. Nu ezitați să verificați documentația utilă a bibliotecii respective.
Rețineți că doar o parte din GraphLab este open source, SFrame, așa că pentru a folosi întreaga bibliotecă avem nevoie de o licență. Există o licență gratuită de 30 de zile și o licență non-comercială pentru studenții sau cei care participă la competițiile Kaggle. Din punctul meu de vedere, GraphLab Create este o bibliotecă foarte intuitivă și ușor de utilizat pentru a analiza datele și a antrena modele de învățare automată.
Săpat în codul Python
Să cercetăm ceva cod Python pentru a vedea cum să descărcați date financiare de pe Internet. Vă sugerez să folosiți notebook-ul IPython pentru a testa următorul cod, deoarece IPython are multe avantaje în comparație cu un IDE tradițional, mai ales atunci când trebuie să combinăm codul sursă, codul de execuție, datele tabelului și diagramele împreună pe același document. Pentru o scurtă explicație despre utilizarea blocnotesului IPython, vă rugăm să consultați articolul Introducere în blocnotesul IPython.
Deci, să creăm un nou notebook IPython și să scriem un cod pentru a descărca prețurile istorice ale indexului S&P 500. Rețineți, dacă preferați să utilizați alte instrumente, puteți începe cu un nou proiect Python în IDE-ul preferat.
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'}
Aici, hist_quotes
este o listă de dicționare, iar fiecare obiect de dicționar este o zi de tranzacționare cu valori Open
, High
, Low
, Close
, Adj_close
, Volume
, Symbol
și Date
. În timpul fiecărei zile de tranzacționare, prețul se modifică de obicei începând de la prețul de deschidere Open
la prețul de închidere Close
și atingând o valoare maximă și minimă High
și Low
. Trebuie să-l citim și să creăm liste cu fiecare dintre cele mai relevante date. De asemenea, datele trebuie să fie ordonate după cele mai recente valori la început, așa că trebuie să le inversăm:
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']))
Putem împacheta toate ghilimele descărcate într-un obiect SFrame
, care este un cadru de date foarte scalabil bazat pe coloană, și este comprimat. Unul dintre avantaje este că poate fi, de asemenea, mai mare decât cantitatea de RAM, deoarece este suportat de disc. Puteți verifica documentația pentru a afla mai multe despre SFrame.
Deci, să stocăm și apoi să verificăm datele istorice:
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)
închide | datetime | înalt | scăzut | deschis | volum |
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 |
Acum putem salva datele pe disc cu metoda SFrame
save
, după cum urmează:
qq.save(“SP500_daily.bin”) # once data is saved, we can use the following instruction to retrieve it qq = gl.SFrame(“SP500_daily.bin/”)
Să vedem cum arată S&P 500
Pentru a vedea cum vor arăta datele S&P 500 încărcate, putem folosi următorul cod:
import matplotlib.pyplot as plt %matplotlib inline # only for those who are using IPython notebook plt.plot(qq['close'])
Rezultatul codului este următorul grafic:
Antrenarea unor modele de învățare automată
Adăugarea rezultatului
După cum am afirmat în partea introductivă a acestui articol, scopul fiecărui model este de a prezice dacă prețul de închidere va fi mai mare decât prețul de deschidere. Prin urmare, în acest caz, putem obține un randament pozitiv atunci când cumpărăm activul suport. Deci, trebuie să adăugăm o coloană de outcome
pe datele noastre, care va fi target
sau variabila predicted
. Fiecare rând al acestei noi coloane va fi:
-
+1
pentru o zi Up cu un preț deClosing
mai mare decât prețul deOpening
. -
-1
pentru o zi Down cu un preț deClosing
mai mic decât prețul deOpening
.
# 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']
Deoarece trebuie să evaluăm cu câteva zile înainte de ultima zi de tranzacționare, trebuie să întârziem datele cu una sau mai multe zile. Pentru acest tip de operație de întârziere , avem nevoie de un alt obiect din pachetul GraphLab numit TimeSeries
. TimeSeries
are o shift
de metodă care întârzie datele cu un anumit număr de rânduri.
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
Adăugarea de predictori
Predictorii sunt un set de variabile caracteristice care trebuie alese pentru a antrena modelul și a prezice rezultatul nostru. Deci, alegerea factorului de prognoză este esențială, dacă nu cea mai importantă, componentă a prognozatorului.
Doar pentru a numi câteva exemple, un factor de luat în considerare poate fi dacă închiderea de astăzi este mai mare decât închiderea de ieri și aceasta ar putea fi prelungită cu închiderea în două zile anterioare etc. O alegere similară poate fi tradusă cu următorul cod:
ts['feat1'] = ts['close'] > ts_1['close'] ts['feat2'] = ts['close'] > ts_2['close']
După cum se arată mai sus, am adăugat două coloane de caracteristici noi, feat1
și feat2
în setul nostru de date ( ts
) care conține 1
dacă comparația este adevărată și 0
în caz contrar.
Acest articol are scopul de a oferi un exemplu de învățare automată aplicată sectorului financiar. Prefer să mă concentrez asupra modului în care modelele de învățare automată pot fi utilizate cu datele financiare și nu vom intra în detalii despre cum să alegem factorii potriviți pentru a antrena modelele. Este prea exhaustiv să explic de ce anumiți factori sunt utilizați în raport cu alții, din cauza creșterii considerabile a complexității. Cercetarea mea de muncă este de a studia multe ipoteze de alegere a factorilor pentru a crea un bun predictor. Deci, pentru început, vă sugerez să experimentați cu o mulțime de combinații diferite de factori, pentru a vedea dacă aceștia pot crește acuratețea modelului.
# 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):]
Antrenarea unui model de arbore de decizie
GraphLab Create are o interfață foarte curată pentru a implementa modele de învățare automată. Fiecare model are o metodă create
folosită pentru a se potrivi modelului cu un set de date de antrenament. Parametrii tipici sunt:
-
training
- este un set de antrenament care conține coloane de caracteristici și o coloană țintă. -
target
- este numele coloanei care conține variabila țintă. -
validation_set
- este un set de date pentru monitorizarea performanței de generalizare a modelului. În cazul nostru, nu avemvalidation_set
. -
features
- este o listă de nume de coloane ale caracteristicilor utilizate pentru antrenamentul modelului. -
verbose
- dacă estetrue
, imprimați informațiile despre progres în timpul antrenamentului.
În timp ce alți parametri sunt tipici modelului în sine, cum ar fi:
-
max_depth
- este adâncimea maximă a unui copac.
Cu următorul cod construim arborele nostru de decizie:
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)
Măsurarea performanței modelului montat
Precizia este o măsură importantă pentru a evalua bunătatea prognozatorului. Este numărul de predicții corecte împărțit la numărul total de puncte de date. Deoarece modelul este echipat cu date de antrenament, acuratețea evaluată cu setul de antrenament este mai bună decât cea obținută cu un set de testare.
Precizia este fracția de predicții pozitive care sunt pozitive. Avem nevoie de precizie pentru a fi un număr mai aproape de 1
, pentru a obține o rată de câștig „perfectă”. decision_tree
, ca un alt clasificator din pachetul GraphLab Create, are metoda sa de evaluate
pentru a obține multe valori importante ale modelului adaptat.
Rechemarea cuantifică capacitatea unui clasificator de a prezice exemple pozitive. Reamintirea poate fi interpretată ca probabilitatea ca un exemplu pozitiv selectat aleatoriu să fie identificat corect de către clasificator. Avem nevoie ca precizia să fie un număr mai aproape de 1
, pentru a obține o rată de câștig „perfectă”.
Următorul cod va arăta acuratețea modelului montat atât cu setul de antrenament, cât și cu setul de testare:
decision_tree.evaluate(training)['accuracy'], decision_tree.evaluate(testing)['accuracy'] (0.6077348066298343, 0.577373211963589)
După cum se arată mai sus, acuratețea modelului cu setul de testare este de aproximativ 57 la sută, ceea ce este cumva mai bine decât aruncarea unei monede (50 la sută).
Predictarea datelor
GraphLab Create are aceeași interfață pentru a prezice date de la diferite modele montate. Vom folosi metoda predict
, care necesită un set de teste pentru a prezice variabila țintă, în cazul nostru outcome
. Acum, putem prezice datele din setul de testare:
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)
datetime | rezultat | previziuni |
05-04-2013 00:00:00 | -1 | -1 |
08-04-2013 00:00:00 | 1 | 1 |
2013-04-09 00:00:00 | 1 | 1 |
10-04-2013 00:00:00 | 1 | -1 |
2013-04-11 00:00:00 | 1 | -1 |
2013-04-12 00:00:00 | -1 | -1 |
15-04-2013 00:00:00 | -1 | 1 |
16-04-2013 00:00:00 | 1 | 1 |
17-04-2013 00:00:00 | -1 | -1 |
18-04-2013 00:00:00 | -1 | 1 |
Fals pozitive sunt cazuri în care modelul prezice un rezultat pozitiv, în timp ce rezultatul real din setul de testare este negativ. Viceversa, fals-negative sunt cazurile în care modelul prezice un rezultat negativ în care rezultatul real din setul de testare este pozitiv.
Strategia noastră de tranzacționare așteaptă un rezultat prezis pozitiv pentru a cumpăra S&P 500 la prețul de Opening
și a-l vinde la prețul de Closing
, așa că speranța noastră este să avem cea mai mică rată a fals-pozitive pentru a evita pierderile. Cu alte cuvinte, ne așteptăm ca modelul nostru să aibă cea mai mare rată de precizie .
După cum putem vedea, există două fals negative (la 2013-04-10 și 2013-04-11) și două fals pozitive (la 2013-04-15 și 2013-04-18) în primele zece valori prezise ale set de testare.
Cu un calcul simplu, luând în considerare acest mic set de zece predicții:
- precizie = 6/10 = 0,6 sau 60%
- precizie = 3/5 = 0,6 sau 60%
- reamintire = 3/5 = 0,6 sau 60%
Rețineți că, de obicei, numerele de mai sus sunt diferite unele de altele, dar în acest caz sunt aceleași.
Testarea inversă a modelului
Simulăm acum modul în care modelul s-ar tranzacționa folosind valorile sale prezise. Dacă rezultatul prezis este egal cu +1
, înseamnă că ne așteptăm la o zi Up . Cu o zi Up cumpărăm indicele la începutul sesiunii și vindem indicele la sfârșitul sesiunii în aceeași zi. În schimb, dacă rezultatul prezis este egal cu -1
, ne așteptăm la o zi Down , deci nu vom tranzacționa în timpul acelei zile.
Profitul și pierderea ( pnl
) pentru o tranzacție zilnică completă, numită și round turn , în acest exemplu este dat de:
-
pnl = Close - Open
(pentru fiecare zi de tranzacționare)
Cu codul afișat mai jos, apelez la funcția de ajutor plot_equity_chart
pentru a crea o diagramă cu curba câștigurilor cumulate (curba capitalurilor proprii). Fără a merge prea adânc, pur și simplu obține o serie de valori de profit și pierdere și calculează seria de sume cumulate de reprezentat.
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
Aici, Sharpe este raportul Sharpe anual, un indicator important al bunătății modelului de tranzacționare.
Luând în considerare tranzacțiile exprimate zi de zi, în timp ce mean
este media listei de profit și pierdere, iar sd
este abaterea standard. Pentru simplitate în formula descrisă mai sus, am considerat un randament fără risc egal cu 0.
Câteva elemente de bază despre tranzacționare
Tranzacționarea indicelui necesită cumpărarea unui activ, care este derivat direct din indice. Mulți brokeri reproduc indicele S&P 500 cu un produs derivat numit CFD (Contract for difference), care este un acord între două părți pentru a schimba diferența dintre prețul de deschidere și prețul de închidere al unui contract.
Exemplu : Cumpărați 1 CFD S&P 500 la Open
(valoarea este 2000), vindeți-l la Close
zilei (valoarea este 2020). Diferența, deci și câștigul, este de 20 de puncte. Dacă fiecare punct are o valoare de 25 USD:
- Câștigul brut este
20 points x $25 = $500
cu 1 contract CFD.
Să presupunem că brokerul păstrează un derapaj de 0,6 puncte pentru propriul venit:

- Câștigul net este
(20 - 0.6) points x $25 = $485
.
Un alt aspect important de luat în considerare este evitarea pierderilor semnificative în cadrul unei tranzacții. Ele pot apărea ori de câte ori rezultatul prezis este +1
, dar valoarea rezultatului real este -1
, deci este un fals pozitiv . În acest caz, sesiunea de sfârșit se dovedește a fi o zi Down cu un preț de închidere mai mic decât cel de deschidere și obținem o pierdere.
Un ordin stop loss trebuie plasat pentru a proteja împotriva unei pierderi maxime pe care am tolera-o în cadrul unei tranzacții, iar un astfel de ordin este declanșat ori de câte ori prețul activului scade sub o valoare fixă pe care am stabilit-o anterior.
Dacă ne uităm la seria temporală descărcată de pe Yahoo Finance la începutul acestui articol, fiecare zi are un preț Low
, care este cel mai mic preț atins în acea zi. Dacă stabilim un nivel de stop de -3
puncte departe de prețul de Opening
și Low - Open = -5
ordinul stop va fi declanșat, iar poziția deschisă va fi închisă cu o pierdere de -3
puncte în loc de -5
. Aceasta este o metodă simplă de a reduce riscul. Următorul cod reprezintă funcția mea de ajutor pentru a simula o tranzacție cu un nivel de oprire:
# 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']
Costuri de tranzacționare
Costurile de tranzacție sunt cheltuieli suportate la cumpărarea sau vânzarea titlurilor de valoare. Costurile de tranzacție includ comisioanele și spread-urile brokerilor (diferența dintre prețul plătit de dealer pentru un titlu și prețul plătit de cumpărător) și trebuie luate în considerare dacă dorim să ne testăm strategia, similar unui scenariu real. Derapajul în tranzacționarea acțiunilor apare adesea atunci când există o schimbare a spread-ului. În acest exemplu și pentru următoarele simulări în curs, costurile de tranzacționare sunt fixate astfel:
- Alunecare = 0,6 puncte
- Comision = 1$ pentru fiecare tranzacție (o rundă va costa 2$)
Doar pentru a scrie câteva cifre, dacă câștigul nostru brut ar fi de 10 puncte, 1 punct = 25 USD, deci 250 USD inclusiv costurile de tranzacționare, câștigul nostru net ar fi (10 - 0.6)*$25 - 2 = $233
.
Următorul cod arată o simulare a strategiei anterioare de tranzacționare cu un stop loss de -3 puncte. Curba albastră este curba randamentelor cumulate. Singurele costuri luate în considerare sunt derapajul (0,6 puncte), iar rezultatul este exprimat în puncte de bază (aceeași unitate de bază a valorilor S&P 500 descărcată de la 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
Următorul cod este folosit pentru a face predicții într-un mod ușor diferit. Vă rugăm să acordați atenție metodei de predict
care este apelată cu un parametru suplimentar output_type = “probability”
. Acest parametru este utilizat pentru a returna probabilitățile valorilor prezise în loc de predicția lor de clasă ( +1
pentru un rezultat prezis pozitiv, -1
pentru un rezultat prezis negativ). O probabilitate mai mare sau egală cu 0.5
este asociată cu o valoare prezisă +1
și o valoare a probabilității mai mică de 0.5
este legată de o valoare prezisă de -1
. Cu cât această probabilitate este mai mare, cu atât avem mai multe șanse de a prezice o adevărată Up Day .
predictions_prob = decision_tree.predict(testing, output_type = 'probability') # predictions_prob will contain probabilities instead of the predicted class (-1 or +1)
Acum testăm înapoi modelul cu o funcție de ajutor numită backtest_ml_model
care calculează seria de returnări cumulative, inclusiv alunecarea și comisioanele, și trasează valorile acestora. Pentru concizie, fără a explica în detaliu funcția backtest_ml_model
, detaliul important de evidențiat este că, în loc să filtram acele zile cu un outcome = 1
, așa cum am făcut în exemplul anterior, acum filtrăm acele predictions_prob
egale sau mai mari decât un threshold = 0.5
, ca următorul:
trades = testing[predictions_prob>=0.5][('datetime', 'gain', 'ho', 'lo', 'open', 'close')]
Amintiți-vă că câștigul net al fiecărei zile de tranzacționare este: Net gain = (Gross gain - SLIPPAGE) * MULT - 2 * COMMISSION
.
O altă măsură importantă folosită pentru a evalua bunătatea unei strategii de tranzacționare este Maximum Drawdown . În general, măsoară cea mai mare scădere unică de la vârf la jos, a valorii unui portofoliu investit. În cazul nostru, este cea mai semnificativă scădere de la vârf în jos a curbei acțiunilor (avem doar un activ în portofoliu, S&P 500). Deci, având în vedere un SArray
de profit și pierdere pnl
, calculăm reducerea astfel:
drawdown = pnl - pnl.cumulative_max() max_drawdown = min(drawdown)
În interiorul funcției de ajutor backtest_summary
este calculată:
- Retragere maximă (în dolari), așa cum se arată mai sus.
- Precizie, cu metoda
Graphlab.evaluation
. - Precizie, cu metoda
Graphlab.evaluation
. - Recall, cu metoda
Graphlab.evaluation
.
Punând totul împreună, următorul exemplu arată curba capitalului propriu reprezentând randamentele cumulate ale strategiei modelului, cu toate valorile exprimate în dolari.
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
Pentru a crește precizia valorilor prognozate, în loc de o probabilitate standard de 0.5
(50 la sută), alegem o valoare de prag mai mare, pentru a fi mai siguri că modelul prezice o zi 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)
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
După cum putem vedea din graficul de mai sus, curba acțiunilor este mult mai bună decât înainte (Sharpe este 6,5 în loc de 3,5), chiar și cu mai puține rotații.
Din acest moment, vom lua în considerare toate modelele următoare cu un prag mai mare decât o valoare standard.
Formarea unui clasificator logistic
Putem aplica cercetările noastre, așa cum am făcut anterior cu arborele de decizie, într-un model de clasificator logistic. GraphLab Create are aceeași interfață cu obiectul Logistic Classifier și vom apela metoda create
pentru a construi modelul nostru cu aceeași listă de parametri. Mai mult decât atât, preferăm să prezicăm vectorul de probabilitate în loc de vectorul de clasă prezis (compus din +1
pentru un rezultat pozitiv și -1
pentru un rezultat negativ), astfel încât am avea un prag mai mare de 0.5
pentru a obține o precizie mai bună în prognoza.
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
În acest caz, există un rezumat foarte asemănător cu Decision Tree. La urma urmei, ambele modele sunt clasificatoare, ele prezic doar o clasă de rezultate binare ( +1
, -1
).
Antrenarea unui model de regresie liniară
Principala diferență a acestui model este că se ocupă de valori continue în loc de clase binare, așa cum am menționat anterior. Nu trebuie să antrenăm modelul cu o variabilă țintă egală cu +1
pentru zilele în sus și -1
pentru zilele în jos , ținta noastră trebuie să fie o variabilă continuă. Deoarece dorim să anticipăm un câștig pozitiv sau, cu alte cuvinte, un preț de închidere mai mare decât prețul de deschidere , acum ținta trebuie să fie coloana de câștig a setului nostru de antrenament. De asemenea, lista de caracteristici trebuie să fie compusă din valori continue, cum ar fi anterioară Open
, Close
, etc.
Pentru concizie, nu voi intra în detalii despre cum să selectăm caracteristicile potrivite, deoarece acest lucru depășește scopul acestui articol, care este mai înclinat să arate cum ar trebui să aplicăm diferite modele de învățare automată asupra unui set de date. Lista parametrilor trecuți metodei create sunt:
-
training
- este un set de antrenament care conține coloane de caracteristici și o coloană țintă. -
target
- este numele coloanei care conține variabila țintă. -
validation_set
- este un set de date pentru monitorizarea performanței de generalizare a modelului. În cazul nostru, nu avemvalidation_set
. -
features
- este o listă de nume de coloane ale caracteristicilor utilizate pentru antrenamentul modelului, pentru acest model vom folosi un alt set referitor la modelele Clasificatorului. -
verbose
- dacă estetrue
, va imprima informații despre progres în timpul antrenamentului. -
max_iterations
- este numărul maxim de treceri permise prin date. Mai multe treceri peste date pot duce la un model antrenat mai precis.
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)
Până acum, avem predicții care reprezintă SArray
de câștiguri prezise, în timp ce predictions_prob
este SArray
cu valorile predictions
normalizate. Pentru a avea o precizie bună și un anumit număr de ture rotunde, comparabil cu modelele anterioare, am ales o valoare de prag de 0.4
. Pentru un predictions_prob
mai mic de 0.4
, funcția de ajutor backtest_linear_model
nu va deschide o tranzacție deoarece este așteptată o zi Down . În caz contrar, se va deschide o tranzacție.
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
Antrenarea unui arbore amplificat
Așa cum am antrenat anterior un arbore de decizie, acum vom antrena un clasificator de arbore îmbunătățit cu aceiași parametri folosiți pentru alte modele de clasificator. În plus, am setat numărul de max_iterations = 12
pentru a crește numărul maxim de iterații pentru boosting. Fiecare iterație are ca rezultat crearea unui arbore suplimentar. De asemenea, am stabilit o valoare a pragului mai mare decât 0.5
pentru a crește precizia.
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
Antrenarea unei păduri aleatorii
Acesta este ultimul nostru model antrenat, un clasificator de pădure aleatoriu, compus dintr-un ansamblu de arbori de decizie. Numărul maxim de arbori de utilizat în model este setat la num_trees = 10
, pentru a evita prea multă complexitate și supraadaptare.
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.
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%.
Concluzie
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.