Strategia mean-reverting intraday con Python

Backtest di una strategia mean-reverting intraday con Python

Sommario

Dopo aver descritto una strategia di mean reversion di base tramite una serie di articoli, in questo articolo vediamo come implementare il backtest di una strategia mean-reverting intraday con Python. In questo caso vogliamo individuare un ritorno verso la media che avvenga all’interno di una giornata di negoziazione.

La stategia intraday

I prezzi delle azioni tendono a seguire random walk geometriche, come viene spesso ricordato da innumerevoli studi di finanza. Questo è vero solo se testiamo il mean-reverting delle serie di prezzi a rigorosi intervalli regolari, ad esempio usando il prezzo di chiusura giornaliero. Il nostro compito è individuare le condizioni per cui il ritorno alla media si verifica con regolarità. La seguente strategia mostra come nel mercato azionario possa effettivamente esserci un ritorno alla media periodico che si verifica nel timeframe intraday.

Le regole della strategia sono le seguenti:

  1. Selezionare tutti i titoli vicini all’apertura del mercato i cui rendimenti dai minimi del giorno precedente alle aperture odierne sono inferiori a una deviazione standard. La deviazione standard è calcolata usando i rendimenti delle chiusure giornaliere degli ultimi 90 giorni. Si tratta di titoli che sono “scesi”.
  2. Selezionare i titoli della lista che hanno i prezzi di aperta superiore alla media mobile dei prezzi di chiusura a 20 giorni.
  3. Liquidare le posizioni alla chiusura del mercato.

Universo di investimento

Iniziamo l’implementazione del backtest della strategia importando le necessarie librerie Python.

				
					import pandas as pd
import numpy as np
import yfinance as yf
from math import sqrt
				
			

Effettuiamo il backtest su un’universo di 3159 azioni del mercato NYSE. E’ possibile scaricare l’elenco dei ticker dal seguente link: NYSE.txt.

Dopo aver scaricato il file, possiamo elaborarlo usando le funzioni del modulo pandas per impostare la lista dei ticker.

				
					# Assicurars che il file NYSE.txt sia nella stessa cartella dello script
stocks = pd.read_csv('NYSE.txt',delimiter="\t")
# Creao una lista vuota per i ticker azionari
stocks_list = []
# Iterazione tramite il dataframe pandas e aggiunta dei ticker nella lista
for symbol in stocks['Symbol']:
    stocks_list.append(symbol)
				
			

Con un rapido controllo possiamo vedere se sono stati caricati correttamente

				
					print(len(stocks_list))
				
			
				
					3159
				
			

Dopo aver acquisito la lista di lista di azioni che  vogliamo usare come nostro “universo di investimento” – possiamo iniziare a scrivere il codice per il backtest vero e proprio.

Il backtest della strategia

La logica del backtest prevede di iterare l’elenco dei ticker. Per ogni ticker scarichiamo i dati dei prezzi OHLCV in un DataFrame e aggiungiamo un paio di colonne per memorizzare i segnali quando i criteri della strategia sono verificati.

Usiamo questi segnali per creare le serie dei rendimenti per ogni titolo, e li memorizziamo in una lista. Infine concateniamo tutte le serie dei rendimenti in un dataframe principale e calcoliamo il rendimento giornaliero complessivo.

				
					# Crea una lista vuota 
frames = []

for stock in stocks_list:
    try:
        # Download dei dati delle azioni in DataFrame
        df = yf.download(stock, start='2000-01-01', end='2020-01-01')
        # Crea la deviazione standard mobile a 90 giorni
        df['Stdev'] = df['Close'].rolling(window=90).std()
        # Crea la media mobile a 20 giorni
        df['Moving Average'] = df['Close'].rolling(window=20).mean()
        # Verifica se il gap tra il minimo del giorno precedente e l'apertura 
        # odierna è maggiore della deviazione standard a 90 giorni
        df['Criteria1'] = (df['Open'] - df['Low'].shift(1)) < -df['Stdev']
        # Verifica se il prezzo di apertura è maggiore della media mobile a 20 giorni
        df['Criteria2'] = df['Open'] > df['Moving Average']
        # Segnale BUY se entrambi i criteri sono veri
        df['BUY'] = df['Criteria1'] & df['Criteria2']
        # Calcola i rendimenti giornalieri percentuali delle azioni
        df['Pct Change'] = (df['Close'] - df['Open']) / df['Open']
        # Redimenti della strategia usando i rendimenti giornalieri delle 
        # azioni quando abbiamo un segnale BUY
        df['Rets'] = df['Pct Change'][df['BUY'] == True]
        # Aggiungere il rendimento della strategia alla lista
        frames.append(df['Rets'])
    except:
        pass

				
			

Dato che abbiamo una lista di oltre 3000 azioni,  ci vuole molto tempo per concludere il backtest… Con la mia macchina ha impiegato 15-20 minuti per essere eseguito quindi dobbiamo pazientare un po’.

Dopo aver ottenuto la lista dei rendimenti della strategia per ciascun titolo, dobbiamo concatenarli in un dataframe principale e calcolare il rendimento giornaliero complessivo della strategia.

				
					

# Concate i singoli dataframe della nostra lista lungo l'asse delle colonne
masterFrame = pd.concat(frames,axis=1)
# Crea la somma dei rendimenti giornalieri delle singole strategie
masterFrame['Total'] = masterFrame.sum(axis=1)
# Conteggia il numero di titoli che sono tradate ogni giorno
masterFrame['Count'] = masterFrame.count(axis=1) - 1
# Divide il rendimenti giornalieri "totali" per il numero di titoli che sono
# tradati ogni giorno per ottenere i rendimenti equamente pesati.
masterFrame['Return'] = masterFrame['Total'] / masterFrame['Count']
				
			

Otteniamo quindi i rendimenti delle strategia equamente pesati basati sul  numero di operazioni e quindi di titoli tradati ogni giorni. Se ad esempio in un giorno sono stati negoziati 2 titoli, pesiamo i rendimenti di ciascun titolo al 50%.

I risultati

Infine tracciamo il grafico della curva di equity e calcoliamo lo Sharpe  Ratio approssimativo e il rendimento annualizzato.

				
					masterFrame['Return'].dropna().cumsum().plot()
				
			
Backtest-Strategia-Intraday-Mean-Reverting-Equity

Lo Sharpe Ratio (escludendo l’elemento risk free per semplicità) può essere calcolato come segue.

				
					
SharpeRatio = (masterFrame['Return'].mean() *252) / (masterFrame['Return'].std() * (sqrt(252)))
print(SharpeRatio)
				
			

e il rendimento annuo può essere calcolato come segue.

				
					Annual_Return = (masterFrame['Return'].dropna().cumsum()[-1]+1)**(365.0/252) - 1
print(Annual_Return)
				
			

Otteniamo uno Sharpe Ratio di oltre 2 e un rendimento annuo di circa l’8,8% – non è poi così male!!

Ovviamente, dobbiamo ricordare che non stiamo prendendo in considerazione alcun costo di transazione, quindi quei rendimenti potrebbero essere pesantemente influenzati in un contesto reale. Inoltre, questa strategia prevede di poter acquistare le azioni che sono scese esattamente al loro prezzo di apertura e prevede di raggiungere sempre il prezzo di chiusura (settlement) alla chiusura delle posizioni alla fine della giornata.

Possiamo inoltre approfondire i rendimenti della strategia tramite l’approcio che abbiamo descritto nell’articolo relativo all’analisi dei rendimenti della strategia  di crossover a media mobile.

Codice completo

In questo articolo abbiamo descritto come implementare il backtest di una strategia mean-reverting intraday 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...

Torna in alto
Scroll to Top