VPIN — Volume-Synchronized Probability of Informed Trading
Nguồn: Easley, López de Prado, O'Hara (2012, Review of Financial Studies) Loại: Tín hiệu microstructure / flow toxicity Tag:
moi:2026-05-16#microstructure#flow-toxicity#hft
Bản chất tín hiệu
VPIN là phiên bản đo lường được trong thời gian thực của chỉ số PIN (Probability of Informed Trading — Easley 1996). Trong khi PIN gốc cần fit Maximum Likelihood trên dữ liệu daily và mất cả tuần để cập nhật, VPIN tính theo "volume time" thay vì "clock time" — cứ mỗi volume bucket đầy thì cập nhật một giá trị mới. Điều này biến nó thành chỉ báo real-time về độc tính của order flow.
Ý nghĩa cốt lõi: VPIN cao = nhiều khả năng có trader có thông tin tốt hơn (informed trader) đang giao dịch một chiều — market maker sẽ thua. VPIN thấp = order flow cân bằng, market maker an toàn. Tài liệu gốc dùng VPIN để giải thích Flash Crash 6/5/2010 trên thị trường Mỹ: VPIN tăng vọt vài giờ trước flash crash, cảnh báo độc tính tăng cao.
Cấu trúc tính toán:
- Chia khối lượng giao dịch thành các bucket có volume bằng nhau $V$ (ví dụ $V$ = 1/50 ADV).
- Trong mỗi bucket, dùng quy tắc Bulk Volume Classification (BVC) để chia volume thành buy-side $V_B$ và sell-side $V_S$ theo standardized return của bucket:
$$V_B^\tau = V \cdot Z\left(\frac{\Delta P^\tau}{\sigma_{\Delta P}}\right), \quad V_S^\tau = V - V_B^\tau$$
trong đó $Z(\cdot)$ là CDF chuẩn (hoặc Student-t).
- VPIN trên cửa sổ $n$ bucket gần nhất:
$$\text{VPIN} = \frac{\sum_{\tau=1}^{n} |V_B^\tau - V_S^\tau|}{n \cdot V}$$
Giá trị thuộc [0, 1]. Trên cổ phiếu Mỹ ngưỡng cảnh báo thường là VPIN > 0.40-0.45.
Cơ chế hoạt động & lợi thế so với BADR/OFI
So với OFI và BADR (đã có trong thư viện): OFI/BADR là microstructure level-1 (best bid/ask), nhanh nhưng dễ noisy và bị manipulation bằng spoofing. VPIN khác về chất:
- Tính theo volume time → tự động "co giãn" theo nhịp thị trường: phiên thanh khoản cao bucket close nhanh, vol thấp bucket close chậm.
- Bắt imbalance dài hạn (10-100 bucket), khó bị fake bằng vài lệnh.
- Có ngữ nghĩa rõ ràng: "xác suất bucket kế tiếp đến từ informed flow".
Hạn chế: chậm hơn OFI vài giây tới vài phút (tùy size bucket), không phù hợp scalping tick-by-tick mà phù hợp swing intraday (giữ 15 phút - vài giờ).
Ứng dụng giao dịch chính
Ba use case thực tế:
Cảnh báo flash crash / dump: khi VPIN > p95 lịch sử và đang tăng, tạm thời thu hẹp spread quote (market maker), hoặc tắt lệnh giao dịch nhà nước (taker).
Filter cho strategy mean-reversion: VPIN cao báo hiệu giá đang được drive bởi informed flow, không phải noise → mean-reversion sẽ thua. Chỉ trade MR khi VPIN < median.
Confirmation cho breakout: khi giá vượt resistance + VPIN tăng → khả năng cao là informed breakout (giữ vị thế dài hơn). Vượt resistance nhưng VPIN giảm → fake breakout (cắt sớm).
Áp dụng đa thị trường
VN30F (Hợp đồng tương lai chỉ số Việt Nam)
- Bucket size V ≈ 5.000 contracts (1/50 ADV ~ 250k). Cửa sổ n = 50 bucket ~ 1 phiên.
- σ_ΔP: rolling 100 bucket, refresh khi bucket mới đóng.
- Ngưỡng: VPIN > 0.35 "watch", > 0.50 "cảnh báo cao".
- Bỏ qua bucket trong 15 phút ATO/ATC (BVC kém chính xác).
- VPIN tăng quanh đáo hạn (3rd Thu) và trước CPI/GDP releases — đây là chỉ báo có giá trị, không phải noise.
US equity futures (ES, NQ, RTY)
- VPIN gốc của Easley-LdP-O'Hara được calibrate trên ES — đây là sản phẩm "home turf".
- ES: bucket V ≈ 30.000 contracts (1/50 ADV ~ 1.5M), n = 50 buckets ~ 1 phiên RTH (regular trading hours).
- NQ tương tự ES nhưng V ≈ 12.000.
- Vai trò lịch sử của VPIN ở US: cảnh báo Flash Crash 6/5/2010 trên ES vài giờ trước event — bài gốc của LdP-O'Hara là chứng minh empirical này.
- Ngưỡng calibrated: trên ES, VPIN > 0.40 là watch, > 0.50 là alert. Trong 2015-2024, VPIN > 0.45 trong 2+ giờ có conditional probability ~ 30% cho |return tomorrow| > 2σ.
- Globex extended hours: VPIN cao trong overnight session có meaning khác (ít liquidity provider) — calibrate riêng cho ETH vs RTH.
Crypto spot (BTC, ETH trên CEX major)
- Crypto market 24/7 — không có "phiên" để align bucket. Dùng pure volume time.
- BTC spot ADV trên Binance ~ 500.000 BTC ~ $30B. V ≈ 10.000 BTC/bucket. n = 50.
- Khác biệt quan trọng: BVC trên crypto có thể dùng tick rule (uptick = buy, downtick = sell) trực tiếp nếu có tick data — chính xác hơn BVC. BVC dùng khi chỉ có aggregated trade data.
- VPIN crypto thường swing rộng hơn equity (0.30-0.70) vì retail flow toxic. Cảnh báo dump: VPIN > 0.60 kèm funding rate > 0.05% / 8h trên perp.
- Liquidation cascade prediction: VPIN spike + open interest spike + funding flip → cascade probability cao trong 4-8h tới.
Crypto perpetual futures
- Perp có 2 nguồn flow toxicity: organic price discovery + funding rate arbitrage. Phải tách riêng.
- BTC perp trên Binance/Bybit: V ≈ 5.000 BTC/bucket, bucket nhanh hơn spot do volume cao hơn.
- Funding rate enhanced VPIN: signal = VPIN × |funding_rate|. Khi cả VPIN cao và funding cực đoan cùng lúc → squeeze warning rất mạnh.
- Use case quan trọng: trước khi enter mean-reversion fade trên perp, check VPIN_perp / VPIN_spot. Nếu > 1.5 → fade rủi ro (perp đang bị informed flow drive).
Cân nhắc cross-market chung
- Bucket size scaling: V = 1/50 ADV là rule chung, nhưng crypto có thể cần V nhỏ hơn (1/100 ADV) do volume profile tail-heavy.
- Distribution của ΔP: Student-t df ~ 4 cho crypto thay vì normal — sửa BVC dùng t-CDF.
- Reference vs adaptive threshold: dùng quantile lịch sử của chính market đó (e.g. 80th percentile) thay vì threshold hardcoded — mỗi market có "natural" VPIN range khác.
- Combine với OFI: OFI fast, VPIN slow. Combo confirm signal mạnh hơn dùng riêng.
Minh họa Python
import numpy as np
import pandas as pd
from scipy.stats import norm
def compute_vpin(trades: pd.DataFrame,
bucket_volume: float,
window_buckets: int = 50) -> pd.DataFrame:
"""
Tính VPIN từ tick-level trade data.
trades: DataFrame có 2 cột tối thiểu — 'price' (giá khớp), 'volume' (số hợp đồng).
Index là timestamp.
bucket_volume: khối lượng mỗi bucket (e.g. 5000 contracts).
window_buckets: số bucket trong cửa sổ tính VPIN (e.g. 50).
"""
# Bước 1: gán bucket id cho từng trade theo cumulative volume
cum_vol = trades['volume'].cumsum()
trades = trades.copy()
trades['bucket_id'] = (cum_vol // bucket_volume).astype(int)
# Bước 2: aggregate per bucket — last price và sum volume
bucket_df = trades.groupby('bucket_id').agg(
ts_end=('volume', lambda x: x.index[-1]),
last_price=('price', 'last'),
first_price=('price', 'first'),
bucket_vol=('volume', 'sum')
)
# Trả delta price của bucket
bucket_df['delta_p'] = bucket_df['last_price'].diff()
# Bước 3: BVC — phân loại buy/sell volume theo Z-score của delta_p
sigma = bucket_df['delta_p'].rolling(100, min_periods=20).std()
z = bucket_df['delta_p'] / sigma
bucket_df['vol_buy'] = bucket_df['bucket_vol'] * norm.cdf(z)
bucket_df['vol_sell'] = bucket_df['bucket_vol'] - bucket_df['vol_buy']
bucket_df['imbalance'] = (bucket_df['vol_buy'] - bucket_df['vol_sell']).abs()
# Bước 4: VPIN rolling
bucket_df['vpin'] = (bucket_df['imbalance'].rolling(window_buckets).sum() /
(window_buckets * bucket_volume))
return bucket_df[['ts_end', 'last_price', 'bucket_vol',
'vol_buy', 'vol_sell', 'vpin']]
def vpin_regime_filter(vpin_series: pd.Series,
low_q: float = 0.30,
high_q: float = 0.70) -> pd.Series:
"""Phân loại regime theo quantile VPIN lịch sử."""
low = vpin_series.expanding(min_periods=200).quantile(low_q)
high = vpin_series.expanding(min_periods=200).quantile(high_q)
regime = pd.Series('normal', index=vpin_series.index)
regime[vpin_series < low] = 'safe_flow' # mean-reversion OK
regime[vpin_series > high] = 'toxic_flow' # tắt MR, follow trend
return regime
# Sử dụng:
# vp_df = compute_vpin(vn30f_trades, bucket_volume=5000, window_buckets=50)
# regime = vpin_regime_filter(vp_df['vpin'])