← Kembali ke artikel
July 20, 2026
5 menit baca

Implementation shortfall dan TCA DIY: mengukur berapa sebenarnya biaya eksekusi Anda

Implementation shortfall dan TCA DIY: mengukur berapa sebenarnya biaya eksekusi Anda
#execution
#tca
#implementation-shortfall
#markouts
#transaction-costs
#algotrading
#backtest
#market-microstructure

Setiap desk institusional punya pipeline transaction cost analysis. Hampir tidak ada yang menjalankan bot crypto punya itu. Setup yang umum hanya mencatat fill, menjumlahkan fee, dan menyebut selisih antara PnL backtest dan PnL live sebagai "slippage" — satu residual tak terjelaskan yang menyerap latency, spread, impact, adverse selection, dan setiap order yang tidak terisi karena harga kabur. Anda tidak bisa memperbaiki biaya yang Anda ukur sebagai satu angka saja. Mesin untuk mengukurnya dengan benar sudah ada sejak 1988, bukan sesuatu yang proprietary, dan di atas log fill Anda sendiri hanya sekitar 200 baris Python. Artikel ini membangunnya.

Hasilnya bukan sekadar dashboard yang lebih cantik. Backtest Anda memuat model biaya — konstanta slippage, probabilitas fill, koefisien impact — dan setiap parameter di dalamnya saat ini cuma tebakan. TCA atas fill live adalah satu-satunya ground truth yang bisa dipakai mengkalibrasi parameter tersebut. Kita sudah membangun sisi simulasi dari loop ini di Fill simulation: the ladder from close-price fantasy to queue-aware reality; artikel ini membangun sisi pengukurannya.

Paper versus realita: apa yang sebenarnya diukur Perold

Trik fundamentalnya berasal dari André Perold (1988, "The Implementation Shortfall: Paper Versus Reality," Journal of Portfolio Management 14(3), 4–9). Jalankan dua portfolio secara paralel. Paper portfolio mengeksekusi setiap keputusan secara instan, dalam ukuran tak terbatas, tanpa biaya, pada harga yang berlaku saat momen keputusan diambil. Real portfolio adalah apa yang benar-benar dilakukan bot Anda: fill parsial, quote yang dikejar, sisa yang dibatalkan, fee. Implementation shortfall adalah selisih return keduanya.

Definisi ini penting karena apa yang ditolaknya untuk disembunyikan. Laporan fee menunjukkan komisi. Laporan fill-versus-limit-price tidak menunjukkan apa-apa (Anda tidak pernah fill lebih buruk dari limit Anda — secara konstruksi). Paper portfolio membebankan Anda untuk segalanya: drift antara keputusan dan arrival, spread yang Anda crossing, impact yang Anda timbulkan, dan — yang krusial — order yang tidak pernah terisi selagi harga kabur. Wagner dan Edwards (1993, "Best Execution," Financial Analysts Journal 49(1), 65–71) menyebut fee yang terlihat sebagai puncak gunung es; untuk apa pun dengan turnover, bagian yang tenggelam justru mendominasi.

Mari tetapkan notasi. Sebuah parent order: sisi s{+1,1}s \in \{+1, -1\} (buy/sell), ukuran XX. Decision price PdP_d adalah mid yang dilihat strategi Anda saat sinyal muncul. Fill datang sebagai (pj,xj)(p_j, x_j) dengan total kuantitas terisi x=jxjXx = \sum_j x_j \le X. Pada horizon TT (parent selesai, dibatalkan, atau timeout), mid-nya adalah PTP_T. Fee eksplisit adalah FF. Implementation shortfall dalam satuan mata uang:

IS=sjxj(pjPd)execution cost  +  s(Xx)(PTPd)opportunity cost  +  F,\mathrm{IS} = \underbrace{s \sum_j x_j\,(p_j - P_d)}_{\text{execution cost}} \;+\; \underbrace{s\,(X - x)(P_T - P_d)}_{\text{opportunity cost}} \;+\; F,

dinormalisasi ke basis point dengan membaginya dengan notional paper XPdX \cdot P_d. Nilai positif berarti Anda membayar. Suku pertama adalah berapa biaya fill Anda relatif terhadap paper portfolio; suku kedua adalah opportunity cost ala Perold — sisa yang tidak terisi, ditandai pada harga tempat ia kabur; suku ketiga adalah satu-satunya bagian yang diakui oleh laporan exchange Anda.

Satu catatan konvensi: industri menyebutnya "arrival price," dan di sebagian besar TCA ekuitas, arrival berarti mid saat order mencapai pasar. Untuk bot, waktu keputusan dan waktu arrival berbeda karena latency internal Anda sendiri plus antrean rate-limit — biaya yang nyata dan terukur. Jadi kita simpan kedua timestamp dan kedua harga, dan biarkan dekomposisi memisahkan keduanya.

Dekomposisi: delay, impact, timing, opportunity, fee

Satu angka IS saja memberitahu Anda bahwa eksekusi itu mahal. Ia tidak memberitahu mengapa, dan perbaikan untuk komponen yang berbeda sama sekali berbeda — Anda tidak menyelesaikan delay cost dan impact cost dengan perubahan yang sama. Expanded implementation shortfall dari Robert Kissell (Kissell, 2006, "The Expanded Implementation Shortfall: Understanding Transaction Cost Components," Journal of Trading 1(3), 6–16; dikembangkan lebih panjang di The Science of Algorithmic Trading and Portfolio Management, Academic Press, 2013) memecah total menjadi komponen-komponen, masing-masing dapat diatribusikan ke tahap berbeda dalam siklus hidup order. Versi praktisi, menggunakan arrival mid P0P_0 (mid pada acknowledgment exchange pertama):

IS=sX(P0Pd)delay  +  sjxj(pjP0)trading cost  +  s(Xx)(PTP0)opportunity  +  F.\mathrm{IS} = \underbrace{s\,X\,(P_0 - P_d)}_{\text{delay}} \;+\; \underbrace{s \sum_j x_j\,(p_j - P_0)}_{\text{trading cost}} \;+\; \underbrace{s\,(X - x)(P_T - P_0)}_{\text{opportunity}} \;+\; F.

Identitas ini secara persis teleskopis kembali ke definisi Perold — jabarkan suku-sukunya dan P0P_0 akan saling meniadakan. Setiap bagian punya pemilik yang berbeda:

Delay cost sX(P0Pd)sX(P_0 - P_d): drift harga antara sinyal muncul dan child order pertama Anda live di exchange. Ini adalah infrastruktur Anda — serialisasi, jaringan, antrean rate-limit, risk check. Jika sinyal Anda memiliki alpha jangka pendek yang genuin, delay cost adalah tempat pertama ia bocor; rata-rata yang konsisten positif menandakan pasar bergerak sesuai arah Anda sebelum Anda tiba — alpha momentum yang meluruh, atau ada orang lain yang lebih cepat men-trading sinyal yang sama.

Trading cost sjxj(pjP0)s\sum_j x_j(p_j - P_0): berapa yang dibayar fill Anda relatif terhadap arrival — spread yang di-cross ditambah market impact ditambah drift intra-schedule. Ini adalah rapor dari algoritma eksekusi, dan kuantitas yang benar-benar dimodelkan oleh riset eksekusi. Almgren, Thum, Hauptmann dan Li (2005, "Direct Estimation of Equity Market Impact," Risk 18(7), 58–62) mengukurnya atas kurang lebih 700.000 order ekuitas AS dari desk Citigroup (Desember 2001–Juni 2003) dan menemukan trading cost berskala dengan volatilitas harian dan participation rate — temporary impact mengikuti power law pada trade rate dengan eksponen mendekati 3/5, permanent impact mendekati linear terhadap ukuran. Kita akan memakai ulang bentuk fungsional mereka saat mengkalibrasi.

Timing risk: bukan suku dalam dekomposisi rata-rata, melainkan varians di sekitarnya. Menyebarkan parent order sepanjang waktu mengurangi impact yang diharapkan tapi mengekspos Anda pada volatilitas; untuk schedule dengan posisi tersisa q(t)q(t), standar deviasi biaya berskala sebagai σ0Tq(t)2dt\sigma \sqrt{\int_0^T q(t)^2\,dt}. Ini persis trade-off yang dioptimasi oleh kerangka Almgren–Chriss. Dalam laporan TCA Anda, ini muncul sebagai dispersi IS antar parent order — laporkan standar deviasinya di samping setiap rata-rata, atau rata-rata akan menyita semua perhatian sementara ekornya akan menyita semua uang Anda.

Opportunity cost s(Xx)(PTP0)s(X - x)(P_T - P_0): kuantitas tak terisi yang ditandai pada harga terminal. Untuk strategi pasif, ini rutin menjadi komponen terbesar sekaligus paling jarang diperiksa, dan inilah suku yang membuat seluruh kerangka jujur — lebih lanjut di bagian jebakan, karena mengabaikannya adalah cara paling umum orang membohongi diri sendiri dengan TCA.

Fee FF: bagian eksplisit. Di crypto, catat fee setelah diskon (tier VIP, rebate token yang dikonversi pada harga saat fill) dan pertahankan tanda pada maker rebate — fee negatif adalah data, bukan noise.

Contoh kasus

Buy X=10X = 10 BTC. Sinyal muncul dengan Pd=60,000P_d = 60{,}000; notional paper $600.000. Child order pertama ter-ack dengan mid P0=60,012P_0 = 60{,}012. Selama dua menit berikutnya, 8 BTC terisi pada VWAP 60.072; harga sedang trending menjauh, algo mematuhi limitnya, dan sisa 2 BTC dibatalkan dengan mid pada PT=60,240P_T = 60{,}240. Fee gabungan 2,5 bps dari notional yang terisi.

Komponen Formula USD bps dari paper
Delay 10×(60,01260,000)10 \times (60{,}012 - 60{,}000) 120 2,0
Trading cost 8×(60,07260,012)8 \times (60{,}072 - 60{,}012) 480 8,0
Opportunity 2×(60,24060,012)2 \times (60{,}240 - 60{,}012) 456 7,6
Fee 8×60,072×2.5e-48 \times 60{,}072 \times 2.5\text{e-}4 120 2,0
Total IS 1.176 19,6

Laporan exchange menunjukkan $120. Biaya riil dari mengimplementasikan keputusan tersebut adalah $1.176 — sepuluh kali lipat, dengan dua komponen terbesar tidak terlihat oleh akuntansi berbasis fee. Kurang lebih 40% di antaranya berasal dari kuantitas yang tidak pernah ter-trading. Laporan TCA yang hanya menganalisis fill akan menilai parent order ini 8 bps dan menyebutnya baik-baik saja.

Dekomposisi waterfall implementation shortfall dari paper portfolio ke real portfolio

Markout: memberi harga pada adverse selection

Implementation shortfall menilai parent order. Ia tidak mengatakan apa pun tentang kualitas fill individual — secara spesifik, apakah Anda secara sistematis bertransaksi dengan counterparty yang tahu sesuatu yang tidak Anda ketahui. Itu diukur dengan markout: menandai setiap fill terhadap mid pada horizon tetap setelah kejadian tersebut.

πτ=s(mtf+τpf)mtf×104 bps,\pi_\tau = \frac{s\,(m_{t_f + \tau} - p_f)}{m_{t_f}} \times 10^4 \ \text{bps},

di mana pfp_f adalah harga fill pada waktu tft_f dan mtm_t adalah mid pada waktu tt. Ini adalah PnL mark-to-market per unit dari fill pada horizon τ\tau, dan ia memiliki anatomi yang jelas pada τ=0\tau = 0: fill maker dimulai pada ++setengah-spread (Anda beli di bid, mid berada di atas Anda); fill taker dimulai pada -setengah-spread. Apa yang terjadi saat τ\tau bertambah adalah kandungan informasi dari trade tersebut:

  • τ=1\tau = 1s mengisolasi sniping dan pickoff quote basi. Jika fill maker Anda sudah rugi satu detik setelah terisi, partisipan yang lebih cepat sedang menghantam quote Anda tepat saat menjadi mispriced — loop update quote Anda lebih lambat dari loop trigger mereka. Markout ini adalah diagnostik latency, bukan diagnostik strategi.
  • τ=10\tau = 10s mengukur adverse selection klasik: fill yang diikuti pergerakan berkelanjutan menembus harga Anda. Bagi market maker, ini adalah biaya yang harus dikalahkan oleh spread capture; mikrostruktur ekuitas menyebut kuantitas terkaitnya realized spread, dilembagakan dalam pelaporan SEC Rule 605 pada horizon 5 menit. Crypto bergerak lebih cepat; 10–60 detik adalah band yang setara.
  • τ=60\tau = 60s memberitahu apakah fill membawa momentum yang melawan Anda melampaui horizon mikrostruktur — untuk strategi taker, apakah alpha sinyal Anda pada 60 detik melebihi spread-plus-impact yang Anda bayar untuk masuk. Kurva markout taker yang mulai di 4-4 bps dan tidak pernah melewati nol adalah strategi yang membayar entry yang tidak sanggup dibiayai oleh alphanya.

Asimetri maker/taker adalah keseluruhan ekonomi trading pasif dalam dua kurva. Kurva maker yang mulai di +2+2 bps (setengah-spread) dan meluruh ke 1-1 bps pada 60 detik mengatakan: Anda menangkap spread tapi memberikan kembali lebih banyak, dan tidak ada tier rebate yang bisa memperbaikinya. Kurva yang sama tapi mengendap di +0,7+0,7 bps mengatakan mesin quoting menghasilkan nilainya. Ini juga persis yang diasumsikan hilang oleh backtest touch-fill naif — simulator yang mengisi Anda setiap kali harga menyentuh limit Anda mengabaikan bahwa terisi berkorelasi dengan salah, itulah sebabnya anak tangga queue-aware pada tangga fill-simulation membutuhkan markout terukur sebagai input, bukan sebagai asumsi.

Kurva markout versus horizon untuk fill maker dan taker menunjukkan peluruhan adverse selection

Komputasinya adalah sebuah merge_asof di atas mid stream Anda sendiri:

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)

Iris laporan lebih jauh berdasarkan simbol, jam dalam sehari, dan jarak quote dari mid. Irisan yang paling actionable untuk strategi maker adalah markout-per-posisi-antrean-saat-fill: fill di depan antrean segar berharga sangat berbeda dari fill di mana level tersebut tersapu melalui posisi Anda.

Pipeline: apa yang harus dicatat

TCA mati di lapisan logging, bukan di lapisan matematika. Matematika di atas membutuhkan angka-angka yang dibuang begitu saja oleh kebanyakan bot, dan tak satu pun bisa direkonstruksi kembali dari riwayat exchange. Hal-hal yang tidak bisa dinegosiasikan:

  • Decision mid, dari feed Anda sendiri, pada saat sinyal. Bukan candle close exchange, bukan rekonstruksi belakangan. Benchmarknya adalah "harga yang diyakini strategi saya saat memutuskan" — hanya proses Anda pada momen itu yang tahu.
  • Kedua timestamp: waktu keputusan dan waktu ack pertama, atau delay cost jadi tidak terukur dan diam-diam bercampur ke trading cost.
  • Setiap fill dengan timestamp exchange, fee, dan flag maker/taker — waktu terima lokal Anda tercemar oleh latency inbound Anda sendiri.
  • Parent order yang dibatalkan dan kedaluwarsa, dicatat seperti yang lain. Parent order dengan nol fill adalah baris paling mahal dalam tabel.
  • Mid stream persisten (atau L1 stream) dengan granularitas 100–250ms, disimpan cukup lama untuk menghitung markout. Jika Anda sudah merekam order book untuk fill simulator, ini gratis.

Dua tabel flat sudah cukup:

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",
}

Fungsi atribusi adalah transkripsi langsung dari dekomposisi:

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")

Dan kueri atribusinya, tempat TCA berhenti menjadi akuntansi dan mulai menjadi riset:

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)}))

Kueri 4 adalah tabel input yang dioptimasi oleh smart order router — routing tanpa TCA per-venue adalah routing berdasarkan jadwal fee, yakni berdasarkan komponen biaya terkecil (Smart order routing di crypto mengambil tabel ini sebagai titik awalnya). Bersama modul markout, inilah sekitar 200 baris yang dijanjikan.

Menutup loop: mengkalibrasi model biaya backtest

Di sinilah pipeline membayar dirinya sendiri. Backtest Anda mengklaim angka-angka: slippage_bps = 5, kurva probabilitas fill, koefisien impact. Setiap satu di antaranya adalah klaim tentang eksekusi live, dan tabel TCA adalah eksekusi live. Loopnya: ukur dengan TCA, fit model biaya, jalankan backtest dengan model yang sudah di-fit, trading, ukur ulang.

Loop kalibrasi tertutup dari fill live melalui atribusi TCA ke model biaya backtest

Untuk biaya taker, pinjam bentuk fungsional dari Almgren dkk. (2005) alih-alih menciptakan sendiri. Hasil mereka — biaya proporsional terhadap volatilitas harian dikali pangkat dari participation rate — memberikan model dua parameter:

trade_bpsσbps=a(xV)b,\frac{\text{trade\_bps}}{\sigma_{\text{bps}}} = a \cdot \left(\frac{x}{V}\right)^{b},

dengan estimasi ekuitas b0,6b \approx 0,6 sebagai prior. Fit pada rata-rata yang di-bin, bukan parent order mentah — biaya parent order individual didominasi oleh noise (itulah suku timing-risk), dan regresi log-log pada data mentah akan dengan senang hati nge-fit noise tersebut:

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

Lalu jujurlah soal margin errornya. Tim Almgren punya 700.000 order dan tetap melaporkan eksponen temporary-impact sebagai 0,600±0,0380,600 \pm 0,038; bot dengan 2.000 parent order tidak berhak mengestimasi bb secara bebas per simbol. Rezim praktisnya: gabungkan lintas simbol setelah dinormalisasi dengan σ\sigma, tarik bb kuat-kuat ke arah 0,6 (atau tetapkan saja dan hanya fit aa), dan fit ulang setiap bulan. Jika aa hasil fit Anda melayang naik, footprint Anda membesar atau pasar menipis — bagaimanapun juga backtest perlu tahu itu.

Untuk strategi maker, target kalibrasinya berbeda dan berasal dari dua modul lainnya:

  • Probabilitas fill: model queue dari fill simulator memprediksi P(fill)P(\text{fill}) per sel (jarak-spread, posisi-antrean); log parent order Anda memberikan rasio fill riil per sel. Ketidaksesuaian adalah bug simulator dengan referensi grid yang menyertainya.
  • Adverse selection: ganti asumsi implisit simulator bahwa "fill itu fungible" dengan tabel markout terukur — fill maker simulasi pada jarak dd di jam hh membawa π10s(d,h)\pi_{10s}(d, h) yang terukur sebagai haircut mark-to-market langsung. Perubahan tunggal ini adalah selisih antara backtest maker yang berhalusinasi dan yang benar-benar melacak; ini adalah input kalibrasi yang hilang, yang ditandai di artikel fill-simulation.
  • Dispersi biaya: masukkan distribusi IS, bukan rata-ratanya, ke dalam backtest. Model biaya yang hanya menggeser rata-rata tidak bisa mereproduksi drawdown yang diciptakan timing risk; bahkan lognormal yang di-fit ke IS per-parent-order mengalahkan konstanta.

Pembahasan lengkap kurva slippage — bentuk fungsional, pengkondisian rezim, kapan power law-nya rusak — adalah artikel tersendiri: Slippage curves and cost models for backtests. Poinnya di sini bersifat arsitektural: model-model di artikel itu tidak bisa di-fit tanpa tabel-tabel dari artikel ini.

Jebakan

Literatur TCA sudah berumur puluhan tahun, begitu pula cara-cara mengakalinya. Tiga mode kegagalan menjelaskan sebagian besar penipuan diri sendiri.

Benchmark gaming. Benchmark apa pun selain arrival price bisa "dipeluk" (di-hug). Contoh klasiknya adalah VWAP: algoritma yang dinilai terhadap interval VWAP bisa melacaknya dalam satu basis point sementara posisi berdarah dua puluh bps relatif terhadap arrival, karena benchmarknya melayang mengikuti harga yang justru sedang Anda dorong sendiri — dan pada participation yang signifikan, print Anda sendiri adalah VWAP-nya, sehingga melacaknya sama saja dengan menilai PR sendiri. Kita sudah membedah politik benchmark di TWAP vs VWAP vs POV; aturan di sisi TCA lebih sederhana: benchmark dipilih sebelum trading, dan IS-versus-arrival selalu dihitung meskipun sebuah scheduler dinilai terhadap benchmark schedulingnya sendiri. Varian yang lebih halus adalah arrival gaming: jika komponen yang menetapkan decision_ts bisa melihat momentum jangka pendek, ia bisa mengatur waktu "keputusan" untuk mempercantik suku delay. Timestamp keputusan adalah milik lapisan sinyal, dicatat sebelum logika eksekusi apa pun berjalan.

Survivorship bias dalam analisis filled-only. Kondisikan analisis biaya Anda hanya pada fill, dan eksekusi pasif akan terlihat gratis. Konkretnya: 100 parent order buy pasif, satu tick di bawah mid. Enam puluh terisi, dan — karena pasif — terisi pada harga rata-rata 3 bps lebih baik dari arrival: "biaya" terukur 3-3 bps, laporan yang membanggakan. Empat puluh yang tidak pernah terisi justru yang harganya melambung menjauh; tandai itu 25 bps adverse saat dibatalkan, dan angka jujurnya adalah 0,6×(3)+0,4×25=+8,20,6 \times (-3) + 0,4 \times 25 = +8,2 bps. Laporan filled-only dan laporan sebenarnya berbeda 11 bps dan berbeda tanda. Ini bukan kasus pinggiran — ini adalah mekanisme trading pasif itu sendiri: terisi berkorelasi dengan harga yang menembus posisi Anda, pengkondisian yang sama yang membuat backtest touch-fill jadi fantasi. Opportunity cost bukan penyempurnaan opsional dari IS; ia adalah suku yang mempertahankan seluruh pengukuran dari bias seleksi. Bias yang sama punya varian taker: order IOC yang meleset, order yang ditolak oleh rate limit atau risk check — jika yang meleset tidak dicatat, biaya dari melesetnya tidak terukur, dan itu paling besar justru di pasar cepat tempat strategi Anda paling ingin trading.

Berbagai jebakan lain, singkatnya: arrival mid yang direkonstruksi dari candle exchange (feed Anda dan candle tersebut berselisih tepat saat itu penting); merata-ratakan bps per-parent-order tanpa bobot notional (seribu fill remeh mengalahkan suara satu order sungguhan); fee dicatat pra-diskon; mid markout diambil dari venue yang berbeda dari fill-nya (basis lintas-venue menyamar sebagai adverse selection); dan pada perp, membiarkan akrual funding bocor ke jendela eksekusi — funding adalah biaya, tapi bukan biaya eksekusi, dan mencampurnya meracuni kedua analisis.

Yang harus dilakukan minggu ini

Tambahkan dua tabel log ke bot Anda — skema di atas adalah daftar kolom, bukan proyek. Jangan backfill apa pun; dua minggu log yang jujur mengalahkan setahun rekonstruksi. Jalankan dekomposisinya dan tiga horizon markout. Anda akan belajar komponen mana yang mendominasi (hampir tidak pernah fee), apakah fill pasif Anda mengalami adverse selection melebihi spread capture-nya, dan seberapa jauh konstanta biaya backtest Anda dari kurva terukur. Lalu sambungkan kurva hasil fit itu kembali ke simulator dan jalankan ulang backtest yang tadinya menyuruh Anda menjalankan strategi ini sejak awal. Paper portfolio Perold, tiga puluh delapan tahun kemudian, masih menjadi satu-satunya lawan jujur yang dimiliki portfolio riil Anda.

Penafian: Informasi yang disediakan dalam artikel ini hanya untuk tujuan edukasi dan informasi serta tidak merupakan nasihat keuangan, investasi, atau trading. Trading mata uang kripto mengandung risiko kerugian yang signifikan.

Penulis

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Selangkah Lebih Maju dari Pasar

Berlangganan newsletter kami untuk wawasan AI trading eksklusif, analisis pasar, dan pembaruan platform.

Kami menghormati privasi Anda. Berhenti berlangganan kapan saja.