Strasmore Research

Pairs trading y cointegración: guía práctica

La cointegración es la base del pairs trading, no la correlación. Calcula a mano el hedge ratio, el spread y el z score con precios de cierre reales.

El pairs trading y la cointegración van de la mano: la operación se basa en una brecha entre dos precios de acciones que vuelve de forma recurrente a un nivel estable, y la cointegración es la propiedad estadística que indica que esa brecha existe. La correlación mide otra cosa. Dos acciones pueden mostrar una correlación alta y aun así separarse de forma permanente, mientras que dos acciones cuyos movimientos diarios apenas coinciden pueden mantener una brecha estable durante años. En esta página se calculan manualmente el hedge ratio, el spread y el z score; se miden los tres con precios de cierre reales y después se dedica el mismo espacio a explicar cómo falla esta construcción.

¿Qué es la cointegración en el pairs trading?

La correlación mide la intensidad de los movimientos porcentuales diarios de dos acciones. Va de -1 a +1. Un valor de +1 significa que ambas subieron y bajaron juntas en cada sesión de la muestra. No dice nada sobre la distancia entre sus precios.

La cointegración describe esa distancia. Tome la acción B, réstele un múltiplo fijo de la acción A y observe el resultado. Si esa serie residual, el spread, se mantiene dentro de un rango acotado en lugar de alejarse sin límite, ambos valores están cointegrados. Los precios pueden moverse libremente en cualquier dirección. La combinación no.

La imagen que usan los libros de texto es la de un perro con correa. El paseador y el perro siguen trayectorias impredecibles por la calle, pero la correa mantiene acotada la distancia entre ellos. La correlación pregunta si ambos dan pasos al mismo tiempo. La cointegración pregunta si la correa mantiene el vínculo.

La tabla siguiente mide la primera idea en cinco pares conocidos durante 2024 y 2025. La columna return_corr muestra la correlación de los movimientos porcentuales diarios, que es lo que normalmente se entiende por correlación. La columna price_corr muestra la correlación de los precios de cierre sin transformar, una cifra que favorece a casi cualquier par de valores que haya subido.

ConsultaCorrelación de rendimientos diarios vs correlación de niveles de precio, cinco pares conocidos (2024-2025)
El SQL exacto detrás de cada cifra
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 por la correlación de los movimientos diarios, HD / LOW ocupa el primer lugar de la tabla, con 0.868, mientras que la correlación entre los dos niveles de precios durante el mismo periodo fue de 0.849. En la última fila, AAPL / MSFT registra una correlación de rendimientos de 0.479 y una correlación de precios de 0.508. Ninguna de las dos columnas responde a la pregunta que plantea un operador de pairs trading. Ambas miden el movimiento conjunto, pero la estrategia necesita una medida de distancia.

¿Cómo se calculan el hedge ratio y el spread?

Cinco pasos, en orden, sobre dos listas de precios de cierre. Python estándar cubre todo con math como único import: sin paquetes, proveedor de datos ni conexión de red.

  1. Centrar cada serie. mean_a = sum(a) / len(a), y lo mismo para la lista b.
  2. Calcular el hedge ratio. Es la pendiente de una línea recta de B frente 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). Se interpreta como el número de unidades de A que compensa una unidad de B.
  3. Construir el spread. spread = [b[i] - beta * a[i] for i in range(len(a))]. En un par cointegrado, esta serie oscila alrededor de un nivel en lugar de seguir una tendencia.
  4. Tomar una ventana móvil. Con w = spread[i - win + 1 : i + 1], la media de la ventana es mu = sum(w) / len(w) y la desviación estándar muestral es sd = math.sqrt(sum((x - mu) ** 2 for x in w) / (len(w) - 1)). Móvil significa que la ventana termina en el día que se está evaluando y no utiliza datos posteriores.
  5. Calcular el z-score y mostrarlo. z = (spread[i] - mu) / sd; después, mostrar una línea de entrada mientras abs(z) > 1.5 y una línea de salida cuando abs(z) < 0.5. Un z de 2 indica que el spread se encuentra dos desviaciones estándar por encima de su propia media reciente.

Dos listas de precios codificadas directamente bastan para observar la ejecución, y un win de 5 mantiene legible el ejemplo sencillo. Para un análisis serio se utiliza algo más cercano a 63 sesiones, aproximadamente un trimestre. Esos umbrales, 1.5 para entrar y 0.5 para salir, son convenciones que se han transmitido de un tutorial a otro. No son resultados empíricos, y ninguno de los paneles siguientes los calculó.

¿Cómo se ve un z score de pairs trading con precios reales?

Este es el mismo cálculo aplicado a dos compañías de bebidas de consumo masivo, KO y PEP. El hedge ratio se obtiene únicamente con los cierres de 2023 y luego permanece fijo durante 2024 y 2025, el periodo que cubre el gráfico. Ajustar la pendiente con la misma muestra que después se utiliza para calcular el z score es sesgo de anticipación en el backtesting, y mantener separados el periodo de ajuste y el periodo de cálculo es la defensa más sencilla contra este problema.

ConsultaPuntuación z semanal del spread KO/PEP, ratio de cobertura ajustado solo con 2023
El SQL exacto detrás de cada cifra
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

La serie contiene 105 lecturas semanales. El periodo comienza con un z score de -1.53 y termina en -0.68. Hay que observar la forma, no un punto aislado. Un par que se comporta como supone la estrategia cruza su umbral, pasa algunas semanas regresando hacia cero y vuelve a cruzarlo. Un par que ha dejado de revertir a la media se mantiene más allá del umbral, lo que en un gráfico se ve como un tramo largo y plano, alejado del centro.

¿Un spread cointegrado siempre vuelve?

No. La forma honesta de comprobarlo es medir qué ocurrió después de cada punto de partida. El panel siguiente agrupa cada sesión entre 2019 y finales de 2025 según el z score de ese día. Después informa el z score promedio del mismo spread veinte sesiones más tarde. Utiliza el logaritmo de la ratio de precios en lugar de un spread estimado. Así, ninguna regresión calculada a posteriori afecta el gráfico.

ConsultaDónde se ubicó el spread KO/PEP veinte sesiones después, según la puntuación z inicial (2019-2025)
El SQL exacto detrás de cada cifra
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

Las sesiones que comenzaron en el bucket z below -2 registraron un promedio de -2.42 ese día. Veinte sesiones más tarde, el mismo spread promedió -0.84, sobre un total de 138 sesiones. En el extremo opuesto, el bucket z above 2 comenzó en 2.64 y registró 0.92 veinte sesiones después. Una curva que se acerca a cero desde ambos extremos es la forma característica de la reversión a la media.

Interprétalo teniendo a la vista la columna de recuento. Los buckets extremos contienen menos sesiones. Además, las sesiones dentro de cada bucket se superponen mucho, por lo que unos pocos periodos prolongados aportan la mayoría de las filas. Un promedio no es una promesa para ninguna entrada individual.

¿Por qué deja de funcionar el pairs trading?

El hedge ratio no es constante. Recalcúlalo en cada año calendario y verás que cambia.

ConsultaRatio de cobertura reajustado cada año calendario, dos pares del sector
El SQL exacto detrás de cada cifra
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

La pendiente de PEP sobre KO medida 2.338 entre 2019 y -0.543 entre 2025, con el segundo par sectorial al lado para comparar. Un backtest que ajusta una sola pendiente sobre todo el historial y la utiliza para operar desde el primer día asigna a cada operación inicial un valor que nadie podía haber tenido en ese momento. La solución es sencilla y eficaz: recalcular el modelo sobre una ventana móvil y evaluar cada día con los parámetros disponibles ese día. Un backtest reproducible permite que otra persona compruebe esa disciplina.

Las acciones corporativas reescriben una pata de la operación de un día para otro. Un split de cuatro por uno convierte una acción en cuatro y reduce a una cuarta parte el precio cotizado. Si se calcula un spread con precios no ajustados a través de esa fecha, este salta a un nivel al que nunca se operó. Las fusiones, spin-offs y modificaciones de índices producen versiones menos visibles del mismo problema. Empieza con un historial de precios ajustado por splits y ten presente que un ticker puede reasignarse a una empresa completamente distinta. Esa es la razón por la que los tickers dañan los datasets.

Una ruptura y una oportunidad se ven iguales mientras ocurren. El z score más amplio que un par haya registrado puede ser la mejor entrada que haya ofrecido o la primera semana de una relación que ha terminado. Ese día, ambos casos parecen un spread demasiado alejado de su media. El dimensionamiento aporta lo que las estadísticas no pueden: el dimensionamiento de posiciones según el criterio de Kelly y el dimensionamiento de posiciones según la volatilidad objetivo son dos marcos para determinar cuánto puede mover un spread de una cuenta. El drawdown máximo mide el efecto que una permanencia prolongada en el lado equivocado de la media tiene sobre una curva de capital.

Un z score móvil no es una prueba de cointegración. Existen pruebas formales. El procedimiento de Engle-Granger ejecuta la regresión y después comprueba la estacionariedad del spread residual. La prueba de Johansen amplía el enfoque a más de dos nombres. Un z score da por supuesto el atributo que esas pruebas verifican. También puede mostrar sin problema un valor elevado en un spread sin una media estable, porque una serie con tendencia siempre se encuentra muy lejos de su propia media móvil.

Notas sobre los datos y lo que estas cifras no incluyen

El panel de correlación utiliza cierres diarios del 2024-01-01 al 2025-12-31 y calcula los rendimientos a partir de cierres consecutivos. Se omite la primera sesión de cada nombre. La trayectoria del z score ajusta el hedge ratio únicamente con los cierres de 2023. Después evalúa 2024 y 2025 con una media móvil de 63 sesiones y una desviación estándar muestral. Las observaciones se toman en la última lectura de cada semana. El panel de reversión carga el historial desde 2018 y muestra datos desde 2019. Así, cada día evaluado tiene una ventana móvil completa detrás. El panel termina a mediados de noviembre de 2025 para que cada día tenga veinte sesiones posteriores dentro de la muestra.

Ninguno de estos paneles constituye una estrategia. No incluyen costos de trading, spread bid-ask, comisión de préstamo de la pata corta, dividendos ni financiamiento. Solo utilizan precios de cierre. Por tanto, una entrada marcada al cierre sería una entrada que nadie podría haber ejecutado a ese precio.

Preguntas frecuentes

¿Cuál es la diferencia entre correlación y cointegración?

La correlación mide si dos acciones se mueven los mismos días y en la misma dirección, en una escala de -1 a +1. La cointegración mide si la distancia entre sus precios se mantiene dentro de un rango estable. Un par puede obtener una puntuación alta en la primera y fallar en la segunda. Esa suele ser la razón por la que un filtro de correlación identifica pares que después se separan gradualmente.

¿Cómo se calcula el hedge ratio en pairs trading?

Se ajusta una línea recta del precio de una acción frente al de la otra y se toma la pendiente. Esa pendiente representa el número de unidades de la primera acción que compensa una unidad de la segunda. Además, es una cantidad que cambia con el tiempo. Por eso, la mayoría de las implementaciones vuelve a estimarla sobre una ventana móvil, en lugar de hacerlo una sola vez para toda la muestra.

¿Qué z score usan los operadores de pairs trading para entrar y salir?

Los ejemplos de tutoriales suelen entrar cuando el z score se sitúa entre 1.5 y 2 desviaciones estándar de la media móvil, y salir cerca de 0.5. Esas cifras son convenciones heredadas de análisis anteriores, no valores óptimos medidos. Modificarlas cambia tanto el número de operaciones como el tiempo que se mantiene cada posición.

¿Pueden dos acciones estar cointegradas con una correlación baja?

Sí. La correlación se calcula sobre las variaciones diarias y la cointegración, sobre el nivel de la brecha. Por eso, dos acciones cuyos movimientos diarios rara vez coinciden pueden mantener una distancia estable a largo plazo. También ocurre lo contrario: una correlación diaria alta junto con una brecha que se amplía año tras año.

¿Por qué deja de funcionar un par cointegrado?

Puede cambiar el vínculo que mantiene unida la brecha: una fusión, una escisión, un cambio en la combinación de negocios de una empresa o una modificación de un índice que altera quién posee la acción. Las estadísticas de los días anteriores a una ruptura se parecen a las de los días previos a una reversión normal. Por eso, el tamaño de la posición debe contemplar este riesgo en lugar de intentar predecirlo.


Cada panel incluye el SQL exacto utilizado, por lo que las ventanas, el periodo de ajuste y los límites de los buckets quedan visibles. Para ejecutar el mismo spread y z score sobre un par que sigues, formula la consulta en lenguaje sencillo en la terminal de Strasmore.

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