Pairs trading et cointégration : guide pratique
La cointégration est le vrai test du pairs trading, pas la corrélation. Calculez à la main le hedge ratio, le spread et le z-score sur des cours réels.
Le pairs trading et la cointégration vont de pair : la stratégie repose sur un écart entre deux cours boursiers qui revient régulièrement vers un niveau stable, tandis que la cointégration est la propriété statistique qui confirme l’existence même d’un tel écart. La corrélation mesure autre chose. Deux actions peuvent afficher une forte corrélation tout en s’écartant durablement l’une de l’autre. À l’inverse, deux actions dont les variations quotidiennes sont peu synchronisées peuvent conserver un écart stable pendant des années. Cette page construit à la main le hedge ratio, le spread et le z-score, mesure ces trois éléments sur des cours de clôture réels, puis accorde la même place aux situations dans lesquelles cette construction échoue.
Qu’est-ce que la cointégration dans le pairs trading ?
La corrélation mesure l’étroitesse des variations quotidiennes en pourcentage de deux actions. Elle va de -1 à +1. Une corrélation de +1 signifie que les deux titres ont progressé et baissé ensemble à chaque séance de l’échantillon. Elle ne dit rien sur l’écart entre leurs cours.
La cointégration porte sur cet écart. Prenez l’action B, soustrayez-y un multiple fixe de l’action A, puis observez ce qu’il reste. Si cette série résiduelle, le spread, reste dans une fourchette bornée au lieu de s’éloigner indéfiniment, les deux titres sont cointégrés. Leurs cours peuvent évoluer librement dans n’importe quelle direction. Pas leur combinaison.
L’image utilisée dans les manuels est celle d’un chien tenu en laisse. Le promeneur et le chien suivent des trajectoires imprévisibles dans la rue, mais la laisse maintient une distance limitée entre eux. La corrélation demande si les deux avancent au même moment. La cointégration demande si la laisse tient.
Le tableau ci-dessous mesure le premier concept sur cinq paires connues en 2024 et 2025. La colonne return_corr indique la corrélation des variations quotidiennes en pourcentage, ce que l’on entend généralement par « corrélation ». La colonne price_corr indique la corrélation des cours de clôture bruts, un chiffre qui tend à surestimer la proximité de presque deux titres quelconques ayant tous deux progressé.
Le SQL exact derrière chaque chiffre
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 DESCClassé selon la corrélation des variations quotidiennes, HD / LOW arrive en tête du tableau avec 0.868, tandis que la corrélation des deux niveaux de cours sur la même période atteint 0.849. Sur la dernière ligne, AAPL / MSFT affiche une corrélation des rendements de 0.479, contre une corrélation des cours de 0.508. Aucune de ces deux colonnes ne répond à la question que se pose un trader en pairs trading. Toutes deux mesurent les évolutions conjointes, alors que la stratégie nécessite une mesure de l’écart.
Comment calculer le hedge ratio et le spread ?
Cinq étapes, dans l’ordre, sur deux listes de cours de clôture. Python standard suffit, avec math comme seul import : aucun package, aucun fournisseur de données et aucun accès réseau.
- Centrer chaque série.
mean_a = sum(a) / len(a), et faire de même pour la listeb. - Estimer le hedge ratio. Il s’agit de la pente d’une droite de régression de B sur 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). Elle indique le nombre d’unités de A qui compense une unité de B. - Construire le spread.
spread = [b[i] - beta * a[i] for i in range(len(a))]. Sur une paire cointégrée, cette série oscille autour d’un niveau au lieu de suivre une tendance. - Prendre une fenêtre glissante. Avec
w = spread[i - win + 1 : i + 1], la moyenne de la fenêtre estmu = sum(w) / len(w)et l’écart-type empirique estsd = math.sqrt(sum((x - mu) ** 2 for x in w) / (len(w) - 1)). Glissante signifie que la fenêtre se termine le jour évalué et n’utilise aucune donnée ultérieure. - Calculer le score et l’afficher.
z = (spread[i] - mu) / sd, puis afficher une ligne d’entrée lorsqueabs(z) > 1.5et une ligne de sortie dès queabs(z) < 0.5. Un z de 2 signifie que le spread se situe à deux écarts-types au-dessus de sa propre moyenne récente.
Deux listes de cours codées en dur suffisent pour suivre l’exécution, et un win de 5 permet de garder un exemple pédagogique lisible. Pour un travail sérieux, on utilise plutôt une fenêtre proche de 63 séances, soit environ un trimestre. Les seuils de 1,5 à l’entrée et de 0,5 à la sortie sont des conventions reprises d’un tutoriel à l’autre. Ils ne résultent d’aucune analyse, et rien dans les panneaux ci-dessous ne les a déterminés.
À quoi ressemble un z-score de pairs trading sur des cours réels ?
Voici le même calcul appliqué à deux valeurs de boissons grand public, KO et PEP. Le hedge ratio est calculé uniquement à partir des clôtures de 2023, puis reste fixe sur 2024 et 2025, la période couverte par le graphique. Ajuster la pente sur le même échantillon que celui utilisé ensuite pour calculer le score crée un biais d’anticipation dans le backtesting. Séparer la fenêtre d’ajustement de la fenêtre de calcul du score est la défense la moins coûteuse contre ce biais.
Le SQL exact derrière chaque chiffre
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 courbe contient 105 observations hebdomadaires. Elle commence la période à un z-score de -1.53 et la termine à -0.68. Analysez sa forme plutôt qu’un point isolé. Lorsqu’un pair évolue comme le suppose la stratégie, il franchit son seuil, revient progressivement vers zéro pendant quelques semaines, puis franchit à nouveau le seuil. Lorsqu’un pair ne revient plus vers sa moyenne, il reste au-delà du seuil. Sur un graphique, cela se traduit par une longue séquence presque horizontale, éloignée du centre.
Un spread cointégré revient-il toujours à sa moyenne ?
Non. Pour le vérifier rigoureusement, il faut mesurer ce qui s’est produit après chaque point de départ. Le graphique ci-dessous regroupe chaque séance de 2019 à fin 2025 selon le z-score observé ce jour-là, puis présente le z-score moyen de ce même spread vingt séances plus tard. Il utilise le logarithme du ratio de cours, et non un spread ajusté. Aucune régression estimée a posteriori n’intervient donc dans le graphique.
Le SQL exact derrière chaque chiffre
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_startLes séances qui ont commencé dans le bucket z below -2 affichaient en moyenne -2.42 ce jour-là. Le même spread affichait en moyenne -0.84 vingt séances plus tard, sur 138 séances. À l’autre extrémité, le bucket z above 2 commençait à 2.64 et affichait 0.92 vingt séances plus tard. Une courbe qui converge vers zéro depuis les deux extrémités correspond à la forme attendue d’un retour à la moyenne.
Il faut lire ces résultats en tenant compte de la colonne des effectifs. Les buckets extérieurs contiennent le moins de séances. En outre, les séances d’un même bucket se chevauchent largement. Quelques longues séquences fournissent donc la majorité des observations. Une moyenne ne constitue pas une promesse pour une entrée donnée.
Pourquoi le pairs trading cesse-t-il de fonctionner ?
Le hedge ratio n’est pas constant. Réestimez-le chaque année civile : il évolue.
Le SQL exact derrière chaque chiffre
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 pente de PEP par rapport à KO, mesurée 2.338 sur 2019 et -0.543 sur 2025, avec la seconde paire du secteur présentée à titre de comparaison. Un backtest qui estime une seule pente sur l’historique complet puis l’utilise pour trader dès le premier jour attribue à chaque transaction initiale un paramètre que personne ne pouvait connaître à ce moment-là. La correction est simple et efficace : réestimer le modèle sur une fenêtre glissante et évaluer chaque jour avec les paramètres disponibles ce jour-là. Un backtest reproductible permet à un tiers de vérifier cette discipline.
Les opérations sur titres peuvent modifier une jambe du spread du jour au lendemain. Un split de quatre pour un transforme une action en quatre et divise par quatre le cours coté. Un spread calculé sur des cours non ajustés à cette date saute alors vers un niveau auquel aucun échange n’a eu lieu. Les fusions, les scissions et les changements de composition d’indice produisent des effets similaires, mais moins visibles. Commencez par un historique de cours ajusté des splits et gardez à l’esprit qu’un ticker peut être réattribué à une société entièrement différente. C’est pourquoi les tickers peuvent détériorer les jeux de données.
Pendant qu’elle se produit, une rupture est impossible à distinguer d’une opportunité. Le z score le plus élevé jamais affiché par une paire est soit le meilleur point d’entrée qu’elle ait offert, soit la première semaine d’une relation qui a pris fin. Dans les deux cas, le spread paraît excessivement étiré ce jour-là. Le dimensionnement des positions compense ce que les statistiques ne peuvent pas déterminer : le dimensionnement des positions selon le critère de Kelly et le dimensionnement des positions ciblant la volatilité sont deux méthodes pour déterminer la part d’un compte qu’un seul spread peut engager. Le drawdown maximal mesure l’effet d’une longue période passée du mauvais côté de la moyenne sur une courbe de capital.
Un z score glissant n’est pas un test de cointégration. Il existe des tests formels. La procédure d’Engle-Granger estime la régression, puis teste la stationnarité du spread résiduel. Le test de Johansen étend cette approche au-delà de deux valeurs. Un z score suppose que la propriété vérifiée par ces tests est bien présente. Il affichera sans difficulté une valeur élevée pour un spread dont la moyenne n’est pas stable, car une série orientée reste toujours très éloignée de sa propre moyenne glissante.
Notes sur les données et éléments exclus de ces chiffres
Le panneau de corrélation utilise les cours de clôture quotidiens du 2024-01-01 au 2025-12-31 et calcule les rendements à partir de clôtures consécutives, en excluant la première séance de chaque valeur. La série des z scores estime le hedge ratio à partir des seules clôtures de 2023, puis évalue 2024 et 2025 avec une moyenne glissante sur 63 séances et un écart type empirique, en retenant la dernière observation de chaque semaine. Le panneau de réversion charge l’historique à partir de 2018 et présente les résultats à partir de 2019. Chaque journée évaluée dispose ainsi d’une fenêtre glissante complète en amont. La série s’arrête à la mi-novembre 2025 afin que chaque journée dispose de vingt séances ultérieures dans l’échantillon.
Aucun de ces panneaux ne constitue une stratégie. Ils n’intègrent ni coûts de transaction, ni bid-ask spread, ni frais d’emprunt sur la jambe short, ni dividendes, ni financement. Ils utilisent uniquement les cours de clôture. Une entrée enregistrée à la clôture correspond donc à un niveau auquel personne n’aurait pu être exécuté.
FAQ
Quelle est la différence entre corrélation et cointégration ?
La corrélation mesure si deux actions évoluent les mêmes jours et dans la même direction, sur une échelle de -1 à +1. La cointégration mesure si l’écart entre leurs cours reste dans une fourchette stable. Une paire peut afficher une corrélation élevée tout en échouant au second test. C’est généralement pourquoi un filtre fondé sur la corrélation fait ressortir des paires qui finissent par diverger.
Comment calcule-t-on le hedge ratio dans le pairs trading ?
Ajustez une droite représentant le cours d’une action en fonction de celui de l’autre, puis retenez sa pente. Cette pente indique le nombre d’unités de la première action qui compense une unité de la seconde. Elle évolue dans le temps. La plupart des implémentations recalculent donc l’ajustement sur une fenêtre glissante plutôt qu’une seule fois sur l’ensemble de l’échantillon.
Quel z score les traders de pairs utilisent-ils pour entrer et sortir ?
Les exemples pédagogiques déclenchent généralement l’entrée autour de 1,5 à 2 écarts types par rapport à la moyenne glissante, puis la sortie près de 0,5. Ces niveaux sont des conventions reprises d’analyses antérieures, et non des optimums mesurés. Les modifier change à la fois le nombre de trades et leur durée de détention.
Deux actions peuvent-elles être cointégrées avec une faible corrélation ?
Oui. La corrélation est calculée sur les variations quotidiennes, tandis que la cointégration porte sur le niveau de l’écart. Deux titres dont les mouvements quotidiens coïncident rarement peuvent donc conserver une distance stable à long terme. L’inverse se produit également : une forte corrélation quotidienne peut s’accompagner d’un écart qui se creuse année après année.
Pourquoi une paire cointégrée cesse-t-elle de fonctionner ?
Le lien qui maintient l’écart peut évoluer : fusion, scission, changement de la composition des activités d’une entreprise ou modification d’un indice qui transforme la répartition de la détention du titre. Les statistiques observées avant une rupture ressemblent à celles qui précèdent une réversion ordinaire. Il faut donc dimensionner le risque en conséquence plutôt que chercher à prévoir la rupture.
Chaque panneau est fourni avec la requête SQL exacte sous-jacente. Les fenêtres, la période d’ajustement et les bornes des buckets sont donc visibles. Pour appliquer le même spread et le même z score à une paire que vous suivez, posez la question en anglais courant sur le terminal Strasmore.