Test di Dickey Fuller Aumentato e Cointegrato per la Valutazione del Pairs Trading

Test di Dickey Fuller Aumentato e Cointegrato per la Valutazione del Pairs Trading

Nel precedente articolo sulla cointegrazione abbiamo simulato due serie temporali non stazionarie che formavano una coppia cointegrata per una specifica combinazione lineare. Abbiamo utilizzato i test statistici Augmented Dickey-Fuller, Phillips-Perron e Phillips-Ouliaris per verificare la presenza di radici unitarie e di cointegrazione.

Purtroppo il test ADF non ci fornisce il parametro di regressione \(\beta\) – l’hedge ratio – necessario per ottenere la combinazione lineare delle due serie temporali. In questo articolo descriviamo la procedura Cointegrated Augmented Dickey-Fuller (CADF), che tenta di risolvere questo problema. Abbiamo già introdotto il CADF nell’ambito della modellazione statistica.

Il CADF ci aiuta a identificare il coefficiente di regressione \(\beta\) per le nostre due serie ma non può identificare quale delle due serie è la variabile dipendente o indipendente per la regressione. Cioè, il valore della “risposta” \(Y\) a partire dalla “caratteristica” \(X\), nell’ambito del machine learning statistico. Descriviamo quindi come evitare questo problema calcolando la statistica del test ADF e usandola per determinare quale delle due regressioni produce una serie stazionaria.

Test di Dickey Fuller aumentato e cointegrato

Lo scopo principale del test CADF è determinare un rapporto di copertura (hedge-ratio) ottimale da utilizzare tra due serie nel trading di ritorno alla media, che era un problema identificato nell’analisi  descritta nel precedente articolo. In particole, ci aiuta a determinare il rapporto long-short di ciascuna coppia quando si effettua un pairs trading Il CADF è una procedura relativamente semplice. Prendiamo i dati storici dei due asset e eseguiamo una regressione lineare tra di loro, si ottiene i coefficienti di regressione \(\alpha\) e \(\beta\), che rappresentano rispettivamente l’intercetta e la pendenza. In particolare, il valore della pendenza ci aiuta a determinare la quantità relativa da tradare per ciascuna coppia. Dopo aver ottenuto il coefficiente di pendenza – il rapporto di copertura – possiamo eseguire un test ADF (come nell’articolo precedente) sui residui della regressione lineare in modo da determinare l’evidenza di stazionarietà e quindi di cointegrazione. Usiamo Python per eseguire la procedura CADF, utilizzando le librerie arch, stasmodels e yfinance rispettivamente per il test ADF e l’acquisizione dei dati storici. Iniziamo costruendo un set di dati sintetici, con note proprietà di cointegrazione, per vedere se la procedura CADF può recuperare la stazionarietà e il rapporto di copertura. Applichiamo quindi la stessa analisi ad alcuni dati storici reali, come anticipazione dell’implementazione di alcune strategie di trading di mean-reverting.

CADF sui dati simulati

Descriviamo l’approccio CADF sui dati simulati. Usiamo le stesse serie temporali simulate dell’articolo precedente.

Ricordiamo che abbiamo creato artificialmente due serie temporali non stazionarie che formano una serie residua stazionaria per una specifica combinazione lineare.

Possiamo utilizzare la funzione OLS della libreria stasmodels per eseguire una regressione lineare tra le due serie. Otteniamo una stima dei coefficienti di regressione e quindi il rapporto di copertura ottimale tra le due serie.

Iniziamo importando le librerie necessarie per il test ADF:

				
					import numpy as np

import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller

				
			
Dato che vogliamo usare le stesse serie stocastiche con un trend sottostante dell’articolo precedente, impostiamo il seed per il generatore di numeri casuali e creiamo una serie storica stocastica a partire da una random walk sottostante, \(z_t\). Quindi creiamo due serie che rinominiamo \(p_t\) e \(q_t\) in modo da non confonderci i nomi originali \(x_t\) e \(y_t\) con i nomi convenzionali per le regressioni delle risposte e dei predittori:
				
					np.random.seed(123)
n = 1000

z = np.zeros(n)
for i in range(1, n):
    z[i] = z[i-1] + np.random.standard_normal(1)

p = 0.3 * z + np.random.standard_normal(1000)
q = 0.6 * z + np.random.standard_normal(1000)
				
			
A questo punto possiamo utilizzare la funzione OLS, che calcola una regressione lineare tra due vettori. In questo caso impostiamo \(q_t\) come variabile indipendente e \(p_t\) come variabile dipendente:
				
					q = sm.add_constant(q)

comb = sm.OLS(p, q)

results = comb.fit()
print(results.summary())
				
			

Se diamo un’occhiata al modello di regressione lineare, possiamo vedere che la stima per il coefficiente di regressione \(\beta\) è di circa 0,5. Questo valore è accettabile dato che \(q_t\) dipende due volte da \(z_t\) rispetto a \(\)p_t[/latex (0,6 rispetto a 0,3):

				
					                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.909
Model:                            OLS   Adj. R-squared:                  0.909
Method:                 Least Squares   F-statistic:                     9924.
Date:                     28 Oct 2017   Prob (F-statistic):               0.00
Time:                        18:04:56   Log-Likelihood:                -1489.2
No. Observations:                1000   AIC:                             2982.
Df Residuals:                     998   BIC:                             2992.
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const         -0.1331      0.046     -2.871      0.004      -0.224      -0.042
x1             0.4795      0.005     99.620      0.000       0.470       0.489
==============================================================================
Omnibus:                        1.077   Durbin-Watson:                   2.003
Prob(Omnibus):                  0.584   Jarque-Bera (JB):                0.952
Skew:                          -0.029   Prob(JB):                        0.621
Kurtosis:                       3.140   Cond. No.                         13.2
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

				
			
Infine, applichiamo il test ADF ai residui del modello lineare per verificare la stazionarietà:
				
					from arch.unitroot import *

resid = results.resid
adf = ADF(resid)
print(adf.summary())
				
			

La statistica del test Dickey-Fuller è molto bassa, fornendoci un P-value molto piccolo. Possiamo probabilmente rifiutare l’ipotesi nulla della presenza di una radice unitaria e concludere che abbiamo una serie stazionaria e quindi una coppia cointegrata. Questo chiaramente conferma la bontà dei dati che abbiamo simulato per avere queste proprietà.

				
					   Augmented Dickey-Fuller Results   
=====================================
Test Statistic                -20.356
P-value                         0.000
Lags                                2
-------------------------------------

Trend: Constant
Critical Values: -3.44 (1%), -2.86 (5%), -2.57 (10%)
Null Hypothesis: The process contains a unit root.
Alternative Hypothesis: The process is weakly stationary.

Process finished with exit code 0


				
			

Vediamo ora come applicare la procedura CADF a diversi set di dati finanziari storici.

CADF sui dati finanziari

Esistono molti modi per formare un set di asset cointegrati. Una approccio comune è utilizzare ETF che presentano caratteristiche simili. Un buon esempio è un ETF che rappresenta un paniere di società di estrazione dell’oro abbinato a un ETF che segue il prezzo spot dell’oro. Allo stesso modo si può usare il petrolio greggio o qualsiasi altra  commodity.

Un’alternativa consiste nel formare coppie di cointegrazione più strette considerando classi di azioni separate sullo stesso titolo, come la Royal Dutch Shell nell’esempio seguente. Un altro esempio è la famosa Berkshire Hathaway holding, gestita da Warren Buffet e Charlie Munger, che si divide in azioni A e B. Tuttavia, in questo caso dobbiamo prestare attenzione perché dobbiamo verificare se siamo in grado di formare una strategia di mean-reverting redditizia su una tale coppia, a seconda di quanto sia stretta la cointegrazione.

EWA e EWC

Nella comunità quantistica un famoso esempio del test CADF applicato ai dati azionari è fornito da Ernie Chan[1]. Si considera una coppia cointegrata da due ETF, con i simboli ticker EWA ed EWC, che rappresentano rispettivamente panieri di titoli azionari australiani e canadesi. Dato che entrambi questi paesi sono fortemente basati sulle materie prime è probabile che questi ETF avranno una simile tendenza stocastica di fondo.

Ernie fa uso di MatLab per il suo lavoro, ma questo è un articolo su Python. Quindi ho pensato che sarebbe stato istruttivo utilizzare le stesse date di inizio e fine della sua analisi storica per vedere come si confrontano i risultati.

Il primo compito è importare la libreria yfinance, che ci sarà utile per scaricare i dati finanziari da Yahoo Finance, quindi otteniamo i prezzi di chiusura aggiustati per EWA ed EWC per il periodo esatto utilizzato nel lavoro di Ernie – dal 26 aprile 2006 al 9 aprile 2012:

				
					import yfinance as yf

EWA = yf.download('EWA', start="2006-04-26", end="2012-04-09")
EWC = yf.download('EWC', start="2006-04-26", end="2012-04-09")

EWA_adjcls = EWA['Adj Close']
EWC_adjcls = EWC['Adj Close']
				
			

Per completezza replichiamo i grafici del lavoro di Ernie, in modo che da vedere lo stesso codice nell’ambiente Python. In primo luogo, tracciamo i prezzi degli stessi ETF.

				
					import matplotlib.pyplot as plt

plt.plot(EWA_adjcls, 'b')
plt.plot(EWC_adjcls, 'r')
plt.show()
				
			
trading-quantitativo-cadf-plot-ewa-ewc-prices
Possiamo notare che differisce leggermente dal grafico riportato nel lavoro di Ernie dato che in stiamo usando i prezzi rettificati, invece dei prezzi di chiusura non rettificati. Possiamo anche creare un grafico a dispersione dei prezzi:
				
					plt.scatter(EWA_adjcls.values, EWC_adjcls.values)
plt.show()
				
			
trading-quantitativo-cadf-plot-ewa-ewc-scatter

A questo punto è necessario ottenere le regressioni lineari tra le due serie dei prezzi. Tuttavia, come detto in precedenza, non è chiaro quale serie sia la variabile dipendente e quale sia la variabile indipendente per la regressione. Quindi proviamo entrambi le ipotesi e facciamo una scelta in base alla negatività della statistica del test ADF.

Usiamo la funzione del modello lineare OLS per la valutare la regressione. In questo modo otteniamo l’intercetta e il coefficiente di regressione per queste coppie.

				
					import statsmodels.api as sm

comb1 = sm.OLS(EWC_adjcls, sm.add_constant(EWA_adjcls)).fit()
comb2 = sm.OLS(EWA_adjcls, sm.add_constant(EWC_adjcls)).fit()

print(comb1.summary())
print(comb2.summary())
				
			
Possiamo tracciare i residui e valutare visivamente la stazionarietà della serie:
				
					resid1 = comb1.resid
plt.plot(resid1)
plt.show()
				
			
trading-quantitativo-cadf-plot-ewa-ewc-resids1
Di seguito vediamo il report della regressione lineare con EWA come variabile indipendente:
				
					OLS Regression Results                            
==============================================================================
Dep. Variable:          EWC Adj Close   R-squared:                       0.921
Model:                            OLS   Adj. R-squared:                  0.921
Method:                 Least Squares   F-statistic:                 1.744e+04
Date:                     28 Oct 2017   Prob (F-statistic):               0.00
Time:                        19:35:22   Log-Likelihood:                -2135.7
No. Observations:                1500   AIC:                             4275.
Df Residuals:                    1498   BIC:                             4286.
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
=================================================================================
                    coef    std err          t      P>|t|      [0.025      0.975]
---------------------------------------------------------------------------------
const             3.3329      0.132     25.180      0.000       3.073       3.593
EWA Adj Close     1.3922      0.011    132.047      0.000       1.372       1.413
==============================================================================
Omnibus:                       59.177   Durbin-Watson:                   0.049
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               65.369
Skew:                           0.508   Prob(JB):                     6.39e-15
Kurtosis:                       3.116   Cond. No.                         64.4
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
				
			

Vediamo che il modello restituisce un’intercetta \(\alpha=3.3329\) e un coefficiente \(\beta=1.3922\).

Allo stesso modo, vediamo di seguito il report per EWC come variabile indipendente:

				
					                            OLS Regression Results                            
==============================================================================
Dep. Variable:          EWA Adj Close   R-squared:                       0.921
Model:                            OLS   Adj. R-squared:                  0.921
Method:                 Least Squares   F-statistic:                 1.744e+04
Date:                     28 Oct 2017   Prob (F-statistic):               0.00
Time:                        19:41:27   Log-Likelihood:                -1577.6
No. Observations:                1500   AIC:                             3159.
Df Residuals:                    1498   BIC:                             3170.
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
=================================================================================
                    coef    std err          t      P>|t|      [0.025      0.975]
---------------------------------------------------------------------------------
const            -1.2306      0.104    -11.822      0.000      -1.435      -1.026
EWC Adj Close     0.6615      0.005    132.047      0.000       0.652       0.671
==============================================================================
Omnibus:                       24.830   Durbin-Watson:                   0.051
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               23.805
Skew:                          -0.273   Prob(JB):                     6.77e-06
Kurtosis:                       2.713   Cond. No.                         121.
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

				
			

In questo caso il modello restituisce un’intercetta\(\alpha=-1.2306\) e un coefficiente \(\beta=0.6615\).

La criticità fondamentale è la notevole differenza dei valori dei coefficienti di regressione tra i due modelli. Dobbiamo quindi utilizzare la statistica del test ADF per determinare il rapporto di copertura (hedge-ratio) ottimale.

Per EWA come variabile indipendente otteniamo:

				
					adf1 = ADF(comb1.resid, lags=1)
print(adf1.summary())
				
			
				
					   Augmented Dickey-Fuller Results   
=====================================
Test Statistic                 -3.650
P-value                         0.005
Lags                                1
-------------------------------------

Trend: Constant
Critical Values: -3.43 (1%), -2.86 (5%), -2.57 (10%)
Null Hypothesis: The process contains a unit root.
Alternative Hypothesis: The process is weakly stationary.
				
			

La statistica del test restituisce un p-value inferiore a 0,05 fornendo la prova che possiamo rifiutare l’ipotesi nulla di una radice unitaria al livello del 5%.

Allo stesso modo per EWC come variabile indipendente ottienamo:

				
					adf2 = ADF(comb2.resid, lags=1)
print(adf2r.summary())
				
			
				
					  Augmented Dickey-Fuller Results   
=====================================
Test Statistic                 -3.655
P-value                         0.005
Lags                                1
-------------------------------------

Trend: Constant
Critical Values: -3.43 (1%), -2.86 (5%), -2.57 (10%)
Null Hypothesis: The process contains a unit root.
Alternative Hypothesis: The process is weakly stationary.
				
			
Anche in questo abbiamo prove per rifiutare l’ipotesi nulla della presenza di una radice unitaria, portando all’evidenza di una serie stazionaria (e coppia cointegrata) al livello del 5%. La statistica del test ADF per EWC come variabile indipendente è leggermente più piccola (più negativa) di quella per EWA come variabile indipendente e quindi la consideriamo come la nostra combinazione lineare per qualsiasi futura applicazione di trading.

Prossimi passi

Abbiamo utilizzato il CADF per ottenere il rapporto di copertura ottimale per due serie temporali cointegrate. Negli articoli successivi prenderemo in considerazione il test di Johansen, che ci consentirà di formare serie temporali di cointegrazione per più di due asset, fornendo un universo di trading molto più ampio da cui scegliere strategie.

Inoltre, considereremo il fatto che il rapporto di copertura stesso non è stazionario e come tale utilizzeremo tecniche per aggiornare il nostro rapporto di copertura quando arrivano nuove informazioni. Possiamo utilizzare l’approccio bayesiano del filtro di Kalman per questo.

Dopo aver esaminato questi test, li applicheremo a una serie di strategie di trading integrate in DataTrader e vedremo come si comportano con costi di transazione realistici.

Sommario

Gli altri articoli di questa serie

Se è la prima volta che atterri su DataTrading, BENVENUTO!

Lascia che mi presenti. Sono Gianluca, ingegnere, trekker e 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..

TUTORIALTrading Algoritmico, la Data Science e il Machine Learning

STRUMENTITradingView, BackTrader, e QuantConnect 

DataTrading vuole essere un punto di ritrovo per scambiare esperienze, opinioni ed idee.

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

Torna su