Co-integration and Error Correction — Lý Thuyết Đồng Tích Hợp
Tác giả: Robert F. Engle, Clive W. J. Granger (1987) Nguồn: Econometrica, Vol. 55, No. 2 (Giải Nobel 2003) Tag:
mới:2026-05-16#cointegration#pairs-trading#mean-reversion
Nội dung chính (Core Concept)
Trước Engle-Granger, giới econometrics đối mặt nghịch lý: nhiều chuỗi kinh tế (GDP, tiền cung, giá cổ phiếu) là non-stationary I(1) — random-walk-like — nên hồi quy chúng trực tiếp cho ra spurious regression với R² cao giả tạo. Engle & Granger định nghĩa cointegration: hai chuỗi $X_t, Y_t \sim I(1)$ được gọi là cointegrated nếu tồn tại $\beta$ sao cho $Z_t = Y_t - \beta X_t \sim I(0)$ — tức tổ hợp tuyến tính là stationary, có mean và variance hữu hạn.
Ý nghĩa kinh tế: hai chuỗi có thể đi lang thang vô tận, nhưng nếu cointegrated thì chênh lệch giữa chúng bị buộc về một equilibrium dài hạn. Engle-Granger chứng minh Representation Theorem: nếu $X_t, Y_t$ cointegrated, tồn tại biểu diễn Error Correction Model (ECM):
$$\Delta Y_t = \alpha (Y_{t-1} - \beta X_{t-1}) + \gamma \Delta X_t + \epsilon_t$$
trong đó $\alpha < 0$ là tốc độ điều chỉnh về equilibrium. Khi $Z_{t-1}$ (residual) dương, $\Delta Y_t$ âm — Y bị kéo xuống. Đây là cơ chế mean-reversion về spread.
Hai bước test Engle-Granger: (1) hồi quy OLS $Y_t = a + bX_t + u_t$; (2) chạy ADF test trên residuals $\hat{u}_t$ — nếu reject unit root, cặp cointegrated.
Ý tưởng chính cho giao dịch (Key Trading Insight)
Đây là nền tảng toán học của pairs trading và stat-arb:
- Cointegration vs Correlation: Hai cổ phiếu có thể correlation = 0.9 nhưng spread phình ra vô tận (không cointegrated) — pairs trading sẽ chết. Ngược lại, cointegrated nghĩa là spread mean-revert chắc chắn trong dài hạn.
- Hedge ratio $\beta$ ≠ 1: Long 1 cổ phiếu A short β cổ phiếu B (không phải 1:1). Hedge ratio đến từ regression coefficient.
- Half-life của ECM: $\alpha$ cho biết tốc độ về equilibrium. Half-life = $-\log(2)/\log(1+\alpha)$. Half-life ngắn → trade nhiều; quá dài (> 60 ngày) → spread tay đôi không profitable sau costs.
- Hạn chế: hệ số $\beta$ có thể drift theo thời gian — cần re-estimate rolling, hoặc dùng Kalman filter (xem [[kalman-pairs-spread]]) để cập nhật adaptive.
Ứng dụng trên VN30F
VN30F là futures chỉ một sản phẩm — không pairs trade trực tiếp, nhưng có thể áp dụng cointegration:
- VN30F vs spot VN30 index: Basis = $F - S \cdot e^{(r-q)\tau}$ thường mean-reverting; nếu cointegrated, basis arbitrage khi |basis z-score| > 2.
- VN30F vs ETF E1VFVN30: ETF tracking VN30, có thể có premium/discount intra-day.
- VN30F vs rổ cổ phiếu trọng số lớn (VCB, VIC, HPG, VHM): test cointegration giữa VN30F và linear combination top-10 components → giao dịch divergence khi index futures lệch rổ.
Quy trình:
- Lấy log-prices, run Engle-Granger 2-step trên window 252 ngày rolling.
- Nếu ADF p < 0.05 → cặp valid; tính z-score $\hat{u}_t$; entry $|z|>2$, exit $|z|<0.5$, stop $|z|>4$.
- Re-test cointegration hàng tuần — nếu break, đóng vị thế ngay.
Code minh họa (Python)
import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller, coint
def engle_granger_test(y: pd.Series, x: pd.Series):
"""Step 1: OLS regression. Step 2: ADF on residuals."""
X = sm.add_constant(x)
model = sm.OLS(y, X).fit()
beta = model.params.iloc[1]
residuals = model.resid
adf_stat, p_value, *_ = adfuller(residuals, maxlag=1, autolag=None)
return {
'beta': beta,
'adf_stat': adf_stat,
'p_value': p_value,
'cointegrated': p_value < 0.05,
'residuals': residuals,
}
def half_life_ou(residuals: pd.Series) -> float:
"""Half-life of mean reversion via OU fit on residuals."""
z = residuals.dropna()
z_lag = z.shift(1).dropna()
delta_z = z.diff().dropna()
z_lag, delta_z = z_lag.align(delta_z, join='inner')
alpha = sm.OLS(delta_z, sm.add_constant(z_lag)).fit().params.iloc[1]
return -np.log(2) / alpha if alpha < 0 else np.inf
def pairs_signal(y: pd.Series, x: pd.Series, lookback=252, entry=2.0, exit=0.5):
rolling_beta = []
z_scores = []
for t in range(lookback, len(y)):
ys, xs = y.iloc[t-lookback:t], x.iloc[t-lookback:t]
res = engle_granger_test(ys, xs)
if not res['cointegrated']:
rolling_beta.append(np.nan); z_scores.append(np.nan); continue
beta = res['beta']
spread = y.iloc[t] - beta * x.iloc[t]
mu, sigma = res['residuals'].mean(), res['residuals'].std()
z = (spread - mu) / sigma
rolling_beta.append(beta); z_scores.append(z)
return pd.Series(z_scores, index=y.index[lookback:])Tài liệu tham khảo
- Engle, R. F., & Granger, C. W. J. (1987). Co-integration and error correction: Representation, estimation, and testing. Econometrica, 55(2), 251-276.
- Johansen, S. (1991). Estimation and hypothesis testing of cointegration vectors in Gaussian vector autoregressive models. Econometrica, 59(6), 1551-1580.
- Vidyamurthy, G. (2004). Pairs Trading: Quantitative Methods and Analysis. Wiley.