配對交易與共整合:避險比率、價差與 z 分數
了解配對交易真正需要的共整合,分辨相關性陷阱,並用真實收盤價手算避險比率、價差與 z 分數,掌握策略可能失效的情況。
配對交易與共整合密不可分:這項交易建立在兩個股價之間的差距會反覆回到穩定水準之上,而共整合是用來判定這種差距確實存在的統計性質。相關性衡量的是另一件事。兩檔股票可能呈現高度相關,卻仍持續彼此偏離;兩檔股票的每日走勢即使幾乎不一致,也可能多年維持穩定差距。本頁將逐步計算避險比率、價差與 z 分數,使用真實收盤價衡量這三項指標,並用同等篇幅說明這套建構方法可能失效的情況。
配對交易中的共整合是什麼?
相關性衡量兩檔股票每日百分比變動的同步程度。其範圍介於 -1 到 +1;+1 表示樣本期間內,兩檔股票每個交易日都同步上漲或下跌。但相關性無法說明兩者價格之間的距離。
共整合描述的正是這個距離。以股票 B 減去股票 A 乘上一個固定倍數,再觀察剩餘部分。若這個剩餘序列,也就是 spread,會維持在有限範圍內,而不是持續偏離,便表示這兩檔股票具有共整合關係。兩檔股票的價格本身可以朝任何方向變動,但這個組合不會任意偏離。
教科書常用的比喻是繫著牽繩的狗。遛狗者和狗都可能沿著街道走出無法預測的路徑,但牽繩會將兩者之間的距離維持在有限範圍內。相關性問的是兩者是否在同一時間邁步。共整合問的是牽繩是否能維持連結。
下方表格以 2024 年和 2025 年的五組常見配對,衡量第一個概念。return_corr 欄是每日百分比變動的相關性,通常所稱的相關性就是指這項數值。price_corr 欄則是收盤價原始數值的相關性。只要兩檔股票都上漲,這個數字幾乎會對任何配對產生偏高評價。
每個數據背後的精確 SQL 語法
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按每日變動相關性排序,HD / LOW 在表格中排名第一,數值為 0.868;同一期間兩檔股票價格水準的相關性則為 0.849。在最末列,AAPL / MSFT 的報酬相關性為 0.479,價格相關性則為 0.508。這兩欄都無法回答配對交易者真正提出的問題。兩者衡量的都是共同變動,而這項交易需要的是距離的衡量方式。
如何計算避險比率與價差?
按照以下順序,針對兩組收盤價計算,共分五個步驟。整個流程只需使用原生 Python,唯一的匯入項目是 math:不需要套件、資料供應商或網路。
- 將每個序列置中。
mean_a = sum(a) / len(a),對b也採用相同方式。 - 估計避險比率。 避險比率是以 A 為自變數、B 為應變數的直線斜率:
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)。可將其解讀為抵銷一單位 B 所需的 A 單位數。 - 建立價差。
spread = [b[i] - beta * a[i] for i in range(len(a))]。對於具共整合關係的配對,這個序列會在某個水準附近波動,而不是持續呈現趨勢。 - 採用追蹤視窗。 使用
w = spread[i - win + 1 : i + 1]後,視窗平均值為mu = sum(w) / len(w),樣本標準差為sd = math.sqrt(sum((x - mu) ** 2 for x in w) / (len(w) - 1))。所謂追蹤,表示視窗截至正在評分的當日,且不使用其後的資料。 - 計算分數並輸出。
z = (spread[i] - mu) / sd;當abs(z) > 1.5時輸出進場訊息,當abs(z) < 0.5時輸出出場訊息。z 值為 2,表示價差高於自身近期平均值兩個標準差。
只要兩組寫死的價格清單,就足以觀察程式執行結果;而 win 設為 5,可讓玩具範例更容易閱讀。實務分析通常使用接近 63 個交易時段的視窗,約為一季。進場門檻 1.5、出場門檻 0.5,都是從一篇教學沿用至另一篇教學的慣例。這些數值並非研究所得,以下各面板也沒有推導出它們。
實際價格上的配對交易 z-score 長什麼樣?
以下是兩檔家喻戶曉的飲料股 KO 與 PEP 的相同計算結果。避險比率僅根據 2023 年收盤價計算,之後在圖表涵蓋的 2024 年與 2025 年期間固定不變。若在同一個樣本期間擬合斜率,再用該樣本進行評分,便會產生回測中的前視偏誤;將擬合期間與評分期間分開,是防範這種偏誤最簡單有效的方法。
每個數據背後的精確 SQL 語法
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這條走勢線包含 105 個週度讀值。觀察區間以 z-score -1.53 開始,並以 -0.68 結束。應解讀整體形狀,不要只看任何單一點位。若配對走勢符合策略假設,價格會穿越門檻,在數週內逐步回到零附近,之後再次穿越門檻。若配對已停止均值回歸,便會停留在門檻之外,並持續維持該狀態;在圖表上,這會呈現為遠離中線的長時間平坦區段。
共整合的 spread 一定會回歸嗎?
不一定。要誠實回答這個問題,應觀察每個起始點之後實際發生的情況。下圖將2019年至2025年年底前的每個交易日,依當日 z score 分組,並回報同一 spread 在20個交易日後的平均 z score。圖中使用價格比率的對數,而非經估計的 spread,因此沒有任何事後估計的迴歸模型介入圖表。
每個數據背後的精確 SQL 語法
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起始於 z below -2 分組的交易日,當日平均 z score 為 -2.42;20個交易日後,同一 spread 的平均 z score 為 -0.84,樣本涵蓋 138 個交易日。另一端的 z above 2 分組,起始值為 2.64,20個交易日後的數值為 0.92。若曲線從兩端都向零靠近,這正是均值回歸形成的形狀。
請同時查看交易日數欄位。外側分組包含的交易日最少,而且同一分組內的交易日高度重疊,因此多數列數是由少數幾段長期走勢所構成。平均值不代表任何單一進場點都會得到相同結果。
為什麼配對交易會失效?
對沖比率不是固定值。 每個曆年重新估計後,數值就會變動。
每個數據背後的精確 SQL 語法
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在 2.338 期間,PEP 相對於 KO 的斜率為 2019;在 -0.543 期間則為 2025,旁邊另列第二組產業配對作為比較。若回測以完整歷史資料估計一個斜率,並從第一天起就依此交易,等於讓每筆早期交易都使用當時不可能取得的數值。修正方法單調但有效:以滾動期間重新估計,並使用當天可取得的參數計算每一天的分數。可重現的回測能讓其他人檢驗這項紀律。
公司行動會在一夜之間改寫其中一條腿。 四比一分割會將一股變成四股,並使報價除以四;若以未調整價格跨越該日期計算價差,價差就會跳到一個從未實際交易過的水準。合併、分拆與指數成分變更,也會以較不明顯的方式造成相同問題。應先使用分割調整後的價格歷史資料,並注意股票代號可能被重新分配給完全不同的公司,這就是股票代號會破壞資料集的原因。
結構性斷裂與交易機會在發生當下看起來完全相同。 一組配對歷來出現過的最大 z score,可能是它曾提供的最佳進場點,也可能是關係已經終止後的第一週。當天兩者看起來都只是價差過度偏離。部位規模承擔了統計模型無法處理的風險:Kelly 準則部位規模與波動度目標部位規模是兩種用來決定單一價差可影響帳戶多少資金的架構,而最大回撤則衡量長時間站在均值錯誤一側會如何影響權益曲線。
滾動 z score 不是共整合檢定。 正式檢定方法確實存在。Engle-Granger 程序先執行迴歸,再檢驗剩餘價差是否具有平穩性;Johansen 檢定則將這個概念延伸至兩個以上的標的。z score 假設這些檢定所檢查的性質成立;即使價差根本沒有穩定均值,它也會正常產生很大的數值,因為趨勢序列總會大幅偏離自身的滾動平均值。
資料說明與這些數字未涵蓋的內容
相關性面板使用2024年1月1日至2025年12月31日的每日收盤價,並根據連續收盤價計算報酬,刪除每個標的的第一個交易日。z score 軌跡僅以2023年的收盤價估計對沖比率,再以63個交易日的滾動平均值與樣本標準差計算2024年和2025年的分數;資料取樣至每週最後一筆讀數。均值回歸面板載入2018年起的歷史資料,並從2019年起報告,因此每個計算日之前都有完整的滾動期間;資料在2025年11月中旬停止,使樣本內每一天之後都保有20個交易日。
這些面板都不是交易策略。它們未納入交易成本、買賣價差、放空腿的借券費用、股息或融資成本。資料僅使用收盤價,這表示以收盤價標示的進場點,是交易者不可能確實成交的價格。
常見問答
相關性與共整合有何不同?
相關性衡量兩檔股票是否在相同交易日朝相同方向變動,範圍為 -1 至 +1。共整合則衡量兩者價格之間的距離是否維持在穩定區間內。某一組股票可能在前者取得高分,卻不符合後者;這通常是相關性篩選找出一組配對後,兩者價格仍逐漸分離的原因。
配對交易中的避險比率如何計算?
將一檔股票的價格對另一檔股票的價格進行線性迴歸,並取其斜率。該斜率代表要用第一檔股票的多少單位,來抵銷第二檔股票的一個單位。避險比率會隨時間變動,因此多數實作會使用移動視窗重新估計,而不是以整段樣本期間只估計一次。
配對交易者會使用哪個 z score 進場與出場?
教學範例通常會在價格偏離移動平均約 1.5 至 2 個標準差時進場,接近 0.5 個標準差時出場。這些數值多半是沿用早期文章的慣例,而非經過測量得出的最佳值。調整門檻會同時改變交易次數與每筆交易的持有時間。
兩檔股票可能在相關性低的情況下仍具共整合關係嗎?
可以。相關性是根據每日變動計算,共整合則是根據價格差距的水準計算。因此,兩檔股票即使日常變動很少同步,長期價格距離仍可能維持穩定。反過來也可能成立:每日相關性很高,但兩者的價格差距逐年擴大。
為什麼具共整合關係的配對會失效?
維持價格差距的關聯可能發生變化,例如合併、分拆、其中一家公司的業務組合改變,或指數調整導致持股者結構改變。關係破裂前幾日的統計特徵,與一般均值回歸前幾日的特徵相同。交易規模必須納入這項風險,而不能試圖預測哪一次會發生斷裂。
本頁的每個面板都附有完整且一致的底層 SQL,因此視窗期間、擬合期間與分組邊界都清楚可見。若要對你關注的配對執行相同的 spread 與 z score,請在 Strasmore terminal 以自然語言提出問題。