Short answer: Transaction cost analysis (TCA) measures what trading actually cost by comparing your own fills with a benchmark, such as the decision price, the arrival price or the market's VWAP or TWAP over the same period. Implementation shortfall, from Perold (1988), adds delay, trading cost, fees and the cost of what did not fill into one figure in currency and basis points.
Calculate it with your own figures: Slippage calculator for trading.
TCA here means measuring execution from your own records, not transaction cost economics, the theory of why firms exist. A US SEC staff report (opens in a new tab) of 5 August 2020 (page 35) says institutional investors generally run TCA to assess executions across brokers, algorithms and market centers.
This page measures cost and software behavior: it does not say what to trade, promises no better results and ranks no broker or exchange. Trading can lose money.
What is transaction cost analysis in trading?
TCA splits the gap between a paper price and what you really paid into observed and estimated parts.
| Cost | What it is | From your records |
|---|---|---|
| Fees | Commission on each fill, in any asset | Observed |
| Spread | Half the quoted spread paid when taking liquidity | Needs the quote at fill time |
| Slippage | Fill price against the decision or arrival price | Observed, if the reference was logged |
| Delay (timing) | Price move between the decision and the first order | Observed |
| Market impact | The price move your own order caused | Estimated only |
| Unfilled quantity | What did not trade, valued at a later price | Observed, given a chosen end time |
Impact stays an estimate: nobody sees the price path without the order. For slippage alone, see slippage in crypto and the slippage calculator.
TCA benchmarks: arrival price, interval VWAP and TWAP
The benchmark changes the answer, so choose it in advance and report it next to every number.
| Benchmark | Formula | What it answers | Blind spot |
|---|---|---|---|
| Decision price | Mid when the decision was logged | Cost of the whole process | Needs a log written at decision time |
| Arrival price | Mid when the first child order leaves | Cost of working the order | Ignores delay before it |
| Interval VWAP | sum(price x volume) / sum(volume) over the order's interval | Tracking of the market in that window | Your own trades are in it, and it moves with the trend |
| Interval TWAP | Mean of the last price at equal time steps | The same, ignoring volume | Thin periods count as much as busy ones |
VWAP and TWAP are also execution methods aimed at these benchmarks; see execution algorithms.
Implementation shortfall: Perold's paper portfolio
Implementation shortfall is the result of a hypothetical paper portfolio traded at benchmark prices, here the decision price, minus the result of the real one.
Perold's 1988 paper (Journal of Portfolio Management 14(3), pages 4 to 9, per its Crossref record (opens in a new tab)) sits behind a login and was not opened for this guide. The definition here follows Joel Hasbrouck's NYU Stern course notes (opens in a new tab) of 14 October 2015, which list explicit costs, costs of interacting with the market, opportunity costs and delay. The form below applies these parts to one order, and the code checks that they add up to paper minus real.
delay = side x filled x (arrival - decision)
trading = side x sum(fill qty x (fill price - arrival))
opportunity = side x (target - filled) x (end price - decision)
fees = sum(fee converted to quote at fill time)
shortfall = delay + trading + opportunity + fees
Transaction cost analysis in Python
A short script can decompose shortfall in currency and basis points (bp) and compare the fills with every benchmark.
Hypothetical order. Buy 10 units: decision price 100.00, arrival price 100.05, 7.5 units filled in three fills, one fee each in USDT, ETH and XYZ, and an end price of 100.60.
from collections import namedtuple
from math import isclose
Fill = namedtuple("Fill", "id t qty px fee fee_asset") # id: venue trade ID
Trade = namedtuple("Trade", "id t qty px") # the venue's public tape, oldest first
order = dict(side=1, target=10.0, decision_px=100.00, arrival_px=100.05, end_px=100.60,
arrival_t=30, end_t=630) # side: 1 buy, -1 sell
fills = [Fill(2, 60, 3.0, 100.10, 0.30, "USDT"), Fill(4, 240, 2.5, 100.25, 0.0004, "ETH"),
Fill(6, 420, 2.0, 100.40, 0.75, "XYZ")]
tape = [Trade(1, 45, 20, 100.08), Trade(2, 60, 3.0, 100.10), Trade(3, 150, 15, 100.20),
Trade(4, 240, 2.5, 100.25), Trade(5, 330, 25, 100.35), Trade(6, 420, 2.0, 100.40),
Trade(7, 540, 18, 100.55)]
rates = {("USDT", 60): 1.0, ("ETH", 240): 3000.0, ("XYZ", 420): 0.5} # to quote, at fill time
def vwap(trades):
return sum(t.qty * t.px for t in trades) / sum(t.qty for t in trades)
def twap(trades, start, end, start_px, n=10): # last price at n equal steps
def last(x):
return ([start_px] + [t.px for t in trades if t.t <= x])[-1]
return sum(last(start + k * (end - start) / n) for k in range(1, n + 1)) / n
def tca(o, fills, tape):
ids = [f.id for f in fills]
if None in ids or "" in ids or len(set(ids)) < len(ids):
raise ValueError("each fill needs its own venue trade ID")
if len({t.id for t in tape}) < len(tape) or any(b.t < a.t for a, b in zip(tape, tape[1:])):
raise ValueError("tape needs unique trade IDs, oldest first")
s, done = o["side"], sum(f.qty for f in fills)
paper_notional = o["target"] * o["decision_px"]
parts = {
"delay": s * done * (o["arrival_px"] - o["decision_px"]),
"trading": s * sum(f.qty * (f.px - o["arrival_px"]) for f in fills),
"opportunity": s * (o["target"] - done) * (o["end_px"] - o["decision_px"]),
"fees": sum(f.fee * rates[(f.fee_asset, f.t)] for f in fills), # no rate: KeyError
}
total = sum(parts.values())
paper = s * o["target"] * (o["end_px"] - o["decision_px"])
real = s * sum(f.qty * (o["end_px"] - f.px) for f in fills) - parts["fees"]
assert isclose(total, paper - real, rel_tol=1e-9, abs_tol=1e-9)
for name, value in parts.items():
print(f"{name:12}{value:9.4f}{value / paper_notional * 1e4:8.2f} bp")
print(f"{'shortfall':12}{total:9.4f}{total / paper_notional * 1e4:8.2f} bp")
window = [t for t in tape if o["arrival_t"] <= t.t <= o["end_t"]]
others = [t for t in window if t.id not in ids]
if not done or not others:
print("no fills or no other trades: benchmarks skipped")
return
avg = sum(f.qty * f.px for f in fills) / done
mine = sum(t.qty for t in window if t.id in ids)
print(f"filled {done}/{o['target']}, average {avg:.4f}, "
f"own share of tape {mine / sum(t.qty for t in window):.1%}")
for name, b in [("arrival", o["arrival_px"]), ("VWAP", vwap(window)), ("VWAP ex own", vwap(others)),
("TWAP", twap(window, o["arrival_t"], o["end_t"], o["arrival_px"]))]:
print(f"{name:12}{b:9.4f}{s * (avg - b) / b * 1e4:8.2f} bp")
tca(order, fills, tape)Output, from Python 3.11.3 on 6 November 2026, all hypothetical:
delay 0.3750 3.75 bp
trading 1.3500 13.50 bp
opportunity 1.5000 15.00 bp
fees 1.8750 18.75 bp
shortfall 5.1000 51.00 bp
filled 7.5/10.0, average 100.2300, own share of tape 8.8%
arrival 100.0500 17.99 bp
VWAP 100.2921 -6.19 bp
VWAP ex own 100.2981 -6.79 bp
TWAP 100.3350 -10.46 bp- Benchmarks: the same fills cost 17.99 bp against arrival and -6.19 bp against VWAP. A negative figure means below that benchmark on a buy, not money made.
- Own volume: removing your 8.8% of the tape moves VWAP slippage from -6.19 to -6.79 bp; the larger your share, the more the benchmark measures you.
What must be logged for TCA to work?
Log the decision and every fill: TCA can only use what was recorded.
- Decision: ID, timestamp, side, quantity, reference price and its source.
- Child order: client order ID, send time, clock source and measured offset.
- Fill: venue trade ID, price, quantity, fee, fee asset, venue time and liquidity flag.
- Rates and market data: the fee asset's rate at fill time and the benchmark's trades or candles, venue named.
| Venue | Price, quantity | Fee | Time | Flag |
|---|---|---|---|---|
| Binance Spot | price, qty | commission, commissionAsset | time | isMaker |
| Bybit V5 | execPrice, execQty | execFee, feeCurrency | execTime | isMaker |
| Coinbase | price, size | commission | trade_time | liquidity_indicator |
| Hyperliquid | px, sz | fee, feeToken | time | crossed, true if taker |
| Kraken Spot | price, vol | fee, in quote currency | time | maker |
| OKX | fillPx, fillSz | fee, feeCcy | fillTime | execType |
Normalize before you sum: OKX signs a fee deduction negative and a rebate positive, Hyperliquid's WebSocket docs (opens in a new tab) call a negative fee a rebate, Binance's sample shows a positive commission, and Bybit's page does not say how a rebate is signed. OKX also says to sort trades by fillTime, not ts, the record's creation time. FIX carries the same facts in an execution report (35=8): LastPx (31), LastQty (32), TransactTime (60), ExecID (17), LastLiquidityInd (851) and a CommissionData component, per the FIX Latest dictionary (opens in a new tab). No private endpoint was called; these need real keys. Audit trail design is in crypto order management system.
What changes for TCA in crypto?
Crypto has no single market price to compare against, and clocks, fee assets and holding costs add work.
- No consolidated tape. The EU tape described in Directive (EU) 2024/790 (opens in a new tab), recital 8, covers shares, ETFs, bonds and OTC derivatives, so crypto VWAP means one venue's trades or your own blend. From public hourly candles on Bybit (opens in a new tab), Kraken (opens in a new tab) and KuCoin (opens in a new tab), BTC/USDT VWAP in each of the 10 hours to 16:00 UTC on 5 November 2026 differed between highest and lowest by a median 4.83 bp of their mean (range 1.36 to 50.12). One day is no comparison of venues, but a report must name its tape.
- Clocks. Venue times come from the venue's clock, yours from your machine; Kraken's public Time endpoint (opens in a new tab) returns
unixtimein whole seconds. At 21:12 UTC on 5 November 2026, NTP showed the machine used for this guide 1.13 seconds behind, an error any local-to-venue delay would carry, so log the measured offset. - Fees in another asset. Convert at the fill-time rate and keep its source. See the trading fee calculator.
- Funding on perpetuals. It is a cost of holding, not of execution, so report it on its own line. See funding rate explained.
Who is bound by best execution rules?
Best execution duties bind licensed firms executing client orders, not someone reviewing fills on their own account.
- MiFID II Article 27 requires investment firms to take all sufficient steps toward the best possible result. Directive (EU) 2024/790 deleted paragraph 6, the firms' annual venue reports, and replaced paragraph 3, the venues' reports, with a duty to tell the client where an order in instruments under the trading obligations was executed, for transposition by 29 September 2025.
- RTS 27 and RTS 28, the detailed report standards: Delegated Regulation (EU) 2026/825 (opens in a new tab), published 23 July 2026, repeals both in Article 10 and applies from 12 February 2028. It sets criteria for assessing execution policies and does not mention transaction cost analysis.
- MiCA Article 78 requires crypto-asset service providers executing client orders to take all necessary steps toward the best possible result and to monitor their arrangements.
This is not legal advice; rules depend on your jurisdiction and each platform's terms. For readers in Turkey: Article 99/A of Capital Markets Law No. 6362 treats activity by foreign crypto-asset platforms directed at Turkish residents as unauthorized.
Transaction cost analysis models and GitHub projects
Post-trade TCA measures what happened, while a pre-trade model estimates what an order might cost.
On GitHub, tcapy (opens in a new tab) describes itself as an open-source TCA library for FX spot, under Apache 2.0, with a last push on 2 February 2024 as read on 6 November 2026. GitHub searches for crypto TCA that day found only small 2026 projects with one star at most.
A small sample is noisy, and your own fills do not rank venues. For a product, check data rights too: section 9.4 of the OKX API Agreement (opens in a new tab) (last updated 28 July 2026) limits market data to personal, non-commercial use.
Reporting of this kind can run on read-only keys, as in crypto portfolio tracker API. For integration work in general, see Fincode's exchange API integration page; naming these venues here does not mean Fincode integrates them or has built on them, and Fincode gives no execution advice.
Frequently asked questions
What is TCA in trading?
Transaction cost analysis: comparing your fills with benchmarks such as arrival price or interval VWAP, plus fees and unfilled quantity.
What is the difference between slippage and implementation shortfall?
Slippage compares fill prices with one reference price. Implementation shortfall also counts delay, fees and whatever did not fill, against a paper portfolio.
Which TCA benchmark should I use?
It depends on the question: decision price covers the whole process, arrival price the working of the order, and interval VWAP or TWAP tracking.
Do best execution rules apply to my own trades?
MiFID II and MiCA duties bind firms executing orders for clients. Reviewing fills on your own account is not that activity, though local law can differ.
Can TCA show that one exchange is cheaper?
Not on its own. It measures your own orders at particular moments, and differences can come from size, timing and market conditions.
Sources
- Perold, The Implementation Shortfall, Journal of Portfolio Management 14(3), 1988 (DOI; publisher page needs a login, full text not opened) (opens in a new tab)
- Hasbrouck, NYU Stern: Securities Trading, Chapter 8, Transaction costs (course notes, 14 October 2015) (opens in a new tab)
- SEC: Staff Report on Algorithmic Trading in U.S. Capital Markets (5 August 2020) (opens in a new tab)
- Directive (EU) 2024/790 amending MiFID II, Official Journal, 8 March 2024 (Publications Office) (opens in a new tab)
- Commission Delegated Regulation (EU) 2026/825, Official Journal, 23 July 2026 (Publications Office) (opens in a new tab)
- ESMA interactive single rulebook: MiFID II Article 27 (opens in a new tab)
- ESMA interactive single rulebook: MiCA Article 78, execution of orders (opens in a new tab)
- Binance Spot API documentation: REST API (account trade list) (opens in a new tab)
- Bybit V5 API: Get Trade History (execution fields) (opens in a new tab)
- Coinbase Advanced Trade API: List Fills (opens in a new tab)
- Hyperliquid API: Info endpoint (userFills) (opens in a new tab)
- Kraken API: Get Trades History (opens in a new tab)
- OKX API v5: Transaction details (fills, fee sign, fillTime) (opens in a new tab)
- FIX Trading Community FIXimate, FIX Latest: ExecutionReport (35=8) fields (redirects to Orchimate) (opens in a new tab)
- Capital Markets Law No. 6362, consolidated text, Article 99/A (Turkish) (opens in a new tab)
This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the editorial policy.