Introduzione al Forecasting delle Serie temporali Finanziare – Parte II

Come descritto nel precedente articolo di questa serie, nel campo della finanza quantitativa in cui vi è un’abbondanza di dati di addestramento, quindi si potrebbe usare un modello come il Support Vector Machine (SVM). Tuttavia, gli SVM soffrono della mancanza di interpretabilità. Questo non è il caso degli Alberi Decisionali e i  Random Forest.

Questi ultimi sono spesso usati per preservare l’interpretabilità, qualcosa che i classificatori “black box” come SVM non forniscono. In definitiva, quando i dati sono molto numerosi (ad esempio i dati di tick), non è molto importante quale classificatore utilizzare. In questa fase sorgono altri fattori come l’efficienza computazionale e la scalabilità dell’algoritmo. La regola empirica prevede che il raddoppio dei dati di allenamento causa un aumento lineare delle prestazioni, ma poiché le dimensioni dei dati diventano considerevoli, questo miglioramento si riduce a un aumento sub-lineare delle prestazioni.

La teoria matematica e statistica alla base dei classificatori supervisionati è abbastanza complessa, ma l’intuizione di base di ciascun classificatore è semplice da comprendere. Inoltre, si noti che ognuna delle seguenti classificazioni avrà un diverso insieme di caratteristiche per le quali avranno migliori prestazioni, quindi se si riscontra un cattivo funzionamento del classificatore, ciò potrebbe essere dovuto al fatto che il set di dati utilizzato viola una di queste caratteristiche utilizzate.

Classificatore Naive Bayes

Nonostate questo classificatore non sia stato descritto nel precedente articolo, per completezza è opportuno introdurre anche questo tipo di classificatore. Il Naive Bayes (in particolare Multinomial Naive Bayes – MNB) è utile quando esiste un set di dati limitato. Questo perché è un classificatore ad alta distorsione. L’ipotesi principale del classificatore MNB è l’indipendenza condizionata. In sostanza, tale metodo non è in grado di discernere le interazioni tra le singole caratteristiche, a meno che non siano state specificatamente aggiunte come una caratteristica extra.

Ad esempio, si consideri uno scenario di classificazione dei documenti, che avviene nelle analisi finanziarie quando si tenta di eseguire l’analisi delle opinioni. L’MNB potrebbe apprendere che singole parole come “gatto” e “cane” potrebbero riferirsi rispettivamente ai documenti relativi a cani e gatti, ma la frase “gatti e cani” (gergo britannico che significa più o meno “piovere a catinelle”) non sarebbe considerata una caratteristica meteorologica dal classificatore! Una soluzione potrebbe essere considerare “gatti e cani” come una caratteristica extra, e quindi associarli a una categoria meteorologica.

Logistic Regression

La regressione logistica offre alcuni vantaggi rispetto al modello di Naive Bayes in quanto vi è meno attenzione sulla correlazione tra le caratteristiche e, per la natura del modello, c’è un’interpretazione probabilistica dei risultati. Questo è più adatto per un ambiente in cui è necessario utilizzare le soglie. Ad esempio, potremmo desiderare di posizionare una soglia dell’80% per un risultato “up” o “down” in modo che sia selezionata correttamente, anziché scegliere la categoria con la probabilità più elevata. In quest’ultimo caso, la previsione di “up” potrebbe essere del 51% e la previsione di “down” potrebbe essere del 49%. In questo caso impostare la categoria su “up” non è una previsione molto precisa.

Decision Tree and Random Forests
Gli alberi decisionali (DT) suddividono lo spazio in una gerarchia di scelte booleane che portano a una categorizzazione, o raggruppamento, in base alle rispettive decisioni. Questo li rende altamente interpretabili (assumendo un numero “ragionevole” di decisioni / nodi nell’albero!). DT ha molti vantaggi, inclusa la capacità di gestire le interazioni tra le caratteristiche, anche quelle non parametriche.
Sono anche utili nei casi in cui è difficile (o impossibile) separare linearmente i dati in classi (che è una condizione richiesta dai SVM). Lo svantaggio dell’uso di alberi decisionali individuali è che sono soggetti all’overfitting (alta varianza). Questo problema viene risolto utilizzando una Random Forest. Le foreste casuali sono in realtà alcuni dei classificatori “migliori” che sono utilizzati nelle applicazioni di machine learning, quindi devono essere sempre prese in considerazione.

Support Vector Machine
Support Vector Machines (SVM), pur avendo un complesso processo di allenamento, è in realtà relativamente semplice da comprendere. Gli SVM lineari tentano essenzialmente di partizionare uno spazio in gruppi distinti, utilizzando confini di separazione lineari. Per alcuni tipi di dati questo può funzionare molto bene e porta a buone previsioni. Tuttavia molti dati non sono linearmente separabili, quindi gli SVM lineari possono avere uno scarso rendimento.

La soluzione è modificare il kernel utilizzato dal SVM, in modo da permette l’uso di confini decisionali non lineari. Quindi sono modelli abbastanza flessibili. Tuttavia, il corretto confine del SVM deve essere scelto tra i migliori risultati. Gli SVM sono particolarmente adatti per i problemi di classificazione dei testi di gradi dimensioni. Hanno lo svantaggio di avere un’elevata complessità computazionale, dalla difficoltà di messa a punto e dal fatto che il modello allenato è difficile da interpretare.

Forecasting del Movimento di un Indice Azionario

L’indice S&P500 è un indice ponderato delle 500 maggiori società quotate in borsa (per capitalizzazione di mercato) nel mercato azionario statunitense. È spesso considerato un “benchmark” azionario. Esistono molti prodotti derivati ​​per consentire la speculazione su questo indice. In particolare, il contratto futures E-Mini S& P500 è uno strumento estremamente liquido per negoziare l’indice.

In questo esempio useremo un set di classificatori per prevedere la direzione del prezzo di chiusura al giorno k in base esclusivamente alle informazioni sui prezzi conosciute al giorno k-1. Un movimento direzionale verso l’alto significa che il prezzo di chiusura del giorno k è superiore al prezzo del giorno k-1, mentre un movimento al ribasso implica un prezzo di chiusura al giorno k è inferiore a quello del giorno  k-1.

Se siamo in grado di determinare la direzione del movimento con un metodo che superi significativamente un tasso di successo del 50%, con un basso errore e una buona significatività statistica, allora siamo sulla buona strada per creare una semplice strategia di trading sistematico, basata sulle nostre previsioni. In questa fase non ci occupiamo degli algoritmi di Machine Learning più aggiornati e di ultima generazione. In questo momento stiamo solo introducendo alcuni concetti e quindi iniziamo la discussione con alcuni metodi elementari.

Implementazione in Python

Per l’implementazione di questi forecaster si utilizza aclune librerie Python, quali NumPy, Pandas e Scikit-Learn, la cui installazione è stata descritta in un precedente articolo.

Il primo passo è importare i principali moduli e le librerie, in paticolare per questo esempio si deve importare i moduli per LogisticRegression, LDA, QDA, LinearSVC (una Support Vector Machine lineare), SVC (una Support Vector Machine non lineare) e i classificatori RandomForest:

            #!/usr/bin/python
# -*- coding: utf-8 -*-
# forecast.py

import datetime
import numpy as np
import pandas as pd

import pandas_datareader as pdr
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.metrics import confusion_matrix
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis as QDA
from sklearn.svm import LinearSVC, SVC
        

Dopo aver importato le librerie, si deve creare un DataFrame di pandas che contenga i i ritorni (variazioni di prezzo) percentuali ritardati per un numero precedente di giorni (predefinito a cinque). La funzione create_lagged_series crea un DataFrame per un determinato simbolo azionario (come restituito da Yahoo Finance) eil periodo specificato.

Il codice è ben commentato quindi dovrebbe essere semplice capire il suo funzionamento:

            def create_lagged_series(symbol, start_date, end_date, lags=5):
    """
    Questo crea un DataFrame Pandas che memorizza i rendimenti percentuali
    del prezzo di chiusura aggiustato delle barre di un titolo azionario scaricate
    da Yahoo Finance, e memorizza una serie di rendimenti ritardati dai giorni di
    negoziazione precedenti (il valore di ritardo predefinito è di 5 giorni).
    Sono inclusi anche i volume degli scambi, e la direzione del giorno precedente.
    """

    # Download dei ddti sulle azioni da Yahoo Finance
    ts = pdr.DataReader(
          symbol, "yahoo",
          start_date-datetime.timedelta(days=365),
          end_date
         )

    # Crea un nuovo DataFrame dei dati ritardati
    tslag = pd.DataFrame(index=ts.index)
    tslag["Today"] = ts["Adj Close"]
    tslag["Volume"] = ts["Volume"]

    # Crea una serie ritardata dei precendenti prezzi di chiusura dei periodi di trading
    for i in range(0, lags):
        tslag["Lag%s" % str(i+1)] = ts["Adj Close"].shift(i+1)

    # Crea il Dataframe dei rendimenti
    tsret = pd.DataFrame(index=tslag.index)
    tsret["Volume"] = tslag["Volume"]
    tsret["Today"] = tslag["Today"].pct_change()*100.0

    # Se qualsiasi dei valori dei rendimenti percentuali è pari a zero, si imposta questi con
    # un numero molto piccolo (in modo da evitare le criticità del modello QDA di Scikit-Learn)
    for i,x in enumerate(tsret["Today"]):
        if (abs(x) < 0.0001):
            tsret["Today"][i] = 0.0001
            
    # Crea la colonna dei rendimenti percentuali ritardati
    for i in range(0, lags):
        tsret["Lag%s" % str(i+1)] = tslag["Lag%s" % str(i+1)].pct_change()*100.0
    
    # Crea la colonna "Direction" (+1 or -1) che indica un giorno rialzista/ribassista
    tsret["Direction"] = np.sign(tsret["Today"])
    tsret = tsret[tsret.index >= start_date]

    return tsret
        

Inseriamo la funzione di classificazione all’interno di una procedura principale. In questo esempio specifico si vuol prevedere la direzione del mercato azionario statunitense nel 2005, utilizzando i dati sui rendimenti dal 2001 al 2004.

Innanzitutto si crea una serie ritardata di S&P500 utilizzando cinque ritardi. La serie include anche il volume degli scambi. Tuttavia, si limita il set predittore da utilizzare a solo i primi due ritardi. Quindi stiamo implicitamente affermando al classificatore che gli ulteriori ritardi sono di minor valore predittivo. Per inciso, questo effetto è studiato più concretamente sotto il concetto statistico di autocorrelazione, che va oltre lo scopo di questo articolo.
Dopo aver creato l’array di predittori X e il vettore di risposta Y, possiamo suddividere gli array in un set di training e un set di test. Il primo sottoinsieme viene utilizzato per addestrare effettivamente il classificatore, mentre il secondo viene utilizzato per testare le prestazioni. Si dividono i dati al 1 ° gennaio 2005, utilizzando i dati precedenti per il training e i dati successivi per il testing.

Una volta creata la divisione tra training e testing, è necessario creare un’array dei modelli di classificazione, ognuno dei quali è una tupla che contiene un identificativo e il nome della funzione o modulo. In questo esempio non si imposta nessun parametro per la regressione logistica, gli analizzatori discriminatori lineari / quadratici o i modelli di classificazione SVM lineare, mentre si utilizza un set di parametri predefiniti per i SVM  radiali (RSVM) e il Random Forest (RF).
Dopo aver iterato i dati sui  modelli, si forma (fitting) ciascun modello sui dati di training e poi si effettuano previsioni sui dati di testing. Infine si calcola il Hit-Rate e la matrice di confusione per ciascun modello:

            
if __name__ == "__main__":
    # Crea una serie ritardata dell'indice S&P500 del mercato azionario US
    snpret = create_lagged_series(
        "^GSPC", datetime.datetime(2001,1,10),
        datetime.datetime(2005,12,31), lags=5
       )

    # Uso il rendimento dei due giorni precedentei come valore
    # di predizione, con la direzione come risposta
    X = snpret[["Lag1","Lag2"]]
    y = snpret["Direction"]

    # I dati di test sono divisi in due parti: prima e dopo il 1 gennaio 2005.
    start_test = datetime.datetime(2005,1,1)

    # Crea il dataset di training e di test
    X_train = X[X.index < start_test]
    X_test = X[X.index >= start_test]
    y_train = y[y.index < start_test]
    y_test = y[y.index >= start_test]

    # Crea i modelli (parametrizzati)
    print("Hit Rates/Confusion Matrices:\n")
    models = [("LR", LogisticRegression()),
              ("LDA", LDA()),
              ("QDA", QDA()),
              ("LSVC", LinearSVC()),
              ("RSVM", SVC(
                 C=1000000.0, cache_size=200, class_weight=None,
                 coef0=0.0, degree=3, gamma=0.0001, kernel='rbf',
                 max_iter=-1, probability=False, random_state=None,
                 shrinking=True, tol=0.001, verbose=False)
                ),
              ("RF", RandomForestClassifier(
                 n_estimators=1000, criterion='gini',
                 max_depth=None, min_samples_split=2,
                 min_samples_leaf=1, max_features='auto',
                 bootstrap=True, oob_score=False, n_jobs=1,
                 random_state=None, verbose=0)
                )]

    # Iterazione attraverso i modelli
    for m in models:

        # Addestramento di ogni modello con il set di dati di training
        m[1].fit(X_train, y_train)

        # Costruisce un array di predizioni sui dati di test
        pred = m[1].predict(X_test)

        # Stampa del hit-rate e della confusion matrix di ogni modello.
        print("%s:\n%0.3f" % (m[0], m[1].score(X_test, y_test)))
        print("%s\n" % confusion_matrix(pred, y_test))
        

Risultati

Di seguito si riporta l’output di tutti i modelli di classificazione. Probabilmente avrai  valori diversi per l’output del RF (Random Forest) dato che quest’ultimo è intrinsecamente stocastico per definizione:
            Hit Rates/Confusion Matrices:

LR:
0.560
[[ 35  35]
 [ 76 106]]

LDA:
0.560
[[ 35  35]
 [ 76 106]]

QDA:
0.599
[[ 30  20]
 [ 81 121]]

LSVC:
0.560
[[ 35  35]
 [ 76 106]]

RSVM:
0.563
[[  9   8]
 [102 133]]

RF:
0.504
[[48 62]
 [63 79]]

        

Da notare che tutti i tassi di successo si collocano tra il 50% e il 60%. Quindi si può concludere che le variabili ritardate non sono indicative della direzione futura. Tuttavia, se si osserva all’analizzatore discriminante quadratico (QDA), si nota che la sua prestazione predittiva complessiva sul set di test è appena inferiore al 60%.
Dalla matrice di confusione per questo modello (e gli altri in generale) si osserva che il tasso positivo per i giorni “down” è molto più alto rispetto ai giorni “up”. Quindi, se vogliamo creare una strategia di trading basata su queste informazioni, potremmo considerare di limitare le operazioni alle sole posizioni short del S&P500 come metodo potenziale per aumentare la redditività.

Negli articoli successivi useremo questi modelli come base di una strategia di trading incorporandoli direttamente nella struttura di backtesting event-driven e utilizzando uno strumento diretto, come un exchange traded fund (ETF), per poter accedere al trading del S&P500.

 

Per il codice completo riportato in questo articolo utilizzando il modulo di backtesting vettoriale VectorBacktest si può consultare il seguente repository di github:
https://github.com/datatrading-info/VectorBacktest

Gli altri articoli di questa serie

Benvenuto su DataTrading!

Sono Gianluca, ingegnere software e data scientist. Sono appassionato di coding, finanza e trading. Leggi la mia storia.

Ho creato DataTrading per aiutare le altre persone ad utilizzare nuovi approcci e nuovi strumenti, ed applicarli correttamente al mondo del trading.

DataTrading vuole essere un punto di ritrovo per scambiare esperienze, opinioni ed idee.

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

Scroll to Top