Strasmore Research

Pairs Trading và Cointegration: Giải thích dễ hiểu

Cointegration mới là nền tảng của pairs trading, không phải correlation. Tự tính hedge ratio, spread và z score từ giá đóng cửa thực tế.

Pairs trading và cointegration thường đi cùng nhau: giao dịch này dựa trên chênh lệch giữa giá của hai cổ phiếu, với xu hướng quay lại một mức ổn định. Cointegration là thuộc tính thống kê cho thấy một chênh lệch ổn định như vậy thực sự tồn tại. Correlation đo lường một khía cạnh khác. Hai cổ phiếu có thể có correlation cao nhưng vẫn tiếp tục tách xa nhau về lâu dài. Ngược lại, hai cổ phiếu có biến động hằng ngày ít đồng điệu vẫn có thể duy trì chênh lệch ổn định trong nhiều năm. Trang này tự xây dựng hedge ratio, spread và z score, đo lường cả ba trên giá đóng cửa thực tế, đồng thời dành dung lượng tương đương để phân tích cách cấu trúc này có thể thất bại.

Cointegration trong giao dịch cặp là gì?

Correlation đo mức độ đồng biến động trong tỷ lệ thay đổi hằng ngày của hai cổ phiếu. Chỉ số này dao động từ -1 đến +1. Mức +1 có nghĩa là hai cổ phiếu đều tăng và giảm cùng nhau trong mọi phiên của mẫu quan sát. Correlation không cho biết khoảng cách giữa hai mức giá.

Cointegration là khái niệm mô tả khoảng cách đó. Lấy cổ phiếu B trừ đi một bội số cố định của cổ phiếu A, rồi xem phần còn lại là gì. Nếu chuỗi phần dư đó, tức spread, duy trì trong một phạm vi hữu hạn thay vì trôi dạt ngày càng xa, hai mã được xem là có cointegration. Bản thân giá của hai cổ phiếu vẫn có thể đi bất kỳ đâu. Tổ hợp của chúng thì không.

Hình ảnh thường dùng trong giáo trình là một con chó bị buộc dây. Người dắt và con chó đều đi theo những lộ trình khó đoán trên đường phố, nhưng sợi dây giữ cho khoảng cách giữa họ không vượt quá một giới hạn. Correlation hỏi liệu hai bên có bước đi cùng thời điểm hay không. Cointegration hỏi liệu sợi dây có giữ được khoảng cách hay không.

Bảng dưới đây đo lường khái niệm thứ nhất trên năm cặp quen thuộc trong năm 2024 và 2025. Cột return_corr là correlation của tỷ lệ thay đổi hằng ngày, cũng là ý nghĩa thông thường của từ này. Cột price_corr là correlation của giá đóng cửa chưa điều chỉnh, một con số có thể làm đẹp mức độ tương đồng của gần như mọi cặp mã cùng tăng giá.

Truy vấnTương quan lợi suất ngày so với tương quan mức giá, năm cặp quen thuộc (2024-2025)
Câu lệnh SQL chính xác đằng sau mỗi con số
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

Xếp theo correlation của biến động hằng ngày, HD / LOW đứng đầu bảng ở mức 0.868, trong khi correlation của hai mức giá trong cùng khoảng thời gian đạt 0.849. Ở dòng cuối, AAPL / MSFT có correlation của lợi suất là 0.479, đi kèm correlation của giá ở mức 0.508. Không cột nào trả lời câu hỏi mà một nhà giao dịch cặp đang đặt ra. Cả hai đều đo mức độ đồng biến động, trong khi giao dịch này cần một thước đo khoảng cách.

Cách tính hedge ratio và spread như thế nào?

Thực hiện theo năm bước, đúng thứ tự, trên hai danh sách giá đóng cửa. Python thuần xử lý được toàn bộ công việc này với math là import duy nhất: không cần package, nhà cung cấp dữ liệu hay kết nối mạng.

  1. Căn giữa từng chuỗi. mean_a = sum(a) / len(a) và thực hiện tương tự với danh sách b.
  2. Ước tính hedge ratio. Đây là hệ số góc của đường thẳng biểu diễn B theo 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). Có thể hiểu đây là số đơn vị A cần dùng để bù trừ cho một đơn vị B.
  3. Xây dựng spread. spread = [b[i] - beta * a[i] for i in range(len(a))]. Với một cặp đồng liên kết, chuỗi này dao động quanh một mức nhất định thay vì hình thành xu hướng.
  4. Chọn cửa sổ trượt. Với w = spread[i - win + 1 : i + 1], giá trị trung bình của cửa sổ là mu = sum(w) / len(w) và độ lệch chuẩn mẫu là sd = math.sqrt(sum((x - mu) ** 2 for x in w) / (len(w) - 1)). Cửa sổ trượt kết thúc tại ngày đang được chấm điểm và không sử dụng dữ liệu của các ngày sau đó.
  5. Tính điểm và in kết quả. z = (spread[i] - mu) / sd, sau đó in dòng tín hiệu vào lệnh khi abs(z) > 1.5 và dòng tín hiệu thoát lệnh khi abs(z) < 0.5. z bằng 2 nghĩa là spread cao hơn mức trung bình gần đây của chính nó hai độ lệch chuẩn.

Hai danh sách giá được cố định trong mã là đủ để theo dõi quá trình chạy này, còn win bằng 5 giúp ví dụ minh họa dễ đọc hơn. Khi triển khai thực tế, thường nên dùng cửa sổ gần 63 phiên, tương đương khoảng một quý. Các ngưỡng 1,5 để vào lệnh và 0,5 để thoát lệnh chỉ là quy ước được truyền lại từ tutorial này sang tutorial khác. Đây không phải là các kết luận thực nghiệm, và không ngưỡng nào trong số đó được rút ra từ các bảng bên dưới.

Z-score của pairs trading trên giá thực tế trông như thế nào?

Dưới đây là cùng phép tính trên hai cổ phiếu đồ uống quen thuộc là KO và PEP. Hệ số hedge được tính từ giá đóng cửa năm 2023, sau đó giữ cố định trong năm 2024 và 2025, là giai đoạn biểu đồ bao quát. Việc ước tính hệ số dốc trên chính mẫu dữ liệu được dùng để chấm điểm sau đó là thiên lệch nhìn trước trong backtesting. Tách riêng khoảng thời gian ước tính và khoảng thời gian chấm điểm là biện pháp đơn giản và ít tốn kém nhất để hạn chế vấn đề này.

Truy vấnZ score theo tuần của spread KO/PEP, hedge ratio chỉ khớp trên dữ liệu năm 2023
Câu lệnh SQL chính xác đằng sau mỗi con số
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

Đường biểu diễn gồm 105 số liệu theo tuần. Khoảng thời gian này bắt đầu ở mức z-score -1.53 và kết thúc ở -0.68. Hãy đọc hình dạng tổng thể thay vì tập trung vào một điểm riêng lẻ. Một cặp cổ phiếu vận động đúng như giả định của chiến lược sẽ vượt qua ngưỡng, dành vài tuần để quay về gần mức 0, rồi lại vượt ngưỡng. Một cặp đã ngừng mean reversion sẽ nằm ngoài ngưỡng và duy trì ở đó. Trên biểu đồ, trạng thái này thể hiện như một đoạn dài gần như phẳng, cách xa vùng trung tâm.

Một spread đồng liên kết có luôn quay về không?

Không. Cách đánh giá thẳng thắn là đo lường kết quả sau mỗi điểm bắt đầu. Biểu đồ dưới đây phân nhóm từng phiên từ năm 2019 đến cuối năm 2025 theo z score của ngày đó, rồi tính z score trung bình của cùng spread sau hai mươi phiên. Biểu đồ sử dụng log của tỷ lệ giá thay vì spread được ước tính, nên không có hồi quy nào được ước tính sau đó tác động đến biểu đồ.

Truy vấnSpread KO/PEP ở đâu sau hai mươi phiên, theo z score ban đầu (2019-2025)
Câu lệnh SQL chính xác đằng sau mỗi con số
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

Các phiên bắt đầu trong nhóm z below -2 có z score trung bình là -2.42 trong ngày đó. Sau hai mươi phiên, cùng spread có z score trung bình là -0.84, dựa trên 138 phiên. Ở đầu đối diện, nhóm z above 2 bắt đầu ở mức 2.64 và có z score là 0.92 sau hai mươi phiên. Một đường cong kéo về gần 0 từ cả hai đầu là hình dạng do hiện tượng hồi quy về trung bình tạo ra.

Hãy đọc biểu đồ cùng với cột số lượng phiên. Các nhóm ở hai đầu có ít phiên nhất. Các phiên trong cùng một nhóm cũng chồng lấn đáng kể. Vì vậy, một số ít giai đoạn kéo dài cung cấp phần lớn số quan sát. Giá trị trung bình không phải là cam kết cho bất kỳ điểm vào lệnh riêng lẻ nào.

Vì sao giao dịch cặp ngừng hiệu quả?

Hedge ratio không cố định. Hãy tái ước tính vào mỗi năm dương lịch; kết quả sẽ thay đổi.

Truy vấnHedge ratio khớp lại theo từng năm dương lịch, hai cặp cùng ngành
Câu lệnh SQL chính xác đằng sau mỗi con số
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

Độ dốc của PEP so với KO được đo 2.338 trong giai đoạn 2019-0.543 trong giai đoạn 2025, kèm theo cặp ngành thứ hai để so sánh. Một backtest ước tính một độ dốc duy nhất trên toàn bộ lịch sử rồi áp dụng từ ngày đầu tiên sẽ gán cho mọi giao dịch ban đầu một con số mà tại thời điểm đó không ai có thể biết. Cách khắc phục đơn giản nhưng hiệu quả: tái ước tính trên cửa sổ dữ liệu trượt và chấm điểm từng ngày bằng các tham số có sẵn tại ngày đó. Một backtest có thể tái lập cho phép người khác kiểm tra kỷ luật này.

Hoạt động doanh nghiệp có thể thay đổi một bên chỉ sau một đêm. Một đợt chia tách four-for-one biến một cổ phiếu thành bốn cổ phiếu và làm giá niêm yết giảm còn một phần tư. Khi đó, spread tính qua ngày này bằng giá chưa điều chỉnh sẽ nhảy lên một mức chưa từng được giao dịch. Sáp nhập, spin-off và thay đổi chỉ số cũng tạo ra hiệu ứng tương tự nhưng ít rõ rệt hơn. Hãy bắt đầu với lịch sử giá đã điều chỉnh theo chia tách và lưu ý rằng một ticker có thể được chuyển sang một công ty hoàn toàn khác. Đây là lý do ticker có thể làm hỏng dataset.

Một cú phá vỡ và một cơ hội giao dịch trông giống hệt nhau trong lúc diễn ra. Mức z score rộng nhất mà một cặp từng ghi nhận có thể là điểm vào tốt nhất trong lịch sử, hoặc là tuần đầu tiên của một mối quan hệ đã chấm dứt. Trong ngày đó, cả hai đều biểu hiện như một spread bị kéo giãn. Quản trị quy mô vị thế phải bổ sung cho những gì thống kê không thể xác định: định cỡ vị thế theo tiêu chí Kellyđịnh cỡ vị thế theo mục tiêu biến động là hai khung để xác định một spread được phép tác động đến bao nhiêu phần trong tài khoản. Mức sụt giảm tối đa đo lường tác động của một giai đoạn dài đứng sai phía của mức trung bình lên đường cong vốn.

Z score trượt không phải là kiểm định đồng liên kết. Có các kiểm định chính thức cho mục đích này. Quy trình Engle-Granger chạy hồi quy, sau đó kiểm định phần spread còn lại về tính dừng. Kiểm định Johansen mở rộng ý tưởng này vượt ra ngoài hai mã. Z score giả định rằng thuộc tính được các kiểm định trên xem xét là tồn tại. Vì vậy, nó vẫn có thể tạo ra một con số lớn trên một spread không có mức trung bình ổn định, bởi một chuỗi đang có xu hướng luôn nằm rất xa mức trung bình trượt của chính nó.

Ghi chú dữ liệu và những yếu tố các con số này không phản ánh

Bảng tương quan sử dụng giá đóng cửa hằng ngày từ 2024-01-01 đến 2025-12-31 và tính lợi suất từ các mức giá đóng cửa liên tiếp, loại bỏ phiên đầu tiên của từng mã. Đường z score ước tính hedge ratio chỉ trên giá đóng cửa năm 2023, sau đó chấm điểm năm 2024 và 2025 bằng mức trung bình trượt 63 phiên và độ lệch chuẩn mẫu, với dữ liệu lấy tại lần ghi nhận cuối cùng của mỗi tuần. Bảng hồi quy về mức trung bình tải dữ liệu lịch sử từ năm 2018 và báo cáo từ năm 2019 trở đi. Vì vậy, mọi ngày được chấm điểm đều có đủ cửa sổ dữ liệu trượt phía trước. Bảng dừng vào giữa tháng 11/2025 để mọi ngày đều có 20 phiên ở phía trước trong mẫu.

Không bảng nào trong số này là một chiến lược. Các bảng không bao gồm chi phí giao dịch, bid-ask spread, phí vay đối với bên bán khống, cổ tức hoặc chi phí tài trợ. Chúng chỉ sử dụng giá đóng cửa. Điều đó có nghĩa là một điểm vào được đánh dấu tại giá đóng cửa là mức giá mà không ai có thể chắc chắn khớp lệnh.

Câu hỏi thường gặp

Tương quan khác đồng liên kết như thế nào?

Tương quan đo lường việc hai cổ phiếu có biến động cùng ngày và cùng hướng hay không, trên thang từ -1 đến +1. Đồng liên kết đo lường việc khoảng cách giữa giá của chúng có duy trì trong một vùng ổn định hay không. Một cặp có thể đạt điểm cao ở tiêu chí đầu tiên nhưng không đạt tiêu chí thứ hai. Đây thường là lý do bộ lọc tương quan đưa ra những cặp sau đó tách xa nhau.

Tỷ lệ phòng hộ trong giao dịch cặp được tính như thế nào?

Hãy hồi quy giá của một cổ phiếu theo giá của cổ phiếu còn lại bằng một đường thẳng, rồi lấy hệ số góc. Hệ số góc này cho biết cần bao nhiêu đơn vị của cổ phiếu thứ nhất để bù trừ cho một đơn vị của cổ phiếu thứ hai. Đây là một đại lượng biến động, nên phần lớn hệ thống sẽ tái ước tính trên một cửa sổ dữ liệu trượt thay vì chỉ ước tính một lần cho toàn bộ mẫu.

Nhà giao dịch cặp sử dụng z score nào để vào và thoát lệnh?

Các ví dụ hướng dẫn thường vào lệnh khi độ lệch đạt khoảng 1,5 đến 2 độ lệch chuẩn so với giá trị trung bình trên cửa sổ trượt, rồi thoát lệnh khi độ lệch trở về gần 0,5. Những mức này là quy ước kế thừa từ các tài liệu trước đó, không phải các mức tối ưu được đo lường. Việc thay đổi chúng sẽ ảnh hưởng cả số lượng giao dịch và thời gian nắm giữ mỗi giao dịch.

Hai cổ phiếu có thể đồng liên kết dù có tương quan thấp không?

Có. Tương quan được tính trên các thay đổi hằng ngày, còn đồng liên kết được tính trên mức độ của khoảng chênh lệch. Vì vậy, hai mã có biến động hằng ngày hiếm khi cùng hướng vẫn có thể duy trì khoảng cách ổn định trong dài hạn. Trường hợp ngược lại cũng xảy ra: tương quan hằng ngày cao nhưng khoảng chênh lệch vẫn mở rộng qua từng năm.

Vì sao một cặp đồng liên kết lại ngừng hoạt động hiệu quả?

Mối liên kết duy trì khoảng chênh lệch có thể thay đổi. Nguyên nhân có thể là một thương vụ sáp nhập, một đợt tách công ty, sự thay đổi trong cơ cấu hoạt động của một doanh nghiệp hoặc việc thay đổi chỉ số làm thay đổi nhóm nhà đầu tư nắm giữ cổ phiếu. Các số liệu thống kê trong những ngày trước khi mối liên kết đứt gãy có thể giống hệt các ngày trước một lần hồi quy bình thường. Vì vậy, nhà giao dịch cần phân bổ quy mô vị thế để quản trị rủi ro này thay vì cố dự đoán nó.


Mỗi bảng trong bài đều đi kèm câu lệnh SQL chính xác ở phía sau. Vì vậy, cửa sổ dữ liệu, giai đoạn ước tính và các mốc phân nhóm đều được hiển thị rõ. Để chạy cùng spread và z score trên một cặp mà bạn đang theo dõi, hãy đặt câu hỏi bằng tiếng Anh tự nhiên trên terminal Strasmore.

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