strategia di trading con lo z-score

Backtest ETF: Strategia di Trading con lo Z-score

Sommario

In questo articolo descriviamo come implementare una strategia di trading con lo z-score basata su una logica mean-reverting ed effettuare il backtest con Python e Pandas. L’articolo fa parte della miniserie “Backtest di una strategia di mean reverting con gli ETF”.

Gli articoli che fanno parte di questa serie sono:

  1. Backtest ETF: Web scraping e Database Sqlite3
  2. Backtest ETF: Creazione di coppie di Ticker
  3. Backtest ETF: Mean-reverting con Python
  4. Backtest ETF: Strategia di Trading con lo Z-score
  5. Backtest ETF: Pair trading con Python 

Nell’articolo precedente abbiamo descritto come creare la serie degli spread tra le due serie di prezzi dei ETF (eseguendo prima una regressione lineare per trovare il rapporto di copertura) e abbiamo eseguito un test di Augmented Dickey Fuller, oltre a calcolare l’emivita per verificare se la serie degli spread fosse un candidato decente per una strategia di pair trading profittevole.

Dobbiamo ora concludere lo script con il calcolo dello Z-Score “normalizzato” della serie degli spread e impostare un sistema di entrata e uscita in stile “bollinger-band” in base al quale vengono effettuate operazioni short se lo Z-score normalizzato sale sopra 2 ed esce quando scende sotto 0, e viceversa per operazioni long (cioè lo Z-Score deve scendere sotto -2 per entrare ed uscire quando sale sopra 0).

Lo Z-score normalizzato

Iniziamo calcolando lo Z-Score tramite una finestra mobile per la media e la deviazione standard, impostata pari al valore dell’emivita calcolata nel precedente articolo. In questo modo evitiamo di introdurre un look-forward bias usando la media dell’intero periodo o di introdurre un data-mining bias usando una finestra di ricerca arbitraria che dovrebbe essere ottimizzata.

Calcoliamo e visualizziamo lo Z -score normalizzato come segue:
				
					
df1['zScore'] = (df1.spread - meanSpread) / stdSpread

df1['zScore'].plot()
				
			
strategia di trading con lo z-score

Backtest della strategia

A questo punto dobbiamo aggiungere una colonna nel dataframe per indicare se dovremmo avere posizioni long, short o flat. Possiamo farlo seguendo un paio di passaggi.

Per prima cosa creiamo una colonna chiamata num units long che prevede le righe con 1 per indicare quando dobbiamo essere long, e le righe rimanenti con uno 0 per indicare nessuna posizione long. Usiamo lo stesso approccio con le posizioni short impostando una colonna chiamata num unit short dove le righe con valore -1 indicano posizioni short e quelle con 0 indicano che non ci sono posizioni short.

Questo si ottiene con il seguente codice, dove impostiamo anche gli Z-score di entra e uscita rispettivamente a 2 e 0):

				
					
entryZscore = 2
exitZscore = 0
 
# calcolo num units long             
df1['long entry'] = ((df1.zScore < - entryZscore) & ( df1.zScore.shift(1) > - entryZscore))
df1['long exit'] = ((df1.zScore > - exitZscore) & (df1.zScore.shift(1) < - exitZscore)) 
df1['num units long'] = np.nan 
df1.loc[df1['long entry'],'num units long'] = 1 
df1.loc[df1['long exit'],'num units long'] = 0 
df1['num units long'][0] = 0 
df1['num units long'] = df1['num units long'].fillna(method='pad') 

# calcolo num units short 
df1['short entry'] = ((df1.zScore >  entryZscore) & ( df1.zScore.shift(1) < entryZscore))
df1['short exit'] = ((df1.zScore < exitZscore) & (df1.zScore.shift(1) > exitZscore))
df1.loc[df1['short entry'],'num units short'] = -1
df1.loc[df1['short exit'],'num units short'] = 0
df1['num units short'][0] = 0
df1['num units short'] = df1['num units short'].fillna(method='pad')
				
			
Successivamente creiamo un’altra colonna, che somma num unit long e num unit short per ottenere le numUnits – la posizione complessiva che dovrebbe avere il nostro portafoglio in quel momento; long (1), short (-1) o flat (0). Generiamo anche una colonna contenente la variazione percentuale della serie degli spread e una colonna con il rendimento del portafoglio, moltiplicando la variazione percentuale della serie degli spread per la posizione corrente del portafoglio (long, short o flat). Sommiamo cumulativamente i rendimenti giornalieri per generare la curva di equity cum rets.
				
					
df1['numUnits'] = df1['num units long'] + df1['num units short']
df1['spread pct ch'] = (df1['spread'] - df1['spread'].shift(1)) / ((df1['x'] * abs(df1['hr'])) + df1['y'])
df1['port rets'] = df1['spread pct ch'] * df1['numUnits'].shift(1)
    
df1['cum rets'] = df1['port rets'].cumsum()
df1['cum rets'] = df1['cum rets'] + 1
				
			

Possiamo ora visualizzare  la curva equity del portafoglio come segue:

				
					
plt.plot(df1['cum rets'])
plt.xlabel("EWC")
plt.ylabel("EWA")
plt.show()
				
			
BackTest-ETF-spread-equity-portfolio

Performance della strategia

Non ci resta che calcolare lo Sharpe Ratio e il Compound Annual Growth Rate (CAGR) per valutare le prestazione della strategia:

				
					
sharpe = ((df1['port rets'].mean() / df1['port rets'].std()) * sqrt(252))

start_val = 1
end_val = df1['cum rets'].iat[-1]

start_date = df1.iloc[0].name
end_date = df1.iloc[-1].name
days = (end_date - start_date).days

CAGR = round(((float(end_val) / float(start_val)) ** (252.0 / days)) - 1, 4)

print("CAGR = {}%".format(CAGR * 100))
print("Sharpe Ratio = {}".format(round(sharpe, 2)))
				
			
				
					
CAGR = 0.95%
Sharpe Ratio = 0.32
				
			

Vediamo che il risultato non sembra affatto eccezionale in termini di rendimenti e tenendo conto delle commissioni di transazione e dei costi di negoziazione, abbiamo un rendimento  quasi flat.

Il prossimo passo è testare la strategia su diverse coppie di ETF e su diversi  intervalli di tempo, come descritto nel prossimo e ultimo articolo relativo alla miniserie “Backtest di una strategia di mean reverting con gli ETF.

Codice completo

In questo articolo abbiamo descritto come implementare una strategia di trading con lo z-score. 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...

Torna in alto
Scroll to Top