L'ascesa del trading automatizzato: le macchine scambiano l'S&P 500

Pubblicato: 2022-03-11

Al giorno d'oggi, oltre il 60 percento delle attività di trading con asset diversi (come azioni, futures su indici, materie prime) non sono più effettuate da trader "umani", ma si affidano invece al trading automatizzato. Esistono programmi specializzati basati su algoritmi particolari che acquistano e vendono automaticamente asset su mercati diversi, con l'obiettivo di ottenere un rendimento positivo nel lungo periodo.

In questo articolo, ti mostrerò come prevedere, con buona precisione, come dovrebbe essere posizionato il prossimo trade per ottenere un guadagno positivo. Per questo esempio, come asset sottostante da negoziare, ho selezionato l'indice S&P 500, la media ponderata di 500 società statunitensi a maggiore capitalizzazione. Una strategia molto semplice da implementare è quella di acquistare l'indice S&P 500 quando Wall Street Exchange inizia a fare trading, alle 9:30, e venderlo nella sessione di chiusura alle 16:00 Eastern Time. Se il prezzo di chiusura dell'indice è superiore al prezzo di apertura, si ha un guadagno positivo, mentre un guadagno negativo si ottiene se il prezzo di chiusura è inferiore al prezzo di apertura. Quindi la domanda è: come facciamo a sapere se la sessione di trading finirà con un prezzo di chiusura superiore al prezzo di apertura? L'apprendimento automatico è uno strumento potente per svolgere un'attività così complessa e può essere uno strumento utile per supportarci nella decisione di trading.

L'apprendimento automatico è la nuova frontiera di molte utili applicazioni della vita reale. Il trading finanziario è uno di questi, ed è usato molto spesso in questo settore. Un concetto importante di Machine Learning è che non abbiamo bisogno di scrivere codice per ogni tipo di possibile regola, come il riconoscimento di schemi. Questo perché ogni modello associato a Machine Learning apprende dai dati stessi e quindi può essere utilizzato in seguito per prevedere nuovi dati invisibili.

Dichiarazione di non responsabilità: lo scopo di questo articolo è mostrare come addestrare i metodi di Machine Learning e negli esempi di codice forniti non vengono spiegate tutte le funzioni. Questo articolo non ha lo scopo di consentire a uno di copiare e incollare tutto il codice ed eseguire gli stessi test forniti, poiché mancano alcuni dettagli che non rientravano nell'ambito dell'articolo. Inoltre, è richiesta una conoscenza di base di Python. L'intenzione principale dell'articolo è mostrare un esempio di come l'apprendimento automatico può essere efficace per prevedere gli acquisti e le vendite nel settore finanziario. Tuttavia, fare trading con denaro reale significa avere molte altre abilità, come la gestione del denaro e la gestione del rischio. Questo articolo è solo un piccolo pezzo del "quadro generale".

Costruisci il tuo primo programma di trading automatizzato di dati finanziari

Quindi, vuoi creare il tuo primo programma per analizzare i dati finanziari e prevedere il giusto trade? Lascia che ti mostri come. Utilizzerò il codice Python per Machine Learning e utilizzeremo i dati storici del servizio Yahoo Finance. Come accennato in precedenza, i dati storici sono necessari per addestrare il modello prima di fare le nostre previsioni.

Per iniziare, dobbiamo installare:

  • Python, e in particolare suggerisco di utilizzare il notebook IPython.
  • Pacchetto Yahoo Finance Python (il nome esatto è yahoo-finance ) tramite il comando da terminale: pip install yahoo-finance .
  • Una versione di prova gratuita del pacchetto Machine Learning chiamato GraphLab. Sentiti libero di controllare la documentazione utile di quella libreria.

Si noti che solo una parte di GraphLab è open source, SFrame, quindi per utilizzare l'intera libreria è necessaria una licenza. C'è una licenza gratuita di 30 giorni e una licenza non commerciale per gli studenti o coloro che partecipano alle competizioni Kaggle. Dal mio punto di vista, GraphLab Create è una libreria molto intuitiva e facile da usare per analizzare dati e addestrare modelli di Machine Learning.

Scavare nel codice Python

Analizziamo un po' di codice Python per vedere come scaricare dati finanziari da Internet. Suggerisco di utilizzare il notebook IPython per testare il codice seguente, perché IPython presenta molti vantaggi rispetto a un IDE tradizionale, soprattutto quando è necessario combinare codice sorgente, codice di esecuzione, dati di tabella e grafici insieme sullo stesso documento. Per una breve spiegazione sull'utilizzo del notebook IPython, consultare l'articolo Introduzione al notebook IPython.

Quindi, creiamo un nuovo notebook IPython e scriviamo del codice per scaricare i prezzi storici dell'indice S&P 500. Nota, se preferisci usare altri strumenti, puoi iniziare con un nuovo progetto Python nel tuo IDE preferito.

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

Qui, hist_quotes è un elenco di dizionari e ogni oggetto dizionario è un giorno di negoziazione con valori Open , High , Low , Close , Adj_close , Volume , Symbol e Date . Durante ogni giornata di negoziazione, il prezzo cambia solitamente partendo dal prezzo di apertura Open al prezzo di chiusura Close , e raggiungendo un valore massimo e minimo High e Low . Dobbiamo leggerlo e creare elenchi di ciascuno dei dati più rilevanti. Inoltre, i dati devono essere ordinati inizialmente in base ai valori più recenti, quindi è necessario invertirli:

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

Possiamo comprimere tutte le virgolette scaricate in un oggetto SFrame , che è un frame di dati basato su colonne altamente scalabile, ed è compresso. Uno dei vantaggi è che può anche essere maggiore della quantità di RAM perché è supportato da disco. Puoi controllare la documentazione per saperne di più su SFrame.

Quindi, archiviamo e quindi controlliamo i dati storici:

 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)
chiudere appuntamento alto basso aprire volume
1283.27 02-01-2001 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

Ora possiamo salvare i dati su disco con il metodo SFrame save , come segue:

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

Vediamo come appare l'S&P 500

Per vedere come appariranno i dati S&P 500 caricati, possiamo usare il seguente codice:

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

L'output del codice è il seguente grafico:

Grafico S&P 500, generalmente in crescita nel tempo, come reso dal codice Python sopra.

Formazione di alcuni modelli di apprendimento automatico

Aggiunta del risultato

Come ho affermato nella parte introduttiva di questo articolo, l'obiettivo di ogni modello è prevedere se il prezzo di chiusura sarà superiore al prezzo di apertura. Quindi, in tal caso, possiamo ottenere un rendimento positivo acquistando l'attività sottostante. Quindi, dobbiamo aggiungere una colonna di outcome sui nostri dati che sarà la variabile target o predicted . Ogni riga di questa nuova colonna sarà:

  • +1 per un giorno Up con un prezzo di Closing superiore al prezzo di Opening .
  • -1 per un giorno Down con un prezzo di Closing inferiore al prezzo di 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']

Poiché dobbiamo valutare alcuni giorni prima dell'ultimo giorno di negoziazione, dobbiamo ritardare i dati di uno o più giorni. Per quel tipo di operazione in ritardo , abbiamo bisogno di un altro oggetto dal pacchetto GraphLab chiamato TimeSeries . TimeSeries ha uno shift metodo che ritarda i dati di un certo numero di righe.

 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

Aggiunta di predittori

I predittori sono un insieme di variabili caratteristiche che devono essere scelte per addestrare il modello e prevedere il nostro risultato . Quindi, la scelta del fattore di previsione è cruciale, se non la più importante, componente del previsore.

Solo per citare alcuni esempi, un fattore da considerare potrebbe essere se la chiusura di oggi è maggiore della chiusura di ieri, e potrebbe essere estesa con la chiusura di due giorni precedenti, ecc. Una scelta simile può essere tradotta con il seguente codice:

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

Come mostrato sopra, ho aggiunto due nuove colonne di funzionalità, feat1 e feat2 nel nostro set di dati ( ts ) contenenti 1 se il confronto è vero e 0 altrimenti.

Questo articolo intende fornire un esempio di Machine Learning applicato al settore finanziario. Preferisco concentrarmi su come i modelli di Machine Learning possono essere utilizzati con i dati finanziari e non entreremo nei dettagli su come scegliere i fattori giusti per addestrare i modelli. È troppo esauriente spiegare perché alcuni fattori vengono utilizzati rispetto ad altri, a causa di un notevole aumento della complessità. La mia ricerca di lavoro consiste nello studiare molte ipotesi di scelta dei fattori per creare un buon predittore. Quindi, per cominciare, ti suggerisco di sperimentare molte diverse combinazioni di fattori, per vedere se possono aumentare l'accuratezza del modello.

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

Formazione di un modello di albero decisionale

GraphLab Create ha un'interfaccia molto pulita per implementare modelli di Machine Learning. Ciascun modello dispone di un metodo create utilizzato per adattare il modello a un set di dati di addestramento. I parametri tipici sono:

  • training - è un set di training contenente colonne di funzionalità e una colonna di destinazione.
  • target - è il nome della colonna contenente la variabile target.
  • validation_set - è un set di dati per monitorare le prestazioni di generalizzazione del modello. Nel nostro caso, non abbiamo validation_set .
  • features - è un elenco di colonne con nomi di feature usate per addestrare il modello.
  • verbose - se true , stampa le informazioni sull'avanzamento durante l'allenamento.

Mentre altri parametri sono tipici del modello stesso, come ad esempio:

  • max_depth - è la profondità massima di un albero.

Con il codice seguente costruiamo il nostro albero decisionale:

 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)

Misurazione delle prestazioni del modello montato

La precisione è una metrica importante per valutare la bontà del previsore. È il numero di previsioni corrette diviso per il numero di punti dati totali. Poiché il modello è dotato di dati di addestramento, l'accuratezza valutata con il set di addestramento è migliore di quella ottenuta con un set di test.

La precisione è la frazione di previsioni positive che sono positive. Abbiamo bisogno che la precisione sia un numero più vicino a 1 , per ottenere una percentuale di vincita "perfetta". Il nostro decision_tree , come un altro classificatore del pacchetto GraphLab Create, ha il suo metodo evaluate per ottenere molte metriche importanti del modello adattato.

Recall quantifica la capacità di un classificatore di prevedere esempi positivi. Il richiamo può essere interpretato come la probabilità che un esempio positivo selezionato casualmente sia correttamente identificato dal classificatore. Abbiamo bisogno che la precisione sia un numero più vicino a 1 , per ottenere una percentuale di vincita "perfetta".

Il codice seguente mostrerà l' accuratezza del modello montato sia con il set di addestramento che con il set di test:

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

Come mostrato sopra, la precisione del modello con il set di test è di circa il 57 percento, il che è in qualche modo migliore del lancio di una moneta (50 percento).

Dati di previsione

GraphLab Create ha la stessa interfaccia per prevedere i dati da diversi modelli adattati. Utilizzeremo il metodo predict , che necessita di un set di test per prevedere la variabile target, nel nostro caso l' outcome . Ora possiamo prevedere i dati dal set di test:

 predictions = decision_tree.predict(testing) # and we add the predictions column in testing set testing['predictions'] = predictions # let's see the first 10 predictions, compared to real values (outcome column) testing[['datetime', 'outcome', 'predictions']].head(10)
appuntamento risultato predizioni
05-04-2013 00:00:00 -1 -1
08-04-2013 00:00:00 1 1
09-04-2013 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

I falsi positivi sono casi in cui il modello prevede un esito positivo mentre l'esito reale del set di test è negativo. Viceversa, i falsi negativi sono casi in cui il modello prevede un esito negativo in cui l'esito reale del set di test è positivo.

La nostra strategia di trading attende un risultato previsto positivamente per acquistare l'S&P 500 al prezzo di Opening e venderlo al prezzo di Closing , quindi la nostra speranza è di avere il tasso di falsi positivi più basso per evitare perdite. In altre parole, ci aspettiamo che il nostro modello abbia il più alto tasso di precisione .

Come possiamo vedere, ci sono due falsi negativi (al 10-04-2013 e 11-04-2013) e due falsi positivi (al 15-04-2013 e 18-04-2013) all'interno dei primi dieci valori previsti del set di prova.

Con un semplice calcolo, considerando questo piccolo insieme di dieci previsioni:

  • precisione = 6/10 = 0,6 o 60%
  • precisione =3/5 = 0,6 o 60%
  • richiamo = 3/5 = 0,6 o 60%

Nota che di solito i numeri sopra sono diversi tra loro, ma in questo caso sono gli stessi.

Test retrospettivo del modello

Ora simuliamo il modo in cui il modello scambierebbe utilizzando i suoi valori previsti. Se il risultato previsto è pari a +1 , significa che ci aspettiamo un giorno Up . Con un giorno Up compriamo l'indice all'inizio della sessione e vendiamo l'indice alla fine della sessione durante lo stesso giorno. Al contrario, se il risultato previsto è pari a -1 ci aspettiamo un giorno Down , quindi non faremo trading durante quel giorno.

Il Profitto e la Perdita ( pnl ) per un trade giornaliero completo, detto anche round turn , in questo esempio è dato da:

  • pnl = Close - Open (per ogni giorno di negoziazione)

Con il codice mostrato di seguito, chiamo la funzione di supporto plot_equity_chart per creare un grafico con la curva dei guadagni cumulativi (curva di equità). Senza andare troppo in profondità, ottiene semplicemente una serie di valori di profitti e perdite e calcola la serie di somme cumulative da tracciare.

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

Modello dell'albero delle decisioni, generalmente in alto e a destra, come reso dal codice Python sopra.

 Mean of PnL is 1.843504 Sharpe is 1.972835 Round turns 511

Qui Sharpe è l'Annual Sharpe Ratio, un importante indicatore della bontà del modello di trading.

Il rapporto di Sharpe è uguale alla radice quadrata di 252, quindi moltiplicato per la media di pnl divisa per la deviazione standard di pnl.

Considerando le operazioni espresse giorno per giorno, mentre mean è la media dell'elenco di profitti e perdite e sd è la deviazione standard. Per semplicità nella formula illustrata sopra, ho considerato un rendimento privo di rischio pari a 0.

Alcune nozioni di base sul trading

Il trading dell'indice richiede l'acquisto di un asset, che è direttamente derivato dall'indice. Molti broker replicano l'indice S&P 500 con un prodotto derivato chiamato CFD (Contract for Difference), che è un accordo tra due parti per scambiare la differenza tra il prezzo di apertura e il prezzo di chiusura di un contratto.

Esempio : acquista 1 CFD S&P 500 Open (il valore è 2000), vendilo alla Close del giorno (il valore è 2020). La differenza, da cui il guadagno, è di 20 punti. Se ogni punto ha un valore di $ 25:

  • Il guadagno lordo è di 20 points x $25 = $500 con 1 contratto CFD.

Diciamo che il broker mantiene uno slippage di 0,6 punti per le proprie entrate:

  • Il guadagno netto è (20 - 0.6) points x $25 = $485 .

Un altro aspetto importante da considerare è evitare perdite significative all'interno di un'operazione. Possono verificarsi ogni volta che il risultato previsto è +1 ma il valore del risultato reale è -1 , quindi è un falso positivo . In tal caso, la sessione finale risulta essere un giorno ribassista con un prezzo di chiusura inferiore all'apertura e otteniamo una perdita.

Un ordine stop loss deve essere inserito per proteggersi da una perdita massima che tollereremmo all'interno di un'operazione e tale ordine viene attivato ogni volta che il prezzo dell'attività scende al di sotto di un valore fisso che abbiamo impostato in precedenza.

Se guardiamo le serie temporali scaricate da Yahoo Finance all'inizio di questo articolo, ogni giorno ha un prezzo Low che è il prezzo più basso raggiunto durante quel giorno. Se impostiamo un livello di stop di -3 punti lontano dal prezzo di Opening e Low - Open = -5 verrà attivato l'ordine stop e la posizione aperta verrà chiusa con una perdita di -3 punti anziché -5 . Questo è un metodo semplice per ridurre il rischio. Il codice seguente rappresenta la mia funzione di supporto per simulare uno scambio con un livello di stop:

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

Costi di negoziazione

I costi di transazione sono le spese sostenute per l'acquisto o la vendita di titoli. I costi di transazione includono le commissioni e gli spread dei broker (la differenza tra il prezzo pagato dal dealer per un titolo e il prezzo pagato dall'acquirente) e devono essere considerati se vogliamo testare la nostra strategia, in modo simile a uno scenario reale. Lo slippage nel trading di azioni si verifica spesso quando c'è una variazione dello spread. In questo esempio e per le successive simulazioni in corso, i costi di negoziazione sono fissati come:

  • Slippage = 0,6 punti
  • Commissione = 1$ per ogni operazione (un giro di turno costerà 2$)

Solo per scrivere alcuni numeri, se il nostro guadagno lordo fosse di 10 punti, 1 punto = $ 25, quindi $ 250 inclusi i costi di trading, il nostro guadagno netto sarebbe (10 - 0.6)*$25 - 2 = $233 .

Il codice seguente mostra una simulazione della precedente strategia di trading con uno stop loss di -3 punti. La curva blu è la curva dei rendimenti cumulativi. Gli unici costi contabilizzati sono lo slippage (0,6 punti) e il risultato è espresso in punti base (la stessa unità base dei valori S&P 500 scaricati da 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)) 

Modello di albero decisionale, generalmente in alto e a destra, ma con picchi meno pronunciati, come reso dal codice Python sopra.

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

Il codice seguente viene utilizzato per fare previsioni in un modo leggermente diverso. Prestare attenzione al metodo predict che viene chiamato con un parametro aggiuntivo output_type = “probability” . Questo parametro viene utilizzato per restituire le probabilità dei valori previsti invece della loro previsione di classe ( +1 per un risultato previsto positivamente, -1 per un risultato previsto negativamente). Una probabilità maggiore o uguale a 0.5 è associata a un valore previsto +1 e un valore di probabilità inferiore a 0.5 è correlato a un valore previsto di -1 . Maggiore è la probabilità, maggiori sono le possibilità di prevedere un vero Up Day .

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

Ora eseguiamo il backtest del modello con una funzione di supporto chiamata backtest_ml_model che calcola la serie di rendimenti cumulativi inclusi lo slippage e le commissioni e ne traccia i valori. Per brevità, senza spiegare a fondo la funzione backtest_ml_model , il dettaglio importante da evidenziare è che invece di filtrare quei giorni con un outcome = 1 come abbiamo fatto nell'esempio precedente, ora predictions_prob quelle forecasts_prob uguali o maggiori di una threshold = 0.5 , come segue:

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

Ricorda che il guadagno netto di ogni giorno di negoziazione è: Net gain = (Gross gain - SLIPPAGE) * MULT - 2 * COMMISSION .

Un'altra importante metrica utilizzata per valutare la bontà di una strategia di trading è il Maximum Drawdown . In generale, misura il più grande calo singolo dal picco al minimo, nel valore di un portafoglio investito. Nel nostro caso, è il calo più significativo dal picco al minimo della curva azionaria (abbiamo solo un asset nel nostro portafoglio, l'S&P 500). Quindi dato un SArray di profitti e perdite pnl , calcoliamo il drawdown come:

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

All'interno della funzione di supporto backtest_summary viene calcolato:

  • Prelievo massimo (in dollari) come mostrato sopra.
  • Precisione, con il metodo Graphlab.evaluation .
  • Precisione, con il metodo Graphlab.evaluation .
  • Richiama, con il metodo Graphlab.evaluation .

Mettendo tutto insieme, l'esempio seguente mostra la curva di equità che rappresenta i rendimenti cumulativi della strategia del modello, con tutti i valori espressi in dollari.

 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) 

Grafico DecisionTree, con l'asse Y etichettato "dollari" e che arriva fino a 30.000, e l'asse X etichettato "# of roundturns" e che si estende fino a 600, come reso dal codice Python sopra. I dati rappresentati graficamente sono identici al rendering precedente.

 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

Per aumentare la precisione dei valori previsti, invece di una probabilità standard di 0.5 (50 percento), scegliamo un valore di soglia più alto, per essere più sicuri che il modello preveda un giorno 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) 

Grafico DecisionTree, con l'asse Y etichettato "dollari" e che arriva fino a 30.000, e l'asse X etichettato "# of roundturns" e che si estende a soli 250 questa volta, come reso dal codice Python sopra. I dati rappresentati graficamente sono simili al rendering precedente, ma ancora più uniformi.

 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

Come possiamo vedere dal grafico sopra, la curva dell'equità è molto migliore di prima (Sharpe è 6,5 invece di 3,5), anche con meno turni.

Da questo momento in poi considereremo tutti i prossimi modelli con una soglia superiore ad un valore standard.

Formazione di un classificatore logistico

Possiamo applicare la nostra ricerca, come abbiamo fatto in precedenza con l'albero decisionale, in un modello di Classificatore Logistico. GraphLab Create ha la stessa interfaccia con l'oggetto Logistic Classifier e chiameremo il metodo create per costruire il nostro modello con lo stesso elenco di parametri. Inoltre, preferiamo prevedere il vettore di probabilità invece del vettore di classe previsto (composto da +1 per un esito positivo e -1 per un esito negativo), quindi avremmo una soglia maggiore di 0.5 per ottenere una migliore precisione nel nostro previsione.

 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) 

Grafico LogisticClassifier, con l'asse Y etichettato "dollari" e questa volta fino a 50.000, e l'asse X etichettato "# of roundturns" e che si estende ora a 450, come reso dal codice Python sopra. I dati rappresentati graficamente sono simili al rendering precedente nella sua tendenza generale.

 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 questo caso, c'è un riepilogo molto simile a Decision Tree. Dopotutto, entrambi i modelli sono classificatori, predicono solo una classe di risultati binari ( +1 , -1 ).

Addestramento di un modello di regressione lineare

La principale differenza di questo modello è che si occupa di valori continui anziché di classi binarie, come accennato in precedenza. Non dobbiamo addestrare il modello con una variabile target pari a +1 per i giorni Up e -1 per i giorni Down , il nostro target deve essere una variabile continua. Dal momento che vogliamo prevedere un guadagno positivo, o in altre parole un prezzo di chiusura superiore al prezzo di apertura , ora target deve essere la colonna del guadagno del nostro set di allenamento. Inoltre, l'elenco delle funzionalità deve essere composto da valori continui, come il precedente Open , Close , ecc.

Per brevità, non entrerò nei dettagli su come selezionare le funzionalità giuste, poiché questo va oltre lo scopo di questo articolo, che è più incline a mostrare come applicare diversi modelli di Machine Learning su un set di dati. L'elenco dei parametri passati al metodo create sono:

  • training - è un set di training contenente colonne di funzionalità e una colonna di destinazione.
  • target - è il nome della colonna contenente la variabile target.
  • validation_set - è un set di dati per monitorare le prestazioni di generalizzazione del modello. Nel nostro caso, non abbiamo validation_set .
  • features - è un elenco di nomi di colonne di feature usate per addestrare il modello, per questo modello useremo un altro set rispetto ai modelli Classifier.
  • verbose - se true , stamperà le informazioni sull'avanzamento durante l'allenamento.
  • max_iterations - è il numero massimo di passaggi consentiti attraverso i dati. Più passaggi sui dati possono risultare in un modello addestrato in modo più accurato.
 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)

Finora, abbiamo previsioni che è SArray dei guadagni previsti, mentre predictions_prob è SArray con valori di predictions normalizzati. Per avere una buona precisione e un certo numero di giri, paragonabile ai modelli precedenti, ho scelto un valore di soglia di 0.4 . Per un predictions_prob inferiore a 0.4 , la funzione helper backtest_linear_model non aprirà uno scambio perché è previsto un giorno di ribasso. In caso contrario, verrà aperto uno scambio.

 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) 

Grafico di regressione lineare, con l'asse Y etichettato "dollari" e che arriva fino a 45.000, e l'asse X etichettato "# of roundturns" e che si estende fino a 350, come reso dal codice Python sopra. I dati rappresentati graficamente sono di nuovo simili, ma non esattamente identici al rendering precedente.

 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

Addestrare un albero potenziato

Come in precedenza abbiamo addestrato un albero decisionale, ora addestreremo un classificatore ad albero potenziato con gli stessi parametri utilizzati per altri modelli di classificatore. Inoltre, impostiamo il numero di max_iterations = 12 per aumentare il numero massimo di iterazioni per il potenziamento. Ogni iterazione comporta la creazione di un albero aggiuntivo. Abbiamo anche impostato un valore di soglia superiore a 0.5 per aumentare la precisione.

 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) 

Grafico BoostedTreesClassifier, con l'asse Y etichettato "dollari" e che arriva fino a 25.000, e l'asse X etichettato "# di roundturns" e che si estende a 250, come reso dal codice Python sopra. I dati rappresentati graficamente sono di nuovo simili al rendering precedente, con un aumento più netto di circa 175 sull'asse 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

Addestrare una foresta casuale

Questo è il nostro ultimo modello addestrato, un Classificatore Forestale Casuale, composto da un insieme di alberi decisionali. Il numero massimo di alberi da utilizzare nel modello è impostato su num_trees = 10 , per evitare troppa complessità e overfitting.

 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) 

Grafico RandomForestClassifier, con l'asse Y etichettato "dollari" e che arriva fino a 40.000, e l'asse X etichettato "# of roundturns" e che si estende fino a 350, come reso dal codice Python sopra. I dati rappresentati graficamente sono di nuovo simili al rendering precedente.

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

Conclusione

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.

Correlati: un'introduzione alla teoria dell'apprendimento automatico e alle sue applicazioni: un tutorial visivo con esempi

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.