Implementation shortfall và tự xây TCA: đo lường chi phí thực sự của việc thực thi lệnh
Mọi bàn giao dịch tổ chức đều có một pipeline phân tích chi phí giao dịch (TCA). Gần như không ai chạy bot crypto có thứ đó. Thiết lập điển hình chỉ ghi lại các fill, cộng phí, rồi gọi phần chênh lệch giữa PnL backtest và PnL thực chạy là "slippage" — một con số dư thừa duy nhất, không được giải thích, gộp chung độ trễ, spread, tác động thị trường (impact), adverse selection, và mọi lệnh không khớp đã trôi mất giá. Bạn không thể sửa một chi phí mà bạn chỉ đo bằng một con số duy nhất. Bộ máy để đo lường nó một cách đúng đắn đã tồn tại từ năm 1988, nó không phải bí mật độc quyền, và trên chính log fill của bạn, nó chỉ khoảng 200 dòng Python. Bài viết này sẽ xây dựng bộ máy đó.
Cái được không phải là một dashboard đẹp hơn. Backtest của bạn chứa một mô hình chi phí — một hằng số slippage, một xác suất khớp lệnh, một hệ số impact — và mọi tham số trong đó hiện tại đều là phỏng đoán. TCA trên các fill thực tế là chân lý duy nhất mà những tham số đó có thể được hiệu chỉnh dựa vào. Chúng tôi đã xây dựng phía mô phỏng của vòng lặp này trong Fill simulation: bậc thang từ ảo tưởng close-price đến thực tế nhận biết hàng đợi; bài viết này xây dựng phía đo lường.
Trên giấy so với thực tế: Perold thực sự đo cái gì
Thủ thuật nền tảng là của André Perold (1988, "The Implementation Shortfall: Paper Versus Reality," Journal of Portfolio Management 14(3), 4–9). Chạy song song hai danh mục. Danh mục trên giấy (paper portfolio) thực thi mọi quyết định ngay lập tức, với khối lượng không giới hạn, chi phí bằng không, tại mức giá hiện hành đúng thời điểm ra quyết định. Danh mục thực là những gì bot của bạn thực sự làm: fill từng phần, đuổi theo quote, hủy phần còn lại, phí giao dịch. Implementation shortfall là chênh lệch giữa lợi nhuận của hai danh mục đó.
Định nghĩa này quan trọng vì những gì nó từ chối che giấu. Bảng kê phí chỉ hiển thị hoa hồng. Báo cáo fill-so-với-limit-price không hiển thị gì cả (bạn không bao giờ khớp tệ hơn giá limit của mình — theo cấu trúc). Danh mục trên giấy tính phí bạn cho mọi thứ: độ trôi giá giữa lúc ra quyết định và lúc lệnh đến thị trường, spread bạn phải vượt qua, impact bạn gây ra, và — quan trọng nhất — những lệnh không bao giờ khớp trong khi giá đã chạy mất. Wagner và Edwards (1993, "Best Execution," Financial Analysts Journal 49(1), 65–71) gọi phí hiển thị là phần đỉnh tảng băng; với bất cứ thứ gì có turnover, phần chìm mới là chủ đạo.
Xác định ký hiệu. Một lệnh cha (parent order): chiều (mua/bán), khối lượng . Giá quyết định là giá giữa (mid) mà chiến lược của bạn thấy khi tín hiệu kích hoạt. Các fill đến dưới dạng với tổng khối lượng khớp . Tại thời điểm chân trời (lệnh cha hoàn tất, bị hủy, hoặc hết giờ), mid là . Phí tường minh là . Implementation shortfall tính theo đơn vị tiền tệ:
được chuẩn hóa thành điểm cơ bản (basis points) bằng cách chia cho notional trên giấy . Giá trị dương nghĩa là bạn phải trả. Số hạng đầu tiên là chi phí các fill của bạn so với danh mục trên giấy; số hạng thứ hai là chi phí cơ hội của Perold — phần chưa khớp được đánh giá theo mức giá mà nó đã trôi tới; số hạng thứ ba là phần duy nhất mà bảng kê sàn giao dịch của bạn thừa nhận.
Một lưu ý về quy ước: ngành gọi đây là "arrival price," và trong hầu hết TCA cổ phiếu, arrival nghĩa là mid tại thời điểm lệnh chạm tới thị trường. Với một bot, thời điểm quyết định và thời điểm arrival khác nhau bởi độ trễ nội bộ của chính bạn cộng với hàng đợi rate-limit — một chi phí thực, đo được. Vì vậy chúng ta giữ cả hai mốc thời gian và cả hai mức giá, để phép phân rã tách riêng chúng.
Phân rã: delay, impact, timing, cơ hội, phí
Một con số IS duy nhất cho bạn biết việc thực thi tốn kém. Nó không cho bạn biết tại sao, và cách khắc phục cho các thành phần khác nhau là hoàn toàn khác nhau — bạn không giải quyết chi phí delay và chi phí impact bằng cùng một thay đổi. Implementation shortfall mở rộng của Robert Kissell (Kissell, 2006, "The Expanded Implementation Shortfall: Understanding Transaction Cost Components," Journal of Trading 1(3), 6–16; được phát triển đầy đủ trong The Science of Algorithmic Trading and Portfolio Management, Academic Press, 2013) tách tổng chi phí thành các thành phần, mỗi thành phần gắn với một giai đoạn riêng biệt trong vòng đời lệnh. Phiên bản thực dụng, dùng arrival mid (mid tại lần xác nhận đầu tiên từ sàn):
Đẳng thức này khai triển đúng trở lại định nghĩa của Perold — mở rộng các số hạng và triệt tiêu. Mỗi phần có một "chủ sở hữu" riêng biệt:
Chi phí delay : độ trôi giá giữa lúc tín hiệu kích hoạt và lệnh con đầu tiên của bạn thực sự sống trên sàn. Đây là hạ tầng của bạn — serialization, mạng, hàng đợi rate-limit, kiểm tra rủi ro. Nếu tín hiệu của bạn có alpha ngắn hạn thực sự, chi phí delay là nơi nó rò rỉ đầu tiên; một giá trị trung bình dương ổn định cho thấy thị trường di chuyển theo hướng có lợi cho bạn trước khi bạn kịp vào lệnh — alpha momentum đang suy giảm, hoặc ai đó nhanh hơn đang giao dịch trên cùng tín hiệu.
Chi phí giao dịch : những gì các fill của bạn phải trả so với arrival — spread bị vượt qua cộng với market impact cộng với độ trôi trong lịch trình. Đây là bảng điểm của thuật toán thực thi, và là đại lượng mà nghiên cứu về thực thi thực sự mô hình hóa. Almgren, Thum, Hauptmann và Li (2005, "Direct Estimation of Equity Market Impact," Risk 18(7), 58–62) đã đo lường trên khoảng 700.000 lệnh cổ phiếu Mỹ từ các bàn giao dịch của Citigroup (tháng 12/2001–6/2003) và phát hiện chi phí giao dịch tỷ lệ với biến động hàng ngày và tỷ lệ tham gia (participation rate) — impact tạm thời tuân theo quy luật lũy thừa theo tốc độ giao dịch với số mũ gần 3/5, impact vĩnh viễn gần như tuyến tính theo khối lượng. Chúng ta sẽ dùng lại dạng hàm này khi hiệu chỉnh.
Rủi ro timing: không phải một số hạng trong phân rã trung bình mà là phương sai xung quanh nó. Trải một lệnh cha ra theo thời gian giảm impact kỳ vọng nhưng khiến bạn tiếp xúc với biến động; với một lịch trình có vị thế còn lại , độ lệch chuẩn của chi phí tỷ lệ theo . Đây chính xác là sự đánh đổi mà khung Almgren–Chriss tối ưu hóa. Trong báo cáo TCA của bạn, nó xuất hiện dưới dạng độ phân tán của IS trên các lệnh cha — hãy báo cáo độ lệch chuẩn bên cạnh mỗi giá trị trung bình, nếu không giá trị trung bình sẽ chiếm hết sự chú ý còn phần đuôi phân phối sẽ lấy hết tiền của bạn.
Chi phí cơ hội : phần khối lượng chưa khớp được đánh giá theo giá cuối cùng. Với các chiến lược thụ động (passive), đây thường xuyên là thành phần lớn nhất và ít được xem xét nhất, và nó là số hạng khiến toàn bộ khung phân tích trở nên trung thực — sẽ nói thêm ở phần bẫy, vì bỏ qua nó là cách phổ biến nhất mà người ta tự lừa dối mình bằng TCA.
Phí : phần tường minh. Trong crypto, hãy ghi log phí sau chiết khấu (bậc VIP, hoàn token quy đổi theo giá tại thời điểm khớp lệnh) và giữ dấu của rebate maker — một khoản phí âm là dữ liệu, không phải nhiễu.
Một ví dụ minh họa
Mua BTC. Tín hiệu kích hoạt với ; notional trên giấy $600.000. Lệnh con đầu tiên được xác nhận với mid . Trong hai phút tiếp theo, 8 BTC khớp tại VWAP 60.072; giá đang trôi ra xa, thuật toán tôn trọng giới hạn của nó, và 2 BTC còn lại bị hủy với mid tại . Phí gộp 2,5 bps trên notional đã khớp.
| Thành phần | Công thức | USD | bps của notional trên giấy |
|---|---|---|---|
| Delay | 120 | 2,0 | |
| Chi phí giao dịch | 480 | 8,0 | |
| Cơ hội | 456 | 7,6 | |
| Phí | 120 | 2,0 | |
| Tổng IS | 1.176 | 19,6 |
Bảng kê sàn giao dịch hiển thị $120. Chi phí thực sự để thực thi quyết định là $1.176 — gấp mười lần, với hai thành phần lớn nhất hoàn toàn vô hình đối với kế toán dựa trên phí. Khoảng 40% trong đó đến từ khối lượng chưa bao giờ được giao dịch. Một báo cáo TCA chỉ phân tích các fill sẽ chấm điểm lệnh cha này ở 8 bps và cho là ổn.

Markout: định giá adverse selection
Implementation shortfall chấm điểm lệnh cha. Nó không nói gì về chất lượng của từng fill riêng lẻ — cụ thể là, liệu bạn có hệ thống giao dịch với các đối tác biết điều gì đó mà bạn không biết hay không. Điều đó được đo bằng markout: đánh giá mỗi fill theo mid tại một mốc thời gian cố định sau khi nó xảy ra.
trong đó là giá fill tại thời điểm và là mid tại thời điểm . Đây là PnL mark-to-market trên mỗi đơn vị của fill tại chân trời , và nó có một cấu trúc rõ ràng tại : một fill maker bắt đầu ở nửa spread (bạn mua tại giá bid, mid ở trên bạn); một fill taker bắt đầu ở nửa spread. Điều gì xảy ra khi tăng lên chính là hàm lượng thông tin của giao dịch:
- s cô lập hiện tượng sniping và bắt bài quote lỗi thời (stale-quote pickoff). Nếu các fill maker của bạn đã lỗ chỉ một giây sau khi khớp, các bên tham gia nhanh hơn đang đánh vào quote của bạn ngay tại thời điểm chúng bị định giá sai — vòng lặp cập nhật quote của bạn chậm hơn vòng lặp kích hoạt của họ. Markout này là chẩn đoán độ trễ, không phải chẩn đoán chiến lược.
- s đo adverse selection kinh điển: các fill tiếp theo là chuyển động giá tiếp tục xuyên qua mức giá của bạn. Với một market maker, đây là chi phí mà việc thu spread phải vượt qua; vi cấu trúc thị trường cổ phiếu gọi đại lượng liên quan là realized spread, được thể chế hóa trong báo cáo SEC Rule 605 với chân trời 5 phút. Crypto di chuyển nhanh hơn; dải 10–60s là tương đương.
- s cho bạn biết liệu các fill có mang theo momentum bất lợi cho bạn vượt ra ngoài chân trời vi cấu trúc hay không — với các chiến lược taker, liệu alpha của tín hiệu tại 60s có vượt quá spread cộng impact mà bạn đã trả để vào lệnh hay không. Một đường cong markout taker bắt đầu tại bps và không bao giờ vượt qua mức không là một chiến lược trả tiền cho các lần vào lệnh mà alpha của nó không đủ khả năng chi trả.
Sự bất đối xứng maker/taker chính là toàn bộ kinh tế học của giao dịch thụ động thể hiện trong hai đường cong. Một đường cong maker bắt đầu tại bps (nửa spread) và suy giảm xuống bps vào lúc 60s cho thấy: bạn thu được spread nhưng trả lại nhiều hơn, và không có bậc rebate nào sửa được điều đó. Cùng đường cong đó ổn định ở bps cho thấy công cụ báo giá đang kiếm được lợi nhuận xứng đáng. Đây cũng chính xác là điều mà các backtest touch-fill ngây thơ giả định bỏ qua — một bộ mô phỏng khớp lệnh cho bạn bất cứ khi nào giá chạm tới limit của bạn bỏ qua việc được khớp lệnh có tương quan với việc bị sai, đó là lý do tại sao bậc thang nhận biết hàng đợi trong chuỗi bài fill-simulation cần markout đã đo lường làm đầu vào thay vì một giả định.

Việc tính toán là một merge_asof trên chính luồng dữ liệu mid của bạn:
import pandas as pd
def markouts(fills: pd.DataFrame, mids: pd.DataFrame,
horizons=("1s", "10s", "60s")) -> pd.DataFrame:
"""fills: [ts, price, qty, side, liquidity]; mids: [ts, mid].
Both UTC-indexed and sorted. Mid stream must be from YOUR captured
feed, not candles reconstructed later."""
fills = fills.sort_values("ts").reset_index(drop=True)
mids = mids.sort_values("ts")
out = fills.copy()
for h in horizons:
probe = fills[["ts"]].copy()
probe["ts"] = probe["ts"] + pd.Timedelta(h)
m = pd.merge_asof(probe, mids, on="ts", direction="backward")
out[f"mo_{h}"] = (fills["side"] * (m["mid"].values - fills["price"])
/ fills["price"] * 1e4)
return out
def markout_report(mo: pd.DataFrame) -> pd.DataFrame:
"""Qty-weighted markouts by liquidity flag. Weighting matters:
a 0.001 BTC fill and a 2 BTC fill are not equal evidence."""
cols = [c for c in mo.columns if c.startswith("mo_")]
def agg(g):
w = g["qty"] / g["qty"].sum()
return pd.Series({c: (g[c] * w).sum() for c in cols}
| {"n": len(g), "qty": g["qty"].sum()})
return mo.groupby("liquidity").apply(agg)
Hãy chia nhỏ báo cáo hơn nữa theo symbol, giờ trong ngày, và khoảng cách quote tính từ mid. Cách phân tách hữu ích nhất cho một chiến lược maker là markout theo vị trí trong hàng đợi tại thời điểm khớp: các fill ở đầu hàng đợi mới định giá rất khác so với các fill nơi mức giá bị quét qua bạn.
Pipeline: cần ghi log những gì
TCA chết ở lớp ghi log, không phải ở lớp toán học. Toán học ở trên cần những con số mà hầu hết các bot vứt bỏ, và không con số nào trong đó có thể được tái tạo lại sau này từ lịch sử sàn giao dịch. Những điều không thể thương lượng:
- Mid tại thời điểm quyết định, từ chính feed của bạn, tại thời điểm tín hiệu. Không phải giá đóng cửa candle của sàn, không phải một sự tái tạo về sau. Benchmark là "mức giá mà chiến lược của tôi tin tưởng khi nó quyết định" — chỉ có tiến trình của bạn tại khoảnh khắc đó mới biết điều này.
- Cả hai mốc thời gian: thời điểm quyết định và thời điểm xác nhận đầu tiên, nếu không chi phí delay không thể đo được và sẽ âm thầm hòa vào chi phí giao dịch.
- Mọi fill kèm mốc thời gian của sàn, phí, và cờ maker/taker — thời gian nhận tại máy chủ của bạn bị nhiễu bởi độ trễ đường vào của chính bạn.
- Các lệnh cha bị hủy và hết hạn, được ghi log giống như mọi thứ khác. Các lệnh cha có zero fill là những hàng tốn kém nhất trong bảng.
- Một luồng mid liên tục (hoặc luồng L1) với độ chi tiết 100–250ms, được giữ lại đủ lâu để tính markout. Nếu bạn đã ghi lại sổ lệnh cho bộ mô phỏng fill, phần này miễn phí.
Hai bảng phẳng là đủ:
PARENTS = {
"parent_id": "str",
"strategy": "str",
"symbol": "str",
"venue": "str",
"algo": "str", # twap | pov | sniper | quote | ...
"side": "int8", # +1 buy, -1 sell
"qty": "float64", # parent size, base units
"limit_px": "float64", # NaN for unconstrained
"decision_ts": "datetime64[ns, UTC]",
"decision_mid": "float64", # mid your feed showed at decision_ts
"arrival_ts": "datetime64[ns, UTC]", # first exchange ack
"arrival_mid": "float64",
"end_ts": "datetime64[ns, UTC]", # filled / cancelled / expired
"terminal_mid": "float64",
"mkt_volume": "float64", # market volume over [arrival_ts, end_ts]
"sigma_bps": "float64", # realized vol estimate at decision time
}
FILLS = {
"parent_id": "str",
"ts": "datetime64[ns, UTC]", # exchange timestamp
"price": "float64",
"qty": "float64",
"fee": "float64", # quote ccy, post-discount, signed
"liquidity": "str", # maker | taker
"venue": "str",
}
Hàm attribution là bản chuyển thể trực tiếp của phép phân rã:
import numpy as np
def is_decomposition(p: pd.Series, fills: pd.DataFrame) -> dict:
"""Expanded implementation shortfall for one parent, bps of paper
notional. Sign convention: positive = cost."""
s, X = p["side"], p["qty"]
paper = X * p["decision_mid"]
x = fills["qty"].sum()
delay = s * X * (p["arrival_mid"] - p["decision_mid"])
trade = s * ((fills["price"] - p["arrival_mid"]) * fills["qty"]).sum()
oppty = s * (X - x) * (p["terminal_mid"] - p["arrival_mid"])
fees = fills["fee"].sum()
bps = lambda v: 1e4 * v / paper
return {
"parent_id": p["parent_id"],
"delay_bps": bps(delay), "trade_bps": bps(trade),
"oppty_bps": bps(oppty), "fees_bps": bps(fees),
"is_bps": bps(delay + trade + oppty + fees),
"fill_ratio": x / X,
"participation": x / max(p["mkt_volume"], x),
}
def tca_table(parents: pd.DataFrame, fills: pd.DataFrame) -> pd.DataFrame:
fg = dict(tuple(fills.groupby("parent_id")))
empty = fills.iloc[0:0]
rows = [is_decomposition(p, fg.get(p["parent_id"], empty))
for _, p in parents.iterrows()]
return parents.merge(pd.DataFrame(rows), on="parent_id")
Và các truy vấn attribution, đây chính là nơi TCA thôi là kế toán và bắt đầu là nghiên cứu:
tca = tca_table(parents, fills)
def wavg(g: pd.DataFrame, col: str) -> float:
w = g["qty"] * g["decision_mid"] # notional weights
return (g[col] * w).sum() / w.sum()
comp = ["delay_bps", "trade_bps", "oppty_bps", "fees_bps", "is_bps"]
by_strat = tca.groupby("strategy").apply(
lambda g: pd.Series({c: wavg(g, c) for c in comp}
| {"is_std": g["is_bps"].std(), "n": len(g)}))
done = tca[tca["fill_ratio"] > 0.99]
done["pov_bin"] = pd.qcut(done["participation"], 6)
impact_curve = done.groupby("pov_bin").apply(lambda g: wavg(g, "trade_bps"))
tca["hour"] = tca["arrival_ts"].dt.hour
by_hour = tca.groupby("hour").apply(lambda g: wavg(g, "is_bps"))
by_venue = tca.groupby("venue").apply(
lambda g: pd.Series({"is_bps": wavg(g, "is_bps"),
"is_std": g["is_bps"].std(),
"fill_ratio": g["fill_ratio"].mean(), "n": len(g)}))
Truy vấn 4 là bảng đầu vào mà một smart order router tối ưu hóa dựa trên đó — định tuyến mà không có TCA theo từng venue là định tuyến theo biểu phí, tức là theo thành phần chi phí nhỏ nhất (Smart order routing trong crypto lấy bảng này làm điểm khởi đầu). Cùng với module markout, đây là khoảng 200 dòng đã hứa hẹn.
Khép kín vòng lặp: hiệu chỉnh mô hình chi phí backtest
Đây là nơi pipeline tự bù đắp cho công sức bỏ ra. Backtest của bạn khẳng định các con số: slippage_bps = 5, một đường cong xác suất khớp lệnh, một hệ số impact. Mỗi con số trong đó là một tuyên bố về việc thực thi thực tế, và bảng TCA chính là việc thực thi thực tế. Vòng lặp: đo bằng TCA, khớp mô hình chi phí, chạy backtest với mô hình đã khớp, giao dịch, đo lại.

Với chi phí taker, hãy mượn dạng hàm từ Almgren và cộng sự (2005) thay vì tự phát minh ra một dạng mới. Kết quả của họ — chi phí tỷ lệ với biến động hàng ngày nhân với một lũy thừa của tỷ lệ tham gia — cho ra một mô hình hai tham số:
với ước lượng cổ phiếu làm giá trị tiên nghiệm (prior). Khớp mô hình trên các giá trị trung bình đã phân nhóm (binned means), không phải trên từng lệnh cha thô — chi phí của từng lệnh cha riêng lẻ bị chi phối bởi nhiễu (đó chính là số hạng rủi ro timing), và một hồi quy log-log trên dữ liệu thô sẽ vui vẻ khớp với nhiễu đó:
done = tca[(tca["fill_ratio"] > 0.99) & (tca["participation"] > 0)]
done["norm_cost"] = done["trade_bps"] / done["sigma_bps"]
bins = done.groupby(pd.qcut(done["participation"], 8)).agg(
pov=("participation", "mean"), cost=("norm_cost", "mean"))
bins = bins[bins["cost"] > 0] # can't log a negative bin
b, log_a = np.polyfit(np.log(bins["pov"]), np.log(bins["cost"]), 1)
def taker_cost_bps(participation, sigma_bps, a=np.exp(log_a), b=b):
return a * sigma_bps * participation ** b
Sau đó, hãy trung thực về sai số. Nhóm của Almgren có 700.000 lệnh và vẫn báo cáo số mũ impact tạm thời là ; một bot với 2.000 lệnh cha không thể tự do ước lượng riêng cho từng symbol. Chế độ thực tiễn: gộp chung các symbol sau khi chuẩn hóa theo , co mạnh về phía 0,6 (hoặc chỉ cố định nó và chỉ khớp ), và hiệu chỉnh lại hàng tháng. Nếu đã khớp của bạn trôi lên, footprint của bạn đã tăng hoặc thị trường đã mỏng đi — dù thế nào thì backtest cũng cần biết điều đó.
Với các chiến lược maker, các mục tiêu hiệu chỉnh khác nhau và đến từ hai module còn lại:
- Xác suất khớp lệnh: mô hình hàng đợi của bộ mô phỏng fill dự đoán theo từng ô (khoảng cách spread, vị trí hàng đợi); log lệnh cha của bạn cung cấp tỷ lệ khớp thực tế theo từng ô. Sự bất đồng là một lỗi trong bộ mô phỏng kèm theo tham chiếu lưới cụ thể.
- Adverse selection: thay giả định ngầm "các fill có thể hoán đổi cho nhau" của bộ mô phỏng bằng bảng markout đã đo lường — một fill maker mô phỏng ở khoảng cách trong giờ mang theo markout đã đo được như một khoản khấu trừ mark-to-market ngay lập tức. Sự thay đổi đơn lẻ này chính là khác biệt giữa một backtest maker ảo tưởng và một backtest bám sát thực tế; đây là đầu vào hiệu chỉnh còn thiếu được nêu ra trong bài viết về fill-simulation.
- Độ phân tán chi phí: đưa phân phối của IS, không phải giá trị trung bình của nó, vào backtest. Một mô hình chi phí chỉ dịch chuyển giá trị trung bình không thể tái tạo các đợt sụt giảm (drawdown) mà rủi ro timing tạo ra; ngay cả một phân phối lognormal khớp trên IS của từng lệnh cha cũng tốt hơn một hằng số.
Phần xử lý đầy đủ về đường cong slippage — dạng hàm, điều kiện theo chế độ thị trường, khi nào quy luật lũy thừa bị phá vỡ — là một bài viết riêng: Đường cong slippage và mô hình chi phí cho backtest. Điểm mấu chốt ở đây mang tính kiến trúc: các mô hình trong bài viết đó không thể khớp được nếu thiếu các bảng trong bài viết này.
Các bẫy
Tài liệu về TCA đã có từ hàng chục năm, và các cách để lách nó cũng vậy. Ba kiểu thất bại chiếm phần lớn sự tự lừa dối.
Gaming benchmark. Bất kỳ benchmark nào khác ngoài arrival price đều có thể bị "ôm sát" một cách gian lận. Ví dụ kinh điển là VWAP: một thuật toán được chấm điểm so với VWAP trong khoảng thời gian có thể bám sát nó trong phạm vi một điểm cơ bản trong khi vị thế chảy máu hai mươi điểm so với arrival, bởi vì benchmark trôi theo chính mức giá bạn đang đẩy — và ở mức tham gia đáng kể, các lệnh khớp của chính bạn chính là VWAP, vì vậy việc bám theo nó chẳng khác gì tự chấm bài tập về nhà của mình. Chúng tôi đã mổ xẻ chính trị benchmark trong TWAP vs VWAP vs POV; quy tắc phía TCA đơn giản hơn: benchmark được chọn trước khi giao dịch, và IS-so-với-arrival luôn được tính ngay cả khi một bộ lập lịch (scheduler) được chấm điểm so với benchmark lập lịch của chính nó. Biến thể tinh vi hơn là gaming arrival: nếu thành phần thiết lập decision_ts có thể nhìn thấy momentum ngắn hạn, nó có thể định thời điểm "quyết định" để làm đẹp số hạng delay. Các mốc thời gian quyết định thuộc về lớp tín hiệu, được ghi log trước khi bất kỳ logic thực thi nào chạy.
Thiên lệch sống sót (survivorship bias) trong phân tích chỉ-các-fill. Điều kiện hóa phân tích chi phí của bạn trên các fill và việc thực thi thụ động trông có vẻ miễn phí. Cụ thể: 100 lệnh mua thụ động, một tick dưới mid. Sáu mươi lệnh khớp, và — vì là thụ động — khớp ở mức giá trung bình tốt hơn 3 bps so với arrival: "chi phí" đo được là bps, một báo cáo đáng tự hào. Bốn mươi lệnh không bao giờ khớp chính là những lệnh mà giá đã bốc lên xa; đánh giá chúng ở mức 25 bps bất lợi tại thời điểm hủy và con số trung thực là bps. Báo cáo chỉ-các-fill và báo cáo trung thực khác nhau 11 bps và khác cả dấu. Đây không phải trường hợp ngoại lệ hiếm gặp — đó là cơ chế của giao dịch thụ động: được khớp lệnh có tương quan với việc giá đi xuyên qua bạn, cùng kiểu điều kiện hóa khiến các backtest touch-fill trở thành ảo tưởng. Chi phí cơ hội không phải một sự tinh chỉnh tùy chọn của IS; nó là số hạng bảo vệ toàn bộ phép đo trước sự thiên lệch chọn mẫu. Cùng thiên lệch này có một biến thể taker: các lệnh IOC bị lỡ, các lệnh bị từ chối bởi rate limit hoặc kiểm tra rủi ro — nếu các lần lỡ không được ghi log, chi phí của việc bỏ lỡ không được đo lường, và nó lớn nhất chính xác ở những thị trường nhanh nơi chiến lược của bạn muốn giao dịch nhất.
Các cạm bẫy linh tinh khác, ngắn gọn: arrival mid được tái tạo từ candle của sàn (feed của bạn và candle bất đồng chính xác vào lúc quan trọng nhất); lấy trung bình bps theo từng lệnh cha mà không có trọng số notional (một nghìn fill vụn vặt lấn át một lệnh thật); phí được ghi log trước chiết khấu; mid dùng cho markout lấy từ một venue khác với venue của fill (basis chéo venue giả trang thành adverse selection); và trên perps, để funding tích lũy rò rỉ vào cửa sổ thực thi — funding là một chi phí, nhưng không phải chi phí thực thi, và trộn lẫn chúng đầu độc cả hai phân tích.
Việc cần làm trong tuần này
Thêm hai bảng log vào bot của bạn — sơ đồ ở trên là một danh sách cột, không phải một dự án. Đừng backfill gì cả; hai tuần log trung thực tốt hơn một năm tái tạo. Chạy phép phân rã và ba chân trời markout. Bạn sẽ biết thành phần nào chiếm chủ đạo (gần như không bao giờ là phí), liệu các fill thụ động của bạn có bị adverse selection vượt quá mức thu spread hay không, và mô hình chi phí trong backtest của bạn cách xa đường cong đo được bao nhiêu. Sau đó đưa đường cong đã khớp trở lại bộ mô phỏng và chạy lại backtest đã bảo bạn chạy chiến lược này ngay từ đầu. Danh mục trên giấy của Perold, ba mươi tám năm sau, vẫn là đối thủ trung thực duy nhất mà danh mục thực của bạn có.
Tác Giả
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.