Ottimizzazione della strategia di crossover delle medie mobili con Python

Ottimizzazione della strategia di crossover delle medie mobili con Python

Sommario

Rimanendo sullo stesso argomento relativo all’ottimizzazione che abbiamo descritto nel precedente articolo sulla costruzione di un portafoglio e alle frontiere efficienti/teoria del portafoglio, in questo articolo descriviamo come effettuare l’ottimizzazione della strategia di crossover delle medie mobili con Python che abbiamo descritto in un precedente articolo.

Ottimizzazione di una strategia con Python

Nel articolo precedente abbiamo implementato un backtest che prevede di usare un numero di giorni per la media mobile breve e la media mobile lunga pari rispettivamente a 42 e 252 giorni. Questi parametri sono sufficienti per un’elaborazione preliminare per testare il codice e assicurarci che funzioni correttamente. Le probabilità che i due specifici valori per i parametri delle medie mobili generino i rendimenti più alti, o lo Sharpe ratio migliore tra tutte le possibili (ragionevoli) combinazioni sono molto scarse.

L’unico modo per verificare i valori ottimali per i parametri di una strategia è eseguire più backtest, usando valori diversi per ogni backtest e memorizzare i risultati. Gli array Numpy sono ideali a tale scopo. Possiamo prevedere un array multidimensionale con tutti i valori impostati su zero. Possiamo quindi eseguire molti backtest dove ognuno prevede diversi periodi delle medie mobili. Memorizzare quindi i risultati di ogni backtest nel relativa cella dell’array numpy per una successiva analisi.

Riprendiamo il codice dell’articolo precedente introducendo il refactoring di un paio di funzioni in modo che possiamo essere richiamate più volte.

				
					import numpy as np

def ma_strat(sp500, short_ma, long_ma):
    # Legge i dati da Yahoo Finance per il ticker
    sp500['short_ma'] = np.round(sp500['Close'].rolling(window=short_ma).mean(), 2)
    sp500['long_ma'] = np.round(sp500['Close'].rolling(window=long_ma).mean(), 2)

    # Crea la colonna con lo spread di differenza della media mobile
    sp500['short_ma-long_ma'] = sp500['short_ma'] - sp500['long_ma']

    # Imposta il numero desidarto della soglia per la differenza dello spread
    # e crea la colonna 'Stance'
    X = 50
    sp500['Stance'] = np.where(sp500['short_ma-long_ma'] > X, 1, 0)
    sp500['Stance'] = np.where(sp500['short_ma-long_ma'] < -X, -1, sp500['Stance'])
    sp500['Stance'].value_counts()

    # Crea le colonne per i rendimenti logaritmici giornalieri del ticker e i 
    # rendimenti logaritmici giornalieri della strategia
    sp500['Market Returns'] = np.log(sp500['Close'] / sp500['Close'].shift(1))
    sp500['Strategy'] = sp500['Market Returns'] * sp500['Stance'].shift(1)

    # Imposta l'equity iniziale della strategia a 1 e genera la curva equity
    sp500['Strategy Equity'] = sp500['Strategy'].cumsum() + 1

    sharpe = annualised_sharpe(sp500['Strategy'])

    return (sp500['Strategy'].cumsum()[-1], sharpe)


# Funzione per calcolare lo Sharpe Ratio - l'elemento Risk free rate è escluso per semplicità
def annualised_sharpe(returns, N=252):
    return np.sqrt(N) * (returns.mean() / returns.std())
				
			

Possiamo usare la funzione numpy.linspace() per creare un array di valori per i quali vogliamo effettuare i backtest. Tali valori rappresentano i range di valori da usare come parametri per il periodo della finestra mobile della media breve e media lunga.

				
					short_ma = np.linspace(10, 60, 25, dtype=int)
				
			
Dato che la finestra della media mobile deve essere un numero intero, dobbiamo specificare l’attributo dtype=int che esegue il cast dei valori come numeri interi. La funzione linspace crea un array di valori che inizia dal numero specificato nel primo parametro, finisce con il numero nel secondo parametro e esegue un numero di passaggi specificato dal terzo valore. Nell’esempio creiamo un array di 25 valori interi equidistanti che iniziano da 10 e terminano a 60. Effettuiamo lo stesso approccio per la media mobile lunga.
				
					long_ma = np.linspace(220, 270, 25, dtype=int)
				
			

In questo caso impostiamo un array di 25 valori interi equidistanti che iniziano da 220 e terminano a 270.

Successivamente creiamo 2 array numpy per memorizzare i risultati dei vari backtest. Un array per memorizzare il p&l finale e un array per memorizzare lo Sharpe ratio. Sono array bidimensionali con dimensione pari alla lunghezza degli array dei valori da usare come parametri per la media mobile breve e lunga.

				
					results_pnl = np.zeros((len(short_ma),len(long_ma)))
results_sharpe = np.zeros((len(short_ma),len(long_ma)))
				
			

Esecuzione di backtest multipli

Vediamo ora il codice che permette di analizzare tutte le varie combinazioni di periodi per le finestre delle medie mobili corte e lunghe, contenute nei due array.

Per prima cosa leggiamo i dati dell’S&P500 da Yahoo Finance.

				
					sp500 = yf.download('^GSPC', start='2000-01-01', end='2020-01-01')
				
			

Iteriamo quindi i due array dei parametri ed eseguiamo il backtest per ogni combinazione.

				
					for i, shortma in enumerate(short_ma):
    for j, longma in enumerate(long_ma):
        pnl, sharpe = ma_strat(sp500,shortma,longma)
        results_pnl[i,j] = pnl
        results_sharpe[i,j] = sharpe
				
			

Analisi dei risultati

Al termine dell’esecuzione otteniamo due array numpy results_pnl e results_sharpe che contengono il P&L finale e lo Sharpe ratio per ogni backtest, con le rispettive combinazioni di finestre della media mobile breve e  lunga.

E’ difficile ottenere informazioni dagli array nel loro formato non elaborato. Visualizziamo quindi i risultati in un grafico a colori, in modo da vedere dove le combinazioni di finestre della media mobile hanno dato i risultati migliori.

				
					plt.pcolor(short_ma,long_ma,results_pnl)
plt.colorbar()
plt.show()
				
			
Ottimizzazione della strategia di crossover delle medie mobili con Python

Possiamo vedere che sembra esserci una zone pià chiara al centro del grafico. In altre parole usare una media mobile corta di circa 26-28 giorni e una media mobile lunga di circa 244-246 giorni può produrre i risultati migliori. Mentre possiamo vedere una zona scura dall’area in basso a destra dove combinazioni di 50 giorni e 226 giorni può produrre i risultati peggiori.

Visualizzando l’array dello Sharpe Ratio, vediamo che i risultati sono quasi identici. Otteniamo lo Sharpe Ratio maggiore usando una media mobile corta di circa 26-28 giorni, come mostrato nel seguente grafico.

				
					plt.pcolor(short_ma,long_ma,results_sharpe)
plt.colorbar()
plt.show()
				
			
Ottimizzazione-Backtest-crossover-SMA-Shape-Ratio

Si spera che quanto sopra possa fornire un’idea dell’approccio per ottimizzare i parametri di input del backtest di una strategia. In questo esempio abbiamo solo due parametri da ottimizzare e comunque il codice ha impiegato un po’ di tempo per essere eseguito. Vediamo che all’aumentare del numero di variabili che devono essere ottimizzate il tempo necessario per eseguire le iterazioni aumenta in modo esponenziale. In altre parole, l’ottimizzazione di  molti parametri attraverso metodi di “forza bruta” può richiedere molto tempo!

Codice completo

In questo articolo abbiamo descritto come effettuare l’ottimizzazione della strategia di crossover delle medie mobili con Python. Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/Backtest_Strategie

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