Skip to content

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ínhGiá trị
Phân loạiStatistical Arbitrage / Mean-Reversion
Khung thời gianDaily / 1H
Paper gốcErnest Chan (2013); Triantafyllopoulos-Montana (2011)
Loại dữ liệuHai chuỗi giá close của asset cointegrated
Hướng giao dịchLong-short delta-neutral spread
CapacityTrung 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}_tinnovation 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 + ε_t với H_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)

python
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.

Powered by dautu.tech