In questo articolo descriviamo come implementare con Python il backtest di strategia di trading algoritmico basata sul crossover delle medie mobili sull’S&P500.
Una semplice strategia di crossover delle media mobili è forse uno degli esempi più semplici, se non il più semplice, di una strategia di trading algoritmico basata su regole di indicatori tecnici. Può essere un buon esempio per coloro che imparano Python ed è già stata oggetto di esempi su datatrading.info. Cerchiamo di mantenerlo il più semplice possibile e integrarlo con futuri sviluppi.
Preparare i dati
Come sempre quando usiamo Python per i test relativi ai dati finanziari, dobbiamo importare i moduli necessari:
import pandas as pd
import numpy as np
import yfinance as yf
Per prima cosa usiamo le funzionalità di yfinance
per scaricare da Yahoo Finance i dati dei prezzi dell’S&P500 dal primo giorno di negoziazione del 2000 fino ad oggi.
sp500 = yf.download('^GSPC', start='2000-01-01', end='2020-01-01')
Facciamo un rapido controllo per verificare in quale formato sono stati scaricati i dati.
print(sp500.head())
Creiamo quindi un grafico dei prezzi di chiusura per verificare il comportamento dell’S&P nel periodo considerato.
La strategia trend following che vogliamo implementare si basa sull’incrocio di due medie mobili semplici; la medie mobile a 2 mesi (42 giorni di negoziazione) e la media mobile a 1 anno (252 giorni di negoziazione).
Per prima cosa calcoliamo i valori delle medie mobili e li aggiungiamo in nuove colonne del dataframe dell’S&P500.
sp500['42d'] = np.round(sp500['Close'].rolling(window=42).mean(),2)
sp500['252d'] = np.round(sp500['Close'].rolling(window=252).mean(),2)
Nel codice precedente creiamo le serie e le aggiungiamo automaticamente al nostro DataFrame.
Andiamo avanti e visualizziamo sullo stesso grafico sia i prezzi di chiusura che le medie mobili.
sp500[['Close','42d','252d']].plot(grid=True,figsize=(8,5))
I segnali della strategia
Abbiamo completato il dataset di partenza. Ora dobbiamo ideare una regola per generare i segnali di trading.
- Buy Signal (andare long) – la media mobile a 42 giorni è per la prima volta di X punti sopra la media a 252 giorni.
- Parcheggio in liquidità – nessuna posizione.
- Sell Signal (andare short) – la media mobile a 42 giorni è per la prima volta di X punti al di sotto della media a 252 giorni.
Per creare questi segnali dobbiamo prima di tutto aggiungere una nuova colonna al DataFrame che contiene la differenza tra le due medie mobili:
sp500['42-252'] = sp500['42d'] - sp500['252d']
Il passo successivo è formalizzare i segnali aggiungendo un’ulteriore colonna chiamata Stance. Impostiamo anche la soglia di segnale “X” su 50 (questo è valore arbitrario e può essere oggetto di ottimizzazione)
X = 50
sp500['Stance'] = np.where(sp500['42-252'] > X, 1, 0)
sp500['Stance'] = np.where(sp500['42-252'] < -X, -1, sp500['Stance'])
sp500['Stance'].value_counts()
L’ultima riga di codice precedente produce il seguente risultato:
1 2608
0 1422
-1 1001
Vediamo che durante il periodo di tempo scelto per eseguire il backtest, abbiamo 1001 giornate di mercato aperto dove la media mobile a 42 giorni si trova per più di 50 punti al di sotto della media mobile a 252 giorni, e 2608 giornate dove la media mobile a 42 giorni si trova per più di 50 punti sopra alla media mobile a 252 giorni.
Produciamo quindi un grafico che mostra una rappresentazione visiva di questa ‘Posizione’. Abbiamo impostato ‘ylim’ (che sono i limiti dell’asse y) appena sopra 1 e appena sotto -1 in modo che possiamo effettivamente vedere le parti orizzontali della linea.
sp500['Stance'].plot(lw=1.5,ylim=[-1.1,1.1])
Possiamo ora testare la strategia di investimento basata sui segnali che abbiamo generato. In questo caso assumiamo per semplicità che possiamo acquistare o vendere direttamente l’indice S&P500 e che abbiamo costi di transazione. In realtà dobbiamo ottenere un’esposizione all’indice tramite ETF, fondi indicizzati o futures sull’indice… e ovviamente ci sarebbero costi di transazione da pagare! Questa omissione non ha però un effetto significativo perchè non prevediamo di entrare e uscire dalle posizione “troppo spesso”.
I risultati
In questo modello, possiamo essere long sul mercato, short sul mercato o flat: questo ci consente di lavorare con i rendimenti del mercato e semplicemente moltiplicare il rendimento giornaliero del mercato per -1 se è short, 1 se è long e 0 se è flat.
Aggiungiamo un’altra colonna al DataFrame per raccogliere i rendimenti logaritmici giornalieri dell’indice e quindi moltiplichiamo quella colonna per la colonna “Stance” per ottenere i rendimenti della strategia:
sp500['Market Returns'] = np.log(sp500['Close'] / sp500['Close'].shift(1))
sp500['Strategy'] = sp500['Market Returns'] * sp500['Stance'].shift(1)
sp['Close']
in modo da utilizzare la ‘Stance’ alla chiusura del giorno precedente per calcolare il rendimento del giorno successivo
Ora possiamo tracciare i rendimenti dell’S&P500 rispetto ai rendimenti della strategia crossover a media mobile sullo stesso grafico per un confronto:
sp500[['Market Returns','Strategy']].cumsum().plot(grid=True,figsize=(8,5))
Possiamo vedere che la strategia sembra funzionare piuttosto bene durante le flessioni del mercato, ma non funziona altrettanto bene durante i rally del mercato o quando il mercato è solamente in rialzo.
Durante il periodo di prova, supera a malapena una semplice strategia di Buy&Hold, appena sufficiente per definirla come una strategia “di successo”.
Una semplice strategia cross over con media mobile testata in Python dall’inizio alla fine in poche righe di codice!!
Codice completo
In questo articolo abbiamo descritto come implementare con Python il backtest di strategia di trading algoritmico basata sul crossover delle medie mobili sull’S&P500. Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/Backtest_Strategie