Momentum Asset Allocation con DataInvestor, Jupyter e Docker

Momentum Asset Allocation con DataInvestor, Jupyter e Docker

Sommario

Nell’articolo precedente abbiamo descritto come configurare un ambiente di backtesting di strategie di trading algoritmico utilizzando il framework di backtesting DataInvestor all’interno di un Jupyter Notebook. Abbiamo isolato l’ambiente di ricerca e le sue dipendenze utilizzando Docker e Docker Compose. In questo articolo descriviamo le regole e i risultati del backtest di una strategia di Momentum Asset Allocation con DataInvestor, la strategia di asset allocation tattica Momentum Top N. Per seguire questo tutorial è vivamente consigliato completare il tutorial precedente.

Strategia di Momentum Asset Allocation con DataInvestor

Per dimostrare come usare il framework di ricerca e backtest di strategie di trading e investimento implementiamo una strategia di asset allocation tattica chiamata Top N Momentum. Si tratta di una strategia dinamica e di lungo periodo, basata sullo momentum dei settori che compongono lo Standard and Poor Depository Reciepts (SPDR). L’universo investibile della strategia si basa su dieci ETF settoriali: XLB, XLC, XLE, XLF, XLI, XLK, XLP, XLU, XLV e XLY.

La logica della strategia prevede di calcolare il momentum basato sul rendimento del periodo di detenzione (HPR) alla fine di ogni mese. Inoltre seleziona i primi N etf da tenere in portafoglio per il mese successivo. Esistono molte varianti di questa strategia che modificano il periodo di detenzione e il numero di ETF nell’universo di trading. In questo caso impostiamo il periodo di ricerca su 126 giorni (circa sei mesi) e N pari ai tre settori con le migliori prestazioni.

La strategia è solo long, non  prevediamo di andare short sul mercato. È anche dinamica perchè l’universo investibile non è costante, infatti i dati  dell’ETF XLC iniziano il 18 giugno 2018, mentre gli altri ETF iniziano il 22 dicembre 1998.

Scaricare i dati storici

Per effettuare il backtest della strategia dobbiamo prima  scaricare i dati storici. Possiamo usare qualsiasi fornitore di dati purché i dati siano formattati secondo quanto previsto dalle funzioni di DataInvestor. DataInvestor è stato progettato per funzionare con i dati di barre OHLC a frequenza giornaliera memorizzate in file CSV con il seguente formato:

  • Date: AAAA-MM-GG.
  • Open: il prezzo di apertura nell’intervallo di tempo.
  • High: il prezzo massimo nell’intervallo di tempo.
  • Low: il prezzo minimo nell’intervallo di tempo.
  • High: il prezzo di chiusura nell’intervallo di tempo.
  • Adj Close: il prezzo di chiusura dopo gli aggiustamenti (frazionamenti azionari, dividendi ecc.).
  • Volume: l’importo scambiato nell’intervallo di tempo.

I file CSV devono essere inseriti nella directory dei dati che abbiamo creato all’interno della nostra applicazione nel precedente articolo, dt_stratdev_data/. Possiamo creare una sottodirectory per questa specifica strategia. Dovremmo ottenere lo storico completo dei dati per i seguenti ETF: XLB, XLC, XLE, XLF, XLI, XLK, XLP, XLU, XLV e XLY.

Attivazione del framework

Ora possiamo attivare il contenitore Docker accedendo alla directory di orchestrazione e digitando docker compose up. Non abbiamo più bisogno del flag --build poiché non stiamo apportando alcuna modifica al Dockerfile. Possiamo quindi accedere all’indirizzo localhost/:8888 e vediamo il menu del notebook Jupyter. Per iniziare creiamo un nuovo notebook.

DataInvestor è un framework di backtesting, è progettato in modo modulare e scalabile, consentendo all’utente di apportare modifiche ed estensioni al software. I tutorial e gli esempi disponibili nella documentazione descrivono gradualmente i diversi componenti. Vale la pena approfondirli per ampliare la tua conoscenza dei diversi componenti e classi. In questo articolo ci concentriamo nei componenti necessari per eseguire il backtest della strategia Momentum Top N. Iniziamo osservando come eseguire un backtest in DataInvestor.

La classe BacktestTradingSession presente nel file datainvestor/trading/backtest.py contiene un metodo run() che esegue il backtest. Per eseguire il metodo dobbiamo crea un’istanza della classe, dobbiamo importare la classe e definire i parametri. Diamo un’occhiata al codice che crea l’istanza di backtest della strategia di Momentum top N:

				
					 
 # Costruisce il backtest della strategia e lo esegue
strategy_backtest = BacktestTradingSession(
        start_dt,
        end_dt,
        strategy_universe,
        strategy_alpha_model,
        signals=signals,
        rebalance='end_of_month',
        long_only=True,
        cash_buffer_percentage=0.01,
        burn_in_dt=burn_in_dt,
        data_handler=strategy_data_handler
    )
    strategy_backtest.run()
				
			

Dato che  ci sono molti parametri da definire, li esaminiamo passo dopo passo. Iniziamo con i parametri più semplici lasciando alla fine quelli più complessi, come strategy_alpha_model. Anche se li descriviamo in ordine sparso, essi sono definiti sia come argomenti parametrici che come parole  chiave della chiave. Pertanto  è necessario mantenere l’ordine  dei parametri quando si istanzia la classe.

Configurazione del backtest

Il backtest necessita di start_dt, end_dt. Dato che la strategia prevede una finestra mobile di sei mesi,  abbiamo anche bisogno del parametro burn_in_dt. Per assicurarci di avere sufficienti dati giornalieri per generare un segnale, lo impostiamo a un anno dalla data di inizio. Osservando la documentazione per la classe BacktestTradingSession possiamo vedere che questi parametri sono timestamp di Pandas, quindi dobbiamo importare la libreria Pandas. Nella funzione principale possiamo iniziare a costruire la strategia. Ricordiamoci che potrebbe essere necessario modificare le date di inizio, fine e burn_in in base ai dati storici disponibili.

				
					 
 import pandas as pd
  
 from datainvestor.trading.backtest import BackTestTradingSession 
  


# Durata del backtest
start_dt = pd.Timestamp('1998-12-22 14:30:00', tz=pytz.UTC)
burn_in_dt = pd.Timestamp('1999-12-22 14:30:00', tz=pytz.UTC)
end_dt = pd.Timestamp('2020-12-31 23:59:00', tz=pytz.UTC)

# Costruttore del backtest della strategia ed esecuzione
strategy_backtest = BacktestTradingSession(
   start_dt,
   end_dt,
   burn_in_dt=burn_in_dt
   )
  
strategy_backtest.run()
				
			

Passiamo ora a creare l’universo della strategia. Come accennato in precedenza, l’ETF XLC viene aggiunto al backtest in un secondo momento. In questo modo abbiamo un universo di asset dinamico. Importiamo la  classe DynamicUniverse. Esplorando il modulo datainvestor/asset/universe vediamo che DataInvestor può gestire sia universi statici che dinamici. 

In un universo statico dobbiamo semplicemente definire i ticker che vogliamo analizzare e convertirli in una classe di asset. Per le azioni, DataInvestor richiede che i simboli abbiano il prefisso “EQ:”. Questo approccio permette di definire un’insieme di diverse classi di asset. Nel caso di un universo dinamico, dobbiamo dire a DataInvestor quando inserire XLC nel backtest. Dobbiamo quindi aggiungere i timestamp degli asset per la creazione dell’universo:

				
					
import pandas as pd
import pytz
import os
  
from datainvestor.asset.universe.dynamic import DynamicUniverse
from datainvestor.trading.backtest import BacktestTradingSession
  


start_dt = pd.Timestamp('1998-12-22 14:30:00', tz=pytz.UTC)
burn_in_dt = pd.Timestamp('1999-12-22 14:30:00', tz=pytz.UTC)
end_dt = pd.Timestamp('2024-04-08 23:59:00', tz=pytz.UTC)

# Costruisce i simboli e gli asset necessari per il backtest
# Utilizziamo gli ETF settoriali US del SPDR, dove tutti iniziano con XL
strategy_symbols = ['XL%s' % sector for sector in "BCEFIKPUVY"]
assets = ['EQ:%s' % symbol for symbol in strategy_symbols]

# Dato che è un universo dinamico di asset (XLC è aggiunto dopo)
# dobbiamo specificare quando includere XLC. Questo viene 
# effettuato tramite un dizionario delle date degli asset
asset_dates = {asset: start_dt for asset in assets}
asset_dates['EQ:XLC'] = pd.Timestamp('2018-06-18 00:00:00', tz=pytz.UTC)
strategy_universe = DynamicUniverse(asset_dates)

# Costruisce il backtest della strategia e lo esegue
strategy_backtest = BacktestTradingSession(
  start_dt,
  end_dt,
  strategy_universe,
  burn_in_dt=burn_in_dt
)

strategy_backtest.run()
				
			

Gestione delle sorgenti dati

Il passo successivo è definire il  data_handler. Dobbiamo importare la classe BacktestDataHandler dal  modulo datainvestor/data/. Questa classe implementa i metodi per accedere ai dati degli ultimi prezzi bid, ask e mid di un asset, nonché ai dati storici dei prezzi OHLC. Questa classe è necessaria per l’esecuzione del backtest e ci aiuta a creare il nuovo AlphaModel. 

La classe richiede inoltre di definire il data_sources. Dato che abbiamo i dati memorizzati in file CSV, possiamo utilizzare la classe CSVDailyBarDataSource. Possiamo inoltre creare una variabile d’ambiente per specificare la directory o sottodirectory che contiene i file CSV. Importiamo entrambe le classi e creiamo le sorgenti dati come segue:

				
					import pandas as pd
import pytz
import os
  
from datainvestor.asset.universe.dynamic import DynamicUniverse
from datainvestor.asset.equity import Equity
from datainvestor.trading.backtest import BacktestTradingSession
from datainvestor.data.backtest_data_handler import BacktestDataHandler
from datainvestor.data.daily_bar_csv import CSVDailyBarDataSource
  


start_dt = pd.Timestamp('1998-12-22 14:30:00', tz=pytz.UTC)
burn_in_dt = pd.Timestamp('1999-12-22 14:30:00', tz=pytz.UTC)
end_dt = pd.Timestamp('2024-04-08 23:59:00', tz=pytz.UTC)

# Costruisce i simboli e gli asset necessari per il backtest
# Utilizziamo gli ETF settoriali US del SPDR, dove tutti iniziano con XL
strategy_symbols = ['XL%s' % sector for sector in "BCEFIKPUVY"]
assets = ['EQ:%s' % symbol for symbol in strategy_symbols]

# Dato che è un universo dinamico di asset (XLC è aggiunto dopo)
# dobbiamo specificare quando includere XLC. Questo viene 
# effettuato tramite un dizionario delle date degli asset
asset_dates = {asset: start_dt for asset in assets}
asset_dates['EQ:XLC'] = pd.Timestamp('2018-06-18 00:00:00', tz=pytz.UTC)
strategy_universe = DynamicUniverse(asset_dates)

# NUOVO CODICE SOTTO
# Per evitare di caricare tutti i file CSV in una directory, impostiamo 
# la sorgente dati per caricare solo i simboli desiderati
csv_dir = os.environ.get('DATAINVESTOR_CSV_DATA_DIR', '.')
strategy_data_source = CSVDailyBarDataSource(csv_dir, Equity, csv_symbols=strategy_symbols)
strategy_data_handler = BacktestDataHandler(strategy_universe, data_sources=[strategy_data_source])
# NUOVO CODICE SOPRA

# Costruisce il backtest della strategia e lo esegue
strategy_backtest = BacktestTradingSession(
  start_dt,
  end_dt,
  strategy_universe,
  burn_in_dt=burn_in_dt,
  data_handler=strategy_data_handler
)

strategy_backtest.run()
				
			

Nella prima cella del notebook Jupyter dichiariamo la variabile d’ambiente come segue:

				
					%env DATAINVESTOR_CSV_DATA_DIR=/data
				
			

Dobbiamo ora impostare i valori dei parametri di BacktestTradingSession. Quando consideriamo la frequenza di ribilanciamento per una strategia, DataInvestor prevede alcune opzioni standard implementate in  datainvestor/system/rebalance/: buy_and_hold, daily, end_of_month e weekly. La nostra strategia Momentum Top N prevede un ribilanciamento ogni mese, quindi dobbiamo impostare la parola chiave rebalance='end_of_month' quando istanziamo la classe. Dato che la strategia non prevede posizioni short, impostiamo il parametro long_only=True.

Il parametro cash_percentage si riferisce alla quantità di liquidità che desideriamo lasciare nel portafoglio come buffer. DataInvestor calcola i ribilanciamenti alla chiusura del mercato tuttavia, come nel trading dal vivo, le operazioni vengono eseguite all’apertura del mercato. In questo modo otteniamo un backtest molto più realistico, ma significa che potrebbe verificarsi uno slippage del prezzo di un asset. Prevediamo quindi una riserva di liquidità in modo che i costi di slippage non impediscano l’esecuzione di un’operazione. Questa è una caratteristica unica di DataInvestor che non si vede spesso in altri software di backtesting e rende la simulazione più realistica.

				
					import pandas as pd
import pytz
import os
  
from datainvestor.asset.universe.dynamic import DynamicUniverse
from datainvestor.asset.equity import Equity
from datainvestor.trading.backtest import BacktestTradingSession
from datainvestor.data.backtest_data_handler import BacktestDataHandler
from datainvestor.data.daily_bar_csv import CSVDailyBarDataSource
  


start_dt = pd.Timestamp('1998-12-22 14:30:00', tz=pytz.UTC)
burn_in_dt = pd.Timestamp('1999-12-22 14:30:00', tz=pytz.UTC)
end_dt = pd.Timestamp('2024-04-08 23:59:00', tz=pytz.UTC)

# Costruisce i simboli e gli asset necessari per il backtest
# Utilizziamo gli ETF settoriali US del SPDR, dove tutti iniziano con XL
strategy_symbols = ['XL%s' % sector for sector in "BCEFIKPUVY"]
assets = ['EQ:%s' % symbol for symbol in strategy_symbols]

# Dato che è un universo dinamico di asset (XLC è aggiunto dopo)
# dobbiamo specificare quando includere XLC. Questo viene 
# effettuato tramite un dizionario delle date degli asset
asset_dates = {asset: start_dt for asset in assets}
asset_dates['EQ:XLC'] = pd.Timestamp('2018-06-18 00:00:00', tz=pytz.UTC)
strategy_universe = DynamicUniverse(asset_dates)

# Per evitare di caricare tutti i file CSV in una directory, impostiamo 
# la sorgente dati per caricare solo i simboli desiderati
csv_dir = os.environ.get('DATAINVESTOR_CSV_DATA_DIR', '.')
strategy_data_source = CSVDailyBarDataSource(csv_dir, Equity, csv_symbols=strategy_symbols)
strategy_data_handler = BacktestDataHandler(strategy_universe, data_sources=[strategy_data_source])

# Costruisce il backtest della strategia e lo esegue
strategy_backtest = BacktestTradingSession(
  start_dt,
  end_dt,
  strategy_universe,
  burn_in_dt=burn_in_dt,
  data_handler=strategy_data_handler,
  # NUOVO CODICE SOTTO       
  long_only=True,
  cash_buffer_percentage=0.01
  # NUOVO CODICE SOPRA
)

strategy_backtest.run()
				
			

Implementazione del Momentum Asset Allocation

La logica della strategia di Momentum asset allocation è gestita tramite la famiglia di classi strategy_alpha_model dove calcoliamo i pesi di allocazione del portafoglio ad ogni ribilanciamento. DataInvestor contiene le classi FixedSignalAlphaModel e SingleSignalAlphaModel all’interno di  datainvestor/alpha_model

La classe FixedSignal è usata per generare segnali di trading fissi. Ad esempio in una strategia sessanta quaranta in cui si desidera rimanere il 60% in un asset e il 40% in un altro, indipendentemente dal comportamento del mercato. SingleSignal genera un valore scalare per ciascun asset indipendentemente dal comportamento del mercato. In altre parole assegna lo stesso peso a ciascun asset ed è pari al valore impostato nell’argomento della parola chiave signal

La strategia Momentum Top N è più complicata. Desideriamo ottenere un portafoglio equamente ponderato, investito nei N settori  più performanti. Prima di creare un nuovo AlphaModel importiamo la classe base astratta AlphaModel.

				
					import pandas as pd
import pytz
import os

from datainvestor.alpha_model.alpha_model import AlphaModel
from datainvestor.asset.universe.dynamic import DynamicUniverse
from datainvestor.asset.equity import Equity
from datainvestor.trading.backtest import BacktestTradingSession
from datainvestor.data.backtest_data_handler import BacktestDataHandler
from datainvestor.data.daily_bar_csv import CSVDailyBarDataSource
				
			

Ed implementiamo la nuova classe che estende AlphaModel.

				
					
class TopNMomentumAlphaModel(AlphaModel):

    def __init__(
        self, signals, mom_lookback, mom_top_n, universe, data_handler
    ):
        self.signals = signals
        self.mom_lookback = mom_lookback
        self.mom_top_n = mom_top_n
        self.universe = universe
        self.data_handler = data_handler
				
			

Gestione dei segnali

Per accedere ai segnali dai dati dobbiamo usare la classe SignalsCollection situata in datainvestor/signals. Questa classe aggrega tutti i segnali e aggiorna l’universo degli asset per il nostro universo dinamico. DataInvestor contiene anche la classe MomentumSignal che calcola il momentum di un specifico asset in una specifica finestra temporale. Dobbiamo quindi importare queste classi da DataInvestor.

				
					
import pandas as pd
import pytz
import os

from datainvestor.alpha_model.alpha_model import AlphaModel
from datainvestor.signals.momentum import MomentumSignal
from datainvestor.signals.signals_collection import SignalsCollection
from datainvestor.asset.universe.dynamic import DynamicUniverse
from datainvestor.asset.equity import Equity
from datainvestor.trading.backtest import BacktestTradingSession
from datainvestor.data.backtest_data_handler import BacktestDataHandler
from datainvestor.data.daily_bar_csv import CSVDailyBarDataSource
				
			

Nella funzione principale dobbiamo ora definire i parametri del modello, il momentum lookback e il numero dei settori migliori da considerare. Come preannunciato, utilizziamo un periodo di sei mesi, ovvero 126 giorni lavorativi, e consideriamo tre settori. Definiamo anche i nostri segnali.

				
					 
start_dt = pd.Timestamp('1998-12-22 14:30:00', tz=pytz.UTC)
burn_in_dt = pd.Timestamp('1999-12-22 14:30:00', tz=pytz.UTC)
end_dt = pd.Timestamp('2024-04-08 23:59:00', tz=pytz.UTC)

# Costruisce i simboli e gli asset necessari per il backtest
# Utilizziamo gli ETF settoriali US del SPDR, dove tutti iniziano con XL
strategy_symbols = ['XL%s' % sector for sector in "BCEFIKPUVY"]
assets = ['EQ:%s' % symbol for symbol in strategy_symbols]

# Dato che è un universo dinamico di asset (XLC è aggiunto dopo)
# dobbiamo specificare quando includere XLC. Questo viene 
# effettuato tramite un dizionario delle date degli asset
asset_dates = {asset: start_dt for asset in assets}
asset_dates['EQ:XLC'] = pd.Timestamp('2018-06-18 00:00:00', tz=pytz.UTC)
strategy_universe = DynamicUniverse(asset_dates)

# Per evitare di caricare tutti i file CSV in una directory, impostiamo 
# la sorgente dati per caricare solo i simboli desiderati
csv_dir = os.environ.get('DATAINVESTOR_CSV_DATA_DIR', '.')


strategy_data_source = CSVDailyBarDataSource(csv_dir, Equity, csv_symbols=strategy_symbols)
strategy_data_handler = BacktestDataHandler(strategy_universe, data_sources=[strategy_data_source])

# NUOVO CODICE SOTTO
# Parametri del modello
mom_lookback = 126  # 6 mesi di giorni lavorativi
mom_top_n = 3  # Numero di asset da include ogni volta

# Genera i segnali usati nel modello alpha top-N momentum 
momentum = MomentumSignal(start_dt, strategy_universe, lookbacks=[mom_lookback])
signals = SignalsCollection({'momentum': momentum}, strategy_data_handler)
# NUOVO CODICE SOPRA

# Costruisce il backtest della strategia e lo esegue
strategy_backtest = BacktestTradingSession(
      start_dt,
      end_dt,
      strategy_universe,
      strategy_alpha_model,
      signals=signals,
      rebalance='end_of_month',
      long_only=True,
      cash_buffer_percentage=0.01,
      burn_in_dt=burn_in_dt,
      data_handler=strategy_data_handler
)

strategy_backtest.run()
				
			

Creazione dei metodi della classe Alpha Model

Ora possiamo definire i  metodi di classe. Il primo metodo è _highest_momentum_asset. Questo metodo calcola i segnali di momentum su tutti gli asset selezionati dall’universo dinamico nella finestra temporale specificata. Inoltre li inserisce in un dizionario indicizzato dai simboli degli asset. Il metodo usa la funzione di ordinamento  integrata in Python insieme al metodo operator.itemgetter. In questo modo possiamo creare una lista degli asset ordinati rispetto al momentum più elevato e selezionare i prime N asset. Ad esempio, l’output di questo metodo potrebbe essere simile a [‘EQ:XLC’, ‘EQ:XLB’, ‘EQ:XLK’] nel caso questi fossero i primi tre asset con il momentum più elevato nel periodo analizzato.

Il secondo metodo è _generate_signals. In questo metodo si richiama il metodo _highest_momentum_asset e produce un dizionario dei pesi dove ciascuno asset è equi-pesato. Ad esempio, l’output potrebbe essere simile a {‘EQ:XLC’: 0.3333333, ‘EQ:XLB’: 0.3333333, ‘EQ:XLK’: 0,3333333}.

L’ultimo metodo è __call__, che trasforma TopNMomentumAlphaModel in una funzione callable. Questo racchiude gli altri due metodi precedenti. Genera i pesi solo se sono stati raccolti dati sufficienti per generare un segnale di momentum nella finestra temporale specificata. Infine, restituisce un dizionario con i pesi.

				
					
import operator
import pandas as pd
import pytz
import os

from datainvestor.alpha_model.alpha_model import AlphaModel
from datainvestor.signals.momentum import MomentumSignal
from datainvestor.signals.signals_collection import SignalsCollection
from datainvestor.asset.universe.dynamic import DynamicUniverse
from datainvestor.asset.equity import Equity
from datainvestor.trading.backtest import BacktestTradingSession
from datainvestor.data.backtest_data_handler import BacktestDataHandler
from datainvestor.data.daily_bar_csv import CSVDailyBarDataSource

class TopNMomentumAlphaModel(AlphaModel):

    def __init__(
        self, signals, mom_lookback, mom_top_n, universe, data_handler
    ):
        self.signals = signals
        self.mom_lookback = mom_lookback
        self.mom_top_n = mom_top_n
        self.universe = universe
        self.data_handler = data_handler

        def _highest_momentum_asset(
            self, dt
        ):
            assets = self.signals['momentum'].assets

            # Calcola il momentum dei rendimenti per ciascun asset nel
            # periodo di lookback specificato
            all_momenta = {
                asset: self.signals['momentum'](
                    asset, self.mom_lookback
                ) for asset in assets
            }

            # Restituisce una lista degli asset con le migliori prestazioni
            # in base al momentum, limitando il numero degli asset 
            # a quello desiderato per ogni mese
            return [
                asset[0] for asset in sorted(
                    all_momenta.items(),
                    key=operator.itemgetter(1),
                    reverse=True
                )
            ][:self.mom_top_n]


        def _generate_signals(
            self, dt, weights
        ):
            top_assets = self._highest_momentum_asset(dt)
            for asset in top_assets:
                weights[asset] = 1.0 / self.mom_top_n
            return weights

        def __call__(
            self, dt
        ):
            assets = self.universe.get_assets(dt)
            weights = {asset: 0.0 for asset in assets}

            # Genera i pesi solo se si è superati il periodo relativo 
            # alla finestra temporale dove calcolare il momentum
            if self.signals.warmup >= self.mom_lookback:
                weights = self._generate_signals(dt, weights)
            return weights
				
			

Esecuzione del backtest

Dopo avere implementato la classe AlphaModel, possiamo aggiungerla e completare la funzione principale.

				
					 

start_dt = pd.Timestamp('1998-12-22 14:30:00', tz=pytz.UTC)
burn_in_dt = pd.Timestamp('1999-12-22 14:30:00', tz=pytz.UTC)
end_dt = pd.Timestamp('2024-04-08 23:59:00', tz=pytz.UTC)

# Costruisce i simboli e gli asset necessari per il backtest
# Utilizziamo gli ETF settoriali US del SPDR, dove tutti iniziano con XL
strategy_symbols = ['XL%s' % sector for sector in "BCEFIKPUVY"]
assets = ['EQ:%s' % symbol for symbol in strategy_symbols]

# Dato che è un universo dinamico di asset (XLC è aggiunto dopo)
# dobbiamo specificare quando includere XLC. Questo viene 
# effettuato tramite un dizionario delle date degli asset
asset_dates = {asset: start_dt for asset in assets}
asset_dates['EQ:XLC'] = pd.Timestamp('2018-06-18 00:00:00', tz=pytz.UTC)
strategy_universe = DynamicUniverse(asset_dates)

# Per evitare di caricare tutti i file CSV in una directory, impostiamo 
# la sorgente dati per caricare solo i simboli desiderati
csv_dir = os.environ.get('DATAINVESTOR_CSV_DATA_DIR', '.')


strategy_data_source = CSVDailyBarDataSource(csv_dir, Equity, csv_symbols=strategy_symbols)
strategy_data_handler = BacktestDataHandler(strategy_universe, data_sources=[strategy_data_source])

# Parametri del modello
mom_lookback = 126  # 6 mesi di giorni lavorativi
mom_top_n = 3  # Numero di asset da include ogni volta

# Genera i segnali usati nel modello alpha top-N momentum 
momentum = MomentumSignal(start_dt, strategy_universe, lookbacks=[mom_lookback])
signals = SignalsCollection({'momentum': momentum}, strategy_data_handler)

# NUOVO CODICE SOTTO
# Genera l'istanza del modello alpha per la strategia top-N momentum
strategy_alpha_model = TopNMomentumAlphaModel(
  signals, mom_lookback, mom_top_n, strategy_universe, strategy_data_handler
)
# NUOVO CODICE SOPRA

# Costruisce il backtest della strategia e lo esegue
strategy_backtest = BacktestTradingSession(
      start_dt,
      end_dt,
      strategy_universe,
      strategy_alpha_model,
      signals=signals,
      rebalance='end_of_month',
      long_only=True,
      cash_buffer_percentage=0.01,
      burn_in_dt=burn_in_dt,
      data_handler=strategy_data_handler
)

strategy_backtest.run()
				
			

Possiamo eseguire il backtest nell’ambiente Jupyter. Per  visualizzare e salvare il report delle performance è sufficiente aggiungere le seguenti righe di codice a una nuova cella.

				
					    from datainvestor.statistics.tearsheet import TearsheetStatistics

    # Report delle Performance
    tearsheet = TearsheetStatistics(
        strategy_equity=strategy_backtest.get_equity_curve(),
        title='Top N Momentum'
    )
    tearsheet.plot_results(filename='TopNMomentum_tearsheet.png')
				
			

Il report delle performance viene salvato nella directory dt_startdev_notebooks e è visibile anche nel notebook.

Momentum Asset Allocation con DataInvestor, Jupyter e Docker

In questo articolo abbiamo descritto come implementare  il backtest di una strategia di momentum asset allocation con DataInvestor, fornito le indicazioni sulle  modalità e funzionalità disponibili per creare un portafoglio dinamico di ETF, nonché una panoramica della logica di DataInvestor per costruire ed eseguire nuove strategie. L’utilizzo dell’ambiente di backtest creato negli ultimi due tutorial consente di esplorare  DataInvestor e di estendere il framework con funzionalità personalizzate.

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