In questo articolo descriviamo come gestire gli split e i dividendi con QuantConnect. I dividendi sono una parte fondamentale degli investimenti, eppure sono spesso trascurati nel mondo del backtesting. Questo può essere causato dai framework che generalmente non forniscono i dati o gli strumenti specifici necessari per gestire un dividendo. Di conseguenza, gli utenti finali devono trovare dati rettificati da terze parti come Alpha Vantage. Fortunatamente per gli utenti di QuantConnect, la piattaforma rende disponibili i dati sui dividendi e gli strumenti per gestirli, in modo semplice come impostare una singola opzione di inizializzazione.
Obiettivo
Con questo articolo esaminiamo come accedere alle informazioni sui dividendi e alle varie opzioni per gestirli all’interno di una strategia Buy & Hold. Nota: sebbene rivolto ai principianti, questo post presuppone che il lettore abbia familiarità con il funzionamento dei meccanismi di base di uno script su QuantConnect. Se arrivi da un’altra piattaforma o sei un principiante assoluto, dai un’occhiata agli altri articoli della serie introduttiva .
Accesso ai dividendi
Su QuantConnect non è necessario aggiungere uno specifico feed di dati per i dividendi. Quando si verificano, gli “eventi” dei dividendi sono inviati alle strategie allo stesso modo dei dati OHLCV
. Gli eventi sono contenuti all’interno della “fetta” di dati che viene passata alla funzione OnData()
. La documentazione ufficiale fornisce una panoramica dei diversi tipi di dati che vengono inviate ai nostri algoritmi: https://www.quantconnect.com/docs/algorithm-reference/handling-data.
Personalmente penso che l’esempio, DividendAlgorithm.py di QuantConnect lo rende il concetto molto più chiaro. L’esempio vale più di mille parole ed è così ben fatto che lo usiamo come base per creare il nostro esempio di codice. Per riassumere, possiamo accedere al dividendo (se presente) tramite uno speciale oggetto/tipo di dati contenuto nella nostra slice data
. Per essere più specifici, vi accediamo utilizzando data.Dividends
. Da notare che Dividends
può essere iterato (in loop) per accedere alle informazioni sui dividendi. Se non è disponibile alcun dividendo (nei giorni in cui non è disponibile), il codice all’interno del ciclo non verrà eseguito o causerà un errore. L’esempio ufficiale implementa questa logica con l’istruzione for kvp in data.Dividends
.
Gestione dei dividendi
Ora che sappiamo come accedere alle informazioni sui dividendi, dobbiamo capire come gestire gli split e i dividendi con QuantConnect. Come accennato in precedenza, QuantConnect ci fornisce alcune opzioni su come gestire i dividendi. Non ci limitiamo semplicemente ad aggiustare i dati come nelle altre piattaforme (anche se possiamo farlo se vogliamo!), ma possiamo decidere di ricevere i dividendi in contanti sul nostro conto. Queste opzioni non sono trattate nella documentazione online del link precedente o nell’esempio DividendAlgorithm.py, ma possono essere trovate con un po’ di paziente ricerca. Le opzioni rientrano in una categoria denominata “Modalità di normalizzazione dei dati” e l’impostazione di modalità diverse influisce sulla gestione degli aggiornamenti del saldo di liquidità o sulla gestione dell’aggiustamento dei prezzi. Nota: durante la mia ricerca, non sono riuscito a trovare le modalità di normalizzazione dei dati nella documentazione ufficiale ma li ho scoperti attraverso i forum. Di seguito la citazione completa di un post sul forum:
Vuoi i prezzi grezzi degli asset, con i dividendi pagati in contanti e gli adeguamenti agli split della quantità delle tue partecipazioni? Questo è abbastanza vicino alla realtà e può essere ottenuto con la nuova impostazione: DataNormalizationMode.Raw.
Preferiresti split adeguati al prezzo, ma i dividendi pagati in contanti? Questo è l’ideale per la creazione di grafici poiché il prezzo è continuo, ma i dividendi vengono comunque pagati in contanti. Puoi utilizzare questa modalità con: DataNormalizationMode.SplitAdjusted.
Che ne dici dei grafici Total Return? Dove i dividendi vengono aggiunti al prezzo dell’asset, quindi il prezzo totale dell’asset include la somma dei dividendi? Questa è una tecnica popolare per tenere conto della ricezione e del reinvestimento dei dividendi. Puoi accedere a questa modalità con: DataNormalizationMode.TotalReturn
Le modalità di normalizzazione dei dati vengono impostate durante l’inizializzazione del nostro algoritmo, come descritto nel seguente esempio.
Gestire gli Split e i Dividendi con QuantConnect
###
### Basic template algorithm simply initializes the date range and cash. This is a skeleton
### framework you can use for designing an algorithm.
###
# region imports
from AlgorithmImports import *
import numpy as np
# endregion
class BasicTemplateAlgorithm(QCAlgorithm):
'''Basic template algorithm simply initializes the date range and cash'''
def Initialize(self):
'''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
self.ticker = 'MSFT'
self.raw_handling = True
self.SetStartDate(2018,5,11) #Data inizio
self.SetEndDate(2018,5,22) #Data Fine
self.SetCash(10000) #Capitale iniziale
# Altri simboli qui: https://www.quantconnect.com/datasets/
self.AddEquity(self.ticker, Resolution.Daily)
# Impostazione del metodo per gestire i dividendi
# https://www.quantconnect.com/forum/discussion/508/update-dividends-splits-and-custom-price-normalization/p1
if self.raw_handling:
self.Securities[self.ticker].SetDataNormalizationMode(DataNormalizationMode.Raw)
else:
self.Securities[self.ticker].SetDataNormalizationMode(DataNormalizationMode.TotalReturn)
def OnData(self, data):
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
Arguments:
data: Slice object keyed by symbol containing the stock data
'''
if not self.Portfolio.Invested:
self.SetHoldings(self.ticker, 1)
for kvp in data.Dividends: # aggiornare il dizionario degli eventi
div_ticker = kvp.Key
div_distribution = kvp.Value.Distribution
div_total_value = div_distribution * self.Portfolio[self.ticker].Quantity
self.Log("{0} >> DIVIDEND >> {1} - ${2} - ${3}".format(self.Time, div_ticker, div_distribution, div_total_value))
self.Log("{0} >> SUMMARY >> {1} | Port Cash: {2} | Port Value: {3} | Holdings: {4} | Price {5}".format(self.Time,
self.ticker, self.Portfolio.Cash, self.Portfolio.TotalPortfolioValue, self.Portfolio[self.ticker].Quantity, self.Portfolio[self.ticker].Price))
Poiché questo articolo è incentrato sui dividendi e non sugli split, il codice dell’esempio i concentra sul test delle modalità di normalizzazione dei dati grezzi (raw) e dei rendimenti totali (total returns). Impostiamo la modalità di normalizzazione durante Initialize()
con una semplice istruzione: self.Securities[self.ticker].SetDataNormalizationMode(DataNormalizationMode.XYZ)
. Naturalmente, nel codice di esempio, abbiamo due versioni di questa riga. Sono racchiusi in un’istruzione if
/ else
per consentire un facile passaggio da una modalità all’altra. Sostituiamo anche XYZ
con i nomi delle modalità (Raw
e TotalReturns
). Tecnicamente, questo è tutto ciò che dobbiamo fare! Seleziona il metodo di normalizzazione preferito e i dividendi verranno gestiti automaticamente. I dividendi sono accreditati in contatti nel conto account o si aggiusta automaticamente il prezzo. È molto semplice. Tuttavia, dal momento che stiamo imparando, esplorando e studiando come funzionano i dividendi in QuantConnect, sono state aggiunte alcune informazioni extra nel log del backtest in modo da poter esaminare i meccanismi. Questo ci permette di ottenere una migliore comprensione di come funziona il motore.
Verifica dell’output
Per vedere come le modalità di normalizzazione influenzano il backtest, possiamo dare un’occhiata ai log intorno alla data in cui si verifica il dividendo. Si noti che nel nostro esempio, l’intervallo di date utilizzato è abbastanza specifico per Microsoft. I dati iniziano alcuni giorni prima di un dividendo e terminano subito dopo.
Normalizzazione dei dati grezzi
L’output seguente è una copia del file di registro generato dall’esecuzione del codice come descritto in precedenza.
2018-05-11 00:00:00 Launching analysis for 219e50fc60d127405081a29f5e1ded58 with LEAN Engine v2.5.0.0.15052
2018-05-11 00:00:00 2018-05-11 00:00:00 >> SUMMARY >> MSFT | Port Cash: 10000.0 | Port Value: 10000.0 | Holdings: 0.0 | Price 97.91
2018-05-12 00:00:00 2018-05-12 00:00:00 >> SUMMARY >> MSFT | Port Cash: 127.26 | Port Value: 9994.96 | Holdings: 101.0 | Price 97.7
2018-05-15 00:00:00 2018-05-15 00:00:00 >> SUMMARY >> MSFT | Port Cash: 127.26 | Port Value: 10028.29 | Holdings: 101.0 | Price 98.03
2018-05-16 00:00:00 2018-05-16 00:00:00 >> DIVIDEND >> MSFT - $0.42 - $42.42
2018-05-16 00:00:00 2018-05-16 00:00:00 >> SUMMARY >> MSFT | Port Cash: 169.68 | Port Value: 9999.0 | Holdings: 101.0 | Price 97.32
2018-05-17 00:00:00 2018-05-17 00:00:00 >> SUMMARY >> MSFT | Port Cash: 169.68 | Port Value: 9981.83 | Holdings: 101.0 | Price 97.15
2018-05-18 00:00:00 2018-05-18 00:00:00 >> SUMMARY >> MSFT | Port Cash: 169.68 | Port Value: 9883.86 | Holdings: 101.0 | Price 96.18
2018-05-19 00:00:00 2018-05-19 00:00:00 >> SUMMARY >> MSFT | Port Cash: 169.68 | Port Value: 9902.04 | Holdings: 101.0 | Price 96.36
2018-05-22 00:00:00 2018-05-22 00:00:00 >> SUMMARY >> MSFT | Port Cash: 169.68 | Port Value: 10027.28 | Holdings: 101.0 | Price 97.6
2018-05-23 00:00:00 2018-05-23 00:00:00 >> SUMMARY >> MSFT | Port Cash: 169.68 | Port Value: 10017.18 | Holdings: 101.0 | Price 97.5
2018-05-23 00:00:00 Algorithm Id:(219e50fc60d127405081a29f5e1ded58) completed in 0.43 seconds at 0k data points per second. Processing total of 74 data points.
Se diamo un’occhiata da vicino ai log, possiamo vedere che il 2018-05-16
abbiamo ricevuto un dividendo di $ 42,42. Possiamo anche vedere che la liquidità sale a $ 174,73 da $ 132,31 del giorno prima (un aumento di $ 42,42). Quello facciamo con quei soldi dipende solo da noi. Potremmo reinvestirlo o forse simulare il pensionamento e presumere che dovremmo accumulare il x% e reinvestire solo il resto? Con la modalità di normalizzazione dei dati RAW, possiamo verificare questi scenari!
Normalizzazione dei dati di rendimento totale
Per testare la modalità di normalizzazione TotalReurn
, dobbiamo modificare la riga seguente da self.raw_handling = True
a self.raw_handling = False
in modo da attivare la modalità di normalizzazione TotalReturn
e otteniamo il seguente output.
2018-05-11 00:00:00 Launching analysis for 580f5feaaf9bf6e7937ded9666c80604 with LEAN Engine v2.5.0.0.15052
2018-05-11 00:00:00 2018-05-11 00:00:00 >> SUMMARY >> MSFT | Port Cash: 10000.0 | Port Value: 10000.0 | Holdings: 0.0 | Price 97.91
2018-05-12 00:00:00 2018-05-12 00:00:00 >> SUMMARY >> MSFT | Port Cash: 127.26 | Port Value: 9994.96 | Holdings: 101.0 | Price 97.7
2018-05-15 00:00:00 2018-05-15 00:00:00 >> SUMMARY >> MSFT | Port Cash: 127.26 | Port Value: 10028.29 | Holdings: 101.0 | Price 98.03
2018-05-16 00:00:00 2018-05-16 00:00:00 >> DIVIDEND >> MSFT - $0.42 - $42.42
2018-05-16 00:00:00 2018-05-16 00:00:00 >> SUMMARY >> MSFT | Port Cash: 127.26 | Port Value: 9956.58 | Holdings: 101.0 | Price 97.32
2018-05-17 00:00:00 2018-05-17 00:00:00 >> SUMMARY >> MSFT | Port Cash: 127.26 | Port Value: 9981.83 | Holdings: 101.0 | Price 97.57
2018-05-18 00:00:00 2018-05-18 00:00:00 >> SUMMARY >> MSFT | Port Cash: 127.26 | Port Value: 9883.86 | Holdings: 101.0 | Price 96.6
2018-05-19 00:00:00 2018-05-19 00:00:00 >> SUMMARY >> MSFT | Port Cash: 127.26 | Port Value: 9902.04 | Holdings: 101.0 | Price 96.78
2018-05-22 00:00:00 2018-05-22 00:00:00 >> SUMMARY >> MSFT | Port Cash: 127.26 | Port Value: 10027.28 | Holdings: 101.0 | Price 98.02
2018-05-23 00:00:00 2018-05-23 00:00:00 >> SUMMARY >> MSFT | Port Cash: 127.26 | Port Value: 10017.18 | Holdings: 101.0 | Price 97.92
2018-05-23 00:00:00 Algorithm Id:(580f5feaaf9bf6e7937ded9666c80604) completed in 0.45 seconds at 0k data points per second. Processing total of 74 data points.
Possiamo vedere che il prezzo delle azioni e la liquidità del conto sono sincronizzati fino al giorno del dividendo. Dopo il dividendo, i due esempi divergono. In questo caso, la nostra liquidità rimane la stessa di prima del dividendo e il prezzo viene quindi aggiustato dal 2018-05-17
. $ 97,57 in modalità TotalReturn
contro $ 97,15 in modalità Raw
.
Date ex-div
Prima di concludere, vale la pena sottolineare che quando la modalità di normalizzazione dei dati è impostata su Raw
, il contante viene versato sul conto alla data di stacco della cedola e non alla data di pagamento. Possiamo verificarlo confrondando la distribuzione con le date ex dividendo riportate in altre risorse online. Questo è qualcosa di cui essere consapevoli poiché normalmente non riceveresti i contanti per il dividendo alla data ex-div. I pagamenti sono generalmente circa 1 mese dopo la data ex-div. Se poi reinvesti immediatamente il denaro, quel denaro rimarrà sul mercato molto più a lungo di quanto potrebbe essere nel mondo reale. Questo, a sua volta, può influenzare il realismo dei risultati del backtesting.
Codice completo
In questo articolo abbiamo descritto come gestire gli split e i dividendi con QuantConnect. Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/QuantConnect