← Quay lại danh sách bài viết
March 19, 2026
5 phút đọc

Kinh Doanh Chênh Lệch Thống Kê và Giao Dịch Theo Cặp trên Thị Trường Crypto: Từ Đồng Tích Hợp đến Bộ Lọc Kalman

Kinh Doanh Chênh Lệch Thống Kê và Giao Dịch Theo Cặp trên Thị Trường Crypto: Từ Đồng Tích Hợp đến Bộ Lọc Kalman
#stat arb
#giao dịch theo cặp
#đồng tích hợp
#kalman
#kinh doanh chênh lệch
#giao dịch thuật toán
#crypto

Năm 1987, một nhóm các nhà vật lý tại Morgan Stanley kiếm được 50 triệu đô la trong một năm bằng cách giao dịch các cặp cổ phiếu theo thuật toán mà không ai trong số họ có thể giải thích đầy đủ cho ban quản lý ngân hàng. Ban quản lý không phản đối. Năm 2026, bạn có thể áp dụng ý tưởng tương tự trên các sàn giao dịch crypto — với hợp đồng tương lai vĩnh cửu, thị trường hoạt động 24/7 và thanh khoản mà Nunzio Tartaglia chắc hẳn sẽ thèm muốn. Nhưng có một điều cần lưu ý: những gì hoạt động với cổ phiếu Ford và GM trong kỷ nguyên tiền internet đòi hỏi sự thích ứng nghiêm túc cho một thế giới mà BTC có thể giảm 20% chỉ trong một đêm và lãi suất tài trợ có thể đổi chiều chỉ trong một block.

Bài viết này là phân tích toàn diện về kinh doanh chênh lệch thống kê và giao dịch theo cặp trên thị trường crypto. Từ lý thuyết toán học (đồng tích hợp, quá trình Ornstein-Uhlenbeck, bộ lọc Kalman) đến code Python hoạt động thực tế mà bạn có thể chạy trên dữ liệu thực. Phong cách kỹ thuật: chúng tôi giải thích các công thức, trình bày code và không che giấu những cạm bẫy.

1. Lịch Sử Ngắn Gọn: Từ Tu Sĩ Dòng Tên đến Các Quant

Kinh doanh chênh lệch thống kê ở dạng hiện đại được sinh ra tại bàn giao dịch của Morgan Stanley vào giữa những năm 1980. Nunzio Tartaglia — cựu tu sĩ Dòng Tên với bằng tiến sĩ vật lý — đã tập hợp một nhóm các nhà toán học, nhà vật lý và nhà khoa học máy tính. Mục tiêu: tìm kiếm các mô hình trong giá cổ phiếu mà các nhà giao dịch truyền thống không thể thấy.

Ý tưởng rất đơn giản đến ngây thơ. Nếu cổ phiếu Coca-Cola và Pepsi lịch sử di chuyển cùng nhau (điều này hợp lý — họ bán cùng loại nước có đường trong chai màu khác nhau), thì sự phân kỳ trong giá của chúng là một bất thường tạm thời. Mua cổ phiếu tụt hậu, bán cổ phiếu dẫn đầu, chờ hội tụ, chốt lợi nhuận. Đây là chiến lược trung tính thị trường: hướng của thị trường không quan trọng với chúng ta.

Nhóm của Tartaglia bao gồm những người sau này sẽ thay đổi toàn bộ Phố Wall:

  • David Shaw — sau đó thành lập D.E. Shaw & Co., một trong những quỹ đầu tư định lượng lớn nhất
  • Peter Muller — thành lập PDT Partners, nhóm stat arb huyền thoại bên trong Morgan Stanley
  • Robert Frey — sau đó gia nhập Renaissance Technologies dưới sự lãnh đạo của Jim Simons

Nhóm hoạt động như một phòng nghiên cứu bên trong một ngân hàng đầu tư. Tự động hóa là hiện đại nhất: các cụm VAX tạo ra tín hiệu, và lệnh thực thi qua các terminal. Trong những năm tốt nhất (1987-1988), chiến lược kiếm được hàng chục triệu đô la. Rồi đến hai năm thua liên tiếp, và năm 1989 Morgan Stanley đóng cửa bàn giao dịch.

Nhưng ý tưởng đã lan rộng. Các cựu thành viên của nhóm đã truyền bá khái niệm giao dịch theo cặp trên toàn Phố Wall. Gatev, Goetzmann và Rouwenhorst công bố bài báo học thuật kinh điển vào năm 2006 — "Pairs Trading: Performance of a Relative-Value Arbitrage Rule" — cho thấy chiến lược giao dịch theo cặp đơn giản liên tục mang lại lợi nhuận hàng năm ~11% từ năm 1962 đến năm 2002 trên cổ phiếu Mỹ. Đây là câu trả lời thuyết phục cho giả thuyết thị trường hiệu quả: thị trường nói chung có thể hiệu quả, nhưng các cặp tài sản cụ thể có hệ thống lệch khỏi trạng thái cân bằng.

Ngày nay, kinh doanh chênh lệch thống kê là một ngành với hàng trăm tỷ đô la AUM, và các thị trường crypto mang lại mảnh đất màu mỡ đặc biệt: thanh khoản phân mảnh, cấu trúc vi mô còn non trẻ, giao dịch suốt ngày đêm, và hợp đồng tương lai vĩnh cửu với lãi suất tài trợ — một công cụ đơn giản là không tồn tại trên các thị trường truyền thống.

2. Nền Tảng Toán Học: Tương Quan Là Một Cái Bẫy

Tại Sao Tương Quan Không Hiệu Quả

Hãy bắt đầu với sai lầm mà hầu như mọi quant mới học đều mắc phải: "BTC và ETH có tương quan với hệ số 0.85, vì vậy chúng ta có thể giao dịch cặp này." Không. Không thể được. À thực ra có thể — nhưng bạn sẽ thua tiền.

Tương quan đo lường mối quan hệ tuyến tính giữa lợi nhuận của hai tài sản. Hai tài sản có thể tương quan hoàn hảo, nhưng giá của chúng có thể phân kỳ mãi mãi. Ví dụ kinh điển: hai bước đi ngẫu nhiên với các gia số tương quan — chúng phân kỳ vô hạn mặc dù tương quan cao. Bạn sẽ mở vị thế mong chờ "hội tụ" sẽ không bao giờ đến.

Đồng tích hợp vs tương quan

Đồng Tích Hợp: Cách Tiếp Cận Đúng Đắn

Đồng tích hợp là thuộc tính của chuỗi giá, không phải lợi nhuận. Hai chuỗi không dừng X(t) và Y(t) được gọi là đồng tích hợp nếu tồn tại một tổ hợp tuyến tính:

S(t) = Y(t) - β · X(t)

là dừng — nghĩa là nó hồi phục về một giá trị trung bình. Hệ số β được gọi là tỷ lệ phòng ngừa, và S(t) là spread.

Trực giác: BTC và ETH có thể tăng vọt lên trăng hoặc lao xuống vực thẳm, nhưng nếu sự khác biệt của chúng (được chuẩn hóa đúng cách) dao động quanh một mức cố định — đó là đồng tích hợp. Và đó chính xác là những gì chúng ta cần để giao dịch.

Kiểm Định Engle-Granger (1987)

Một thủ tục hai bước mà Robert Engle và Clive Granger đã được trao giải Nobel Kinh tế năm 2003:

Bước 1. Hồi quy OLS: Y(t) = α + β · X(t) + ε(t). Chúng ta thu được tỷ lệ phòng ngừa β và phần dư ε(t).

Bước 2. Kiểm định ADF (Augmented Dickey-Fuller) trên phần dư ε(t). Giả thuyết null: ε(t) có gốc đơn vị (không dừng). Nếu p-value < 0.05, chúng ta bác bỏ H₀ — các chuỗi là đồng tích hợp.

Quan trọng: đối với kiểm định đồng tích hợp, bạn không thể sử dụng các giá trị tới hạn ADF tiêu chuẩn. Các giá trị tới hạn Engle-Granger được xác định qua mô phỏng Monte Carlo và tính đến sự phụ thuộc giữa các biến trong hồi quy OLS. Trong statsmodels, điều này được thực hiện đúng trong hàm coint().

Kiểm Định Johansen

Đối với hệ thống có nhiều hơn hai biến (ví dụ: BTC, ETH và SOL đồng thời), kiểm định Johansen được sử dụng. Nó tìm tất cả các mối quan hệ đồng tích hợp trong hệ thống và cho phép xây dựng danh mục đầu tư của nhiều tài sản. Kiểm định dựa trên mô hình VAR (véc tơ tự hồi quy) và sử dụng hai tiêu chí: thống kê dấu vết và thống kê giá trị riêng tối đa.

Quá Trình Ornstein-Uhlenbeck

Nếu spread được đồng tích hợp, động lực của nó có thể được mô hình hóa như một quá trình Ornstein-Uhlenbeck (OU):

dS(t) = θ(μ - S(t))dt + σ dW(t)

trong đó:

  • θ — tốc độ hồi phục trung bình
  • μ — mức trung bình dài hạn
  • σ — biến động
  • W(t) — quá trình Wiener (chuyển động Brown)

Từ các tham số quá trình OU, chu kỳ bán hủy của hồi phục trung bình được tính:

t½ = ln(2) / θ

Chu kỳ bán hủy là một chỉ số cực kỳ quan trọng. Nếu t½ = 5 ngày, spread hồi phục về mức trung bình trong khoảng 5 ngày. Nếu t½ = 200 ngày, bạn sẽ phải ngồi trong một vị thế nửa năm chờ hội tụ. Đối với các chiến lược crypto, chu kỳ bán hủy tối ưu là 1-30 ngày. Ngắn hơn — quá nhanh, hoa hồng ăn hết lợi nhuận. Dài hơn — quá chậm, rủi ro thay đổi cấu trúc.

Trong thực tế, θ được ước lượng qua hồi quy:

ΔS(t) = a + b · S(t-1) + ε(t)

trong đó θ = -b, và t½ = -ln(2) / b.

Chuẩn Hóa Z-Score

Để tạo tín hiệu giao dịch, spread được chuẩn hóa:

z(t) = (S(t) - μ̂) / σ̂

trong đó μ̂ và σ̂ là giá trị trung bình và độ lệch chuẩn lăn của spread. Z-score cho thấy spread đã lệch bao nhiêu độ lệch chuẩn so với mức trung bình. Ngưỡng vào lệnh thông thường: |z| > 2.0; ngưỡng thoát lệnh: |z| < 0.5.

3. Lựa Chọn Cặp trên Thị Trường Crypto

BTC-ETH: Cặp Kinh Điển (Đôi Khi) Hoạt Động

BTC và ETH là cặp rõ ràng nhất và thanh khoản nhất. Tương quan lợi nhuận liên tục trên 0.7. Nhưng đồng tích hợp lại là câu chuyện khác. Nó xuất hiện và biến mất:

  • Trong thị trường đi ngang năm 2023, BTC/ETH đồng tích hợp đáng tin cậy (p-value < 0.01)
  • Trong giai đoạn phân kỳ 2024-2025 (BTC tăng theo dòng vốn ETF, ETH tụt hậu), đồng tích hợp bị phá vỡ
  • Đến đầu năm 2026, sau khi ra mắt ETH ETF và phục hồi tỷ lệ ETH/BTC, đồng tích hợp ổn định trở lại

Kết luận: đồng tích hợp phải được giám sát liên tục. Các tham số hồi quy được tính toán lại trên cửa sổ lăn, và chiến lược tự động tắt nếu p-value kiểm định ADF vượt quá ngưỡng.

Các Cặp Theo Lĩnh Vực

Thị trường crypto được phân đoạn thuận tiện theo lĩnh vực, và các cặp trong cùng lĩnh vực thường thể hiện đồng tích hợp ổn định:

Lĩnh Vực Ví Dụ Cặp Đặc Điểm
Blockchain L1 SOL/AVAX, NEAR/APT Thanh khoản cao, chu kỳ bán hủy 3-10 ngày
Giao thức DeFi AAVE/COMP, UNI/SUSHI Thanh khoản trung bình, chu kỳ bán hủy 5-15 ngày
Giải pháp L2 ARB/OP, MATIC/MANTA Biến động spread cao
Memecoin DOGE/SHIB Khó đoán nhưng thú vị (không khuyến nghị)

Các cặp tốt nhất cho stat arb có ba thuộc tính: (1) đồng tích hợp ổn định trên cửa sổ lịch sử >6 tháng, (2) thanh khoản đủ — khối lượng hàng ngày >$10M mỗi tài sản, (3) chu kỳ bán hủy hợp lý — từ 1 đến 30 ngày.

Spot vs Hợp Đồng Tương Lai Vĩnh Cửu (Cơ Sở)

Một loại "cặp" riêng biệt là cùng một tài sản trên thị trường spot và futures. Sự khác biệt giữa giá hợp đồng tương lai vĩnh cửu và giá spot (cơ sở) là dừng theo định nghĩa: cơ chế lãi suất tài trợ nén nó trở về không. Điều này làm cho giao dịch cơ sở trở thành một trong những hình thức stat arb đáng tin cậy nhất trong crypto.

4. Ba Cách Tiếp Cận Giao Dịch

A. Giao Dịch Cơ Sở: Spot-Futures và Carry Lãi Suất Tài Trợ

Hình thức stat arb "thuần túy" nhất trong crypto. Cơ chế:

  1. Mua tài sản trên spot (ví dụ: 1 BTC)
  2. Mở short trên hợp đồng tương lai vĩnh cửu (1 BTC)
  3. Nếu lãi suất tài trợ dương (long trả cho short) — bạn nhận tài trợ mỗi 8 giờ

Với lãi suất tài trợ trung bình 0.01% mỗi 8 giờ, đó là ~0.03% mỗi ngày hoặc ~11% hàng năm không có rủi ro định hướng. Trong thị trường bull, lãi suất tài trợ có thể tăng lên 0.05-0.1% mỗi 8 giờ — đó đã là 55-110% hàng năm.

Rủi ro: tài trợ âm (thị trường đảo chiều), thanh lý vị thế short trong khi giá tăng mạnh (cần bộ đệm ký quỹ), và phí sàn giao dịch.

Tính đến tháng 3 năm 2026, lãi suất tài trợ BTC trung bình đã ổn định ở mức ~0.015% mỗi 8 giờ — khoảng 50% cao hơn mức năm 2024.

B. Kinh Doanh Chênh Lệch Xuyên Sàn

Cùng một đồng coin, hai sàn giao dịch, giá khác nhau. Lý do — sự khác biệt về thanh khoản, thành phần nhà giao dịch và tốc độ cập nhật sổ lệnh.

Ví dụ: BTC trên Binance: 87,150.BTCtre^nBybit:87,150. BTC trên Bybit: 87,175. Spread: $25 (0.029%).

Chiến lược: mua trên Binance, bán trên Bybit. Vấn đề: vào thời điểm cả hai lệnh được thực thi, spread có thể đã biến mất. Giải pháp: duy trì số dư trên cả hai sàn và thực thi đồng thời.

Phí điển hình:

  • Binance: ~0.075% taker (với giảm giá ~0.05%)
  • Bybit: ~0.03% taker (VIP)
  • Tổng cộng: ~0.08%

Điều này có nghĩa là spread phải vượt quá 0.08% để chiến lược có lợi nhuận. Năm 2026, các spread như vậy xuất hiện:

  • Trên các cặp ít thanh khoản hơn (altcoin) — thường xuyên
  • Trên các cặp chính (BTC, ETH) — chỉ trong những thời điểm biến động cao
  • Giữa CEX và DEX — thường xuyên hơn, nhưng với rủi ro MEV và trượt giá

Không có co-location, độ trễ API là 10-100 ms. Với mạng tối ưu hóa — ~1 ms. Hầu hết nhà giao dịch bán lẻ hoạt động trong phạm vi 100-500 ms, đủ cho nhiều chiến lược kinh doanh chênh lệch nhưng không đủ để cạnh tranh với các tổ chức.

C. Giao Dịch Theo Cặp Với Đòn Bẩy

Giao dịch theo cặp cổ điển trên hai tài sản khác nhau sử dụng đòn bẩy. Đây là phức tạp nhất trong ba chiến lược — và có tiềm năng lợi nhuận nhất.

Cơ chế sử dụng cặp SOL/AVAX làm ví dụ:

  1. Tính toán tỷ lệ phòng ngừa β (ví dụ: β = 1.3)
  2. Khi z-score > +2: short SOL, long AVAX × β
  3. Khi z-score < -2: long SOL, short AVAX × β
  4. Thoát lệnh: |z-score| < 0.5 hoặc timeout (ví dụ: 30 ngày)

Với đòn bẩy 3x mỗi chiều và hồi phục spread trung bình 2σ → 3σ:

  • Lợi nhuận mục tiêu mỗi giao dịch: ~3-6%
  • Tần suất trung bình: 2-4 giao dịch mỗi tháng mỗi cặp
  • Lợi nhuận hàng năm dự kiến: 30-60% (trước hoa hồng và trượt giá)

Rủi ro chính: tương quan có thể phá vỡ vào thời điểm tồi tệ nhất (thường trong sự cố thị trường). Chi tiết hơn trong phần 8.

5. Bộ Lọc Kalman cho Tỷ Lệ Phòng Ngừa Thích Ứng

Tại Sao Tỷ Lệ Phòng Ngừa Tĩnh Là Vấn Đề

Cách tiếp cận cổ điển: ước lượng β qua OLS trên cửa sổ lịch sử và cố định nó. Vấn đề: β thay đổi theo thời gian. Thị trường crypto đặc biệt không dừng — sự thay đổi xu hướng (DeFi summer → NFT hype → AI tokens) làm thay đổi các mối quan hệ cơ bản giữa các tài sản.

Sử dụng OLS lăn (hồi quy lăn) là một biện pháp nửa vời. Bạn phải chọn độ dài cửa sổ: quá ngắn — nhiễu; quá dài — độ trễ. Bộ lọc Kalman giải quyết vấn đề này một cách thanh lịch.

Bộ lọc Kalman

Mô Hình Không Gian Trạng Thái

Chúng ta biểu diễn mối quan hệ giữa Y(t) và X(t) như một mô hình tuyến tính với các hệ số thay đổi theo thời gian:

Phương trình quan sát:

Y(t) = α(t) + β(t) · X(t) + ε(t),   ε(t) ~ N(0, R)

Phương trình trạng thái:

[α(t+1), β(t+1)]ᵀ = [α(t), β(t)]ᵀ + w(t),   w(t) ~ N(0, Q)

Các tham số α(t) và β(t) được coi như một trạng thái ẩn dịch chuyển chậm (bước đi ngẫu nhiên). Bộ lọc Kalman ước lượng tối ưu trạng thái ẩn này từ các quan sát nhiễu.

  • R (nhiễu quan sát) — phương sai của nhiễu quan sát. R càng lớn, bộ lọc phản ứng với dữ liệu mới càng chậm.
  • Q (nhiễu trạng thái) — ma trận hiệp phương sai của nhiễu trạng thái. Q càng lớn, bộ lọc thích ứng càng nhanh.

Tỷ lệ Q/R xác định "độ mịn" của bộ lọc — tương tự như chọn độ dài cửa sổ trong OLS lăn, nhưng không cắt bỏ dữ liệu cứng nhắc.

Ưu Điểm So Với OLS Lăn

Các spread được tính bằng bộ lọc Kalman dừng hơn và hồi phục trung bình hơn so với spread từ hồi quy lăn. Bộ lọc Kalman sử dụng tất cả các quan sát quá khứ với trọng số giảm theo cấp số nhân, thay vì cắt bỏ dữ liệu ở độ dài cửa sổ cố định. Ngoài ra, bộ lọc Kalman không cần điều chỉnh tham số "độ dài cửa sổ" — thay vào đó, nó tự động hiệu chỉnh sự cân bằng giữa quán tính và khả năng thích ứng thông qua các ma trận Q và R.

Triển Khai Với filterpy

import numpy as np
from filterpy.kalman import KalmanFilter

def create_kalman_filter(
    delta: float = 1e-4,
    obs_noise: float = 1.0
) -> KalmanFilter:
    """
    Creates a Kalman filter for adaptive hedge ratio estimation.

    delta: state noise variance (Q = delta * I).
           Larger delta → faster adaptation, more noise.
    obs_noise: observation noise variance (R).
    """
    kf = KalmanFilter(dim_x=2, dim_z=1)

    kf.x = np.zeros((2, 1))

    kf.F = np.eye(2)

    kf.P = np.eye(2) * 1000

    kf.Q = np.eye(2) * delta

    kf.R = np.array([[obs_noise]])

    return kf

def estimate_hedge_ratio(
    prices_y: np.ndarray,
    prices_x: np.ndarray,
    delta: float = 1e-4,
    obs_noise: float = 1.0
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    Estimates the adaptive hedge ratio using a Kalman filter.

    Returns:
        alphas: array of intercepts (α)
        betas: array of hedge ratios (β)
        spreads: array of spreads Y - α - β*X
    """
    n = len(prices_y)
    kf = create_kalman_filter(delta, obs_noise)

    alphas = np.zeros(n)
    betas = np.zeros(n)
    spreads = np.zeros(n)

    for t in range(n):
        kf.H = np.array([[1.0, prices_x[t]]])

        kf.predict()

        kf.update(np.array([[prices_y[t]]]))

        alphas[t] = kf.x[0, 0]
        betas[t] = kf.x[1, 0]
        spreads[t] = prices_y[t] - kf.x[0, 0] - kf.x[1, 0] * prices_x[t]

    return alphas, betas, spreads

Tham số delta là chìa khóa. Đối với các cặp crypto có biến động cao (memecoin, altcoin vốn hóa nhỏ), hãy sử dụng delta = 1e-3. Đối với các cặp ổn định (BTC/ETH, SOL/AVAX) — delta = 1e-5.

6. Tín Hiệu Vào Lệnh và Thoát Lệnh

Ngưỡng Z-Score

Logic tín hiệu cơ bản:

def generate_signals(
    spreads: np.ndarray,
    lookback: int = 60,
    entry_z: float = 2.0,
    exit_z: float = 0.5,
    stop_z: float = 4.0
) -> np.ndarray:
    """
    Generates trading signals based on spread z-score.

    Returns array: +1 (long spread), -1 (short spread), 0 (flat)
    """
    signals = np.zeros(len(spreads))
    position = 0

    for t in range(lookback, len(spreads)):
        window = spreads[t - lookback:t]
        mu = np.mean(window)
        sigma = np.std(window)

        if sigma < 1e-10:
            continue

        z = (spreads[t] - mu) / sigma

        if position == 0:
            if z > entry_z:
                position = -1  # Short spread (short Y, long X)
            elif z < -entry_z:
                position = 1   # Long spread (long Y, short X)
        else:
            if position == 1 and z > -exit_z:
                position = 0
            elif position == -1 and z < exit_z:
                position = 0
            elif abs(z) > stop_z:
                position = 0

        signals[t] = position

    return signals

Bộ Lọc Động Lượng

Tín hiệu hồi phục trung bình thuần túy có thể được cải thiện với các bộ lọc:

  1. Bộ lọc động lượng: đừng mở vị thế nếu spread tiếp tục phân kỳ. Chờ spread đảo chiều trước khi vào lệnh. Về mặt kỹ thuật: z-score đã vượt qua ngưỡng, nhưng sự thay đổi spread hiện tại đã hướng về mức trung bình.

  2. Bộ lọc biến động: tăng ngưỡng vào lệnh trong giai đoạn biến động cao. Khi thị trường hoảng loạn, z-score có thể duy trì trên 3σ trong nhiều tuần.

  3. Bộ lọc đồng tích hợp: trước mỗi giao dịch, xác minh rằng đồng tích hợp vẫn còn (kiểm định ADF lăn). Nếu p-value > 0.1 — tạm dừng giao dịch.

Thoát Lệnh Theo Thời Gian

Nếu một vị thế đã mở lâu hơn 2× chu kỳ bán hủy và spread chưa hồi phục — đóng nó cưỡng bức. Nếu spread không hồi phục trong thời gian 2× dự kiến, đồng tích hợp có khả năng đã phá vỡ và không còn gì để chờ đợi.

7. Backtesting: Làm Đúng Cách

Phân Tích Walk-Forward

Backtest tiêu chuẩn (train trên tất cả dữ liệu → test trên tất cả dữ liệu) là vô ích cho stat arb. Các tham số hồi quy được khớp quá mức với dữ liệu và kết quả sẽ quá lạc quan.

Cách tiếp cận walk-forward:

  1. Chia dữ liệu thành các giai đoạn: [train₁ → test₁] → [train₂ → test₂] → ...
  2. Trên mỗi giai đoạn train: ước lượng đồng tích hợp, tính tỷ lệ phòng ngừa, chọn ngưỡng z-score
  3. Trên giai đoạn test: giao dịch với tham số cố định
  4. Kết hợp tất cả giai đoạn test để đánh giá cuối cùng

Cấu hình điển hình cho crypto: train = 180 ngày, test = 30 ngày, bước = 30 ngày.

Backtest chiến lược spread

Mô Hình Chi Phí Giao Dịch

Đối với crypto, bạn cần tính đến:

Thành Phần Giá Trị Điển Hình Ghi Chú
Phí maker 0.02% Lệnh giới hạn
Phí taker 0.05-0.075% Lệnh thị trường
Trượt giá 0.01-0.1% Phụ thuộc vào thanh khoản
Lãi suất tài trợ ±0.01%/8h Cho vị thế futures
Spread (bid-ask) 0.01-0.05% Trên các sàn lớn

Vào và thoát một vị thế theo cặp liên quan đến 4 giao dịch (2 chiều × vào + thoát). Tổng chi phí: ~0.3-0.5% mỗi vòng quay. Điều này có nghĩa là lợi nhuận trung bình mỗi giao dịch phải vượt quá 0.5% để có kỳ vọng dương.

Mô Hình Trượt Giá

Mô hình tuyến tính: trượt giá = k × (order_size / ADV), trong đó ADV là khối lượng hàng ngày trung bình. Đối với crypto, k ≈ 0.1 cho top-10 coin và k ≈ 0.3-0.5 cho altcoin.

Mô hình thực tế hơn là tác động căn bậc hai: trượt giá = k × sqrt(order_size / ADV). Nó phản ánh tốt hơn cấu trúc vi mô thị trường thực tế.

Các Chỉ Số

def calculate_metrics(returns: np.ndarray, rf: float = 0.04) -> dict:
    """
    Calculates key strategy metrics.
    rf: risk-free rate (annual)
    """
    daily_rf = rf / 365
    excess = returns - daily_rf

    ann_return = np.mean(returns) * 365
    ann_vol = np.std(returns) * np.sqrt(365)

    sharpe = (ann_return - rf) / ann_vol if ann_vol > 0 else 0

    cumulative = np.cumprod(1 + returns)
    running_max = np.maximum.accumulate(cumulative)
    drawdowns = (cumulative - running_max) / running_max
    max_dd = np.min(drawdowns)

    calmar = ann_return / abs(max_dd) if max_dd != 0 else 0

    win_rate = np.mean(returns > 0) if len(returns) > 0 else 0

    gains = returns[returns > 0].sum()
    losses = abs(returns[returns < 0].sum())
    profit_factor = gains / losses if losses > 0 else float('inf')

    return {
        'annual_return': f'{ann_return:.1%}',
        'annual_volatility': f'{ann_vol:.1%}',
        'sharpe_ratio': f'{sharpe:.2f}',
        'max_drawdown': f'{max_dd:.1%}',
        'calmar_ratio': f'{calmar:.2f}',
        'win_rate': f'{win_rate:.1%}',
        'profit_factor': f'{profit_factor:.2f}',
    }

Các chuẩn mực cho crypto stat arb:

  • Sharpe > 1.5 — chiến lược tốt
  • Drawdown tối đa < 15% — rủi ro chấp nhận được
  • Calmar > 2.0 — tỷ lệ lợi nhuận/drawdown xuất sắc
  • Profit factor > 1.5 — lợi thế bền vững

8. Các Vấn Đề Thực Tế

Trượt Giá và Thanh Khoản

Trong backtest, bạn vào lệnh ngay lập tức ở giá mid. Trong thực tế — không phải vậy. Trên altcoin với khối lượng hàng ngày 5M,lnh5M, lệnh 50K có thể dịch chuyển giá 0.2-0.5%. Đối với chiến lược theo cặp, đó là trượt giá gấp đôi (hai chiều) và có thể ăn hết toàn bộ lợi nhuận.

Giải pháp: sử dụng lệnh giới hạn (maker, không phải taker), chia nhỏ lệnh (TWAP/VWAP), và giới hạn chặt chẽ kích thước vị thế so với ADV (tối đa 1-2% khối lượng hàng ngày).

Rủi Ro Lãi Suất Tài Trợ

Với giao dịch cơ sở, bạn nhận lãi suất tài trợ, nhưng nó có thể chuyển sang âm. Trong thị trường bear tháng 12 năm 2022, lãi suất tài trợ BTC là -0.02% mỗi 8 giờ — nếu bạn đang giữ vị thế "long spot + short perp", bạn đang trả 60/ngaˋychomo^~i60/ngày cho mỗi 100K vị thế.

Bảo vệ: giám sát lãi suất tài trợ theo thời gian thực và đóng vị thế khi lãi suất đảo chiều. Cách tiếp cận nâng cao hơn là kinh doanh chênh lệch lãi suất tài trợ giữa các sàn (long trên sàn có lãi suất tài trợ thấp, short trên sàn có lãi suất tài trợ cao).

Phá Vỡ Tương Quan Trong Khủng Hoảng

Tháng 3 năm 2020, tháng 5 năm 2021, tháng 11 năm 2022, tháng 8 năm 2024 — trong mọi sự cố crypto, các tương quan phá vỡ. Chính xác hơn, các tương quan tăng cường (mọi thứ cùng giảm), nhưng đồng tích hợp phá vỡ — spread có thể bay lên 10σ và không bao giờ quay lại.

Đây là gót chân Achilles của giao dịch theo cặp. Chiến lược kiếm tiền nhỏ đều đặn, sau đó mất một khoản lớn trong một ngày. Hồ sơ "nhặt xu trước bánh xe tải" kinh điển.

Bảo vệ:

  1. Stop-loss nghiêm ngặt: đóng vị thế khi z-score > 4σ
  2. Giới hạn đòn bẩy: tối đa 2-3x mỗi chiều
  3. Bộ lọc VIX/biến động: giảm kích thước vị thế khi biến động ngụ ý cao
  4. Đa dạng hóa: giao dịch 10-20 cặp đồng thời, không đặt tất cả vào một cặp

Yêu Cầu Vốn

Cho crypto stat arb nghiêm túc:

  • Giao dịch cơ sở: từ $50K (trên một cặp, một sàn)
  • Kinh doanh chênh lệch xuyên sàn: từ $100K (số dư trên hai sàn)
  • Danh mục giao dịch theo cặp (10 cặp): từ $200K
  • Cấp độ tổ chức: từ $1M

Với số tiền nhỏ hơn, hoa hồng và kích thước vị thế tối thiểu làm cho chiến lược không khả thi.

9. Triển Khai Python End-to-End

Lấy Dữ Liệu

import ccxt
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

def fetch_ohlcv(
    exchange_id: str,
    symbol: str,
    timeframe: str = '1h',
    days: int = 365
) -> pd.DataFrame:
    """Fetch OHLCV data via ccxt."""
    exchange = getattr(ccxt, exchange_id)({
        'enableRateLimit': True,
    })

    since = int((datetime.now() - timedelta(days=days)).timestamp() * 1000)
    all_candles = []

    while True:
        candles = exchange.fetch_ohlcv(
            symbol, timeframe, since=since, limit=1000
        )
        if not candles:
            break
        all_candles.extend(candles)
        since = candles[-1][0] + 1
        if len(candles) < 1000:
            break

    df = pd.DataFrame(
        all_candles,
        columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']
    )
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
    df.set_index('timestamp', inplace=True)
    return df

sol = fetch_ohlcv('binance', 'SOL/USDT', '1h', 365)
avax = fetch_ohlcv('binance', 'AVAX/USDT', '1h', 365)

prices = pd.DataFrame({
    'SOL': sol['close'],
    'AVAX': avax['close']
}).dropna()

Kiểm Định Đồng Tích Hợp

from statsmodels.tsa.stattools import coint, adfuller
from statsmodels.regression.linear_model import OLS
from statsmodels.tools import add_constant

def test_cointegration(y: np.ndarray, x: np.ndarray) -> dict:
    """
    Full cointegration test with diagnostics.
    """
    score, pvalue, crit_values = coint(y, x)

    x_const = add_constant(x)
    model = OLS(y, x_const).fit()
    alpha, beta = model.params
    spread = y - alpha - beta * x

    adf_stat, adf_pvalue, _, _, adf_crit, _ = adfuller(spread, maxlag=20)

    spread_lag = spread[:-1]
    spread_diff = np.diff(spread)
    spread_lag_const = add_constant(spread_lag)
    hl_model = OLS(spread_diff, spread_lag_const).fit()
    theta = -hl_model.params[1]
    half_life = np.log(2) / theta if theta > 0 else np.inf

    return {
        'coint_pvalue': pvalue,
        'cointegrated': pvalue < 0.05,
        'hedge_ratio': beta,
        'intercept': alpha,
        'adf_statistic': adf_stat,
        'adf_pvalue': adf_pvalue,
        'half_life_hours': half_life,
        'half_life_days': half_life / 24,
        'spread_mean': np.mean(spread),
        'spread_std': np.std(spread),
    }

result = test_cointegration(
    prices['SOL'].values,
    prices['AVAX'].values
)
print(f"Cointegration: {result['cointegrated']} "
      f"(p-value: {result['coint_pvalue']:.4f})")
print(f"Hedge ratio: {result['hedge_ratio']:.4f}")
print(f"Half-life: {result['half_life_days']:.1f} days")

Bộ Lọc Kalman + Backtester

from filterpy.kalman import KalmanFilter

class PairsBacktester:
    """
    Walk-forward backtester for pairs trading
    with Kalman filter.
    """

    def __init__(
        self,
        prices_y: np.ndarray,
        prices_x: np.ndarray,
        kalman_delta: float = 1e-4,
        obs_noise: float = 1.0,
        entry_z: float = 2.0,
        exit_z: float = 0.5,
        stop_z: float = 4.0,
        lookback: int = 60,
        fee_rate: float = 0.001,    # 0.1% round trip per leg
        slippage_rate: float = 0.0005,  # 0.05% slippage per leg
    ):
        self.prices_y = prices_y
        self.prices_x = prices_x
        self.n = len(prices_y)
        self.kalman_delta = kalman_delta
        self.obs_noise = obs_noise
        self.entry_z = entry_z
        self.exit_z = exit_z
        self.stop_z = stop_z
        self.lookback = lookback
        self.fee_rate = fee_rate
        self.slippage_rate = slippage_rate

    def run(self) -> pd.DataFrame:
        """Run the backtest. Returns a DataFrame with results."""
        kf = KalmanFilter(dim_x=2, dim_z=1)
        kf.x = np.zeros((2, 1))
        kf.F = np.eye(2)
        kf.P = np.eye(2) * 1000
        kf.Q = np.eye(2) * self.kalman_delta
        kf.R = np.array([[self.obs_noise]])

        alphas = np.zeros(self.n)
        betas = np.zeros(self.n)
        spreads = np.zeros(self.n)

        for t in range(self.n):
            kf.H = np.array([[1.0, self.prices_x[t]]])
            kf.predict()
            kf.update(np.array([[self.prices_y[t]]]))
            alphas[t] = kf.x[0, 0]
            betas[t] = kf.x[1, 0]
            spreads[t] = (
                self.prices_y[t] - kf.x[0, 0]
                - kf.x[1, 0] * self.prices_x[t]
            )

        positions = np.zeros(self.n)
        z_scores = np.zeros(self.n)
        position = 0

        for t in range(self.lookback, self.n):
            window = spreads[t - self.lookback:t]
            mu = np.mean(window)
            sigma = np.std(window)
            if sigma < 1e-10:
                continue

            z = (spreads[t] - mu) / sigma
            z_scores[t] = z

            if position == 0:
                if z > self.entry_z:
                    position = -1
                elif z < -self.entry_z:
                    position = 1
            else:
                if position == 1 and z > -self.exit_z:
                    position = 0
                elif position == -1 and z < self.exit_z:
                    position = 0
                elif abs(z) > self.stop_z:
                    position = 0

            positions[t] = position

        spread_returns = np.diff(spreads) / np.abs(
            spreads[:-1] + 1e-10
        )
        pnl = np.zeros(self.n)

        for t in range(1, self.n):
            if positions[t - 1] != 0:
                raw_return = positions[t - 1] * spread_returns[t - 1]
                pnl[t] = raw_return

                if positions[t] != positions[t - 1]:
                    total_cost = 2 * (self.fee_rate + self.slippage_rate)
                    pnl[t] -= total_cost

        return pd.DataFrame({
            'price_y': self.prices_y,
            'price_x': self.prices_x,
            'alpha': alphas,
            'beta': betas,
            'spread': spreads,
            'z_score': z_scores,
            'position': positions,
            'pnl': pnl,
            'cumulative_pnl': np.cumsum(pnl),
        })

bt = PairsBacktester(
    prices_y=prices['SOL'].values,
    prices_x=prices['AVAX'].values,
    kalman_delta=1e-4,
    entry_z=2.0,
    exit_z=0.5,
    stop_z=4.0,
    lookback=60,
    fee_rate=0.001,
    slippage_rate=0.0005,
)
results = bt.run()

daily_pnl = results['pnl'].resample('D').sum() if hasattr(
    results.index, 'freq'
) else results['pnl']
metrics = calculate_metrics(daily_pnl.values)
for k, v in metrics.items():
    print(f'{k}: {v}')

Bộ Khung Giao Dịch Trực Tiếp

import ccxt
import asyncio
import logging

logger = logging.getLogger(__name__)

class LivePairsTrader:
    """
    Minimal skeleton for live pairs trading.
    For production: add retry logic, monitoring,
    alerts, balance reconciliation.
    """

    def __init__(
        self,
        exchange_id: str,
        symbol_y: str,
        symbol_x: str,
        api_key: str,
        secret: str,
        position_size_usd: float = 1000.0,
        entry_z: float = 2.0,
        exit_z: float = 0.5,
    ):
        self.exchange = getattr(ccxt, exchange_id)({
            'apiKey': api_key,
            'secret': secret,
            'enableRateLimit': True,
        })
        self.symbol_y = symbol_y
        self.symbol_x = symbol_x
        self.position_size = position_size_usd
        self.entry_z = entry_z
        self.exit_z = exit_z
        self.position = 0  # +1, -1, 0

        self.kf = create_kalman_filter(delta=1e-4)
        self.spread_history = []

    async def update(self):
        """One update cycle."""
        ticker_y = self.exchange.fetch_ticker(self.symbol_y)
        ticker_x = self.exchange.fetch_ticker(self.symbol_x)
        price_y = ticker_y['last']
        price_x = ticker_x['last']

        self.kf.H = np.array([[1.0, price_x]])
        self.kf.predict()
        self.kf.update(np.array([[price_y]]))

        alpha = self.kf.x[0, 0]
        beta = self.kf.x[1, 0]
        spread = price_y - alpha - beta * price_x
        self.spread_history.append(spread)

        if len(self.spread_history) < 60:
            logger.info(f"Warming up: {len(self.spread_history)}/60")
            return

        window = np.array(self.spread_history[-60:])
        z = (spread - np.mean(window)) / np.std(window)

        logger.info(
            f"β={beta:.4f} spread={spread:.4f} z={z:.2f} "
            f"pos={self.position}"
        )

        new_position = self.position

        if self.position == 0:
            if z > self.entry_z:
                new_position = -1
            elif z < -self.entry_z:
                new_position = 1
        else:
            if self.position == 1 and z > -self.exit_z:
                new_position = 0
            elif self.position == -1 and z < self.exit_z:
                new_position = 0

        if new_position != self.position:
            await self._execute_trade(
                new_position, price_y, price_x, beta
            )
            self.position = new_position

    async def _execute_trade(
        self, target: int, price_y: float, price_x: float,
        beta: float
    ):
        """Execute a pairs trade."""
        if target == 0:
            logger.info("Closing position")
        elif target == 1:
            size_y = self.position_size / price_y
            size_x = (self.position_size * beta) / price_x
            logger.info(
                f"Long spread: buy {size_y:.4f} {self.symbol_y}, "
                f"sell {size_x:.4f} {self.symbol_x}"
            )
        elif target == -1:
            size_y = self.position_size / price_y
            size_x = (self.position_size * beta) / price_x
            logger.info(
                f"Short spread: sell {size_y:.4f} {self.symbol_y}, "
                f"buy {size_x:.4f} {self.symbol_x}"
            )

    async def run_loop(self, interval_seconds: int = 60):
        """Main loop."""
        logger.info(
            f"Starting live trading: "
            f"{self.symbol_y}/{self.symbol_x}"
        )
        while True:
            try:
                await self.update()
            except Exception as e:
                logger.error(f"Error in update: {e}")
            await asyncio.sleep(interval_seconds)

Thay Vì Kết Luận

Kinh doanh chênh lệch thống kê không phải là Chén Thánh. Đó là một nghề thủ công. Giữa "tôi biết đồng tích hợp là gì" và "tôi có một chiến lược hoạt động ổn định" là một hố sâu của các chi tiết kỹ thuật: xử lý dữ liệu đúng cách, backtesting walk-forward chính xác, mô hình trượt giá thực tế, giám sát thời gian thực.

Các thị trường tiền mã hóa vẫn cung cấp nhiều cơ hội hơn cho stat arb so với các thị trường truyền thống — thanh khoản phân mảnh, cơ sở hạ tầng thị trường còn non trẻ, và các công cụ độc đáo như hợp đồng tương lai vĩnh cửu với lãi suất tài trợ tạo ra những kém hiệu quả đã bị kinh doanh chênh lệch về không trên NYSE từ lâu.

Nhưng cửa sổ đang đóng lại. Các nhà đầu tư tổ chức đang gia nhập thị trường crypto, vốn kinh doanh chênh lệch đang tăng (theo ước tính, khối lượng vốn kinh doanh chênh lệch trên các sàn crypto tăng 215% vào năm 2025), và biên lợi nhuận đang bị nén lại. Nếu bạn định làm stat arb trong crypto — tốt nhất là bắt đầu ngay bây giờ.

Tất cả code trong bài viết này có sẵn như một điểm khởi đầu. Đừng chạy nó trong môi trường sản xuất mà không kiểm tra nghiêm túc. Và hãy nhớ: chiến lược duy nhất được đảm bảo hoạt động là quản lý rủi ro.


Các công trình học thuật chính:

  • Engle, R.F. & Granger, C.W.J. (1987). "Co-Integration and Error Correction: Representation, Estimation, and Testing". Econometrica, 55(2), 251-276.
  • Gatev, E., Goetzmann, W.N. & Rouwenhorst, K.G. (2006). "Pairs Trading: Performance of a Relative-Value Arbitrage Rule". The Review of Financial Studies, 19(3), 797-827.
  • Vidyamurthy, G. (2004). Pairs Trading: Quantitative Methods and Analysis. Wiley.
  • Avellaneda, M. & Lee, J.H. (2010). "Statistical Arbitrage in the US Equities Market". Quantitative Finance, 10(7), 761-782.
  • Frontiers (2026). "Deep learning-based pairs trading: real-time forecasting of co-integrated cryptocurrency pairs". Frontiers in Applied Mathematics and Statistics.

Các thư viện hữu ích:

  • statsmodels — đồng tích hợp, ADF, OLS
  • filterpy — bộ lọc Kalman
  • ccxt — API thống nhất cho 100+ sàn giao dịch
  • arbitragelab — thư viện chuyên dụng cho giao dịch theo cặp (OU, Kalman, copulas)
Tuyên bố miễn trừ trách nhiệm: Thông tin được cung cấp trong bài viết này chỉ nhằm mục đích giáo dục và thông tin, không cấu thành lời khuyên về tài chính, đầu tư hoặc giao dịch. Giao dịch tiền mã hóa tiềm ẩn rủi ro thua lỗ đáng kể.

Tác Giả

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Đi Trước Thị Trường

Đăng ký nhận bản tin của chúng tôi để có những thông tin chuyên sâu độc quyền về AI trading, phân tích thị trường và các cập nhật nền tảng.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất kỳ lúc nào.