Signal 15 — Ornstein-Uhlenbeck Mean Reversion
Tại sao quan trọng
Trong khi z-score rolling cho ta "spread đang xa mean bao nhiêu σ", OU model cho thêm half-life — câu trả lời cho câu hỏi sống còn: spread sẽ mất bao lâu để về mean? Nếu half-life > horizon trade của bạn, signal vô nghĩa.
| Thuộc tính | Giá trị |
|---|---|
| Phân loại | Mean-Reversion / Stochastic Process |
| Khung thời gian | Daily / 1H / 15min |
| Paper gốc | Uhlenbeck-Ornstein (1930); Avellaneda-Lee (2010) |
| Loại dữ liệu | Spread series (pair, basis, etc.) |
| Hướng giao dịch | Mean-revert; long khi z<−2, short khi z>+2 |
| Capacity | Trung bình (phụ thuộc spread instrument) |
Ý tưởng (Concept)
Stochastic process OU mô tả một biến động lực mean-reverting: $$ dX_t = \kappa(\theta - X_t), dt + \sigma, dW_t $$
trong đó:
θ: long-term mean (mức "công bằng").κ: tốc độ mean reversion. Cao → revert nhanh.σ: volatility.
Hệ quả quan trọng — half-life: $$ T_{1/2} = \frac{\ln 2}{\kappa} $$
Đây là expected time để gap về mean giảm một nửa. Nếu bạn entry tại |z| = 2σ và T_half = 5 days, expected exit ~5 ngày sau ở |z| ≈ 1σ.
Equilibrium distribution: X_∞ ~ N(θ, σ²/(2κ)). → standardized z-score: (X - θ) / sqrt(σ²/(2κ)).
Công thức (Formula)
Ước lượng OU bằng OLS trên discrete equation: $$ X_t - X_{t-1} = \alpha + \beta X_{t-1} + \varepsilon_t $$
trong đó:
β = -κ Δt→κ = -β / Δtα = κ θ Δt→θ = α / κ Δt = -α / βσ² = Var(ε) / Δt
Trading thresholds (Bertram 2010 — optimal entry/exit):
- Entry:
|z| > z_entry(thường 1.5-2.0). - Exit:
|z| < z_exit(thường 0-0.5). - z =
(X_t - θ) / sqrt(σ²/(2κ)).
Cách giao dịch (Entry/Exit Rules)
- Pre-condition:
- OU β phải < 0 và significant (t-stat < -2.5).
- Half-life giữa 1-30 ngày (nếu daily). Quá nhanh = noise, quá chậm = trend không phải revert.
- ADF test reject unit root (p < 0.05).
- Entry LONG:
z < -2.0và half-life < horizon plan. - Entry SHORT:
z > +2.0và half-life < horizon plan. - Exit:
|z| < 0.5hoặc time = 2× T_half. - Stop loss:
|z| > 3.5(regime broken) hoặcholding > 3 × T_half. - Sizing:
notional ∝ 1/σ_zđể equalize risk across spreads. - Timeframe khuyến nghị: Daily cho equity spreads, 1H cho FX/crypto, 15min cho intraday VN30F basis.
Code Python (Python Implementation)
import numpy as np
import pandas as pd
from scipy import stats
def fit_ou(series: pd.Series, dt: float = 1.0) -> dict:
"""Estimate OU parameters via OLS on Δx = α + β x_{t-1} + ε."""
x = series.dropna().values
dx = np.diff(x)
x_lag = x[:-1]
slope, intercept, r_value, p_value, std_err = stats.linregress(x_lag, dx)
beta = slope
alpha = intercept
if beta >= 0:
return {'kappa': np.nan, 'theta': np.nan, 'sigma': np.nan,
'half_life': np.inf, 'p_value': p_value,
'mean_reverting': False}
kappa = -beta / dt
theta = -alpha / beta
resid = dx - (alpha + beta * x_lag)
sigma = resid.std() / np.sqrt(dt)
half_life = np.log(2) / kappa
return {
'kappa': kappa, 'theta': theta, 'sigma': sigma,
'half_life': half_life, 'p_value': p_value,
'mean_reverting': p_value < 0.05 and half_life > 0,
}
def ou_zscore(series: pd.Series, params: dict) -> pd.Series:
"""Z-score against OU equilibrium distribution."""
eq_std = params['sigma'] / np.sqrt(2 * params['kappa'])
return (series - params['theta']) / eq_std
def ou_signal(series: pd.Series, lookback: int = 252,
entry: float = 2.0, exit_thresh: float = 0.5) -> pd.DataFrame:
"""Rolling OU fit + entry/exit signals."""
out = []
for t in range(lookback, len(series)):
window = series.iloc[t - lookback:t]
p = fit_ou(window)
if not p['mean_reverting']:
out.append((series.index[t], np.nan, 0))
continue
z = (series.iat[t] - p['theta']) / (p['sigma'] / np.sqrt(2 * p['kappa']))
sig = -1 if z > entry else (1 if z < -entry else (0 if abs(z) < exit_thresh else np.nan))
out.append((series.index[t], z, sig))
df = pd.DataFrame(out, columns=['date', 'z', 'signal']).set_index('date')
df['signal'] = df['signal'].ffill().fillna(0)
return df
# Example: VN30F basis (futures - spot)
# basis = df['vn30f_close'] - df['vn30_spot']
# params = fit_ou(basis.iloc[-252:])
# print(f"Half-life: {params['half_life']:.1f} days, κ={params['kappa']:.3f}")
# signals = ou_signal(basis)Ưu nhược điểm
Ưu điểm:
- Cung cấp half-life — tham số quan trọng để chọn horizon (filter signals).
- Bertram 2010 cho công thức optimal threshold maximize Sharpe.
- Áp dụng đa năng: pairs spread, basis (futures-spot), yield curve, vol carry.
- Kết hợp tốt với [[kalman-pairs-spread]] — dùng Kalman để tracking spread, OU để định cấp entry/exit.
Nhược điểm:
- Giả định constant parameters trong lookback window — vi phạm khi regime change.
- ADF test có low power với chuỗi ngắn (<252 obs).
- Khi half-life > lookback, fit không ổn định.
- Trên trending series (không cointegrated), OU fit cho giả mean-reversion → false signals. Phải pre-screen bằng [[hurst-exponent]] (H < 0.5).