strategia trading analisi sentiment dei dati sentdex con datatrader

Strategia di trading di analisi del sentiment dei dati Sentdex con DataTrader

Sommario

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

Se è la prima volta che atterri su DataTrading, BENVENUTO!

Lascia che mi presenti. Sono Gianluca, ingegnere, trekker e 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.

TUTORIAL

Oltre ai “soliti” trucchi relativi all’arbitraggio statistico, al trend-following e all’analisi fondamentale, molti fondi quantitativi (e trader retail!) si impegnano in tecniche di elaborazione del linguaggio naturale (NLP) per costruire strategie sistematiche. Tali tecniche rientrano nell’ambito della Sentiment Analysis .

In questo articolo descriviamo ed implementiamo un gruppo di strategie di trading quantitativo che si basano una serie di segnali di sentiment generati da un’API del fornitore. Questi segnali forniscono una scala intera che va da -3 (“sentimento negativo più forte”) a +6 (“sentimento positivo più forte”), associata a una data e a un simbolo ticker, che può essere utilizzata come soglia di ingresso e di uscita in un backtesting event-driven.

Una sfida chiave nello sviluppo di un tale sistema è l’integrazione degli eventi che rappresentano il sentiment, che sono archiviati in un file CSV di righe “datetime-ticker-sentiment”, in un sistema di trading basato sugli eventi di solito progettato per  operare direttamente sui dati dei prezzi.

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

Analisi del sentiment

L’obiettivo dell’analisi del sentiment è, generalmente, quello di prendere grandi quantità di dati “non strutturati” (come post di blog, articoli di giornale, rapporti di ricerca, tweet, video, immagini, ecc.) e utilizzare tecniche di PNL per quantificare il “sentiment” positivo o negativo su determinati  asset.

Per le azioni, in particolare, equivale spesso a un’analisi  di apprendimento automatico statistico del linguaggio utilizzato, individuando se contiene un fraseggio rialzista o ribassista. Questo fraseggio può essere quantificato in termini di forza del sentiment, che si traduce in valori numerici. In altre parole, i valori positivi riflettono un sentimento rialzista mentre i valori negativi rappresentano un sentimento ribassista.

Negli ultimi anni c’è stata una crescita costante di fornitori di analisi di sentiment, tra cui Sentdex , PsychSignal e Accern . Tutti utilizzano tecniche proprietarie per identificare “entità” all’interno di dati alternativi e quindi associare un punteggio di sentimento con timestamp a qualsiasi informazione estratta. Queste informazioni possono quindi essere aggregate in un periodo di tempo (ad esempio un giorno), al fine di produrre tuple di date-entity-sentiment. Tali tuple costituiscono la base di un segnale di trading.

Descrivere le metodologie di analisi di grandi quantità di “big data” e quantificare il sentimento esula dallo scopo di questo articolo. Uno strumento di analisi del sentimento pronto per la produzione end-to-end è una grande impresa di ingegneria del software. Quindi i trader retails preferisco spesso ottenere questi segnali dai fornitori e utilizzarli come parte di un portafoglio più ampio di segnali quantitativi per formare una strategia.

Questo articolo descrive una strategia di trading basata sui dati del sentiment di un particolare fornitore, cioè Sentdex, e le modalità di generazione di segnali long-only grazie a questi dati.

Le API di Sentdex e file di esempio

Sentdex fornisce un’API che consente di scaricare i dati sul sentiment per un’ampia varietà di strumenti finanziari. I dati sono disponibili con timeframe di un minuto o di un giorno. Maggiori dettagli sulla loro offerta (a pagamento) possono essere trovati nella loro pagina API .

L’API non sarà discussa in questo articolo poiché è un prodotto a pagamento ed è utile principalmente come generatore di eventi di paper trading o di trading reale. Dato che questo articolo riguarda le strategie di backtesting su dati storici, è più appropriato utilizzare un file statico, archiviato localmente per rappresentare i dati del sentiment.

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

Di seguito viene presentato 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
				
			

Da notare 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 utilizzati nelle tre simulazioni descritte in questo articolo.

La strategia di trading

La complessità di questa implementazione deriva principalmente dagli  adeguamenti al framework open-source di backtesting event-driven DataTrader, piuttosto che dalla strategia stessa, che è abbastanza semplice dopo aver generato il segnale del sentiment. La strategia è stata volutamente mantenuta semplice e vi sono ampi margini di modifica e ottimizzazione, che saranno oggetto di articoli successivi.

In questo esempio la strategia è solo long, ma è facilmente modificabile per includere posizioni short. La strategia determina le soglie di entrata e di uscita, che  sono poi rispettivamente  usate per aprire o chiudere posizioni long.

Implementiamo tre strategie, identiche ad eccezione della selezione dei titoli su cui operano. 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:

  • Entrata long su un ticker se il suo valore di sentiment raggiunge +6
  • Chiusura di una posizione su un ticker se il suo valore di sentiment raggiunge -1

Non esiste un’allocazione percentuale per ciascuna azione. Usiamo una quantità fissa di azioni per ciascuna allocazione durante tutta la strategia. Tuttavia, questa quantità fissa viene modificata per ciascuno dei tre settori di cui sopra.

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

Dati

Per attuare questa strategia è necessario disporre di dati sui prezzi OHLCV giornalieri per le azioni nel periodo coperto da questi backtest. In questo articolo vengono eseguite tre simulazioni separate, ciascuna contenente un gruppo di cinque titoli dell’S&P500. Il primo gruppo è costituito da titoli tecnologici/di base di consumo:

TickerNomePeriodoCollegamento
MSFTMicrosoft15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
AMZNAmazon.com15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
GOOGAlphabet15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
AAPLApple15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
IBMInternational Business Machines15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza

Il secondo gruppo è costituito da una serie di titoli di difesa, anch’essi dell’S&P500:

TickerNomePeriodoCollegamento
BAThe Boeing Company15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
LMTLockheed Martin15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
NOCNorthrop Grumman15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
GDGeneral Dynamics15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
RTNRaytheon15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza

L’ultimo set di ticker è costituito da titoli energetici, ancora una volta dall’S&P500:

TickerNomePeriodoCollegamento
XOMExxon Mobile15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
CVXChevron15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
SLBSchlumberger15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
OSSIOccidental Petroleum15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza
COPConocoPhilips15 ottobre 2012 – 2 febbraio 2016Yahoo Finanza

Questi dati dovranno essere inseriti nella directory specificata dal file delle impostazioni di QSTrader se si desidera replicare i risultati.

Inoltre, il file di esempio dell’API Sentdex dovrà essere posizionato nella stessa directory dei dati di DataTrader.

Implementazione Python

 

Gestione del sentiment con DataTrader

Per eseguire il backtest delle strategie basate sul sentiment, è necessario capire come incorporare i “segnali” del sentiment nel backtest.

L’attuale modello di DataTrader per il backtesting prevede un “motore” per la gestione della risposta agli eventi. Questo è implementato in un  ciclo while di grandi dimensioni che itera su tutti  gli oggetti TickEventBarEvent. Il codice permette di concatenare qualsiasi dataset di serie finanziare storiche di ticker, archiviati in un database o in file CSV, e quindi iterare riga per riga, con ogni riga è un elemento di un DataFrame pandas formato da un TickEvent o da un BarEvent per  ogni ticker.

Il precedente codice che implementava questa logica era il seguente:

				
					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)
				
			

Questo codice continua il ciclo 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

				
			

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

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

In particolare, 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.

La prima cosa consiste nell’importare le 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. In una strategia live questa sarebbe una delle prime parti da ottimizzare. 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
        )
				
			

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 BarEvent TickEvent.

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.

Quindi la strategia 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 datatrading.info,  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
				
			

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 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. Inoltre, ha 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.

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

Sebbene questa strategia 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 PositionSizer RiskManager presenti in DataTrader. Ciò contribuirà ad avvicinare queste strategie  all’implementazione per il live-trading.

Codice completo

Per il codice completo riportato in questo articolo, utilizzando il framework open-source di backtesting event-driven DataTrader si può consultare il seguente repository di github:
https://github.com/datatrading-info/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()
				
			
Torna su