Signal 18 — CUSUM Event Filter
Cảnh báo
CUSUM filter là sampling mechanism — quyết định khi nào label/trade, không phải trade hướng nào. Output của nó là tập hợp event timestamps, dùng để feed vào triple-barrier labeling hoặc thuật toán ML.
| Thuộc tính | Giá trị |
|---|---|
| Phân loại | Event Sampling / Change-Point Detection |
| Khung thời gian | Tick / 1m / 5m / Daily (flexible) |
| Paper gốc | López de Prado (2018), AFML Ch. 2 |
| Loại dữ liệu | Log-prices (any frequency) |
| Hướng giao dịch | Sampling trigger (kết hợp với strategy chính) |
| Capacity | Tùy strategy ghép — bản thân không trade |
Ý tưởng (Concept)
Hầu hết quant strategy bị two-fold problem:
- Time bars over-sample trong giai đoạn yên lặng (1m bar lúc 11h trưa ít info), và under-sample trong giai đoạn nhiễu (1m bar lúc news release chứa nhiều info).
- Nếu label/trade mỗi bar đều nhau → mô hình ML học sequence trống rỗng, học noise.
López de Prado đề xuất CUSUM filter — sampling chỉ khi cumulative log-return từ event trước vượt threshold $h$:
$$S_t^+ = \max{0,\ S_{t-1}^+ + y_t}, \quad S_t^- = \min{0,\ S_{t-1}^- + y_t}$$
trong đó $y_t = \log(p_t / p_{t-1})$. Khi $S_t^+ > h$ hoặc $S_t^- < -h$, trigger event và reset $S^\pm = 0$.
Trực giác: chỉ "đánh thức" hệ thống khi giá đã di chuyển đủ xa — bỏ qua sideways chop. Output là chuỗi timestamps không đều, dày trong vol cao, thưa trong vol thấp. Khi feed vào ML/labeling, mỗi sample mang lượng information đồng đều — RBF-equivalent của information-driven bars (volume bars, dollar bars).
Khái niệm CUSUM gốc đến từ statistical process control (Page 1954) — phát hiện change-point trong manufacturing. AFML dùng cho financial change-point/event detection.
Công thức (Formula)
Symmetric CUSUM:
$$S_t^+ = \max{0,\ S_{t-1}^+ + (y_t - \mathbb{E}[y_t])}$$ $$S_t^- = \min{0,\ S_{t-1}^- + (y_t - \mathbb{E}[y_t])}$$
Trigger: event tại $t$ nếu $S_t^+ \geq h$ (upward shift) hoặc $S_t^- \leq -h$ (downward shift). Sau trigger: reset $S^\pm = 0$.
Threshold $h$: thường set bằng rolling volatility của log returns × $k$ (e.g. $k$ = 2 đến 4):
$$h_t = k \cdot \sigma_t^{(\text{rolling EWMA})}$$
Volatility-scaled threshold đảm bảo số event ~ tỉ lệ phần trăm cố định của thời gian, không bị flood trong high-vol regime.
Cách giao dịch (Entry/Exit Rules)
CUSUM filter không tự generate trade — nó là building block cho 2 pipeline chính:
Pipeline A — Triple-Barrier Labeling (AFML Ch. 3):
- Detect CUSUM events ${t_i}$.
- Tại mỗi $t_i$, đặt 3 barriers: profit-take (PT), stop-loss (SL), time-out (T).
- Label sample bằng barrier nào hit trước (+1, -1, 0).
- Train ML classifier (RF, XGBoost) trên features tại $t_i$, predict label.
- Trade khi predicted prob > threshold.
Pipeline B — Event-Driven Strategy đơn giản:
- Tại mỗi CUSUM event: tính direction signal (vd. tick rule: sign của return trong N tick trước event).
- Entry direction = sign; size = inverse vol.
- Exit: time-out 1H hoặc next CUSUM event.
- Stop loss: -2 ATR(14).
Hyperparameter khuyến nghị:
- Daily VN30F: $h = 2 \cdot \sigma_{20d}$ (~2% return).
- 1-minute VN30F: $h = 3 \cdot \sigma_{60m, EWMA}$.
- Số event ideal: 20–100 per năm cho daily, 5–20 per ngày cho intraday.
Code Python (Python Implementation)
import numpy as np
import pandas as pd
def cusum_filter(log_prices: pd.Series, threshold) -> pd.DatetimeIndex:
"""
Symmetric CUSUM event filter.
log_prices: pd.Series of log-prices, indexed by datetime.
threshold: scalar (constant h) OR pd.Series same index (time-varying h_t).
Returns timestamps of events.
"""
events = []
s_pos, s_neg = 0.0, 0.0
diff = log_prices.diff().dropna()
if np.isscalar(threshold):
h_t = pd.Series(threshold, index=diff.index)
else:
h_t = threshold.reindex(diff.index).ffill()
for t, y in diff.items():
s_pos = max(0.0, s_pos + y)
s_neg = min(0.0, s_neg + y)
if s_pos >= h_t.loc[t]:
events.append(t)
s_pos = 0.0
elif s_neg <= -h_t.loc[t]:
events.append(t)
s_neg = 0.0
return pd.DatetimeIndex(events)
def ewma_volatility(log_prices: pd.Series, span: int = 60) -> pd.Series:
"""EWMA-style volatility of log returns."""
return log_prices.diff().ewm(span=span).std()
def cusum_events_volatility_scaled(prices: pd.Series,
k: float = 3.0,
span: int = 60) -> pd.DatetimeIndex:
"""CUSUM events với threshold = k × EWMA vol."""
log_prices = np.log(prices)
h_t = k * ewma_volatility(log_prices, span=span)
return cusum_filter(log_prices, threshold=h_t)
def triple_barrier_labels(prices: pd.Series, events: pd.DatetimeIndex,
pt: float = 0.02, sl: float = 0.01,
timeout: pd.Timedelta = pd.Timedelta('1D')) -> pd.DataFrame:
"""
Triple-barrier labeling cho mỗi event.
pt = profit take return; sl = stop loss return; timeout = max horizon.
Direction giả định long; có thể mở rộng cho meta-labeling.
"""
out = []
for t0 in events:
window = prices.loc[t0 : t0 + timeout]
if window.empty:
continue
rets = window / prices.loc[t0] - 1
pt_hit = (rets >= pt).idxmax() if (rets >= pt).any() else pd.NaT
sl_hit = (rets <= -sl).idxmax() if (rets <= -sl).any() else pd.NaT
times = [t for t in (pt_hit, sl_hit, window.index[-1]) if pd.notna(t)]
first = min(times)
if first == pt_hit: label = 1
elif first == sl_hit: label = -1
else: label = 0
out.append({'t0': t0, 't1': first, 'label': label,
'ret': prices.loc[first] / prices.loc[t0] - 1})
return pd.DataFrame(out).set_index('t0')
# Ví dụ usage trên VN30F daily
# df = pd.read_csv('vn30f_daily.csv', parse_dates=['date'], index_col='date')
# events = cusum_events_volatility_scaled(df['close'], k=3.0, span=20)
# labels = triple_barrier_labels(df['close'], events, pt=0.03, sl=0.015,
# timeout=pd.Timedelta('5D'))
# print(f"Số events: {len(events)}, Label distribution: {labels['label'].value_counts()}")Ưu nhược điểm
Ưu điểm:
- Loại noise rất hiệu quả — bỏ qua sideways → tăng SNR cho ML model.
- Information-equivalent sampling — mỗi event chứa lượng info đồng đều, tránh autocorrelation giả tạo trong time bars.
- Flexible threshold — có thể scale theo vol, volume, ATR, hoặc constant.
- Là building block chuẩn cho AFML pipeline (kết hợp triple-barrier, meta-labeling, sample weighting).
Nhược điểm:
- Không phải trade signal — phải ghép với strategy/model khác.
- Sample timestamps không đều → khó tính daily P&L, Sharpe theo cách truyền thống; cần adjustment.
- Hyperparameter $h$ và $k$ ảnh hưởng số event và label distribution mạnh — cần walk-forward optimization.
- Trên very illiquid asset (VN30F sau hours), thiếu tick → CUSUM ít hữu ích.
Liên kết
- Theory: [[lopez-de-prado-2018]] (AFML Ch. 2 & 3 — sampling và labeling).
- Complement: [[market-regime-filter]] (gate strategy), [[hurst-exponent]] (trend regime).
- Down-stream: kết hợp với [[ofi]], [[badr]] hoặc [[microprice]] làm feature tại event timestamps.