Strasmore Research

Pairs Trading e Cointegração: Como Funciona

Entenda por que cointegração importa mais que correlação no pairs trading e calcule hedge ratio, spread e z-score com preços de fechamento reais.

Pairs trading e cointegração andam juntos: a operação depende de uma diferença entre os preços de duas ações que retorna repetidamente a um nível estável, enquanto a cointegração é a propriedade estatística que indica se essa diferença existe de fato. Correlação mede outra coisa. Duas ações podem apresentar alta correlação e ainda assim se afastar indefinidamente, enquanto duas ações cujas variações diárias quase não coincidem podem manter uma diferença estável durante anos. Esta página constrói manualmente o hedge ratio, o spread e o z-score, mede os três com preços de fechamento reais e dedica o mesmo espaço às situações em que essa construção falha.

O que é cointegração em pairs trading?

Correlação é o grau de proximidade entre as variações percentuais diárias de duas ações. Ela vai de -1 a +1. O valor +1 significa que as duas ações subiram e caíram juntas em todas as sessões da amostra. A correlação não diz nada sobre a distância entre os dois preços.

Cointegração é uma afirmação sobre essa distância. Subtraia de uma ação B um múltiplo fixo da ação A e observe o que resta. Se essa série residual, o spread, permanecer dentro de uma faixa limitada em vez de se afastar indefinidamente, as duas ações são cointegradas. Os preços podem seguir qualquer trajetória. A combinação entre eles, não.

A imagem usada nos livros é a de um cachorro preso a uma guia. O passeador e o cachorro seguem trajetórias imprevisíveis pela rua, mas a guia mantém limitada a distância entre os dois. A correlação pergunta se eles caminham no mesmo momento. A cointegração pergunta se a guia mantém a distância sob controle.

A tabela abaixo mede a primeira ideia em cinco pares conhecidos ao longo de 2024 e 2025. A coluna return_corr mostra a correlação das variações percentuais diárias, que é o que as pessoas normalmente querem dizer quando usam a palavra. A coluna price_corr mostra a correlação dos preços de fechamento brutos, um número que favorece quase quaisquer dois ativos que tenham subido juntos.

QueryCorrelação dos retornos diários vs correlação dos níveis de preço, cinco pares conhecidos (2024-2025)
O SQL exato por trás de cada número
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

Ordenado pela correlação das variações diárias, HD / LOW aparece no topo da tabela, com 0.868, enquanto a correlação dos dois níveis de preço no mesmo período foi de 0.849. Na última linha, AAPL / MSFT apresenta uma correlação dos retornos de 0.479, ao lado de uma correlação dos preços de 0.508. Nenhuma das duas colunas responde à pergunta feita por um operador de pairs trading. Ambas medem co movimento, mas a estratégia precisa de uma medida da distância.

Como calcular o hedge ratio e o spread?

São cinco etapas, nesta ordem, aplicadas a duas listas de preços de fechamento. O Python básico cobre tudo, com math como único import: sem pacotes, fornecedor de dados ou rede.

  1. Centralize cada série. mean_a = sum(a) / len(a), e faça o mesmo com a lista b.
  2. Calcule o hedge ratio. Ele é a inclinação de uma reta de B em relação a 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). Interprete-o como o número de unidades de A que compensa uma unidade de B.
  3. Monte o spread. spread = [b[i] - beta * a[i] for i in range(len(a))]. Em um par cointegrado, essa série oscila em torno de um nível, em vez de seguir uma tendência.
  4. Use uma janela móvel. Com w = spread[i - win + 1 : i + 1], a média da janela é mu = sum(w) / len(w) e o desvio-padrão amostral é sd = math.sqrt(sum((x - mu) ** 2 for x in w) / (len(w) - 1)). Móvel significa que a janela termina no dia avaliado e não usa dados posteriores.
  5. Calcule o score e exiba os sinais. z = (spread[i] - mu) / sd. Em seguida, exiba uma linha de entrada quando abs(z) > 1.5 e uma linha de saída quando abs(z) < 0.5. Um z-score de 2 coloca o spread dois desvios-padrão acima da própria média recente.

Duas listas de preços definidas no código bastam para acompanhar a execução, e um win de 5 mantém o exemplo didático legível. Em aplicações reais, usa-se algo mais próximo de 63 sessões, cerca de um trimestre. Esses limiares, 1,5 para entrar e 0,5 para sair, são convenções reproduzidas de um tutorial para outro. Não são resultados de uma análise, e nada nos painéis abaixo os determinou.

Como é um z-score de pairs trading com preços reais?

Este é o mesmo cálculo aplicado a duas empresas conhecidas do setor de bebidas, KO e PEP. O hedge ratio é calculado apenas com os fechamentos de 2023 e permanece fixo em 2024 e 2025, período coberto pelo gráfico. Ajustar a inclinação na mesma amostra usada depois para calcular o score é viés de antecipação no backtesting, e manter separadas a janela de ajuste e a janela de cálculo do score é a defesa mais simples contra esse problema.

Queryz score semanal do spread KO/PEP, hedge ratio ajustado apenas em 2023
O SQL exato por trás de cada número
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

A série contém 105 leituras semanais. Ela começa a janela com um z-score de -1.53 e termina com -0.68. Observe o formato, não um ponto isolado. Um par que se comporta como a estratégia pressupõe cruza o limite, passa algumas semanas voltando em direção a zero e cruza novamente. Um par que deixou de reverter à média permanece além do limite, o que no gráfico aparece como um longo trecho plano, distante do centro.

Um spread cointegrado sempre volta?

Não. A forma mais honesta de avaliar isso é medir o que aconteceu depois de cada ponto de partida. O painel abaixo agrupa cada sessão de 2019 até o fim de 2025 pelo z-score daquele dia e mostra o z-score médio do mesmo spread vinte sessões depois. O cálculo usa o logaritmo da razão entre os preços, e não um spread estimado. Assim, nenhuma regressão ajustada posteriormente interfere no gráfico.

QueryOnde o spread KO/PEP estava vinte sessões depois, por z score inicial (2019-2025)
O SQL exato por trás de cada número
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

As sessões que começaram no bucket z below -2 apresentaram média de -2.42 no dia. Vinte sessões depois, o mesmo spread tinha média de -0.84, considerando 138 sessões. No outro extremo, o bucket z above 2 começou em 2.64 e registrou 0.92 vinte sessões depois. Uma curva que converge para zero a partir dos dois extremos é o formato produzido pela reversão à média.

Leia o gráfico considerando a coluna de contagem. Os buckets extremos têm o menor número de sessões. Além disso, as sessões dentro de cada bucket se sobrepõem bastante, de modo que alguns períodos longos respondem pela maior parte das linhas. Uma média não é uma promessa para nenhuma entrada específica.

Por que o pairs trading deixa de funcionar?

O hedge ratio não é constante. Recalcule-o a cada ano-calendário: ele muda.

QueryHedge ratio reajustado a cada ano-calendário, dois pares do setor
O SQL exato por trás de cada número
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

A inclinação de PEP sobre KO medida 2.338 entre 2019 e -0.543 entre 2025, com o segundo par do setor ao lado para comparação. Um backtest que ajusta uma única inclinação para todo o histórico e a utiliza desde o primeiro dia atribui a cada operação inicial um parâmetro que ninguém poderia ter conhecido naquele momento. A solução é simples e eficaz: recalcular o ajuste numa janela móvel e avaliar cada dia com os parâmetros disponíveis naquele dia. Um backtest reproduzível permite que outra pessoa verifique essa disciplina.

Eventos societários reescrevem uma das pernas de um dia para o outro. Um desdobramento de 4 por 1 transforma uma ação em quatro e reduz a cotação a um quarto. Um spread calculado sem ajustes de preços ao longo dessa data salta para um nível em que nunca houve negociação. Fusões, cisões e mudanças de índice produzem versões menos evidentes do mesmo efeito. Comece por um histórico de preços ajustado por desdobramentos e tenha em conta que um ticker pode ser reassociado a uma empresa completamente diferente. É por isso que tickers corrompem bases de dados.

Uma quebra estrutural e uma oportunidade parecem idênticas enquanto acontecem. O maior z score já observado num par pode ser a melhor entrada que ele ofereceu ou a primeira semana de uma relação que terminou. No dia, ambos parecem um spread esticado. O dimensionamento assume o papel que a estatística não consegue cumprir: dimensionamento de posição pelo critério de Kelly e dimensionamento de posição por meta de volatilidade são dois modelos para definir quanto de uma conta um único spread pode movimentar. O drawdown máximo mede o efeito de uma longa permanência do lado errado da média sobre a curva de patrimônio.

Um z score móvel não é um teste de cointegração. Existem testes formais. O procedimento de Engle-Granger executa a regressão e depois testa a estacionariedade do spread residual. O teste de Johansen amplia a ideia para além de dois ativos. Um z score pressupõe a propriedade que esses testes verificam. Ele pode apresentar um número elevado num spread sem média estável, pois uma série com tendência sempre fica muito distante da própria média móvel.

Notas sobre os dados e o que estes números não incluem

O painel de correlação usa preços de fechamento diários de 2024-01-01 a 2025-12-31 e calcula os retornos a partir de fechamentos consecutivos, excluindo a primeira sessão de cada ativo. O traçado do z score ajusta o hedge ratio apenas com os fechamentos de 2023. Depois, calcula os valores de 2024 e 2025 com média móvel de 63 sessões e desvio-padrão amostral, usando a última leitura de cada semana. O painel de reversão carrega o histórico desde 2018 e apresenta dados a partir de 2019. Assim, cada dia avaliado tem uma janela móvel completa atrás de si. A análise termina em meados de novembro de 2025 para que cada dia tenha vinte sessões posteriores dentro da amostra.

Nenhum destes painéis é uma estratégia. Eles não incluem custos de negociação, spread bid-ask, custo de aluguel da posição vendida, dividendos ou financiamento. Usam apenas preços de fechamento. Isso significa que uma entrada marcada no fechamento é uma entrada que ninguém poderia ter executado naquele preço.

Perguntas frequentes

Qual é a diferença entre correlação e cointegração?

A correlação mede se duas ações se movimentam nos mesmos dias e na mesma direção, numa escala de -1 a +1. A cointegração mede se a distância entre os preços permanece dentro de uma faixa estável. Um par pode ter uma pontuação alta na primeira medida e falhar na segunda. Esse é o motivo mais comum para um filtro de correlação identificar pares que depois se afastam.

Como se calcula o hedge ratio em pairs trading?

Ajuste uma reta relacionando o preço de uma ação ao preço da outra e use a inclinação. Essa inclinação representa o número de unidades da primeira ação que compensa uma unidade da segunda. Como é uma quantidade variável, a maioria das implementações recalcula o ajuste numa janela móvel, em vez de fazê-lo uma única vez para toda a amostra.

Qual z score os traders de pares usam para entrar e sair?

Exemplos de tutoriais normalmente indicam entrada em torno de 1,5 a 2 desvios-padrão em relação à média móvel e saída perto de 0,5. Esses números são convenções herdadas de textos anteriores, não valores ótimos medidos. Alterá-los muda tanto o número de trades quanto o tempo de permanência em cada posição.

Duas ações podem ser cointegradas com baixa correlação?

Sim. A correlação é calculada sobre as variações diárias, enquanto a cointegração é calculada sobre o nível da diferença. Por isso, dois ativos cujos movimentos diários raramente coincidem ainda podem manter uma distância estável no longo prazo. O inverso também ocorre: uma correlação diária alta pode coexistir com uma diferença que se amplia ano após ano.

Por que um par cointegrado deixa de funcionar?

A relação que mantém a diferença unida pode mudar. Isso pode ocorrer por causa de uma fusão, um spin-off, uma mudança no mix de negócios de uma empresa ou uma alteração de índice que mude quem detém a ação. As estatísticas nos dias anteriores a uma ruptura são iguais às observadas antes de uma reversão normal. Por isso, o tamanho da posição precisa considerar esse risco, em vez de tentar prevê-lo.


Cada painel aqui é disponibilizado com o SQL exato correspondente. Assim, as janelas, o período de ajuste e os limites dos buckets ficam visíveis. Para calcular o mesmo spread e z score de um par que você acompanha, faça a pergunta em inglês simples no terminal da Strasmore.

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