Time-Series Momentum (TSMOM)
Nguồn: Moskowitz, Ooi, Pedersen (2012, Journal of Financial Economics) Loại: Tín hiệu trend-following per-asset Tag:
moi:2026-05-16#momentum#trend-following#cta#cross-asset
Bản chất tín hiệu
Khác với Cross-Sectional Momentum (Jegadeesh-Titman 1993, đã có trong thư viện) — so sánh tài sản với nhau và mua top/bán bottom — Time-Series Momentum so sánh tài sản với chính nó trong quá khứ:
$$\text{Signal}t = \text{sign}(R)$$
Quy tắc đơn giản: nếu lợi suất 12 tháng (bỏ 1 tháng gần nhất) dương → long; âm → short. Position size scale theo volatility target:
$$w_t = \text{sign}(R_{t-12m,t-1m}) \cdot \frac{\sigma_{target}}{\sigma_t^{ex-ante}}$$
Moskowitz-Ooi-Pedersen (MOP 2012) chứng minh TSMOM hoạt động trên 58 tài sản trải khắp 4 lớp (equity index futures, bond futures, FX, commodity futures) trong 25 năm 1985-2009. Mean Sharpe per-asset ~ 0.3-0.4; portfolio Sharpe (equal vol-weighted) ~ 1.0-1.2.
Đặc tính lý thuyết quan trọng:
- TSMOM có payoff dạng straddle — kiếm tiền trong cả uptrend lẫn downtrend, lỗ trong choppy range.
- Tương quan dương với volatility risk premium ngược dấu — TSMOM hưởng lợi khi vol bùng nổ kèm theo trend, thua khi vol bùng nổ mà không có trend.
- "Crisis alpha": TSMOM thường có lợi suất dương lớn trong các crisis kéo dài (2008 GFC, 2020 COVID dump) do trend down rõ rệt.
Khác biệt sống còn so với Cross-Sectional Momentum:
| Chiều so sánh | Cross-Sectional | Time-Series |
|---|---|---|
| Universe | Nhiều tài sản | Từng tài sản độc lập |
| Cần short bán | Có (long top — short bottom) | Không bắt buộc (có thể long-only flat) |
| Hoạt động khi | Có winners + losers rõ rệt | Có trend trên từng tài sản |
| Drawdown | Khi cross-section reverse | Khi tất cả tài sản choppy |
Cấu trúc lookback và variations
TSMOM gốc dùng 12-1 lookback (12 tháng bỏ tháng gần nhất). Các biến thể phổ biến:
- EWMA TSMOM: thay vì lookback cố định, dùng signal = EWMA(returns, halflife=63d). Mượt hơn, ít whipsaw.
- Multi-horizon ensemble: trung bình của signals 1m, 3m, 6m, 12m. Robust hơn trên dataset có regime shift.
- Risk-adjusted TSMOM: signal = $R_{lookback} / \sigma_{lookback}$ thay vì raw return. Đây là chuẩn quasi-Sharpe được dùng nhiều ở CTA.
Volatility scaling: $\sigma_t^{ex-ante}$ thường tính bằng EWMA(σ², halflife=60d) hoặc GARCH(1,1) forecast. Target vol cho retail ~ 10-15%, prop ~ 20-25%.
Ứng dụng giao dịch chính
TSMOM là xương sống của các CTA fund (Winton, Man AHL, Aspect, Trend), trị giá hàng trăm tỷ USD AUM. Triển khai chuẩn:
- Chọn universe đa lớp tài sản (futures liquid).
- Tính TSMOM signal cho mỗi tài sản (sign × vol-scaling).
- Giới hạn gross exposure (e.g. sum |w_i| < 4).
- Rebalance hàng tuần hoặc daily.
- Stop-loss: khi drawdown asset > 20% → tạm tắt, chờ signal flip mới re-enter.
Áp dụng đa thị trường
VN30F (Hợp đồng tương lai chỉ số Việt Nam)
- Lookback khuyến nghị: 60-126 phiên (~ 3-6 tháng). 252 phiên cho VN có quá ít data per regime; 60 phiên cân bằng tốt.
- Vol scaling: target 15% annualized, dùng EWMA(σ, halflife=20d).
- Backtest 2018-2024 (data front month rolled): TSMOM 60d trên VN30F cho Sharpe ~ 0.6-0.8 sau cost, drawdown max ~ 18%.
- Filter regime: kết hợp với Hurst > 0.50 hoặc ADX(14) > 20 để tránh trade trong range tight.
- Lưu ý cost: VN30F phí thấp (~ 0.3-0.5 bps roundtrip), nên có thể rebalance daily không quá đắt.
US equity futures (ES, NQ, RTY, YM, MES, MNQ)
- ES + NQ là 2 sản phẩm core của mọi CTA. Lookback chuẩn 252 daily.
- TSMOM trên ES backtest 2000-2024: Sharpe ~ 0.4-0.5 standalone, lift lên 0.7-0.9 khi combined với bond futures (ZN, ZB).
- Cross-asset diversification: long-bias ES (drift up) cộng với TSMOM cho phép vol scaling — riêng signal có thể flip short trong bear market 2022.
- Mua MES/MNQ thay vì ES/NQ khi notional nhỏ (target margin < $5K).
Crypto spot (BTC, ETH, top altcoins)
- Crypto là môi trường TSMOM mạnh nhất từng được ghi nhận: Sharpe live của TSMOM 30-90d trên BTC ~ 1.0-1.5 trên 2017-2023 (theo các paper của Liu-Tsyvinski).
- Lookback nên ngắn hơn: 30-90 ngày, không phải 252. Crypto cycle ngắn hơn.
- Vol target cao hơn (25-35%) vì base vol crypto cao.
- Whipsaw rủi ro lớn quanh các sự kiện regulatory (SEC announcements, FTX collapse): khuyến nghị có hard stop khi 1-day move > 4σ.
- Triển khai trên BTC: long khi 60d return > 0 AND price > 200d MA; short khi cả hai âm; flat otherwise.
Crypto perpetual futures (BTC perp, ETH perp, altcoin perp)
- Perpetual futures cho phép leverage và short — phù hợp hoàn hảo với TSMOM full-symmetric.
- Quan trọng: tính funding-adjusted return cho signal. Funding negative + short signal = double win. Funding positive + long signal = phải net funding cost.
- Lookback 30d phổ biến hơn 90d trong cộng đồng quant crypto.
- Liquidity check: chỉ dùng altcoin perp có OI > $100M để tránh slippage thảm.
Lưu ý chung khi triển khai cross-asset
- Risk parity weighting giữa các lớp: equal contribution to portfolio variance, không equal notional. Crypto vol cao cần weight nhỏ hơn equity futures.
- Correlation regime: trong crisis macro, correlation tăng → diversification benefit giảm. Cần có cap trên gross exposure khi rolling correlation > 0.50.
- Cost realistic: VN30F ~ 0.5 bps, ES ~ 0.3 bps, BTC perp ~ 2-5 bps roundtrip. Backtest phải trừ đủ.
Minh họa Python
import numpy as np
import pandas as pd
def tsmom_signal(prices: pd.Series,
lookback: int = 60,
skip_recent: int = 0,
vol_window: int = 20,
target_vol: float = 0.15) -> pd.DataFrame:
"""
Tính TSMOM signal vol-scaled cho 1 tài sản.
prices: chuỗi giá close daily.
lookback: cửa sổ tính momentum (60d cho VN30F, 252d cho ES, 30-90d cho crypto).
skip_recent: số phiên skip ở gần nhất (0 cho intraday/crypto, 21 cho daily macro).
target_vol: vol mục tiêu annualized.
"""
log_ret = np.log(prices).diff()
# Cumulative return lookback (bỏ skip_recent phiên gần nhất)
if skip_recent > 0:
cum_ret = log_ret.shift(skip_recent).rolling(lookback - skip_recent).sum()
else:
cum_ret = log_ret.rolling(lookback).sum()
sign = np.sign(cum_ret)
# Vol ex-ante annualized
sigma_daily = log_ret.ewm(halflife=vol_window).std()
sigma_annual = sigma_daily * np.sqrt(252)
weight = sign * (target_vol / sigma_annual.replace(0, np.nan))
weight = weight.clip(-3, 3) # cap leverage
return pd.DataFrame({
'cum_return': cum_ret,
'sign': sign,
'sigma_annual': sigma_annual,
'weight': weight
})
def multi_asset_tsmom_portfolio(price_df: pd.DataFrame,
lookback_per_asset: dict,
target_vol: float = 0.15,
max_gross: float = 4.0) -> pd.DataFrame:
"""
Portfolio TSMOM trên nhiều tài sản với lookback riêng.
price_df: DataFrame, columns = asset tickers (e.g. 'VN30F', 'ES', 'BTC', 'ETH').
lookback_per_asset: dict {ticker: lookback_days}.
"""
weights = pd.DataFrame(index=price_df.index, columns=price_df.columns, dtype=float)
for asset in price_df.columns:
lb = lookback_per_asset.get(asset, 60)
sig = tsmom_signal(price_df[asset], lookback=lb, target_vol=target_vol)
weights[asset] = sig['weight']
# Risk parity: chia weight theo số tài sản có signal
n_active = (weights.abs() > 0).sum(axis=1)
weights = weights.div(n_active.replace(0, np.nan), axis=0)
# Cap gross exposure
gross = weights.abs().sum(axis=1)
scale = (max_gross / gross.replace(0, np.nan)).clip(upper=1.0)
weights = weights.mul(scale, axis=0)
return weights
# Ví dụ portfolio đa thị trường:
# lookback_cfg = {'VN30F': 60, 'ES': 252, 'NQ': 252, 'BTC': 60, 'ETH': 60}
# w = multi_asset_tsmom_portfolio(price_df, lookback_cfg, target_vol=0.15)
# pnl = (w.shift(1) * price_df.pct_change()).sum(axis=1)