Signal 14 — Kalman Filter Adaptive Pairs Spread
Khác biệt với pairs trading cổ điển
Gatev-Goetzmann-Lesmond 2006 dùng fixed hedge ratio ước lượng OLS trên formation period. Vấn đề: hệ số β thay đổi theo regime. Kalman Filter cho phép β_t cập nhật mỗi tick mới — adaptive cointegration. Phù hợp khi pair có structural break (vd. tăng/giảm sector weight).
| Thuộc tính | Giá trị |
|---|---|
| Phân loại | Statistical Arbitrage / Mean-Reversion |
| Khung thời gian | Daily / 1H |
| Paper gốc | Ernest Chan (2013); Triantafyllopoulos-Montana (2011) |
| Loại dữ liệu | Hai chuỗi giá close của asset cointegrated |
| Hướng giao dịch | Long-short delta-neutral spread |
| Capacity | Trung bình (phụ thuộc liquidity pair) |
Ý tưởng (Concept)
Pair trading cổ điển: nếu Y_t ≈ α + β X_t + ε_t cointegrated, thì spread_t = Y_t - β X_t - α mean-revert. Nhưng β thay đổi theo thời gian — vd. tương quan giữa VN30F và US ES1! biến động khi global risk-on/off.
Kalman Filter mô hình β_t như hidden state với dynamics: $$ \beta_t = \beta_{t-1} + \eta_t, \quad \eta_t \sim \mathcal{N}(0, Q) $$ $$ Y_t = \beta_t X_t + \varepsilon_t, \quad \varepsilon_t \sim \mathcal{N}(0, R) $$
Filter cập nhật β_t đệ quy mỗi observation, đồng thời output innovation e_t = Y_t - \hat{Y}_t và innovation variance S_t. z-score chuẩn hóa: z_t = e_t / sqrt(S_t) → khi |z_t| > 2, signal mean-reversion.
Công thức (Formula)
State-space:
- State:
β_t(1-D hoặc 2-D nếu thêm intercept). - Observation:
Y_t = H_t · β_t + ε_tvớiH_t = [X_t, 1].
Kalman recursion:
# Predict
β_pred = β
P_pred = P + Q
# Innovation
e = Y - H · β_pred
S = H · P_pred · H' + R
# Update
K = P_pred · H' / S # Kalman gain
β = β_pred + K · e
P = P_pred - K · H · P_pred
# Normalized residual (z-score)
z = e / sqrt(S)Trading signal: z_t mean-revert quanh 0.
Cách giao dịch (Entry/Exit Rules)
- Entry SHORT spread (
Y - βX):z_t > +2.0(spread quá cao).- Short
Y, longβ_t × X(delta-neutral).
- Entry LONG spread:
z_t < -2.0.- Long
Y, shortβ_t × X.
- Exit: khi
|z_t| < 0.5(mean revert). - Stop loss:
|z_t| > 4.0(break of mean-reversion regime, exit ngay).- Half-life decay check: nếu spread không revert trong
T_half = log(2) / |κ|ngày, đóng.
- Timeframe khuyến nghị: Daily cho equities; 1H cho crypto pairs.
Code Python (Python Implementation)
import numpy as np
import pandas as pd
from pykalman import KalmanFilter
def kalman_pairs(Y: pd.Series, X: pd.Series,
delta: float = 1e-4, R_obs: float = 1e-3):
"""
Y, X: two cointegrated price series (aligned).
delta: trans_cov for hidden beta drift (smaller = slower adaptation).
Returns: pd.DataFrame with beta, alpha, residual (e), z-score.
"""
obs_mat = np.vstack([X.values, np.ones(len(X))]).T[:, np.newaxis] # (T,1,2)
trans_cov = delta / (1 - delta) * np.eye(2)
kf = KalmanFilter(
n_dim_obs=1, n_dim_state=2,
initial_state_mean=np.zeros(2),
initial_state_covariance=np.ones((2, 2)),
transition_matrices=np.eye(2),
observation_matrices=obs_mat,
observation_covariance=R_obs,
transition_covariance=trans_cov,
)
state_means, state_covs = kf.filter(Y.values)
beta_t = pd.Series(state_means[:, 0], index=Y.index)
alpha_t = pd.Series(state_means[:, 1], index=Y.index)
pred = beta_t * X + alpha_t
residual = Y - pred
# Innovation variance ≈ H·P·H' + R
obs_var = np.array([
obs_mat[i, 0, :] @ state_covs[i] @ obs_mat[i, 0, :].T + R_obs
for i in range(len(Y))
])
z_score = residual / np.sqrt(obs_var)
return pd.DataFrame({
'beta': beta_t, 'alpha': alpha_t,
'residual': residual, 'z_score': z_score,
})
def generate_signals(z: pd.Series, entry: float = 2.0, exit_thresh: float = 0.5):
pos = pd.Series(0, index=z.index)
in_short, in_long = False, False
for t in range(1, len(z)):
if not in_short and not in_long:
if z.iat[t] > entry: in_short = True; pos.iat[t] = -1
elif z.iat[t] < -entry: in_long = True; pos.iat[t] = 1
elif in_short:
if z.iat[t] < exit_thresh: in_short = False; pos.iat[t] = 0
else: pos.iat[t] = -1
elif in_long:
if z.iat[t] > -exit_thresh: in_long = False; pos.iat[t] = 0
else: pos.iat[t] = 1
return pos
# Example
# df = pd.read_csv('pair_data.csv', parse_dates=['date'], index_col='date')
# kf_out = kalman_pairs(df['VN30F'], df['ES1'])
# signals = generate_signals(kf_out['z_score'])Ưu nhược điểm
Ưu điểm:
- Adaptive
β— handle regime change tốt hơn fixed OLS. - Output natural z-score và confidence (innovation variance) cho risk sizing.
- Recursive update O(1) per tick → low latency.
Nhược điểm:
- Tuning sensitivity:
delta(transition cov) khó chọn — quá nhỏ thì như OLS, quá lớn thì β quá noisy. - Pair chưa cointegrated thực sự sẽ làm Kalman tracking noise vô nghĩa. Phải Engle-Granger / Johansen test trước.
- Trên VN futures, pair candidates hạn chế: VN30F vs VN30-spot (basis), VN30F vs ES1!, VN30F vs sector ETF.
- Khi correlation collapse (crisis), tail risk lớn.