Strasmore Research
Onderzoeken Matt ConnorDoor Matt Connor

Pairs trading en co-integratie uitgelegd

Co-integratie is essentieel voor pairs trading, in tegenstelling tot correlatie. Leer handmatig de hedge ratio, spread en z-score berekenen aan de hand van werkelijke slotkoersen.

Pairs trading en co-integratie
Pairs trading en co-integratie horen bij elkaar: de transactie is gebaseerd op een verschil tussen twee aandelenkoersen dat telkens terugkeert naar een stabiel niveau, en co-integratie is de statistische eigenschap die garandeert dat een dergelijk verschil überhaupt bestaat. Correlatie meet iets anders. Twee aandelen kunnen een hoge correlatie vertonen en toch voorgoed uit elkaar drijven, terwijl twee aandelen waarvan de dagelijkse koersbewegingen nauwelijks overeenkomen, jarenlang een stabiel verschil kunnen handhaven. Deze pagina bouwt handmatig de hedge ratio, de spread en de z-score op, meet alle drie aan de hand van werkelijke slotkoersen en besteedt vervolgens evenveel aandacht aan de wijze waarop deze constructie kan falen.

Wat is co-integratie bij pairs trading?

Correlatie geeft de nauwkeurigheid aan van de dagelijkse procentuele bewegingen van twee aandelen. Deze waarde loopt van min één tot plus één, waarbij plus één betekent dat ze in elke sessie van de steekproef samen stegen en daalden. Het zegt echter niets over de afstand tussen de twee koersen.

Co-integratie is een uitspraak over die afstand. Neem aandeel B, trek daar een vast veelvoud van aandeel A vanaf en kijk wat er overblijft. Als die resterende reeks, de spread, binnen een begrensd bereik blijft in plaats van weg te dwalen, dan zijn de twee namen geco-integreerd. De koersen zelf kunnen alle kanten op gaan. De combinatie kan dat niet.

Het beeld dat in studieboeken wordt gebruikt, is dat van een hond aan een riem. De wandelaar en de hond leggen beide onvoorspelbare paden af over straat, maar de riem houdt de afstand tussen hen begrensd. Correlatie vraagt of de twee op hetzelfde moment een stap zetten. Co-integratie vraagt of de riem standhoudt.

Het onderstaande overzicht meet het eerste concept voor vijf bekende paren over 0.479 en 0.508. De return_corr-kolom is de correlatie van de dagelijkse procentuele bewegingen; dit is wat mensen doorgaans bedoelen met het woord. De price_corr-kolom is de correlatie van de ruwe slotkoersen, een getal dat bijna elke twee namen die beide zijn gestegen, gunstig stemt.

QueryCorrelatie van dagelijkse rendementen versus prijsniveau-correlatie, vijf bekende paren (2024-2025)
De exacte SQL achter elk getal
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 DESC
Run this yourself

Gerangschikt op correlatie van dagelijkse bewegingen staat HD / LOW bovenaan het overzicht met 0.868, terwijl de correlatie van de twee koersniveaus over hetzelfde venster 0.849 bedroeg. In de onderste rij heeft AAPL / MSFT een rendementscorrelatie van 0.479 naast een koerscorrelatie van 0.508. Geen van beide kolommen beantwoordt de vraag die een pairs trader stelt. Beide meten de gezamenlijke beweging, terwijl de trade een maatstaf voor afstand behoeft.

Hoe berekent u de hedge ratio en de spread?

Vijf stappen, in volgorde, over twee lijsten met slotkoersen. Standaard Python dekt dit volledig met math als enige import: geen pakketten, geen dataleverancier, geen netwerk.

  1. Centreer elke reeks. mean_a = sum(a) / len(a), en hetzelfde voor lijst b.
  2. Bepaal de hedge ratio. Dit is de helling van een rechte lijn van B ten opzichte van 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). Lees dit als het aantal eenheden van A dat één eenheid van B compenseert.
  3. Construeer de spread. spread = [b[i] - beta * a[i] for i in range(len(a))]. Bij een gecointegreerd paar schommelt deze reeks rond een bepaald niveau in plaats van een trend te vertonen.
  4. Gebruik een voortschrijdend venster. Met w = spread[i - win + 1 : i + 1] is het gemiddelde van het venster mu = sum(w) / len(w) en de steekproefstandaardafwijking sd = math.sqrt(sum((x - mu) ** 2 for x in w) / (len(w) - 1)). Voortschrijdend betekent dat het venster eindigt op de dag die wordt beoordeeld en geen gegevens daarna gebruikt.
  5. Score en print. z = (spread[i] - mu) / sd, print vervolgens een instapregel terwijl abs(z) > 1.5 en een uitstapregel zodra abs(z) < 0.5. Een z-score van twee plaatst de spread twee standaardafwijkingen boven het eigen recente gemiddelde.

Twee hard-coded prijslijsten zijn voldoende om dit proces te observeren, en een win van vijf houdt een eenvoudig voorbeeld leesbaar. Serieus werk gebruikt een waarde die dichter bij drieënzestig sessies ligt, ongeveer een kwartaal. Die drempelwaarden, anderhalf voor instappen en een half voor uitstappen, zijn conventies die van de ene handleiding naar de andere worden doorgegeven. Het zijn geen onderzoeksresultaten en niets in de onderstaande panelen heeft deze waarden geselecteerd.

Hoe ziet een z-score bij pairs trading eruit op basis van werkelijke koersen?

Hieronder ziet u dezelfde berekening voor twee bekende namen in de drankenindustrie, KO en PEP. De hedge ratio is uitsluitend gebaseerd op de slotkoersen van 2023 en blijft vervolgens gefixeerd gedurende 2024 en 2025, de periode die de grafiek beslaat. Het bepalen van de hellingshoek op basis van dezelfde steekproef die u vervolgens gebruikt voor de scoring, staat bekend als look-ahead bias bij backtesting. Het gescheiden houden van de fit-periode en de scoring-periode is de meest eenvoudige verdediging hiertegen.

QueryWekelijkse z-score van de KO/PEP spread, hedge ratio gebaseerd op 2023
De exacte SQL achter elk getal
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 week
Run this yourself

De grafiek bevat 105 wekelijkse metingen. Het venster opent bij een z-score van -1.53 en sluit bij -0.68. Kijk naar het verloop van de lijn in plaats van naar een enkel punt. Een paar dat zich gedraagt volgens de aannames van de strategie, kruist de drempelwaarde, beweegt gedurende enkele weken terug richting nul en kruist de lijn vervolgens opnieuw. Een paar dat niet langer rond het gemiddelde schommelt, blijft voorbij de drempelwaarde liggen; op een grafiek ziet dit eruit als een lang, vlak traject ver verwijderd van het midden.

Keert een gecointegreerde spread altijd terug naar het gemiddelde?

Nee, en de eerlijkste manier om dit te beoordelen is door te meten wat er na elk startpunt is gebeurd. Het onderstaande overzicht groepeert elke sessie van 2019 tot eind 2025 op basis van de z-score van die dag, en rapporteert vervolgens de gemiddelde z-score van dezelfde spread twintig sessies later. Hierbij wordt de logaritme van de prijsverhouding gebruikt in plaats van een gefitte spread, waardoor er geen achteraf geschatte regressie invloed heeft op de grafiek.

QueryPositie van de KO/PEP spread na twintig sessies, per startende z-score (2019-2025)
De exacte SQL achter elk getal
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_start
Run this yourself

Sessies die begonnen in de z below -2-bucket hadden een gemiddelde van -2.42 op de dag zelf, en dezelfde spread noteerde gemiddeld -0.84 twintig sessies later, over een totaal van 138 sessies. Aan de andere kant begon de z above 2-bucket op 2.64 en mat 0.92 na twintig sessies. Een curve die vanuit beide uiteinden naar nul trekt, is de vorm die mean reversion aanneemt.

Lees dit met de kolom voor het aantal sessies in gedachten. De buitenste buckets bevatten de minste sessies, en sessies binnen een bucket overlappen elkaar aanzienlijk, waardoor een handvol lange periodes het merendeel van de rijen levert. Een gemiddelde is geen garantie voor een individuele positie.

Waarom werkt pairs trading niet meer?

De hedge ratio is geen constante. Als u deze elk kalenderjaar opnieuw berekent, verandert de waarde.

QueryHedge ratio per kalenderjaar opnieuw berekend, twee sectorparen
De exacte SQL achter elk getal
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 year
Run this yourself

De helling van PEP ten opzichte van KO gemeten 2.338 over 2019 en -0.543 over 2025, met het tweede sectorpaar ter vergelijking. Een backtest die één helling berekent over de volledige historie en deze vanaf de eerste dag verhandelt, geeft elke vroege transactie een cijfer dat niemand op dat moment had kunnen hanteren. De oplossing is saai en effectief: bereken opnieuw op basis van een voortschrijdend venster en beoordeel elke dag met de parameters die op die dag beschikbaar waren. Een reproduceerbare backtest maakt die discipline controleerbaar voor anderen.

Corporate actions herschrijven een been van de positie overnacht. Een vier-voor-één aandelensplitsing verandert één aandeel in vier en verlaagt de koers tot een kwart; een spread die op basis van niet-gecorrigeerde koersen over die datum wordt berekend, springt naar een niveau dat nooit is verhandeld. Fusies, spin-offs en indexwijzigingen veroorzaken stillere versies van hetzelfde. Begin met split-gecorrigeerde koershistorie en houd er rekening mee dat een symbool kan worden toegewezen aan een volledig ander bedrijf, wat de reden is waarom tickers datasets kunnen verstoren.

Een breuk en een kans zien er identiek uit terwijl ze zich voordoen. De breedste z-score die een paar ooit heeft laten zien, is ofwel het beste instapmoment ooit, of de eerste week van een relatie die is beëindigd. Beide zien er op de dag zelf uit als een opgerekte spread. Positiegrootte draagt het gewicht dat de statistieken niet kunnen dragen: Kelly-criterium voor positiebepaling en volatiliteitsgestuurde positiebepaling zijn twee kaders voor de mate waarin één spread de rekening mag beïnvloeden, en maximum drawdown meet wat een lange periode aan de verkeerde kant van het gemiddelde doet met een equity curve.

Een voortschrijdende z-score is geen cointegratietest. Er bestaan formele tests. De Engle-Granger-procedure voert de regressie uit en test vervolgens de resterende spread op stationariteit; de Johansen-test breidt dit concept uit naar meer dan twee namen. Een z-score veronderstelt de eigenschap die deze tests controleren en zal vrolijk een hoog getal weergeven voor een spread zonder enig stabiel gemiddelde, aangezien een trendmatige reeks zich altijd ver van het eigen voortschrijdend gemiddelde bevindt.

Data-opmerkingen en wat deze cijfers buiten beschouwing laten

Het correlatiepaneel gebruikt dagelijkse slotkoersen van 01-01-2024 tot en met 31-12-2025 en berekent rendementen op basis van opeenvolgende slotkoersen, waarbij de eerste sessie van elke naam wordt weggelaten. De z-score-trace berekent de hedge ratio uitsluitend op basis van de slotkoersen van 2023 en beoordeelt vervolgens 2024 en 2025 met een voortschrijdend gemiddelde en steekproefstandaarddeviatie over 63 sessies, bemonsterd tot de laatste waarde van elke week. Het reversiepaneel laadt de historie vanaf 2018 en rapporteert vanaf 2019, zodat elke beoordeelde dag een volledig voortschrijdend venster achter zich heeft. Het stopt medio november 2025, zodat elke dag twintig sessies voor zich heeft binnen de steekproef.

Geen van deze panelen vormt een strategie. Ze bevatten geen handelskosten, geen bid-ask spread, geen leenvergoeding op het short-been, geen dividenden en geen financieringskosten. Er wordt enkel met slotkoersen gewerkt, wat betekent dat een instapmoment gemarkeerd op een slotkoers een instap is die niemand op dat niveau had kunnen uitvoeren.

Veelgestelde vragen

Wat is het verschil tussen correlatie en co-integratie?

Correlatie meet of twee aandelen op dezelfde dagen in dezelfde richting bewegen, op een schaal van min één tot plus één. Co-integratie meet of de afstand tussen hun koersen binnen een stabiele bandbreedte blijft. Een paar kan hoog scoren op de eerste maatstaf en falen op de tweede; dit is de gebruikelijke reden waarom een correlatiescreening paren oplevert die vervolgens uit elkaar drijven.

Hoe berekent u de hedge ratio bij pairs trading?

Zet de koers van het ene aandeel af tegen die van het andere in een lineaire regressie en bepaal de hellingshoek. Die hellingshoek is het aantal eenheden van het eerste aandeel dat één eenheid van het tweede aandeel afdekt. Omdat dit een variabele grootheid is, passen de meeste implementaties deze aan op basis van een voortschrijdend venster in plaats van een eenmalige berekening over de gehele steekproef.

Welke z-score gebruiken pairs traders voor in- en uitstapmomenten?

In educatieve voorbeelden wordt doorgaans ingestapt rond anderhalf tot twee standaarddeviaties van het voortschrijdend gemiddelde en uitgestapt nabij nul komma vijf. Deze getallen zijn conventies uit eerdere publicaties en geen gemeten optima; het wijzigen ervan beïnvloedt zowel het aantal transacties als de gemiddelde aanhoudperiode.

Kunnen twee aandelen co-geïntegreerd zijn bij een lage correlatie?

Ja. Correlatie wordt berekend op dagelijkse koersveranderingen, terwijl co-integratie kijkt naar het niveau van de spread. Twee namen waarvan de dagelijkse bewegingen zelden samenvallen, kunnen dus nog steeds een stabiele afstand op de lange termijn behouden. Het omgekeerde komt ook voor: een hoge dagelijkse correlatie in combinatie met een spread die jaar na jaar groter wordt.

Waarom stopt een co-geïntegreerd paar met werken?

De samenhang die de spread bijeenhoudt kan veranderen: een fusie, een afsplitsing, een verschuiving in de bedrijfsactiviteiten van een onderneming of een indexwijziging die de aandeelhoudersstructuur verandert. De statistieken in de dagen vóór een dergelijke breuk zien er hetzelfde uit als de dagen vóór een normale mean reversion; dit risico moet worden opgevangen door middel van positiegrootte in plaats van door voorspelling.


Elk paneel hier bevat de bijbehorende SQL-code, waardoor de vensters, de fit-periode en de grenswaarden inzichtelijk zijn. Om dezelfde spread en z-score toe te passen op een paar dat u volgt, kunt u de vraag in begrijpelijke taal stellen op de Strasmore-terminal.

#pairs trading#cointegration#mean reversion#backtesting#statistics