Strategia di analisi del sentiment dei mercati con DataTrader

In questo articolo descriviamo come effettuare il backtest di una strategia di analisi del sentiment tramite un sistema di trading quantitativo. Infatti, oltre ai “soliti” trucchi relativi all’arbitraggio statistico, al trend-following e all’analisi fondamentale, molti fondi quantitativi (e anche noi trader retail!) ci dedichiamo a tecniche di elaborazione del linguaggio naturale (NLP) per costruire strategie sistematiche. Queste tecniche rientrano nell’ambito della Sentiment Analysis

Una delle sfide principali nello sviluppo di un sistema simile consiste nell’integrare eventi di sentiment, archiviati in un file CSV con righe “datetime-ticker-sentiment”, in un sistema di trading basato su eventi, tipicamente progettato per operare direttamente sui dati dei prezzi. L’obbiettivo descrivere ed implementare un gruppo di strategie di trading quantitativo basate su una serie di segnali di sentiment che otteniamo da un’API fornita da un provider. Questi segnali forniscono una scala intera da -3 (“sentimento negativo più forte”) a +6 (“sentimento positivo più forte”), associata a una data e a un simbolo ticker, che utilizziamo come soglia di ingresso e di uscita in un backtest event-driven.

L’articolo inizia con una breve discussione sulle modalità di esecuzione dell’analisi di sentiment e con la descrizione tecnica delle API del fornitore e dei file di esempio. Proseguiamo descrivendo la funzionalità di sentiment che abbiamo recentemente aggiunto a DataTrader, incluso il relativo codice Python. Infine analizziamo i risultati di tre distinti backtest della strategia di sentiment applicata ai titoli S&P500 nei settori della tecnologia, della difesa e dell’energia.

Analisi del sentiment

Con l’analisi del sentiment vogliamo, in generale, elaborare grandi quantità di dati “non strutturati” (come post di blog, articoli di giornale, rapporti di ricerca, tweet, video, immagini, ecc.) e applicare tecniche di PNL per quantificare il “sentiment” positivo o negativo relativo a determinati asset.

Nel caso delle azioni, ci concentriamo in particolare sull’analisi statistica del linguaggio usato, per capire se contiene un fraseggio rialzista o ribassista. Questo fraseggio lo quantifichiamo in termini di forza del sentiment, che traduciamo in valori numerici. In altre parole, i valori positivi indicano un sentimento rialzista, mentre quelli negativi riflettono un sentimento ribassista.

Negli ultimi anni abbiamo assistito a una crescita costante di fornitori di analisi del sentiment, tra cui Sentdex, PsychSignal e Accern. Tutti applicano tecniche proprietarie per identificare “entità” all’interno dei dati alternativi e associare un punteggio di sentiment con timestamp a ogni informazione estratta. Possiamo poi aggregare queste informazioni in un determinato intervallo temporale (ad esempio un giorno) per ottenere tuple del tipo data-entità-sentiment. Queste tuple rappresentano la base di un segnale di trading.

Non intendiamo descrivere in dettaglio le metodologie di analisi di grandi volumi di “big data” e la quantificazione del sentiment, poiché questo esula dallo scopo dell’articolo. Sviluppare uno strumento di analisi del sentiment end-to-end, pronto per l’uso in produzione, richiede un notevole impegno ingegneristico. Per questo motivo, molti trader retail come noi preferiamo ottenere questi segnali direttamente dai fornitori e integrarli in un portafoglio più ampio di segnali quantitativi per costruire strategie sistematiche.

In questo articolo presentiamo una strategia di analisi del sentiment basata sui dati forniti da Sentdex e mostriamo come possiamo generare segnali long-only a partire da questi dati.

Le API di Sentdex e file di esempio

Sentdex mette a disposizione un’API che consente di scaricare dati di sentiment per un’ampia varietà di strumenti finanziari. Possiamo ottenere questi dati con una risoluzione temporale di un minuto o di un giorno. Troviamo maggiori dettagli sulla loro offerta (a pagamento) alla pagina API.

Non approfondiamo l’uso dell’API in questo articolo, poiché si tratta di un servizio a pagamento pensato soprattutto per generare eventi in scenari di paper trading o trading reale. Poiché ci concentriamo sulle strategie di backtesting con dati storici, preferiamo utilizzare un file statico archiviato localmente per rappresentare i dati di sentiment.

Fortunatamente, Sentdex fornisce un file di dati campione (scaricabile qui) che contiene quasi cinque anni di segnali di sentiment, a risoluzione giornaliera, per molti dei componenti dell’S&P500.

Di seguito presentiamo uno snippet del file:

				
					date,symbol,sentiment_signal
2012-10-15,AAPL,6
2012-10-16,AAPL,2
2012-10-17,AAPL,6
2012-10-18,AAPL,6
2012-10-19,AAPL,6
2012-10-20,AAPL,6
2012-10-21,AAPL,1
2012-10-22,MSFT,6
2012-10-22,GOOG,6
2012-10-22,AAPL,-1
2012-10-23,AAPL,-3
2012-10-23,GOOG,-3
2012-10-23,MSFT,6
2012-10-24,GOOG,-1
2012-10-24,MSFT,-3
2012-10-24,AAPL,-1
				
			

Notiamo che ogni riga contiene una data, un simbolo ticker e un numero intero che rappresenta la forza del sentimento, compresa tra +6 (“forte sentimento positivo”) e -3 (“forte sentimento negativo”).

Questo file di esempio costituisce la base dei dati sul sentiment che utilizziamo nelle tre simulazioni descritte in questo articolo.

La strategia di analisi del sentiment

La complessità di questa implementazione deriva principalmente dagli adeguamenti al framework open-source di backtesting event-driven DataTrader, più che dalla strategia in sé, che risulta abbastanza semplice dopo aver generato il segnale del sentiment. Manteniamo volutamente la strategia semplice, lasciando ampi margini di modifica e ottimizzazione che tratteremo in articoli successivi.

In questo esempio operiamo solo long, ma possiamo facilmente modificare la strategia per includere posizioni short. Stabiliamo soglie di entrata e uscita, che utilizziamo per aprire o chiudere posizioni long.

Implementiamo tre strategie identiche, con l’unica differenza nella selezione dei titoli su cui operiamo. L’elenco delle azioni è il seguente:

  • Tecnologia: MSFT, AMZN, GOOG, IBM, AAPL
  • Energia: XOM, CVX, SLB, OXY, COP
  • Difesa: BA, GD, NOC, LMT, RTN

Le regole della strategia sono le seguenti:

  • Apriamo una posizione long su un ticker se il suo valore di sentiment raggiunge +6
  • Chiudiamo una posizione su un ticker se il suo valore di sentiment scende a -1

Non applichiamo un’allocazione percentuale per ciascun titolo. Utilizziamo una quantità fissa di azioni per ciascuna allocazione durante tutta la strategia. Tuttavia, modifichiamo questa quantità fissa in base ai tre settori sopra indicati.

Una modifica ovvia sarebbe creare un investimento pesato in dollari che regola dinamicamente l’allocazione in base alle dimensioni del capitale. Tuttavia, in questo articolo semplifichiamo il dimensionamento della posizione per facilitare la comprensione della logica base di generazione dell’evento di sentiment.

Set di dati storici

Per attuare questa strategia di analisi del sentiment abbiamo bisogno dei dati giornalieri OHLCV dei titoli relativi al periodo coperto da questi backtest. Eseguiamo tre simulazioni separate, ciascuna con un gruppo di cinque titoli dell’S&P500.

Abbiamo un gruppo comprende titoli tecnologici/di consumo di base:

Ticker Nome Periodo
MSFT Microsoft 15 ottobre 2012 – 2 febbraio 2016
AMZN Amazon.com 15 ottobre 2012 – 2 febbraio 2016
GOOG Alphabet 15 ottobre 2012 – 2 febbraio 2016
AAPL Apple 15 ottobre 2012 – 2 febbraio 2016
IBM International Business Machines 15 ottobre 2012 – 2 febbraio 2016

Prendiamo poi in considerazione un gruppo che comprende titoli del settore difesa, anch’essi facenti parte dell’S&P500:

Ticker Nome Periodo
BA The Boeing Company 15 ottobre 2012 – 2 febbraio 2016
LMT Lockheed Martin 15 ottobre 2012 – 2 febbraio 2016
NOC Northrop Grumman 15 ottobre 2012 – 2 febbraio 2016
GD General Dynamics 15 ottobre 2012 – 2 febbraio 2016
RTN Raytheon 15 ottobre 2012 – 2 febbraio 2016

Infine effettuiamo il backtest su un gruppo formato da titoli energetici dell’S&P500:

Ticker Nome Periodo
XOM Exxon Mobile 15 ottobre 2012 – 2 febbraio 2016
CVX Chevron 15 ottobre 2012 – 2 febbraio 2016
SLB Schlumberger 15 ottobre 2012 – 2 febbraio 2016
OSSI Occidental Petroleum 15 ottobre 2012 – 2 febbraio 2016
COP ConocoPhilips 15 ottobre 2012 – 2 febbraio 2016

Dobbiamo inserire questi dati nella directory specificata dal file delle impostazioni di QSTrader per replicare i risultati.

Inoltre, posizioniamo il file di esempio dell’API Sentdex nella stessa directory dei dati di DataTrader.

Implementazione Python

 

Gestione del sentiment con DataTrader

Per eseguire il backtest di una strategia di analisi del sentiment, dobbiamo capire come incorporare i “segnali” del sentiment nel backtest.

Il modello attuale di DataTrader per il backtesting prevede un “motore” che gestisce la risposta agli eventi. Implementiamo questo motore in un ciclo while che itera su tutti gli oggetti TickEvent e BarEvent. Il codice ci consente di concatenare qualsiasi dataset di serie storiche finanziarie di ticker, archiviati in un database o in file CSV, e quindi iterare riga per riga, con ogni riga che rappresenta un elemento di un DataFrame pandas formato da un TickEvent o da un BarEvent per ciascun ticker.

In precedenza questa logica era implementata con il seguente codice:

				
					
while self.price_handler.continue_backtest:
    try:
        event = self.events_queue.get(False)
    except queue.Empty:
        self.price_handler.stream_next()
    else:
        if event is not None:
            if event.type == EventType.TICK or event.type == EventType.BAR:
                self.cur_time = event.time
                self.strategy.calculate_signals(event)
                self.portfolio_handler.update_portfolio_value()
                self.statistics.update(event.time, self.portfolio_handler)
            elif event.type == EventType.SIGNAL:
                self.portfolio_handler.on_signal(event)
            elif event.type == EventType.ORDER:
                self.execution_handler.execute_order(event)
            elif event.type == EventType.FILL:
                self.portfolio_handler.on_fill(event)
            else:
                raise NotImplemented("Unsupported event.type '%s'" % event.type)
				
			

Vediamo che il ciclo continua al termine del backtest, determinato dall’oggetto PriceHandler. Ad ogni iterazione si estrae  (se esiste) l’ultimo Event dalla coda e lo invia al corretto gestore a seconda del tipo di evento. Tuttavia, in questo caso il file CSV dei segnali di sentiment precedentemente menzionato contiene anche il timestamp di ogni segnale. Quindi è necessario “iniettare” l’appropriato segnale di sentiment per un particolare ticker nel corretto momento del backtest.

A tale scopo abbiamo creato un nuovo evento chiamato SentimentEvent. Memorizza un timestamp, un ticker e un valore di sentiment (che può essere un valore a virgola mobile, un intero o una stringa) che viene poi inviato  all’oggetto Strategy per generare un  SignalEvent. Il codice del SentimentEvent implementato all’interno di DataTrader è il seguente:

				
					
class SentimentEvent(Event):
    """
    Gestisce l'evento di streaming di un valore "Sentiment" associato
    a un ticker. Può essere utilizzato per un servizio generico
    "data-ticker-sentiment", spesso fornito da molti fornitori di dati.
    """
    def __init__(self, timestamp, ticker, sentiment):
        """
        Inizializza il SentimentEvent.

        Parameters:
        timestamp - il timestamp in cui l'ordine è stato eseguito.
        ticker - Il simbolo del ticker, ad es. "GOOG".
        sentiment - Una stringa, un valore float o un valore intero
            di "sentiment", ad es. "rialzista", -1, 5.4, ecc.
        """
        self.type = EventType.SENTIMENT
        self.timestamp = timestamp
        self.ticker = ticker
        self.sentiment = sentiment

				
			

Gestione dei dati

È stata anche creata una nuova gerarchia di oggetti chiamata AbstractSentimentHandler. In questo modo possiamo gestire differenti sottoclassi degli oggetti del gestore del sentiment a seconda del fornitore dei dati, che condividono un’interfaccia comune verso il motore degli eventi di DataTrader. Dato che indicator di sentimento sono quasi sempre tuple “timestamp-ticker-sentiment”, è utile creare un’interfaccia unificata.

Per gestire il file CSV di esempio di Sentdex abbiamo implementato l’oggetto SentdexSentimentHandler. Come con la maggior parte dei gestori, richiede un handle per la coda degli eventi, un sottoinsieme di ticker su cui agire e una data di inizio e fine:

				
					
class SentdexSentimentHandler(AbstractSentimentHandler):
    """
    SentdexSentimentHandler è progettato per fornire al motore di backtesting
    un gestore di analisi del sentimento del provider Sentdex
    (http://sentdex.com/financial-analysis/).

    Utilizza un file CSV con tuple / righe di data-ticker-sentiment.
    Quindi, per evitare impliciti bias di lookahead, viene fornito
    un metodo specifico "stream_sentiment_events_on_date" che
    consente di recuperare solo i segnali di sentiment
    per una data particolare.
    """
    def __init__(
        self, csv_dir, filename,
        events_queue, tickers=None,
        start_date=None, end_date=None
    ):
        self.csv_dir = csv_dir
        self.filename = filename
        self.events_queue = events_queue
        self.tickers = tickers
        self.start_date = start_date
        self.end_date = end_date
        self.sent_df = self._open_sentiment_csv()
				
			

Questa classe contiene due metodi principali. Il primo metodo è _open_sentiment_csv che permette di aprire un file CSV e trasferire i dati all’interno di un pandas DataFrame  insieme al ticker associato e al filtro delle date:

				
					
    def _open_sentiment_csv(self):
        """
        Apre il file CSV contenente le informazioni sull'analisi
        del sentiment per tutti i titoli rappresentati e lo
        inserisce in un DataFrame pandas.
        """
        sentiment_path = os.path.join(self.csv_dir, self.filename)
        sent_df = pd.read_csv(
            sentiment_path, parse_dates=True,
            header=0, index_col=0,
            names=("Date", "Ticker", "Sentiment")
        )
        if self.start_date is not None:
            sent_df = sent_df[self.start_date.strftime("%Y-%m-%d"):]
        if self.end_date is not None:
            sent_df = sent_df[:self.end_date.strftime("%Y-%m-%d")]
        if self.tickers is not None:
            sent_df = sent_df[sent_df["Ticker"].isin(self.tickers)]
        return sent_df

				
			

Il secondo metodo è stream_next, usato per “trasmettere in streaming” il  successivo segnale di sentiment all’interno della coda degli eventi. Poiché il file CSV Sentdex contiene più ticker nella stessa data, è necessario specificare un stream_date in modo da non introdurre un lookahead bias. In altre parole, il gestore di eventi non dovrebbe mai vedere un segnale di sentiment che viene generato “in futuro” sbirciando troppo avanti nel file CSV.

Fondamentalmente, questo metodo produce  più oggetti SentimentEvent, tutti quelli che sono stati generati in un determinato giorno:

				
					
while self.price_handler.continue_backtest:
    try:
        event = self.events_queue.get(False)
    except queue.Empty:
        self.price_handler.stream_next()
    else:
        if event is not None:
            if event.type == EventType.TICK or event.type == EventType.BAR:
                self.cur_time = event.time
                # Creazione di ogni evento di sentiment
                if self.sentiment_handler is not None:
                    self.sentiment_handler.stream_next(
                        stream_date=self.cur_time
                    )
                self.strategy.calculate_signals(event)
                self.portfolio_handler.update_portfolio_value()
                self.statistics.update(event.time, self.portfolio_handler)
            elif event.type == EventType.SENTIMENT:
                self.strategy.calculate_signals(event)
            elif event.type == EventType.SIGNAL:
                self.portfolio_handler.on_signal(event)
            elif event.type == EventType.ORDER:
                self.execution_handler.execute_order(event)
            elif event.type == EventType.FILL:
                self.portfolio_handler.on_fill(event)
            else:
                raise NotImplemented("Unsupported event.type '%s'" % event.type)
				
			

Gestione degli eventi

La modifica finale al codebase di DataTrader è all’interno dell’oggetto Backtest. Abbiamo modificato il dispatcher di eventi per gestire i nuovi tipi di oggetti SentimentEvent che devono essere inviati a un appropriato oggetto Strategy.

All’interno della gestione degli eventi TICK o eventi BAR, sono state aggiunte alcune righe in più. Controlliamo se si tratta di una strategia che contiene il SentimentHandler o meno, ed in caso positivo sono creati tutti gli oggetti SentimentEvent oggetti per uno specifico giorno, a cui si fa riferimento nel file di sentiment di Sentdex.

Inoltre, è stata aggiornata la logica di invio di tali eventi all’oggetto Strategy, che li analizza per generare i segnali:

				
					
    def stream_next(self, stream_date=None):
        """
        Trasmetti il set successivo di valori di sentiment di
        un ticker negli oggetti SentimentEvent.
        """
        if stream_date is not None:
            stream_date_str = stream_date.strftime("%Y-%m-%d")
            date_df = self.sent_df.ix[stream_date_str:stream_date_str]
            for row in date_df.iterrows():
                sev = SentimentEvent(
                    stream_date, row[1]["Ticker"],
                    row[1]["Sentiment"]
                )
                self.events_queue.put(sev)
        else:
            print("No stream_date provided for stream_next sentiment event!")

				
			

Queste sono le modifiche che abbiamo apportato a DataTrader, e sono disponibile nell’ultima versione disponibile su Github , quindi se desideri replicare queste strategie, assicurati di aggiornare la tua copia locale di DataTrader  con l’ultima versione.

Codice della strategia di analisi del sentiment

I codici completi per questa strategia e per eseguire il backtest sono disponibili alla fine dell’articolo.

Le modifiche di cui sopra a DataTrader forniscono la struttura necessaria per eseguire una strategia di analisi del sentiment. Tuttavia dobbiamo descrivere come implementare le regole di entrata e di uscita della nostra strategia. A quanto pare, la maggior parte del “lavoro sporco” è stato effettuato nei moduli descritti in precedenza. L’esecuzione della strategia stessa è relativamente semplice.

Iniziamo con l’importazione delle librerie necessarie, tra cui gli oggetti base di DataTrader che in interagiscono con una sottoclasse di  Strategy:

				
					
# sentdex_sentiment_strategy.py

from datatrader.event import (SignalEvent, EventType)
from datatrader.strategy.base import AbstractStrategy
				
			

La nuova sottoclasse si chiama SentdexSentimentStrategy. Richiede solo un elenco di ticker su cui agire, un handle per la coda degli eventi, un valore intero sent_buy per la soglia di sentiment di ingresso e un corrispondente sent_sell per la soglia di uscita. Entrambi sono specificati successivamente nel codice del backtest.

Inoltre, per la negoziazione è richiesta una quantità base di azioni. Al fine di mantenere la strategia relativamente semplice, il dimensionamento della posizione prevede esclusivamente una quantità di base per ciascun ticker in qualsiasi momento della strategia, sia in acquisto che  in vendita. In altre parole, non prevediamo nessuna regolazione dinamica delle dimensioni delle posizioni o dell’allocazione percentuale per i ticker. Questa logica sarebbe una delle prime parti da ottimizzare in una strategia live. Dato che probabilmente il codice di dimensionamento della posizione può distrarre dall’obiettivo principale di questa strategia, cioè il “sentiment”, in questo articolo è stato deciso di mantenerlo semplice.

Infine usiamo self.invested come dizionario per memorizzare lo stato di negoziazione di ogni ticker. In particolare per ogni ticker è associato un valore booleano True/ False, a seconda che una posizione long sia aperta o meno:

				
					
class SentdexSentimentStrategy(AbstractStrategy):
    """
    Requisiti:
    tickers - La lista dei simboli dei ticker
    events_queue - La coda degli eventi
    sent_buy - soglia di entrata
    sent_sell - soglia di uscita
    base_quantity - Numero di azioni ogni azione
    """
    def __init__(
        self, tickers, events_queue,
        sent_buy, sent_sell, base_quantity
    ):
        self.tickers = tickers
        self.events_queue = events_queue
        self.sent_buy = sent_buy
        self.sent_sell = sent_sell
        self.qty = base_quantity
        self.time = None
        self.tickers.remove("SPY")
        self.invested = dict(
            (ticker, False) for ticker in self.tickers
        )
				
			

Calcolo dei segnali

Come per tutte le sottoclassi di AbstractStrategy il metodo calculate_signals contiene le effettive regole di trading basate sugli eventi. In tutte le altre strategie di DataTrader descritte fino ad oggi, questo metodo gestisce gli oggetti BarEventTickEvent.

In ogni strategia presentata finora, la prima riga di questo metodo controlla sempre il tipo di evento ( if event.type == EventType...). In questo modo si fornisce una maggiore flessibilità nelle sottoclassi AbstractStrategy, poiché possono rispondere a eventi arbitrari, non solo a quelli basati sui dati dei prezzi degli asset.

Una volta che l’evento è stato confermato come un SentimentEvent, il codice controlla se quel particolare ticker è già stato scambiato. In caso contrario, controlla se il sentiment supera il valore intero della soglia di ingresso del sentiment e quindi crea un valore della quantità base di azioni per andare long. Se siamo già a mercato per questo ticker e la soglia del sentiment corrente è inferiore alla soglia di uscita  prevista, si chiude la posizione.

La strategia di analisi del sentiment presentata di seguito va solo long. È molto semplice estenderla anche per lo short trading. Un esempio di codice per lo shorting è stato descritto in altre strategie di trading presenti su tradingquant.it, in particolare nel codice descritto per il pairs trading con il filtro di Kalman.

				
					

    def calculate_signals(self, event):
        """
        Calcola i segnali della strategia
        """
        if event.type == EventType.SENTIMENT:
            ticker = event.ticker
            # Segnale Long
            if (
                    self.invested[ticker] is False and
                    event.sentiment >= self.sent_buy
            ):
                print("LONG %s at %s" % (ticker, event.timestamp))
                self.events_queue.put(SignalEvent(ticker, "BOT", self.qty))
                self.invested[ticker] = True
            # Chiusura segnale
            if (
                    self.invested[ticker] is True and
                    event.sentiment <= self.sent_sell
            ):
                print("CLOSING LONG %s at %s" % (ticker, event.timestamp))
                self.events_queue.put(SignalEvent(ticker, "SLD", self.qty))
                self.invested[ticker] = False
				
			

Configurazione della strategia

Come per tutte le strategie implementate con DataTrader, esiste un corrispondente file di backtest che specifica i parametri della strategia. È molto simile a molti dei file di backtest precedenti e quindi  il codice completo viene  riportato solo alla fine di questo articolo.

Le differenze principali sono l’inizializzazione dell’oggetto SentimentHandler e l’impostazione dei parametri per le soglie di ingresso e di uscita. Questi sono impostati a 6 per l’ingresso e -1 per l’uscita, come indicato nelle regole della strategia descritta in precedenza. È istruttivo (e potenzialmente più redditizio!) ottimizzare questi valori per vari set di ticker.

Il file sentdex_sample.csv è inserito nella directory CSV_DATA_DIR di DataTrader, dove di solito risiedono anche i dati sui prezzi. Le date di inizio e di fine riflettono i dati forniti dal file di esempio di Sentdex che contiene le previsioni del sentiment.

				
					
..
..
start_date = datetime.datetime(2012, 10, 15)
end_date = datetime.datetime(2016, 2, 2)
..
..

# Uso della strategia di Sentdex Sentiment
sentiment_handler = SentdexSentimentHandler(
    config.CSV_DATA_DIR, "sentdex_sample.csv",
    events_queue, tickers=tickers, 
    start_date=start_date, end_date=end_date
)

base_quantity = 2000
sent_buy = 6
sent_sell = -1
strategy = SentdexSentimentStrategy(
    tickers, events_queue, 
    sent_buy, sent_sell, base_quantity
)
strategy = Strategies(strategy, DisplayStrategy())
				
			

Per eseguire questa strategia è necessario utilizzare il proprio ambiente virtuale di DataTrader (come sempre) e digitare nel terminale quanto segue, dove l’elenco dei ticker deve essere  modificato per adattarsi alla particolare strategia in uso. Assicurarsi di includere SPY se si desidera un confronto con il benchmark.

Il seguente esempio consiste in una selezione di titoli del settore difensivo dell’S&P500, tra cui Boeing, General Dynamics, Lockheed Martin, Northrop-Grumman e Raytheon:

				
					$ python sentdex_sentiment_backtest.py --tickers=BA,GD,LMT,NOC,RTN,SPY
				
			

Di seguito un estratto dell’output  per il set di azione del settore difensivo:

				
					..
..
---------------------------------
Backtest complete.
Sharpe Ratio: 1.62808089233
Max Drawdown: 0.0977963517677
Max Drawdown Pct: 0.0977963517677
				
			

Risultati della strategia

Costi di transazione

Vediamo ora i risultati della strategia di analisi del sentiment al netto dei costi di transazione. I costi sono simulati utilizzando i prezzi fissi delle azioni statunitensi di Interactive Brokers per le azioni del Nord America. Questi sono ragionevolmente rappresentativi  dei costi che potrebbero essere applicatei ad una vera strategia di trading.

Sentimento sui titoli S&P500 Tech

La quantità base di condivisioni utilizzate per ciascun ticker è 2.000.

trading-algoritmico-datatrader-sentdex-sentiment-tech-tearsheet

La strategia di analisi del sentiment delle azioni tecnologiche registra un CAGR del 21,0% rispetto al benchmark del 9,4%, utilizzando 2.000 azioni di ciascuno dei cinque ticker. Genera ampi guadagni in soli tre mesi, vale a dire maggio 2013, ottobre 2013 e luglio 2015. Il resto del tempo è per lo più basso o piatto. Notiamo un’ampia durata di prelievo di 318 giorni tra la metà del 2014 e la metà del 2015 e un ampio prelievo massimo giornaliero del 17,23%, rispetto al 13,04% del benchmark.

Inoltre si ha uno Sharpe ratio di 1,12 rispetto al 0,75 del benchmark, ma le prestazioni non sono abbastanza significative da giustificare il passaggio live di questa strategia.

Sentiment sui titoli energetici S&P500

La quantità base di condivisioni utilizzate per ciascun ticker è 5.000.

La strategia di analisi del sentiment di un mix di titoli energetici si comporta in modo abbastanza diverso rispetto al paniere di titoli tecnologici. È molto volatile, registrando mesi con grandi guadagni e altri mesi con grandi perdite. Il suo drawdown massimo è pari al 27,49%, che è sufficiente ad eliminare da qualsiasi ulteriore considerazione come strategia quantitativa profittevole. Inoltre, la strategia sembra perdere ogni efficacia dopo la metà del 2014, quando scende sott’acqua e rimane piatta fino al 2015.

Ha uno scarso Sharpe ratio pari  a 0,63 rispetto al benchmark di 0,75. Quindi questa non è una strategia praticabile se portata avanti nella sua forma attuale.

Sentiment sui titoli della difesa S&P500

La quantità base di condivisioni utilizzate per ciascun ticker è 2.000.

Le azioni del settore difesa forniscono una storia diversa rispetto a tecnologia ed energia. La strategia possiede molti mesi di solidi guadagni e ha un Sharpe ratio giornaliero di 1,69 per sole posizioni long. Il suo drawdown massimo di 9,69%  è inferiore a quello del benchmark. Ha anche un CAGR  interessanti pari al 25,45%. Nonostante questi vantaggi, ha ottenuto la maggior parte dei suoi guadagni nel 2013, con il 2014 e il 2015 che hanno registrato rendimenti molto inferiori.

Conclusioni

Sebbene questa strategia di analisi del sentiment sia sicuramente interessante, c’è ancora molto da fare per metterla in produzione. Dovrebbe essere testata su un periodo molto più ampio. Inoltre, l’aggiunta di posizioni short consentirebbe alla strategia di essere in qualche modo neutrale rispetto al mercato, sperando di ridurre il beta di mercato.

L’ottimizzazione del dimensionamento della posizione e la gestione del rischio sono i passaggi logici successivi e avrebbero probabilmente un effetto significativo sulla performance. Un’ultima modifica consisterebbe nell’aumentare la diversificazione aggiungendo molti più titoli al mix, magari trasversalmente ai settori. Chiaramente vi sono notevoli margini di miglioramento.

Negli articoli successivi molte di queste ottimizzazioni verranno esplorate tramite la modifica degli oggetti PositionSizerRiskManager presenti in DataTrader. Ciò contribuirà ad avvicinare queste strategie  all’implementazione per il live-trading.

Codice Completo

Il codice completo presentato in questo articolo, basato sul framework di trading quantitativo event-driven DataTrader, è disponibile nel seguente repository GitHub: https://github.com/tradingquant-it/DataTrader.

				
					# sentdex_sentiment_strategy.py

from datatrader.event import (SignalEvent, EventType)
from datatrader.strategy.base import AbstractStrategy

class SentdexSentimentStrategy(AbstractStrategy):
    """
    Requisiti:
    tickers - La lista dei simboli dei ticker
    events_queue - La coda degli eventi
    sent_buy - soglia di entrata
    sent_sell - soglia di uscita
    base_quantity - Numero di azioni ogni azione
    """
    def __init__(
        self, tickers, events_queue,
        sent_buy, sent_sell, base_quantity
    ):
        self.tickers = tickers
        self.events_queue = events_queue
        self.sent_buy = sent_buy
        self.sent_sell = sent_sell
        self.qty = base_quantity
        self.time = None
        self.tickers.remove("SPY")
        self.invested = dict(
            (ticker, False) for ticker in self.tickers
        )

    def calculate_signals(self, event):
        """
        Calcola i segnali della strategia
        """
        if event.type == EventType.SENTIMENT:
            ticker = event.ticker
            # Segnale Long
            if (
                    self.invested[ticker] is False and
                    event.sentiment >= self.sent_buy
            ):
                print("LONG %s at %s" % (ticker, event.timestamp))
                self.events_queue.put(SignalEvent(ticker, "BOT", self.qty))
                self.invested[ticker] = True
            # Chiusura segnale
            if (
                    self.invested[ticker] is True and
                    event.sentiment <= self.sent_sell
            ):
                print("CLOSING LONG %s at %s" % (ticker, event.timestamp))
                self.events_queue.put(SignalEvent(ticker, "SLD", self.qty))
                self.invested[ticker] = False
				
			
				
					# sentiment_sentdex_backtest.py

import click
import datetime
import numpy as np

from datatrader import settings
from datatrader.compat import queue
from datatrader.price_parser import PriceParser
from datatrader.price_handler.yahoo_daily_csv_bar import YahooDailyCsvBarPriceHandler
from datatrader.sentiment_handler.sentdex_sentiment_handler import SentdexSentimentHandler
from datatrader.strategy.base import Strategies
from datatrader.position_sizer.naive import NaivePositionSizer
from datatrader.risk_manager.example import ExampleRiskManager
from datatrader.portfolio_handler import PortfolioHandler
from datatrader.compliance.example import ExampleCompliance
from datatrader.execution_handler.ib_simulated import IBSimulatedExecutionHandler
from datatrader.statistics.tearsheet import TearsheetStatistics
from datatrader.trading_session import TradingSession

from sentdex_sentiment_strategy import SentdexSentimentStrategy


def run(config, testing, tickers, filename):
    # Impostazione delle variabili necessarie per il backtest
    # Informazioni sul Backtest
    events_queue = queue.Queue()
    csv_dir = config.CSV_DATA_DIR
    initial_equity = PriceParser.parse(500000.00)

    # Uso del Manager dei Prezzi di Yahoo Daily
    start_date = datetime.datetime(2012, 10, 15)
    end_date = datetime.datetime(2016, 2, 2)
    price_handler = YahooDailyCsvBarPriceHandler(
        csv_dir, events_queue, tickers,
        start_date=start_date, end_date=end_date
    )

    # Uso della strategia Sentdex Sentiment trading
    sentiment_handler = SentdexSentimentHandler(
        config.CSV_DATA_DIR, "sentdex_sample.csv",
        events_queue, tickers=tickers,
        start_date=start_date, end_date=end_date
    )

    base_quantity = 2000
    sent_buy = 6
    sent_sell = -1
    strategy = SentdexSentimentStrategy(
        tickers, events_queue,
        sent_buy, sent_sell, base_quantity
    )
    strategy = Strategies(strategy)

    # Uso di un Position Sizer standard
    position_sizer = NaivePositionSizer()

    # Uso di Manager di Risk di esempio
    risk_manager = ExampleRiskManager()

    # Use del Manager di Portfolio di default
    portfolio_handler = PortfolioHandler(
        PriceParser.parse(initial_equity), events_queue, price_handler,
        position_sizer, risk_manager
    )

    # Uso del componente ExampleCompliance
    compliance = ExampleCompliance(config)

    # Uso un Manager di Esecuzione che simula IB
    execution_handler = IBSimulatedExecutionHandler(
        events_queue, price_handler, compliance
    )

    # Uso delle statistiche di default
    title = ["Sentiment Sentdex Strategy"]
    statistics = TearsheetStatistics(
        config, portfolio_handler, title,
        benchmark="SPY"
    )

    # Settaggio del backtest
    backtest = TradingSession(
        config, strategy, tickers,
        initial_equity, start_date, end_date, events_queue,
        price_handler=price_handler,
        portfolio_handler=portfolio_handler,
        compliance=compliance,
        position_sizer=position_sizer,
        execution_handler=execution_handler,
        risk_manager=risk_manager,
        statistics=statistics,
        sentiment_handler=sentiment_handler,
        title=title, benchmark='SPY'
    )
    results = backtest.start_trading(testing=testing)
    statistics.save(filename)
    return results


@click.command()
@click.option('--config', default=settings.DEFAULT_CONFIG_FILENAME, help='Config filename')
@click.option('--testing/--no-testing', default=False, help='Enable testing mode')
@click.option('--tickers', default='SPY', help='Tickers (use comma)')
@click.option('--filename', default='', help='Pickle (.pkl) statistics filename')
def main(config, testing, tickers, filename):
    tickers = tickers.split(",")
    config = settings.from_file(config, testing)
    run(config, testing, tickers, filename)


if __name__ == "__main__":
    main()
				
			

Benvenuto su TradingQuant!

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

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

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

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

Torna in alto