Strasmore Research
Analizy Matt ConnorAutor: Matt Connor

Handel parami i kointegracja: jak wyliczyć spread

Zrozumienie różnicy między korelacją a kointegracją w strategii pairs trading. Instrukcja ręcznego wyznaczania współczynnika zabezpieczenia, spreadu oraz z-score na cenach zamknięcia.

Handel parami i kointegracja

Handel parami (pairs trading) oraz kointegracja są ze sobą ściśle powiązane. Strategia ta opiera się na rozbieżności między cenami dwóch akcji, która powraca do stabilnego poziomu, natomiast kointegracja stanowi właściwość statystyczną potwierdzającą istnienie takiej zależności. Korelacja mierzy inne zjawisko. Dwie spółki mogą wykazywać wysoką korelację, a mimo to trwale oddalać się od siebie. Z kolei dwa walory, których dzienne zmiany cen są słabo skorelowane, mogą utrzymywać stały spread przez lata. Niniejsza strona przedstawia ręczne wyliczenie współczynnika zabezpieczenia (hedge ratio), spreadu oraz wyniku z-score, dokonuje pomiaru wszystkich trzech parametrów na podstawie rzeczywistych cen zamknięcia, a następnie w równym stopniu omawia przyczyny niepowodzeń tej konstrukcji.

Czym jest kointegracja w handlu parami?

Korelacja określa ścisłość dziennych zmian procentowych dwóch akcji. Przyjmuje wartości od -1 do +1, gdzie +1 oznacza, że w badanym okresie walory rosły i spadały jednocześnie na każdej sesji. Wskaźnik ten nie informuje jednak o dystansie między cenami obu instrumentów.

Kointegracja odnosi się właśnie do tego dystansu. Jeśli od ceny akcji B odejmiemy stałą wielokrotność ceny akcji A, otrzymamy wynik. Jeżeli tak powstały szereg, czyli spread, pozostaje w ograniczonym przedziale zamiast wykazywać tendencję do nieograniczonego oddalania się, oznacza to, że oba walory są skointegrowane. Same ceny mogą poruszać się dowolnie, jednak ich kombinacja pozostaje stabilna.

W literaturze często stosuje się metaforę psa na smyczy. Zarówno właściciel, jak i pies poruszają się w nieprzewidywalny sposób, ale smycz sprawia, że dystans między nimi pozostaje ograniczony. Korelacja sprawdza, czy oba podmioty wykonują kroki w tym samym momencie. Kointegracja sprawdza, czy smycz trzyma.

Poniższa tabela przedstawia pierwszy z tych wskaźników dla pięciu znanych par w latach 2024 i 2025. Kolumna return_corr zawiera korelację dziennych zmian procentowych, czyli wartość, którą inwestorzy zazwyczaj mają na myśli, używając tego terminu. Kolumna price_corr przedstawia korelację surowych cen zamknięcia – liczbę, która wypada korzystnie dla niemal każdej pary spółek, które odnotowały wzrosty.

ZapytanieKorelacja stóp zwrotu dziennych a korelacja poziomów cen, pięć wybranych par (2024–2025)
Dokładny kod SQL dla każdej liczby
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

Pod względem korelacji dziennych zmian, HD / LOW znajduje się na szczycie zestawienia z wynikiem 0.868, podczas gdy korelacja poziomów cen dla tych samych aktywów w tym samym okresie wyniosła 0.849. W ostatnim wierszu AAPL / MSFT wykazuje korelację stóp zwrotu na poziomie 0.479 przy korelacji cen wynoszącej 0.508. Żadna z tych kolumn nie odpowiada na pytanie kluczowe dla tradera par. Obie mierzą współzależność ruchów, podczas gdy strategia ta wymaga miary dystansu.

Jak obliczyć współczynnik zabezpieczenia (hedge ratio) oraz spread?

Pięć kroków, w określonej kolejności, dla dwóch list cen zamknięcia. Standardowy Python wystarcza do wykonania wszystkich obliczeń przy użyciu math jako jedynego importu: bez zewnętrznych pakietów, dostawców danych czy połączeń sieciowych.

  1. Wyśrodkuj każdą z serii. mean_a = sum(a) / len(a), a następnie powtórz to samo dla listy b.
  2. Dopasuj współczynnik zabezpieczenia. Jest to nachylenie prostej regresji dla B względem 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). Należy go interpretować jako liczbę jednostek aktywa A, która równoważy jedną jednostkę aktywa B.
  3. Zbuduj spread. spread = [b[i] - beta * a[i] for i in range(len(a))]. W przypadku pary skointegrowanej seria ta oscyluje wokół określonego poziomu, zamiast wykazywać trend.
  4. Zastosuj kroczące okno czasowe. Przy użyciu w = spread[i - win + 1 : i + 1], średnia dla okna wynosi mu = sum(w) / len(w), a odchylenie standardowe próby to sd = math.sqrt(sum((x - mu) ** 2 for x in w) / (len(w) - 1)). Okno kroczące oznacza, że kończy się ono w dniu wyceny i nie uwzględnia żadnych danych po tym terminie.
  5. Oceń i wyświetl wyniki. z = (spread[i] - mu) / sd, a następnie wyświetl linię wejścia, gdy abs(z) > 1.5, oraz linię wyjścia, gdy abs(z) < 0.5. Wartość z równa dwa oznacza, że spread znajduje się dwa odchylenia standardowe powyżej swojej niedawnej średniej.

Dwie sztywno zakodowane listy cen wystarczą do obserwacji działania algorytmu, a win o wartości pięć pozwala zachować czytelność przykładu. W profesjonalnych zastosowaniach używa się wartości bliższych sześćdziesięciu trzem sesjom, co odpowiada około jednemu kwartałowi. Progi wejścia na poziomie półtora oraz wyjścia na poziomie pół to konwencje powielane w kolejnych poradnikach. Nie są to wyniki badań, a żaden z poniższych paneli nie służył do ich wyznaczenia.

Jak wygląda wskaźnik z-score w strategii pairs trading na rzeczywistych cenach?

Poniżej przedstawiono analogiczne obliczenia dla dwóch znanych spółek z sektora napojów: KO oraz PEP. Współczynnik zabezpieczenia (hedge ratio) wyznaczono wyłącznie na podstawie cen zamknięcia z 2023 roku, a następnie utrzymano go na stałym poziomie w latach 2024 i 2025, które obejmuje wykres. Dopasowanie nachylenia regresji na tym samym zbiorze danych, na którym przeprowadza się ocenę, stanowi błąd wyprzedzenia w backtestingu, a rozdzielenie okresu dopasowania od okresu oceny jest najprostszą metodą ochrony przed tym zjawiskiem.

ZapytanieTygodniowy wskaźnik z-score dla spreadu KO/PEP, współczynnik zabezpieczenia (hedge ratio) dopasowany wyłącznie na danych z 2023 r.
Dokładny kod SQL dla każdej liczby
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

Wykres zawiera 105 odczytów tygodniowych. Okno otwiera się przy wartości z-score wynoszącej -1.53, a zamyka przy -0.68. Należy analizować kształt wykresu, a nie pojedyncze punkty. Para zachowująca się zgodnie z założeniami strategii przekracza próg, przez kilka tygodni powraca w stronę zera i ponownie przecina linię środkową. Para, która przestała wykazywać tendencję do powrotu do średniej, pozostaje poza progiem, co na wykresie objawia się jako długi, płaski odcinek w znacznej odległości od wartości średniej.

Czy skointegrowany spread zawsze powraca do średniej?

Nie, a najbardziej rzetelnym sposobem, aby się o tym przekonać, jest pomiar wyników następujących po każdym punkcie startowym. Poniższy panel grupuje każdą sesję od 2019 roku do końca 2025 roku według wartości z-score z danego dnia, a następnie przedstawia średni z-score tego samego spreadu dwadzieścia sesji później. Wykorzystano logarytm stosunku cen zamiast dopasowanego spreadu, dzięki czemu żadna regresja szacowana ex-post nie wpływa na wykres.

ZapytaniePoziom spreadu KO/PEP po dwudziestu sesjach w podziale na początkowy wskaźnik z-score (2019–2025)
Dokładny kod SQL dla każdej liczby
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

Sesje, które rozpoczęły się w koszyku z below -2, osiągnęły średnią -2.42 w danym dniu, a ten sam spread wyniósł średnio -0.84 dwadzieścia sesji później, w oparciu o 138 sesji. Na drugim końcu spektrum, koszyk z above 2 rozpoczął się od poziomu 2.64 i osiągnął 0.92 po dwudziestu sesjach. Krzywa, która z obu stron dąży do zera, obrazuje proces powrotu do średniej.

Należy analizować te dane, mając na uwadze kolumnę liczebności. Zewnętrzne koszyki zawierają najmniej sesji, a sesje wewnątrz koszyka w znacznym stopniu się pokrywają, co oznacza, że większość wierszy wynika z niewielkiej liczby długotrwałych trendów. Średnia nie stanowi gwarancji dla żadnej pojedynczej transakcji.

Dlaczego handel parami przestaje działać?

Współczynnik zabezpieczenia (hedge ratio) nie jest stały. Wymaga on ponownego wyliczenia w każdym roku kalendarzowym, ponieważ ulega zmianie.

ZapytanieWspółczynnik zabezpieczenia (hedge ratio) aktualizowany corocznie, dwie pary sektorowe
Dokładny kod SQL dla każdej liczby
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

Nachylenie relacji PEP do KO mierzone 2.338 w okresie 2019 oraz -0.543 w okresie 2025, wraz z drugą parą sektorową dla porównania. Backtest, który dopasowuje jedno nachylenie do pełnej historii i stosuje je od pierwszego dnia, przypisuje każdej wczesnej transakcji wynik, którego nikt nie mógł osiągnąć w tamtym czasie. Rozwiązanie jest mało efektowne, ale skuteczne: należy dokonywać ponownego dopasowania w kroczącym oknie czasowym i oceniać każdy dzień przy użyciu parametrów dostępnych w danym momencie. Powtarzalny backtest pozwala innej osobie zweryfikować tę dyscyplinę.

Działania korporacyjne zmieniają jedną nogę strategii z dnia na dzień. Split w stosunku cztery do jednego zamienia jedną akcję w cztery i obniża cenę kwotowaną do jednej czwartej, przez co spread obliczony dla tej daty na cenach niekorygowanych skacze do poziomu, który nigdy nie wystąpił w obrocie. Fuzje, wydzielenia spółek (spin-offy) i zmiany w indeksach powodują podobne, choć mniej gwałtowne zjawiska. Należy rozpocząć od historii cen skorygowanej o splity i pamiętać, że ticker może zostać przypisany do zupełnie innej spółki, co jest powodem, dla którego tickery psują zbiory danych.

Awaria i okazja wyglądają identycznie w momencie wystąpienia. Najwyższy wynik z-score, jaki kiedykolwiek odnotowała para, jest albo najlepszym punktem wejścia w historii, albo pierwszym tygodniem relacji, która dobiegła końca. W obu przypadkach spread wygląda w danym dniu na rozciągnięty. Wielkość pozycji przejmuje ciężar, którego statystyka nie jest w stanie udźwignąć: kryterium Kelly’ego w doborze wielkości pozycji oraz targetowanie zmienności w doborze wielkości pozycji to dwa modele określające, w jakim stopniu dany spread może wpływać na rachunek, a maksymalne obsunięcie kapitału (maximum drawdown) mierzy, jak długi okres utrzymywania się po niewłaściwej stronie średniej wpływa na krzywą kapitału.

Kroczący z-score nie jest testem kointegracji. Istnieją formalne testy. Procedura Engle’a-Grangera przeprowadza regresję, a następnie testuje pozostały spread pod kątem stacjonarności; test Johansena rozszerza tę koncepcję na więcej niż dwa podmioty. Z-score zakłada właściwości, które te testy weryfikują, i bez problemu wykaże wysoką wartość dla spreadu, który w ogóle nie posiada stabilnej średniej, ponieważ szereg z trendem zawsze znajduje się daleko od własnej kroczącej średniej.

Uwagi do danych i pominięte czynniki

Panel korelacji wykorzystuje dzienne ceny zamknięcia od 1 stycznia 2024 r. do 31 grudnia 2025 r. i oblicza stopy zwrotu z kolejnych sesji, pomijając pierwszą sesję dla każdego instrumentu. Wykres z-score dopasowuje współczynnik zabezpieczenia wyłącznie do cen zamknięcia z 2023 r., a następnie ocenia lata 2024 i 2025 przy użyciu kroczącej średniej z 63 sesji oraz odchylenia standardowego próby, próbkowanych do ostatniego odczytu każdego tygodnia. Panel odwrócenia (reversion) ładuje historię od 2018 r. i raportuje od 2019 r., dzięki czemu każdy oceniany dzień posiada pełne okno kroczące, a kończy się w połowie listopada 2025 r., aby każdy dzień miał przed sobą dwadzieścia sesji wewnątrz próby.

Żaden z tych paneli nie stanowi strategii inwestycyjnej. Nie uwzględniają one kosztów transakcyjnych, spreadu bid-ask, kosztów pożyczenia akcji do krótkiej sprzedaży, dywidend ani finansowania. Wykorzystano wyłącznie ceny zamknięcia, co oznacza, że wejście oznaczone po cenie zamknięcia jest wejściem, którego nikt nie mógł zrealizować po tej cenie.

Najczęściej zadawane pytania

Jaka jest różnica między korelacją a kointegracją?

Korelacja mierzy, czy dwie spółki poruszają się w tym samym kierunku w te same dni, w skali od minus jeden do plus jeden. Kointegracja mierzy, czy odległość między ich cenami utrzymuje się w stabilnym przedziale. Para może uzyskać wysoki wynik w pierwszym wskaźniku i nie spełniać warunków drugiego; jest to najczęstsza przyczyna, dla której skanery oparte na korelacji wskazują pary, które następnie rozbiegają się.

Jak obliczyć współczynnik zabezpieczenia w handlu parami?

Należy dopasować linię prostą do cen jednej spółki względem drugiej i wyznaczyć jej nachylenie. Nachylenie to określa liczbę jednostek pierwszej spółki, która równoważy jedną jednostkę drugiej. Jest to wartość zmienna, dlatego w większości implementacji przelicza się ją w kroczącym oknie czasowym, zamiast wyznaczać raz dla całej próby.

Jakiego wyniku z-score używają traderzy par do otwierania i zamykania pozycji?

W przykładach szkoleniowych pozycje otwiera się zazwyczaj w okolicach od półtora do dwóch odchyleń standardowych od średniej kroczącej, a zamyka w pobliżu zera i pięciu dziesiątych. Liczby te są konwencjami przejętymi z wcześniejszych opracowań, a nie wyliczonymi optimami. Ich zmiana wpływa zarówno na liczbę transakcji, jak i na czas utrzymywania każdej z nich.

Czy dwie spółki mogą być skointegrowane przy niskiej korelacji?

Tak. Korelację oblicza się na podstawie dziennych zmian, a kointegrację na podstawie poziomu luki, więc dwa podmioty, których dzienne ruchy rzadko są zbieżne, mogą utrzymywać stabilną długoterminową odległość. Zdarza się również sytuacja odwrotna: wysoka dzienna korelacja przy luce, która pogłębia się z roku na rok.

Dlaczego skointegrowana para przestaje działać?

Więź utrzymująca lukę może ulec zmianie: wskutek fuzji, wydzielenia spółki, zmiany profilu działalności jednej z firm lub zmiany w indeksie, która wpływa na strukturę akcjonariatu. Statystyki z dni poprzedzających załamanie wyglądają tak samo jak przed zwykłym powrotem do średniej, dlatego ryzyko to należy uwzględniać w zarządzaniu wielkością pozycji, zamiast próbować je przewidzieć.


Każdy panel zawiera bezpośredni kod SQL, dzięki czemu okna czasowe, okres dopasowania oraz granice przedziałów są w pełni widoczne. Aby zastosować ten sam spread i z-score dla wybranej pary, należy zadać pytanie w języku angielskim w terminalu Strasmore.

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