Skip to content

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ínhGiá trị
Phân loạiEvent Sampling / Change-Point Detection
Khung thời gianTick / 1m / 5m / Daily (flexible)
Paper gốcLópez de Prado (2018), AFML Ch. 2
Loại dữ liệuLog-prices (any frequency)
Hướng giao dịchSampling trigger (kết hợp với strategy chính)
CapacityTùy strategy ghép — bản thân không trade

Ý tưởng (Concept)

Hầu hết quant strategy bị two-fold problem:

  1. 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).
  2. 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):

  1. Detect CUSUM events ${t_i}$.
  2. Tại mỗi $t_i$, đặt 3 barriers: profit-take (PT), stop-loss (SL), time-out (T).
  3. Label sample bằng barrier nào hit trước (+1, -1, 0).
  4. Train ML classifier (RF, XGBoost) trên features tại $t_i$, predict label.
  5. 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)

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

Powered by dautu.tech