Pairs trading e cointegrazione: guida pratica
La cointegrazione è il test chiave per il pairs trading, non la correlazione. Calcola a mano hedge ratio, spread e z-score su prezzi di chiusura reali.
Il pairs trading e la cointegrazione sono strettamente legati: l’operazione si basa su un divario tra i prezzi di due azioni che tende a tornare verso un livello stabile, mentre la cointegrazione è la proprietà statistica che conferma l’esistenza stessa di questo divario. La correlazione misura un fenomeno diverso. Due azioni possono registrare una correlazione elevata e continuare comunque ad allontanarsi indefinitamente, mentre due azioni i cui movimenti giornalieri coincidono poco possono mantenere un divario stabile per anni. Questa pagina costruisce manualmente l’hedge ratio, lo spread e lo z-score, li misura tutti e tre su prezzi di chiusura reali e dedica lo stesso spazio ai casi in cui questo approccio non funziona.
Che cos’è la cointegrazione nel pairs trading?
La correlazione misura quanto sono ravvicinati i movimenti percentuali giornalieri di due azioni. Va da -1 a +1: +1 significa che, in ogni seduta del campione, le due azioni sono salite e scese insieme. Non dice nulla sulla distanza tra i due prezzi.
La cointegrazione riguarda proprio quella distanza. Si prende l’azione B, si sottrae un multiplo fisso dell’azione A e si osserva ciò che rimane. Se questa serie residua, lo spread, resta entro un intervallo limitato invece di allontanarsi senza sosta, i due titoli sono cointegrati. I prezzi possono muoversi liberamente in qualsiasi direzione. La combinazione dei due prezzi no.
L’immagine usata nei manuali è quella di un cane al guinzaglio. Il conduttore e il cane seguono percorsi imprevedibili lungo la strada, mentre il guinzaglio mantiene entro limiti la distanza tra loro. La correlazione chiede se i due si muovono nello stesso momento. La cointegrazione chiede se il guinzaglio tiene.
La tabella seguente misura il primo concetto su cinque coppie note nel 2024 e nel 2025. La colonna return_corr indica la correlazione dei movimenti percentuali giornalieri, che è ciò che normalmente si intende con questo termine. La colonna price_corr indica la correlazione dei prezzi di chiusura non rettificati, un valore che tende a sopravvalutare quasi tutte le coppie di titoli che hanno registrato entrambe un rialzo.
L'esatto SQL dietro ogni numero
WITH
px AS (
SELECT
ticker,
date,
toFloat64(any(close)) AS close_px
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('KO', 'PEP', 'HD', 'LOW', 'XOM', 'CVX', 'V', 'MA', 'AAPL', 'MSFT')
AND date BETWEEN '2024-01-01' AND '2025-12-31'
GROUP BY ticker, date
),
rets AS (
SELECT
ticker,
date,
close_px,
close_px / lagInFrame(close_px) OVER (
PARTITION BY ticker ORDER BY date
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
) - 1 AS ret
FROM px
)
SELECT
p.pair AS pair,
round(corr(a.ret, b.ret), 3) AS return_corr,
round(corr(a.close_px, b.close_px), 3) AS price_corr
FROM
(
SELECT
tupleElement(t, 1) AS leg_a,
tupleElement(t, 2) AS leg_b,
tupleElement(t, 3) AS pair
FROM
(
SELECT arrayJoin([
('KO', 'PEP', 'KO / PEP'),
('HD', 'LOW', 'HD / LOW'),
('XOM', 'CVX', 'XOM / CVX'),
('V', 'MA', 'V / MA'),
('AAPL', 'MSFT', 'AAPL / MSFT')
]) AS t
)
) AS p
INNER JOIN rets AS a ON a.ticker = p.leg_a
INNER JOIN rets AS b ON b.ticker = p.leg_b AND b.date = a.date
WHERE isFinite(a.ret) AND isFinite(b.ret)
GROUP BY pair
ORDER BY return_corr DESCIn base alla correlazione dei movimenti giornalieri, HD / LOW è in cima alla tabella con un valore di 0.868, mentre la correlazione dei due livelli di prezzo nella stessa finestra temporale è pari a 0.849. Nell’ultima riga, AAPL / MSFT presenta una correlazione dei rendimenti di 0.479 e una correlazione dei prezzi di 0.508. Nessuna delle due colonne risponde alla domanda che interessa a chi opera nel pairs trading. Entrambe misurano il co-movimento, mentre la strategia richiede una misura della distanza.
Come si calcolano hedge ratio e spread?
Cinque passaggi, nell’ordine, su due liste di prezzi di chiusura. Python standard è sufficiente, con math come unico import: nessun package, nessun data vendor, nessuna connessione di rete.
- Centrare ciascuna serie.
mean_a = sum(a) / len(a), e lo stesso vale per la listab. - Stimare l’hedge ratio. È la pendenza di una retta di B rispetto ad A:
beta = sum((a[i] - mean_a) * (b[i] - mean_b) for i in range(len(a))) / sum((x - mean_a) ** 2 for x in a). Si interpreta come il numero di unità di A che compensa una unità di B. - Costruire lo spread.
spread = [b[i] - beta * a[i] for i in range(len(a))]. Su una coppia cointegrata, questa serie oscilla intorno a un livello invece di seguire un trend. - Applicare una finestra mobile. Con
w = spread[i - win + 1 : i + 1], la media della finestra èmu = sum(w) / len(w)e la deviazione standard campionaria èsd = math.sqrt(sum((x - mu) ** 2 for x in w) / (len(w) - 1)). Mobile significa che la finestra termina nel giorno oggetto della valutazione e non usa dati successivi. - Calcolare lo z-score e stampare i risultati.
z = (spread[i] - mu) / sd, poi si stampa una riga di ingresso quandoabs(z) > 1.5e una riga di uscita quandoabs(z) < 0.5. Uno z-score pari a 2 indica che lo spread si trova due deviazioni standard sopra la propria media recente.
Per osservare l’esecuzione bastano due liste di prezzi hard-coded; un win pari a 5 mantiene leggibile l’esempio didattico. Per un’analisi reale si usa una finestra più vicina a 63 sedute, cioè circa un trimestre. Le soglie di 1,5 per l’ingresso e 0,5 per l’uscita sono convenzioni tramandate da un tutorial all’altro. Non sono risultati empirici e nessuno dei pannelli qui sotto le ha ricavate.
Come appare uno z score di pairs trading sui prezzi reali?
Ecco lo stesso calcolo applicato a due società del comparto delle bevande di largo consumo, KO e PEP. L’hedge ratio deriva esclusivamente dalle chiusure del 2023 e resta poi fisso per tutto il 2024 e il 2025, il periodo coperto dal grafico. Stimare la pendenza sullo stesso campione utilizzato per calcolare lo score successivo introduce look-ahead bias nel backtesting; mantenere distinti il periodo di stima e quello di scoring è la difesa più semplice e meno costosa.
L'esatto SQL dietro ogni numero
WITH
daily AS (
SELECT
date,
anyIf(toFloat64(close), ticker = 'PEP') AS pep,
anyIf(toFloat64(close), ticker = 'KO') AS ko
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('KO', 'PEP')
AND date BETWEEN '2023-01-01' AND '2025-12-31'
GROUP BY date
HAVING pep > 0 AND ko > 0
),
fitted AS (
SELECT covarSamp(pep, ko) / varSamp(ko) AS beta
FROM daily
WHERE date < '2024-01-01'
),
spread AS (
SELECT
daily.date AS date,
daily.pep - fitted.beta * daily.ko AS spread_usd
FROM daily
CROSS JOIN fitted
),
scored AS (
SELECT
date,
(spread_usd - avg(spread_usd) OVER (
ORDER BY date ROWS BETWEEN 62 PRECEDING AND CURRENT ROW))
/ stddevSampStable(spread_usd) OVER (
ORDER BY date ROWS BETWEEN 62 PRECEDING AND CURRENT ROW) AS z
FROM spread
)
SELECT
toString(toMonday(date)) AS week,
round(argMax(z, date), 2) AS z_score
FROM scored
WHERE date >= '2024-01-01'
AND isFinite(z)
GROUP BY week
ORDER BY weekLa serie comprende 105 osservazioni settimanali. Apre il periodo con uno z score pari a -1.53 e lo chiude a -0.68. È più utile osservare la forma della serie che concentrarsi su un singolo punto. Una coppia che si comporta come previsto dalla strategia attraversa la soglia, trascorre alcune settimane tornando verso lo zero e poi attraversa nuovamente la soglia. Una coppia che ha smesso di mostrare mean reversion si stabilizza oltre la soglia e vi rimane; sul grafico questo appare come un lungo tratto piatto, lontano dal punto centrale.
Un cointegrated spread torna sempre verso la media?
No. Il modo corretto per verificarlo è misurare cosa è successo a partire da ogni punto iniziale. Il pannello seguente raggruppa ogni seduta dal 2019 alla fine del 2025 in base allo z score di quella giornata, quindi riporta lo z score medio dello stesso spread venti sedute dopo. Utilizza il logaritmo del rapporto tra i prezzi, anziché uno spread stimato, così nessuna regressione calcolata a posteriori incide sul grafico.
L'esatto SQL dietro ogni numero
WITH
daily AS (
SELECT
date,
anyIf(toFloat64(close), ticker = 'PEP') AS pep,
anyIf(toFloat64(close), ticker = 'KO') AS ko
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('KO', 'PEP')
AND date BETWEEN '2018-01-01' AND '2025-12-31'
GROUP BY date
HAVING pep > 0 AND ko > 0
),
spread AS (
SELECT
date,
log(pep / ko) AS log_ratio
FROM daily
),
scored AS (
SELECT
date,
(log_ratio - avg(log_ratio) OVER (
ORDER BY date ROWS BETWEEN 62 PRECEDING AND CURRENT ROW))
/ stddevSampStable(log_ratio) OVER (
ORDER BY date ROWS BETWEEN 62 PRECEDING AND CURRENT ROW) AS z
FROM spread
),
horizon AS (
SELECT
date,
z,
leadInFrame(z, 20) OVER (
ORDER BY date ROWS BETWEEN CURRENT ROW AND 20 FOLLOWING
) AS z_fwd
FROM scored
WHERE isFinite(z)
)
SELECT
multiIf(z < -2, 'z below -2',
z < -1, 'z -2 to -1',
z < 0, 'z -1 to 0',
z < 1, 'z 0 to 1',
z < 2, 'z 1 to 2',
'z above 2') AS z_bucket,
round(avg(z), 2) AS avg_z_start,
round(avg(z_fwd), 2) AS avg_z_20d_later,
count() AS episode_count
FROM horizon
WHERE date >= '2019-01-01'
AND date <= '2025-11-15'
AND isFinite(z_fwd)
GROUP BY z_bucket
ORDER BY avg_z_startLe sedute iniziate nel bucket z below -2 presentavano in media -2.42 quel giorno; venti sedute dopo, lo stesso spread mostrava in media -0.84, su un totale di 138 sedute. All’estremo opposto, il bucket z above 2 partiva da 2.64 e registrava 0.92 venti sedute più tardi. Una curva che converge verso zero da entrambe le estremità è la forma tipica della mean reversion.
Interpretate il grafico tenendo conto della colonna dei conteggi. I bucket estremi contengono il minor numero di sedute e le sedute all’interno di uno stesso bucket si sovrappongono in misura significativa; per questo, pochi periodi prolungati alimentano la maggior parte delle righe. Una media non è una garanzia sul risultato di una singola entrata.
Perché il pairs trading smette di funzionare?
L’hedge ratio non è costante. Ricalibrarlo ogni anno solare mostra che cambia.
L'esatto SQL dietro ogni numero
WITH daily AS (
SELECT
date,
anyIf(toFloat64(close), ticker = 'PEP') AS pep,
anyIf(toFloat64(close), ticker = 'KO') AS ko,
anyIf(toFloat64(close), ticker = 'LOW') AS lowes,
anyIf(toFloat64(close), ticker = 'HD') AS hd
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('KO', 'PEP', 'HD', 'LOW')
AND date BETWEEN '2019-01-01' AND '2025-12-31'
GROUP BY date
HAVING ko > 0 AND pep > 0 AND hd > 0 AND lowes > 0
)
SELECT
toYear(date) AS year,
round(covarSamp(pep, ko) / varSamp(ko), 3) AS pep_on_ko_beta,
round(covarSamp(lowes, hd) / varSamp(hd), 3) AS lowes_on_hd_beta
FROM daily
GROUP BY year
ORDER BY yearLa pendenza di PEP rispetto a KO misurata 2.338 tra 2019 e -0.543 tra 2025, con la seconda coppia dello stesso settore riportata a confronto. Un backtest che stima una sola pendenza sull’intera storia e la utilizza per gli scambi fin dal primo giorno assegna a ogni operazione iniziale un valore che all’epoca nessuno avrebbe potuto conoscere. La soluzione è semplice ed efficace: ricalibrare su una finestra mobile e valutare ogni giorno usando i parametri disponibili in quella data. Un backtest riproducibile consente a terzi di verificare questa disciplina.
Le operazioni sul capitale riscrivono una gamba da un giorno all’altro. Un frazionamento 4-for-1 trasforma un’azione in quattro e riduce a un quarto il prezzo quotato; uno spread calcolato su quella data usando prezzi non rettificati salta a un livello al quale il titolo non ha mai scambiato. Fusioni, spin-off e variazioni degli indici producono versioni meno evidenti dello stesso effetto. Occorre partire da una storia dei prezzi rettificata per i frazionamenti e ricordare che un simbolo può essere riassegnato a una società completamente diversa: è questo il motivo per cui i ticker si rompono nei dataset.
Un break strutturale e un’opportunità si presentano nello stesso modo mentre si verificano. Il punteggio z più alto mai registrato da una coppia può indicare il miglior punto di ingresso mai offerto oppure la prima settimana di una relazione ormai conclusa. Quel giorno entrambi i casi appaiono come uno spread eccessivamente esteso. Il dimensionamento delle posizioni colma ciò che le statistiche non possono stabilire: il position sizing secondo il criterio di Kelly e il position sizing con targeting della volatilità sono due metodi per determinare quanto un singolo spread possa incidere sul conto, mentre il maximum drawdown misura l’effetto di una lunga permanenza dalla parte sbagliata della media su una curva del capitale.
Un punteggio z mobile non è un test di cointegrazione. Esistono test formali. La procedura di Engle-Granger esegue la regressione e poi verifica la stazionarietà dello spread residuo; il test di Johansen estende il concetto oltre due titoli. Un punteggio z presuppone la proprietà verificata da questi test e può tranquillamente produrre un valore elevato per uno spread privo di una media stabile, perché una serie in trend si trova sempre molto distante dalla propria media mobile.
Note sui dati e aspetti esclusi da questi numeri
Il pannello della correlazione utilizza le chiusure giornaliere dal 2024-01-01 al 2025-12-31 e calcola i rendimenti tra chiusure consecutive, escludendo la prima seduta di ciascun titolo. Il tracciato del punteggio z stima l’hedge ratio utilizzando solo le chiusure del 2023, quindi valuta il 2024 e il 2025 con una media mobile di 63 sedute e una deviazione standard campionaria, prendendo come riferimento l’ultima lettura di ogni settimana. Il pannello della mean reversion utilizza la storia dal 2018 e riporta i dati dal 2019 in poi; ogni giornata valutata dispone quindi di una finestra mobile completa alle sue spalle. Il campione si ferma a metà novembre 2025, così ogni giornata ha venti sedute successive disponibili all’interno del campione.
Nessuno di questi pannelli costituisce una strategia. Non includono costi di trading, bid-ask spread, costi di borrow sulla gamba short, dividendi o costi di finanziamento. Considerano solo i prezzi di chiusura: un ingresso valorizzato alla chiusura è quindi un ingresso che nessuno avrebbe potuto eseguire esattamente a quel prezzo.
FAQ
Qual è la differenza tra correlazione e cointegrazione?
La correlazione misura se due azioni si muovono negli stessi giorni e nella stessa direzione, su una scala da -1 a +1. La cointegrazione misura se la distanza tra i loro prezzi resta all’interno di un intervallo stabile. Una coppia può avere un punteggio elevato nella prima misura e non superare la seconda. È il motivo più comune per cui uno screening basato sulla correlazione individua coppie che poi divergono.
Come si calcola l’hedge ratio nel pairs trading?
Si adatta una retta al prezzo di un’azione in funzione del prezzo dell’altra e si prende la pendenza. Questa pendenza indica il numero di unità della prima azione che compensa un’unità della seconda. È una grandezza variabile. Per questo la maggior parte delle implementazioni ricalcola il rapporto su una finestra mobile, invece di stimarlo una sola volta sull’intero campione.
Quale z score usano i pairs trader per entrare e uscire?
Gli esempi dei tutorial prevedono di norma l’ingresso a circa 1,5-2 deviazioni standard dalla media mobile e l’uscita vicino a 0,5. Questi valori sono convenzioni ereditate da analisi precedenti, non livelli ottimali misurati. Modificarli cambia sia il numero di operazioni sia la durata media delle posizioni.
Due azioni possono essere cointegrate con una correlazione bassa?
Sì. La correlazione viene calcolata sulle variazioni giornaliere, mentre la cointegrazione riguarda il livello del divario. Due titoli i cui movimenti giornalieri raramente coincidono possono comunque mantenere una distanza stabile nel lungo periodo. Può verificarsi anche il contrario: una correlazione giornaliera elevata associata a un divario che si amplia anno dopo anno.
Perché una coppia cointegrata smette di funzionare?
Il legame che mantiene il divario entro i suoi limiti può cambiare. Le cause possono essere una fusione, uno spin-off, una modifica della composizione del business di una società oppure un cambiamento dell’indice che modifica la platea dei detentori del titolo. Le statistiche dei giorni precedenti a una rottura appaiono uguali a quelle dei giorni precedenti a una normale reversion. Per questo il rischio deve essere dimensionato, non previsto.
Ogni pannello qui include il relativo SQL esatto. Le finestre, il periodo di stima e i limiti dei bucket sono quindi tutti visibili. Per calcolare lo stesso spread e lo stesso z score su una coppia che segui, formula la domanda in inglese naturale sul terminale Strasmore.