# Transaction cost analysis: TCA for crypto, with Python

URL: https://fincodelabs.com/en/blog/transaction-cost-analysis · Publisher: Fincode (Fincode Yazılım ve Teknoloji A.Ş.), Istanbul, Turkey · Category: Backtesting and risk · Published: 2026-11-06 · Updated: 2026-11-06

Transaction cost analysis (TCA) for crypto: cost types, arrival, VWAP and TWAP benchmarks, implementation shortfall in tested Python, and what to log.

**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.

TCA here means measuring execution from your own records, not transaction cost economics, the theory of why firms exist. A [US SEC staff report](https://www.sec.gov/files/algo_trading_report_2020.pdf) 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.

**Costs in TCA and whether your own records can show them**

| 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](https://fincodelabs.com/en/blog/slippage-crypto) and the [slippage calculator](https://fincodelabs.com/en/tools/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.

**Common benchmarks**

| 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](https://fincodelabs.com/en/blog/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](https://api.crossref.org/works/10.3905/jpm.1988.409150)) sits behind a login and was not opened for this guide. The definition here follows Joel Hasbrouck's [NYU Stern course notes](https://pages.stern.nyu.edu/~jhasbrou/Teaching/POST%202015%20Fall/classNotes/STPPTradingCosts.pdf) 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.

```python
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.

**Fill fields in official documents read 6 November 2026 (alphabetical, not a ranking)**

| 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](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions) 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](https://fiximate.fixtrading.org/en/FIX.Latest/msg9.html). No private endpoint was called; these need real keys. Audit trail design is in [crypto order management system](https://fincodelabs.com/en/blog/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](https://publications.europa.eu/resource/celex/32024L0790), recital 8, covers shares, ETFs, bonds and OTC derivatives, so crypto VWAP means one venue's trades or [your own blend](https://fincodelabs.com/en/blog/smart-order-routing-crypto). From public hourly candles on [Bybit](https://bybit-exchange.github.io/docs/v5/market/kline), [Kraken](https://docs.kraken.com/api-reference/market-data/get-ohlc-data) and [KuCoin](https://www.kucoin.com/docs-new/rest/spot-trading/market-data/get-klines), 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](https://docs.kraken.com/api-reference/market-data/get-server-time) returns `unixtime` in 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](https://fincodelabs.com/en/tools/crypto-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](https://fincodelabs.com/en/blog/perpetual-futures-funding-rate).

## 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](https://publications.europa.eu/resource/celex/32026R0825), 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](https://github.com/cuemacro/tcapy) 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](https://www.okx.com/help/okx-api-agreement) (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](https://fincodelabs.com/en/blog/crypto-portfolio-tracker-api). For integration work in general, see Fincode's [exchange API integration](https://fincodelabs.com/en/services/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)](https://doi.org/10.3905/jpm.1988.409150)
- [Hasbrouck, NYU Stern: Securities Trading, Chapter 8, Transaction costs (course notes, 14 October 2015)](https://pages.stern.nyu.edu/~jhasbrou/Teaching/POST%202015%20Fall/classNotes/STPPTradingCosts.pdf)
- [SEC: Staff Report on Algorithmic Trading in U.S. Capital Markets (5 August 2020)](https://www.sec.gov/files/algo_trading_report_2020.pdf)
- [Directive (EU) 2024/790 amending MiFID II, Official Journal, 8 March 2024 (Publications Office)](https://publications.europa.eu/resource/celex/32024L0790)
- [Commission Delegated Regulation (EU) 2026/825, Official Journal, 23 July 2026 (Publications Office)](https://publications.europa.eu/resource/celex/32026R0825)
- [ESMA interactive single rulebook: MiFID II Article 27](https://www.esma.europa.eu/publications-and-data/interactive-single-rulebook/mifid-ii/article-27-obligation-execute-orders)
- [ESMA interactive single rulebook: MiCA Article 78, execution of orders](https://www.esma.europa.eu/publications-and-data/interactive-single-rulebook/mica/article-78-execution-orders-crypto-assets)
- [Binance Spot API documentation: REST API (account trade list)](https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md)
- [Bybit V5 API: Get Trade History (execution fields)](https://bybit-exchange.github.io/docs/v5/order/execution)
- [Coinbase Advanced Trade API: List Fills](https://docs.cdp.coinbase.com/api-reference/advanced-trade-api/rest-api/orders/list-fills)
- [Hyperliquid API: Info endpoint (userFills)](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint)
- [Kraken API: Get Trades History](https://docs.kraken.com/api-reference/account-data/get-trades-history)
- [OKX API v5: Transaction details (fills, fee sign, fillTime)](https://www.okx.com/docs-v5/en/)
- [FIX Trading Community FIXimate, FIX Latest: ExecutionReport (35=8) fields (redirects to Orchimate)](https://fiximate.fixtrading.org/en/FIX.Latest/msg9.html)
- [Capital Markets Law No. 6362, consolidated text, Article 99/A (Turkish)](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf)

> 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](https://fincodelabs.com/en/editorial-policy).

---

Published by Fincode (Fincode Yazılım ve Teknoloji A.Ş.), a software company in Istanbul, Turkey: https://fincodelabs.com/en/blog/transaction-cost-analysis

Cite as: Fincode, "Transaction cost analysis: TCA for crypto, with Python", updated November 6, 2026, https://fincodelabs.com/en/blog/transaction-cost-analysis
