Strategia di trading indice SP500 con i modelli ARIMA e GARCH

Strategia di trading sull’indice S&P500 con i modelli ARIMA e GARCH

Sommario

In questo articolo descriviamo come applicare tutte le  metodologie presentate nei precedenti articoli relativi all’analisi delle serie temporali ad una strategia di trading sull’indice S&P500 del mercato azionario statunitense.

Vediamo come combinando i modelli ARIMA e GARCH possiamo  significativamente sovraperformare un approccio “Buy-and-Hold” a lungo termine.

Panoramica della strategia

L’idea della strategia è relativamente semplice ma se vuoi sperimentarla ti consiglio vivamente di leggere i precedenti  post sull’analisi delle serie temporali per capire i concetti matematici alla base di questo studio!

La strategia viene eseguita su base “rolling”:

  1. Per ogni giorno, n, si usano i rendimenti logaritmici differenziati di un indice del mercato azionario dei precedenti k giorni come finestra per adattare un modello ARIMA e GARCH ottimale.
  2. Il modello combinato è usato per fare una previsione per i rendimenti del giorno successivo.
  3. Se la previsione è negativa il titolo viene shortato alla chiusura, mentre se è positiva si va long.
  4. Se la previsione è nella stessa direzione del giorno precedente, non cambia nulla.

Per questa strategia abbiamo usato il periodo storico massimo dei dati disponibili su Yahoo Finance per l’S&P500. Inoltre abbiamo considerato k=500 come parametro che può essere ottimizzato per migliorare le prestazioni o ridurre il drawdown.

Eseguiamo un semplice  backtest vettorializzato usando Python. Quindi le prestazioni ottenute in un sistema di trading reale sarebbero probabilmente leggermente inferiori a quelle ottenute nel backtest, a causa delle commissioni e dello slippage.

Implementazione della strategia

Per implementare la strategia usiamo tilizzeremo parte del codice che abbiamo creato in precedenza nella serie di articoli sull’analisi delle serie temporali.

Quando si tratta di modellazione delle serie temporali di dati finanziari, i modelli autoregressivi (modelli che utilizzano valori precedenti per prevedere il futuro) come ARMA, ARIMA o GARCH e le sue varie varianti sono  quelli solitamente preferiti per spiegare le basi della modellazione delle serie temporali. Tuttavia, l’applicazione pratica di queste tecniche  in strategie di trading reali e il confronto con strategie benchmark (come il Buy and Hold) non sono così comuni. Inoltre, non è facile trovare un’implementazione pronta per l’uso che possa essere facilmente replicata per altri mercati, asset, ecc.

Modello ARIMA+GARCH

				
					import yfinance as yf
import pandas as pd
import numpy as np

symbol='^GSPC'
start = '2002-01-01'
end = '2018-12-31'
SP500 = yf.download(symbol, start=start, end=end)

log_ret = np.log(SP500['Adj Close']) - np.log(SP500['Adj Close'].shift(1))
				
			

Una volta ottenuto il DataFrame con i rendimenti dell’indice, creiamo il set di dati relativi agli ultimi k giorni per adattare il modello. Inoltre, creiamo un DataFrame chiamato forecasts per memorizzare i risultati del nostro modello.

				
					# Creazione del dataset
windowLength = 500
foreLength = len(log_ret) - windowLength

windowed_ds = []
for i in range(foreLength-1):
    windowed_ds.append(log_ret[i:i + windowLength])

# creazione del dataframe forecasts con zeri
forecasts = log_ret.iloc[windowLength:].copy() * 0
				
			
				
					import pmdarima
import arch

import warnings

warnings.filterwarnings("ignore")


def fit_arima(series, range_p=range(0, 6), range_q=range(0, 6)):
    final_order = (0, 0, 0)
    best_aic = np.inf
    arima = pmdarima.ARIMA(order=final_order)

    for p in range_p:
        for q in range_q:
            if (p == 0) and (q == 0):
                next
            arima.order = (p, 0, q)
            arima.fit(series)

            aic = arima.aic()

            if aic < best_aic:
                best_aic = aic
                final_order = (p, 0, q)

    arima.order = final_order
    return arima.fit(series)
				
			
Creiamo ora un ciclo dove richiamare il metodo fit_arima per ogni set di dati, usando i residui per adattare un modello GARCH(1,1) e quindi calcoliamo la previsione di un set per entrambi i modelli. Il risultato finale sarà la somma di entrambe le previsioni. Nota: questo approccio in due fasi non è l’ideale perchè le parti ARIMA e GARCH devono essere trattate e ridotte al minimo come un unico modello. Tuttavia, python no dispone di una simile libreria.
				
					
for i, window in enumerate(windowed_ds):
    # ARIMA model
    arima = fit_arima(window)
    arima_pred = arima.predict(n_periods=1)

    # GARCH model
    garch = arch.arch_model(arima.resid())
    garch_fit = garch.fit(disp='off', show_warning=False, )
    garch_pred = garch_fit.forecast(horizon=1).mean.iloc[-1]['h.1']

    forecasts.iloc[i] = arima_pred + garch_pred

    print(f'Date {str(forecasts.index[i].date())} : Fitted ARIMA order {arima.order} - Prediction={forecasts.iloc[i]}')

				
			
Dopo aver calcolato le previsioni, effettuiamo un confronto confronto tra la strategia ARIMA+GARCH e il benchmark Buy and Hold.
				
					
for i, window in enumerate(windowed_ds):
    # ARIMA model
    arima = fit_arima(window)
    arima_pred = arima.predict(n_periods=1)

    # GARCH model
    garch = arch.arch_model(arima.resid())
    garch_fit = garch.fit(disp='off', show_warning=False, )
    garch_pred = garch_fit.forecast(horizon=1).mean.iloc[-1]['h.1']

    forecasts.iloc[i] = arima_pred + garch_pred

    print(f'Date {str(forecasts.index[i].date())} : Fitted ARIMA order {arima.order} - Prediction={forecasts.iloc[i]}')

				
			

Una volta calcolate le nostre previsioni, faremo implementiamo il codice per fornire il confronto tra la strategia Buy and Hold e ARIMA+GARCH.

				
					
# Memorizzazione dei nuovi segnali calcolati
forecasts.columns=['Date','Signal']
forecasts.set_index('Date', inplace=True)
forecasts.to_csv('prova.csv')

# Otteniamo il periodo che ci interessa
forecasts = forecasts[(forecasts.index>='2004-01-01') & (forecasts.index<='2018-12-31')]

# Calcolo direzione delle previsioni
forecasts['Signal'] = np.sign(forecasts['Signal'])
forecasts.index = pd.to_datetime(forecasts.index)

# Creo un dataframe che contiene le statistiche della strategia
stats=SP500.copy()
stats['LogRets']=log_ret
stats = stats[(stats.index>='2004-01-01') & (stats.index<='2018-12-31')]
stats.loc[stats.index, 'StratSignal'] = forecasts.loc[stats.index, 'Signal']
stats['StratLogRets'] = stats['LogRets'] * stats['StratSignal']
stats.loc[stats.index, 'CumStratLogRets'] = stats['StratLogRets'].cumsum()
stats.loc[stats.index, 'CumStratRets'] = np.exp(stats['CumStratLogRets'])

# Calcolo del confronto con il benchmark
start_stats = pd.to_datetime('2004-01-02')
end_stats = pd.to_datetime('2012-12-31')

results = stats.loc[(stats.index > start_stats) & (stats.index < end_stats), ['Adj Close', 'LogRets', 'StratLogRets']].copy()

results['CumLogRets'] = results['LogRets'].cumsum()
results['CumRets'] = 100 * (np.exp(results['CumLogRets']) - 1)

results['CumStratLogRets'] = results['StratLogRets'].cumsum()
results['CumStratRets'] = 100 * (np.exp(results['CumStratLogRets']) - 1)

buy_hold_first = SP500.loc[start_stats, 'Adj Close']
buy_hold_last = SP500.loc[end_stats, 'Adj Close']
buy_hold = (buy_hold_last-buy_hold_first)/buy_hold_first

strategy = np.exp(results.loc[results.index[-1], 'CumStratLogRets']) - 1

pct_pos_returns = (results['LogRets'] > 0).mean() * 100
pct_strat_pos_returns = (results['StratLogRets'] > 0).mean() * 100

print(f'Returns:')
print(f'Buy_n_Hold - Return in period: {100 * buy_hold:.2f}% - Positive returns: {pct_pos_returns:.2f}%')
print(f'Strategy - Return in period: {100 * strategy:.2f}% - Positive returns: {pct_strat_pos_returns:.2f}%')

import matplotlib.pyplot as plt

columns = ['CumLogRets', 'CumStratLogRets']
plot_df = results[columns]
plot_df.plot(figsize=(15,7))

				
			
trading-algoritmico-ARIMA_GARCH-strategy-returns

Conclusione

Nel grafico vediamo che l’equity della strategia rimane sotto il benchmark Buy & Hold per quasi 3 anni, ma durante il crollo del mercato azionario del 2008/2009 va molto bene. E’ probabile che in questo periodo si è verificata una correlazione seriale significativa ed è stata ben catturata dai modelli ARIMA e GARCH. Dal 2009 il mercato si è ripreso ed è entrato in quella che sembra essere più una tendenza stocastica, le prestazioni del modello ricominciano a risentirne.

Si noti che questa strategia può essere facilmente applicata a diversi indici del mercato azionario, azioni o altre classi di attività. Ti incoraggio vivamente a provare lo studio per altri strumenti, poiché potresti ottenere risultati migliori da quelli qui presentati.

Prossimi passi

Ora che abbiamo terminato la descrizione dei modelli della famiglia ARIMA e GARCH, continuiamo l’analisi delle serie temporali considerando i processi a memoria lunga, i modelli dello spazio degli stati e le serie temporali cointegrate.

Queste successive aree dell’analisi delle serie temporali ci introducono modelli che possono migliorare le nostre previsioni rispetto a quanto descritto finora, il che aumenterà significativamente la nostra redditività e/o ridurrà il rischio.

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

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