Portafogli Strategici di ETF: Backtest con DataTrader

In un precedente articolo abbiamo descritto la funzione di ribilanciamento mensile implementata nel framework di backtesting DataTrader ed eseguito dei test su un portafoglio semplificato di ETF azionari e obbligazionari. In questo articolo vediamo come gestire direttamente i pesi tramite il backtest di portafogli strategici di ETF.

A tale scopo apportato al motore del framework per gestire direttamente i pesi degli strumenti presenti in portafoglio. Presentiamo in particolare due nuovi portafogli di ETF. Il primo prevede una pesatura “strategica” degli ETF in portafoglio, mentre il secondo assegna la stessa percentuale di allocazione a ciascun ETF. Entrambi i portafogli includono lo stesso insieme di ETF: un mix di azioni statunitensi (large e small cap), obbligazioni statunitensi (investment grade e ad alto rendimento), azioni dei mercati emergenti e “asset alternativi” come materie prime e REIT.

Per questi test utilizziamo strumenti del mercato statunitense, poiché offrono una maggiore disponibilità di dati storici, permettendoci così un backtest più robusto a partire dal 2007.

Portafogli Strategici di ETF

Per effettuare il backtest di portafogli strategici di ETF abbiamo implementato strategie di trading molto simili, che si differenziano soltanto per le percentuali di allocazione degli strumenti in portafoglio e per le date di inizio. Alla fine di ogni mese, la strategia liquida completamente il portafoglio e ribilancia (in dollari) ciascun asset in base al patrimonio aggiornato.

La prima strategia applica semplicemente una ponderazione del 60% a SPY e del 40% ad AGG, che rappresentano rispettivamente le azioni statunitensi a grande capitalizzazione e le obbligazioni investment grade.

Con la seconda strategia, destiniamo il 30% del portafoglio alle azioni statunitensi tramite SPY e IJS, il 25% alle azioni dei mercati emergenti tramite EFA ed EEM, il 25% alle obbligazioni statunitensi (investment grade e ad alto rendimento) tramite AGG e JNK, il 10% alle materie prime tramite DJP e il 10% ai fondi di investimento immobiliare (REIT).

La terza strategia utilizza lo stesso set di ETF della seconda, ma assegna a tutti gli asset la medesima percentuale di allocazione, pari al 12,5%.

Abbiamo scelto le date di inizio esclusivamente in base alla disponibilità dei dati. La strategia n. 1 parte il 29 settembre 2003, mentre le strategie n. 2 e n. 3 iniziano il 4 dicembre 2007. Tutte e tre si concludono il 12 ottobre 2016.

Ticker# 1 – 60/40 SPY/AGG# 2 – Pesi “strategici”# 3 – Pesi Uguali
SPY60,0%25,0%12,5%
IJS0,0%5,0%12,5%
EFA0,0%20,0%12,5%
EEM0,0%5,0%12,5%
AGG40,0%20,0%12,5%
JNK0,0%5,0%12,5%
DJP0,0%10,0%12,5%
RWR0,0%10,0%12,5%

Set di dati storici

Per attuare questa strategia, dobbiamo disporre dei dati sui prezzi OHLCV degli ETF nel periodo coperto dal backtest:

TickerNomePeriodo
SPIYSPDR S&P 500 ETF29 settembre 2003 – 12 ottobre 2016
IJSiShares S&P Small Cap ETF 600 Value4 dicembre 2007 – 12 ottobre 2016
EFAiShares MSCI EAFE ETF4 dicembre 2007 – 12 ottobre 2016
EEMETF iShares MSCI Emerging Markets4 dicembre 2007 – 12 ottobre 2016
AGGETF iShares Core US Aggregate Bond29 settembre 2003 – 12 ottobre 2016
JNKSPDR Barclays Capital High Yield Bond ETF4 dicembre 2007 – 12 ottobre 2016
DJPiPath Bloomberg Commodity Index Total Return ETN4 dicembre 2007 – 12 ottobre 2016
RWRSPDR Dow Jones REIT ETF4 dicembre 2007 – 12 ottobre 2016

Dobbiamo inserire questi dati nella directory specificata nel file di configurazione di DataTrader se vogliamo replicare i risultati.

Implementazione Python con DataTrader

Per effettuare il backtest dei portafogli strategici di ETF, applichiamo la stessa procedura di liquidazione integrale e ribilanciamento mensile descritta nel precedente articolo.

Per semplificare la generazione di più portafogli separati senza duplicare eccessivamente il codice, abbiamo creato un nuovo file chiamato monthly_rebalance_run.py. Questo file contiene il codice “boilerplate” necessario per eseguire un backtest con ribilanciamento mensile complesso.

Per generare i backtest separati dei due portafogli descritti in questo articolo, importiamo all’interno di monthly_rebalance_run.py la funzione run_monthly_rebalance. Poi eseguiamo lo script, impostando tra i parametri iniziali il ticker del benchmark (SPY), il dizionario ticker_weights con le percentuali di allocazione degli ETF, il titolo del report dei risultati, le date di inizio/fine e il capitale iniziale.

Questa struttura ci consente di modificare facilmente la composizione del portafoglio, a patto di avere i dati storici disponibili. A titolo di esempio, riportiamo il codice per il mix 60/40 tra azioni e obbligazioni USA:

				
					# equities_bonds_60_40_etf_portfolio_backtest.py

import datetime

from datatrader import settings
from monthly_rebalance_run import run_monthly_rebalance


if __name__ == "__main__":
    ticker_weights = {
        "SPY": 0.6,
        "AGG": 0.4,
    }
    run_monthly_rebalance(
        settings.DEFAULT_CONFIG_FILENAME, False, "",
        "SPY", ticker_weights, "Strategia ETF mix 60/40 Azioni/Obbligazioni USA",
        datetime.datetime(2003, 9, 29), datetime.datetime(2016, 10, 12),
        500000.00
    )
				
			

Il codice per altre strategie di portfolio è riportato nel paragrafo “Codice completo” alla fine dell’articolo, sebbene siano molto simili allo snippet di cui sopra.

Per eseguire uno qualsiasi dei backtest è necessario posizionarsi nella directory dove è presente il file monthly_rebalance_run.py e quindi digitare nella console quanto segue:

				
					$ python equities_bonds_60_40_etf_portfolio_backtest.py
				
			

Ricordarsi di cambiare il nome del file a seconda di quale backtest si vuol eseguire.

Risultati del Backtest

I risultati del backtest di portafogli strategici di ETF qui presentati sono forniti al netto dei costi delle commissioni. I costi sono simulati utilizzando il prezzo fisso di Interactive Brokers per le azioni del Nord America . Non tengono conto delle differenti commissioni per gli ETF, ma sono ragionevolmente rappresentativi di ciò che potrebbe essere ottenuto in una vera strategia di trading.

Portafoglio ETF con mix 60/40 di azioni / obbligazioni statunitensi

La strategia stessa è identica a quella dell’articolo precedente ed è stata ripubblicata qui grazie all’utilizzo di dati storici aggiuntivi che risalgono al 2003. Di seguito viene fornito il report della strategia:

trading-algoritmico-datatrader-etf-strategy-60-40-tearsheet

Il benchmark è formato da un portafoglio buy-and-hold (cioè nessun ribilanciamento mensile) composto esclusivamente dell’ETF SPY.

Lo Sharpe Ratio del benchmark e quello del portafoglio sono identici a 0.4. Inoltre abbiamo un drawdown maggiore investendo solo in SPY. Il CAGR della strategia è del 4,43%, inferiore al 5,92% dello SPY. Ciò è dovuto ai costi di transazione per eseguire il ribilanciamento nonché alla sottoperformance dell’ETF AGG rispetto allo SPY.

AGG ha in qualche modo attutito l’ampio drawdown del 2008 per il portafoglio, ma non in misura significativa. A causa del crollo del 2008/2009, il portafoglio è in drawdown per 1242 giorni, quasi 3 anni e mezzo, rispetto ai 1365 giorni del benchmark. Tieni presente, tuttavia, che questo periodo ha avuto un effetto drammatico su quasi tutti i portafogli ETF.

Portafoglio ETF “strategico”

Di seguito il report delle prestazioni della strategia:

trading-algoritmico-datatrader-etf-strategy-strategic-tearsheet

Usiamo come benchmark un portafoglio buy-and-hold (cioè senza ribilanciamento mensile) composto esclusivamente dall’ETF SPY.

Nel portafoglio con allocazione strategica abbiamo provato a replicare il portafoglio descritto in questo post [1]. Tuttavia, non siamo riusciti a trovare strumenti idonei per replicare gli indici specifici di VWEXH, RPIBX, PREMX e VGSIX, che rappresentano rispettivamente obbligazioni spazzatura statunitensi, obbligazioni dei mercati esteri sviluppati, obbligazioni dei mercati emergenti e REIT statunitensi.

Abbiamo inoltre faticato a reperire dati sufficienti per gli ETF scelti per rappresentare RPIBX e PREMX, ossia VWOB (Vanguard Emerging Markets Govt Bd ETF) e BNDX (Vanguard Total International Bond ETF). Questi ultimi due sono arrivati sul mercato solo alla fine del 2013, il che ci avrebbe costretti a lavorare con un backtest limitato a pochi anni. Una durata così breve non ci permette di valutare adeguatamente le prestazioni di una strategia con ribilanciamento mensile.

Per questo motivo, abbiamo ridotto il portafoglio a otto ETF, rispetto ai dieci del post citato. Li abbiamo descritti nel paragrafo precedente, intitolato “Dati”. Il portafoglio prevede un’allocazione del 30% sulle azioni statunitensi, 25% sulle azioni dei mercati emergenti, 25% sulle obbligazioni statunitensi, 10% sulle materie prime e 10% sul settore immobiliare.

La performance di questa strategia si è rivelata nettamente inferiore rispetto al portafoglio 60/40. Abbiamo registrato un CAGR negativo. Alcune flessioni dei rendimenti derivano dalla necessità di negoziare otto titoli distinti ogni mese. Tuttavia, quasi tutti gli altri ETF presenti nel portafoglio hanno sottoperformato SPY in modo significativo, penalizzando i rendimenti complessivi.

In particolare, le materie prime e il reddito fisso non hanno ottenuto buoni risultati negli ultimi cinque anni di dati in-sample, rispetto alle azioni statunitensi. Questo ci porta a concludere che adottare una ponderazione uguale potrebbe addirittura peggiorare la situazione. Nella sezione successiva applichiamo la strategia con percentuali di allocazione uguali, per testare questa ipotesi all’interno dei nostri portafogli strategici di ETF.

Portafoglio ETF con uguale allocazione

Di seguito il report delle prestazioni della strategia:

trading-algoritmico-datatrader-etf-strategy-egual-weight-tearsheet

Utilizziamo come benchmark un portafoglio buy-and-hold (cioè senza ribilanciamento mensile) composto esclusivamente dall’ETF SPY.

Come ci aspettavamo, la performance di questo portafoglio risulta significativamente peggiore rispetto all’allocazione strategica o al mix 60/40. Dal 2008 in poi, il portafoglio resta in drawdown e registra un CAGR di quasi -5%. Il drawdown massimo di questa strategia si aggira intorno al 70%.

Notiamo però chiaramente che quasi tutto il drawdown deriva dalla crisi finanziaria del 2008. Se avessimo effettuato il backtest un anno dopo, i risultati sarebbero probabilmente stati abbastanza diversi, anche se ancora inferiori rispetto allo SPY.

In sintesi, risulta chiaramente difficile costruire un portafoglio a pari allocazione o allocato in dollari che riesca a reggere eventi di mercato significativi come il crollo del 2008. Nei prossimi post esploreremo i modi per mitigare questi problemi.

Conclusioni

La volatilità diseguale, che misuriamo attraverso le deviazioni standard storiche dei rendimenti, motiva il concetto di parità di rischio, secondo cui allochiamo il capitale sulla base del “rischio” anziché sul peso dello strumento nel portafoglio.

La parità di rischio richiede calcoli storici sui flussi di rendimenti e quindi si struttura in modo fondamentalmente diverso rispetto alle strategie descritte sopra. Una versione futura di DataTrader includerà questi calcoli, così potremo costruire un’ampia varietà di portafogli a parità di rischio con l’obiettivo di aumentare i valori dello Sharpe Ratio.

Codice completo

Il codice completo per il backtest di portafogli strategici di ETF, basato sul framework di trading quantitativo event-driven DataTrader, è disponibile nel seguente repository GitHub: https://github.com/tradingquant-it/DataTrader.

				
					# Monthly_rebalance_run.py

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 examples.strategies.monthly_liquidate_rebalance_strategy import MonthlyLiquidateRebalanceStrategy

from datatrader.position_sizer.rebalance import LiquidateRebalancePositionSizer
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


def run_monthly_rebalance(
    config, testing, filename,
    benchmark, ticker_weights, title_str,
    start_date, end_date, equity
):
    config = settings.from_file(config, testing)
    tickers = [t for t in ticker_weights.keys()]

    # Imposta le variabili necessarie per il backtest
    events_queue = queue.Queue()
    csv_dir = config.CSV_DATA_DIR
    initial_equity = PriceParser.parse(equity)

    # Uso di Yahoo Daily Price Handler
    price_handler = YahooDailyCsvBarPriceHandler(
        csv_dir, events_queue, tickers,
        start_date=start_date, end_date=end_date
    )

    # Uso della strategia "monthly liquidate Rebalance"
    strategy = MonthlyLiquidateRebalanceStrategy(tickers, events_queue)
   # strategy = Strategies(strategy, DisplayStrategy())

    # Uso del sizer delle posizioni con specifici pesi dei ticker
    position_sizer = LiquidateRebalancePositionSizer(ticker_weights)

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

    # Uso del Portfolio Handler standard
    portfolio_handler = PortfolioHandler(
        initial_equity, events_queue, price_handler,
        position_sizer, risk_manager
    )

    # Uso del componente ExampleCompliance
    compliance = ExampleCompliance(config)

    # Uso di un IB Execution Handler simulato
    execution_handler = IBSimulatedExecutionHandler(
        events_queue, price_handler, compliance
    )

    # Uso delle statistiche standard
    title = [title_str]
    statistics = TearsheetStatistics(
        config, portfolio_handler, title, benchmark
    )

    # Setup del backtest
    backtest = TradingSession(
        config, strategy, tickers,
        initial_equity, start_date, end_date,
        events_queue, price_handler=price_handler,
        position_sizer=position_sizer,
        execution_handler=execution_handler,
        title=title, benchmark=tickers[0],
    )
    results = backtest.start_trading(testing=testing)
    statistics.save(filename)
    return results
				
			
				
					# equities_bonds_60_40_etf_portfolio_backtest.py

import datetime

from datatrader import settings
from monthly_rebalance_run import run_monthly_rebalance


if __name__ == "__main__":
    ticker_weights = {
        "SPY": 0.6,
        "AGG": 0.4,
    }
    run_monthly_rebalance(
        settings.DEFAULT_CONFIG_FILENAME, False, "",
        "SPY", ticker_weights, "Strategia ETF mix 60/40 Azioni/Obbligazioni USA",
        datetime.datetime(2003, 9, 29), datetime.datetime(2016, 10, 12),
        500000.00
    )
				
			
				
					# strategy_weight_etf_portfolio_backtest.py

import datetime

from datatrader import settings
from monthly_rebalance_run import run_monthly_rebalance


if __name__ == "__main__":
    ticker_weights = {
        "SPY": 0.25,
        "IJS": 0.05,
        "EFA": 0.20,
        "EEM": 0.05,
        "AGG": 0.20,
        "JNK": 0.05,
        "DJP": 0.10,
        "RWR": 0.10
    }
    run_monthly_rebalance(
        settings.DEFAULT_CONFIG_FILENAME, False, "",
        "SPY", ticker_weights, "Strategia con Allocazione Strategica ETF",
        datetime.datetime(2007, 12, 4), datetime.datetime(2016, 10, 12),
        500000.00
    )
				
			
				
					# equal_weight_etf_portfolio_backtest.py

import datetime

from datatrader import settings
from monthly_rebalance_run import run_monthly_rebalance


if __name__ == "__main__":
    ticker_weights = {
        "SPY": 0.125,
        "IJS": 0.125,
        "EFA": 0.125,
        "EEM": 0.125,
        "AGG": 0.125,
        "JNK": 0.125,
        "DJP": 0.125,
        "RWR": 0.125
    }
    run_monthly_rebalance(
        settings.DEFAULT_CONFIG_FILENAME, False, "",
        "SPY", ticker_weights, "Strategia con uguale percentuale di ETF",
        datetime.datetime(2007, 12, 4), datetime.datetime(2016, 10, 12),
        500000.00
    )
				
			

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