Finora, nei corsi di TradingQuant abbiamo trattato l’identificazione di strategie di trading algoritmico, il backtesting e il trading automatico utilizzando un ambiente di sviluppo software. Ora concentriamo l’attenzione sulla creazione e sull’implementazione di strategie di trading. In questa lezione spieghiamo i metodi statistici per testare la mean reversion di una serie temporale, concentrandoci sul concetto di stazionarietà e su come verificarla.
Uno degli strumenti fondamentali per il trader quantitativo è la mean reversion. Questo concetto descrive una serie temporale che tende a tornare verso un valore medio. Matematicamente, rappresentiamo una tale serie continua con un processo Ornstein-Uhlenbeck. Al contrario, un random walk (moto Browniano) non conserva memoria dei valori passati in ciascun momento. La natura mean-reverting di una serie temporale permette di sviluppare strategie di trading profittevoli.
Testare la Mean Reversion
Una serie temporale continua che mostra mean reversion segue un’equazione differenziale stocastica di Ornstein-Uhlenbeck:
\(\begin{eqnarray}
d x_t = \theta (\mu – x_t) dt + \sigma dW_t
\end{eqnarray}\)
Qui, θ indica la velocità del ritorno alla media, μ rappresenta il prezzo medio di lungo periodo, σ misura la varianza del processo e Wt rappresenta un processo Wiener o moto browniano.
L’equazione mostra che la direzione dei prezzi futuri dipende dalla distanza tra il prezzo medio e il prezzo corrente, con l’aggiunta di rumore gaussiano.
Verifichiamo questa proprietà con il test Augmented Dickey-Fuller, che ora analizziamo.
Il Test Dickey-Fuller Aumentato (ADF)
L’ADF verifica la presenza di un trend o di radici unitarie in campioni di serie temporali autoregressive. Se una serie segue la mean reversion, il suo prezzo futuro risulta proporzionale al prezzo attuale.
Descriviamo queste serie storiche con un modello di regressione lineare di ordine p:
\(\begin{eqnarray}
\Delta y_t = \alpha + \beta t + \gamma y_{t-1} + \delta_1 \Delta y_{t-1} + \cdots + \delta_{p-1} \Delta y_{t-p+1} + \epsilon_t
\end{eqnarray}\)
Dove α è una costante, β rappresenta la componente temporale e Δyt = y(t) – y(t-1).
Il test ADF assume che il processo autoregressivo con ordine p = 1 abbia media nulla, ossia γ = 0, il che implica α = β = 0, rendendo il processo casuale e non mean-reverting.
Se riusciamo a scartare l’ipotesi γ = 0, allora il movimento dei prezzi segue un andamento proporzionale e difficilmente risulta casuale.
Come eseguiamo il test ADF? Innanzitutto, calcoliamo la statistica \(DF_{\tau}\), che otteniamo dividendo la costante di proporzionalità campionaria \(\hat{\gamma}\) per il suo errore standard:
\(\begin{eqnarray}
DF_{\tau} = \frac{\hat{\gamma}}{SE(\hat{\gamma})}
\end{eqnarray}\)
Dickey e Fuller hanno calcolato la distribuzione di questa statistica, permettendoci di respingere l’ipotesi nulla in base a soglie di significatività. La statistica risulta negativa, quindi più è negativa, maggiore è la probabilità di rigettare l’ipotesi.
In sintesi, per i trader è importante sapere che ogni deriva di lungo periodo del prezzo è molto più rilevante rispetto a fluttuazioni brevi, perciò il modello considera nulla la deriva (β = 0).
Poiché utilizziamo un modello autoregressivo di ordine p, dobbiamo scegliere un valore per p. Nelle strategie di trading, basta spesso impostare p = 1 per rigettare l’ipotesi nulla.
Possiamo eseguire il test Dickey-Fuller aumentato usando le librerie pandas e statsmodels. Pandas ci consente di scaricare facilmente i dati OHLCV da Yahoo Finance, mentre statsmodels fornisce una funzione pronta per applicare l’ADF.
Implementazione in Python
Eseguiremo il test ADF sui dati storici delle azioni Google, dal 1° gennaio 2000 al 1° gennaio 2013.

# Import the Time Series library
import statsmodels.tsa.stattools as ts
# Import Datetime and the Pandas DataReader
import yfinance as yf
from datetime import datetime
start_date = datetime(2000, 1, 1)
end_date = datetime(2013, 1, 1)
df = yf.download("GOOG", start=start_date, end=end_date, group_by='ticker', auto_adjust=False)
goog = df['GOOG']
# test ADF
result = ts.adfuller(goog['Adj Close'], maxlag=1)
# Visualizza i risultati
print('Statistiche ADF:', result[0])
print('p-value:', result[1])
print('Lags usati:', result[2])
print('Numero di osservazioni:', result[3])
print('Valori critici:')
for key, value in result[4].items():
print(f' {key}: {value}')
Statistiche ADF: -2.190010463926695
p-value: 0.20989103942739207
Lags usati: 0
Numero di osservazioni: 2106
Valori critici:
1%: -3.4334588739173006
5%: -2.8629133710702983
10%: -2.5675011176676956
Poiché il valore della statistica test supera tutti i valori critici ai livelli 1, 5 o 10 percento, non possiamo respingere l’ipotesi nulla di γ = 0. Di conseguenza, risulta improbabile che la serie temporale sia mean reverting.
Un metodo alternativo per testare la mean reversion di una serie temporale mean reverting si basa sul concetto di stazionarietà, che introduciamo ora.
Testing per la Stazionarità
Una serie temporale (o processo stocastico) risulta fortemente stazionaria se la sua distribuzione di probabilità congiunta rimane invariata nel tempo o nello spazio. In particolare, aspetto cruciale per i trader, la media e la varianza del processo non variano nel tempo o nello spazio, e nessuno dei due segue un trend.
Le serie di prezzi stazionarie mostrano una caratteristica fondamentale: i prezzi si allontanano dal loro valore iniziale più lentamente rispetto al moto browniano geometrico. Analizzando il tasso di questo comportamento, possiamo determinare la natura delle serie temporali.
Ora introduciamo un parametro utile per descrivere la stazionarietà di una serie temporale: l’Esponente di Hurst.
Esponente Hurst
L’Esponente di Hurst rappresenta un valore scalare utile per determinare (nei limiti della stima statistica) se una serie si comporta in modo mean reverting, random walk o trending.
Per calcolare l’esponente, analizziamo la varianza dei logaritmi di una serie di prezzi per valutare il comportamento della struttura. Per un intervallo di tempo arbitrario τ, otteniamo la seguente varianza:
\(\begin{eqnarray}
{\rm Var}(\tau) = \langle |\log(t+\tau)-\log(t)|^2 \rangle
\end{eqnarray}\)
Confrontando il tasso di diffusione con quello di un moto browniano geometrico, consideriamo τ sufficientemente grande. In tal caso, la varianza risulta proporzionale a τ, come nel caso del GBM:
\(\begin{eqnarray}
\langle |\log(t+\tau)-\log(t)|^2 \rangle \sim \tau
\end{eqnarray}\)
Se osserviamo alcune autocorrelazioni (come qualsiasi movimento sequenziale del prezzo con correlazione diversa da zero), la relazione sopra non risulta più valida.
Tuttavia, possiamo modificarla includendo l’esponente “2H”, così da calcolare il valore H dell’Esponente di Hurst:
\(\begin{eqnarray}
\langle |\log(t+\tau)-\log(t)|^2 \rangle \sim \tau^{2H}
\end{eqnarray}\)
Possiamo quindi caratterizzare una serie temporale in base al seguente schema:
- H < 0,5 – La serie temporale mostra comportamento mean reverting;
- H = 0,5 – La serie segue un moto browniano geometrico;
- H > 0,5 – La serie è in trend.
Oltre a caratterizzare la natura della serie, l’Esponente di Hurst indica anche il grado con cui la serie mostra quel comportamento. Ad esempio, un valore H vicino a 0 indica una forte mean reversion, mentre un valore H vicino a 1 segnala un trend marcato.
Implementazione in Python
Per calcolare l’Esponente di Hurst sui prezzi di Google, gli stessi usati per l’ADF, puoi utilizzare il seguente codice Python:
from numpy import cumsum, log, polyfit, sqrt, std, subtract
from numpy.random import randn
# Import the Time Series library
import statsmodels.tsa.stattools as ts
# Import Datetime and the Pandas DataReader
import yfinance as yf
from datetime import datetime
start_date = datetime(2000, 1, 1)
end_date = datetime(2013, 1, 1)
df = yf.download("GOOG", start=start_date, end=end_date, group_by='ticker', auto_adjust=False)
goog = df['GOOG']
def hurst(ts):
"""Returns the Hurst Exponent of the time series vector ts"""
# Create the range of lag values
lags = range(2, 100)
# Calculate the array of the variances of the lagged differences
tau = [sqrt(std(subtract(ts[lag:], ts[:-lag]))) for lag in lags]
# Use a linear fit to estimate the Hurst Exponent
poly = polyfit(log(lags), log(tau), 1)
# Return the Hurst exponent from the polyfit output
return poly[0]*2.0
# Create a Gometric Brownian Motion, Mean-Reverting and Trending Series
gbm = log(cumsum(randn(100000))+1000)
mr = log(randn(100000)+1000)
tr = log(cumsum(randn(100000)+1)+1000)
# Output the Hurst Exponent for each of the above series
# and the price of Google (the Adjusted Close price) for
# the ADF test given above in the article
print("Hurst(GBM): %s"% hurst(gbm))
print("Hurst(MR): %s" % hurst(mr))
print("Hurst(TR): %s" % hurst(tr))
# Assuming you have run the above code to obtain 'goog'!
print("Hurst(GOOG): %s" % hurst(goog['Adj Close']))
Hurst(GBM): 0.500606209426
Hurst(MR): 0.000313348900533
Hurst(TR): 0.947502376783
Hurst(GOOG): 0.50788012261
Notiamo come il movimento browniano geometrico possiede un esponente di Hurst, H, che è quasi esattamente 0,5. La serie di mean reverting ha H quasi uguale a zero, mentre la serie in trend ha H vicino a 1.
È interessante notare che Google ha anche H quasi uguale a 0,5 che indica che è estremamente simile ad una random walk geometrica (almeno nel periodo considerato).
Conclusioni
Ora che abbiamo un metodo per caratterizzare la natura di una serie temporale di prezzi, e testare la mean reversion, dobbiamo discutere quanto il valore H sia statisticamente significativo. Dobbiamo essere in grado di determinare se sia possiamo rifiutare l’ipotesi nulla che H = 0.5 in modo da valutare un comportamento mean reverting o di trend.
Nella lezione successiva descriveremo come calcolare che H è statisticamente significativa. Inoltre, prenderemo in considerazione il concetto di cointegrazione, che ci consentirà di creare le nostre serie temporali di mean-reverting da più serie di prezzi differenti. Infine, uniremo insieme queste tecniche statistiche al fine di formare una strategia di trading mean reverting di base.
Il codice completo presentato in questa lezione è disponibile nel seguente repository GitHub: “https://github.com/tradingquant-it/TQResearch“