Where the KO/PEP spread sat twenty sessions later, by starting z score (2019-2025)
Answered against 22 years of US equities and 12 years of US options data and published with the query that produced it. This result is stored as of 2026-08-13, from Pairs Trading and Cointegration Explained.
| z_bucket | avg_z_start | avg_z_20d_later | episode_count |
|---|---|---|---|
| z below -2 | -2.42 | -0.84 | 138 |
| z -2 to -1 | -1.4 | -0.59 | 406 |
| z -1 to 0 | -0.52 | -0.21 | 354 |
| z 0 to 1 | 0.52 | 0.04 | 371 |
| z 1 to 2 | 1.41 | 0.66 | 331 |
| z above 2 | 2.64 | 0.92 | 129 |
- Rows × columns
- 6 × 4
- Computed
- Completeness
- No missing values
- Source
- US exchange, SIP and OPRA market data
- Licence
- Strasmore terms · free, no signup
What each column holds
| Column | Type | Range | Notes |
|---|---|---|---|
z_bucket |
text | 6 distinct values (z -1 to 0, z -2 to -1, z 0 to 1…) | |
avg_z_start |
number | -2.42 to 2.64 | |
avg_z_20d_later |
number | -0.84 to 0.92 | |
episode_count |
number | 129 to 406 | count |
Computed from Strasmore's warehouse of US exchange, SIP and OPRA market data. Equity prices are delayed; options greeks and implied volatility are end-of-day. This result is stored, not recomputed on load — it is exactly the numbers that were returned on , and the query below is what returned them.
the exact SQL behind every number
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 your own version of this
The same 22 years of US equities and 12 years of options data are queryable in SQL or plain English. A free account runs 100 queries a day and takes no card.
More from this analysisPairs Trading and Cointegration Explained
Hedge ratio refitted each calendar year, two sector pairs
ranking 7×3
→
Daily-return correlation vs price-level correlation, five familiar pairs (2024-2025)
ranking 5×3
→
Weekly z score of the KO/PEP spread, hedge ratio fitted on 2023 only
series 105×2
→
Variance ratio by block length: does SPY variance scale like independent draws?
ranking 7×3
→
Lag-one autocorrelation: signed returns against absolute returns, 2016 to 2025
ranking 6×4
→
One position, one year at a time: SPY annualized Sharpe by calendar year
table 14×5
→
See all 2,170 queries →