Skip to content
python.financial

Transaction costs are every reduction between a strategy's paper portfolio and the portfolio that can actually be implemented. They include explicit charges, bid-ask spread, timing and execution-price shortfall, market impact, unfilled-order opportunity cost, financing, borrow, funding, and taxes where applicable.

Slippage is narrower. It is the signed difference between a chosen benchmark price and the achieved execution price. Slippage can be favorable or adverse. Calling every cost "slippage" hides which assumptions need data, calibration, or a different execution model.

A zero-cost backtest is a frictionless upper bound, not a deployable estimate. A fixed basis-point haircut is useful for a first sensitivity test, but it is not evidence that costs are realistic.

Cost components

Component Typical measurement or input Common backtest mistake
Commissions, exchange and regulatory fees, rebates, taxes Broker and venue schedules effective on the trade date Using today's schedule for the entire history or ignoring minimum charges
Bid-ask spread Executable bid and ask at venue arrival Trading every order at a midpoint or subtracting spread twice
Delay and latency Decision price versus arrival price Filling a close-derived signal at that same close
Market impact Price response conditional on side, size, rate, volatility, liquidity, and venue Applying one constant to every order size
Opportunity cost Paper-portfolio value versus actual value for unfilled or cancelled quantity Treating a non-fill as zero cost
Financing and inventory Cash rate, margin rate, borrow fee, locate availability, funding, and variation margin Charging costs only on trade dates

Fees can be known ex ante only when the schedule and routing are known. Spread, impact, and opportunity cost are stochastic. Estimate their distributions, not just one favorable average.

The gap between the decision price and the real result

Andre Perold's implementation-shortfall framework compares an implemented portfolio with the paper portfolio available when the investment decision was made. The full portfolio formulation captures delayed and unexecuted quantity. A filled-order approximation is useful for auditing individual executions.

For fill (i):

implicit_cost_i = side_i * quantity_i * (fill_price_i - benchmark_price_i)
total_cost_i = implicit_cost_i + explicit_fees_i
cost_bps_i = 10,000 * total_cost_i / (quantity_i * benchmark_price_i)
  • (side_i) is +1 for a buy and -1 for a sell.
  • (quantity_i) is a positive filled quantity.
  • (benchmark_price_i) might be the decision midpoint, arrival midpoint, or another prespecified price.
  • (fill_price_i) is the volume-weighted average execution price for that fill set.
  • (explicit_fees_i) is positive for charges and negative for net rebates.

The side sign makes a buy above its benchmark and a sell below its benchmark both positive costs. State the benchmark because decision-price, arrival-price, close, and VWAP shortfalls answer different questions. An arrival benchmark omits the delay between decision and venue arrival.

Order-level Python example

This round trip buys 1,000 shares against a $100 decision benchmark and later sells them against a $101 benchmark. Both executions are adverse, and each order pays $3.50 in explicit fees.

import numpy as np


side = np.array([1, -1])  # buy, sell
quantity = np.array([1_000.0, 1_000.0])
benchmark = np.array([100.00, 101.00])
fill = np.array([100.08, 100.94])
fees = np.array([3.50, 3.50])

implicit_cost = side * (fill - benchmark) * quantity
total_cost = implicit_cost + fees
cost_bps = 10_000 * total_cost / (quantity * benchmark)

paper_pnl = (benchmark[1] - benchmark[0]) * quantity[0]
realized_pnl = (fill[1] - fill[0]) * quantity[0] - fees.sum()

print(f"Buy cost: ${total_cost[0]:.2f} ({cost_bps[0]:.2f} bps)")
print(f"Sell cost: ${total_cost[1]:.2f} ({cost_bps[1]:.2f} bps)")
print(f"Round-trip implementation shortfall: ${total_cost.sum():.2f}")
print(f"Paper PnL at benchmarks: ${paper_pnl:.2f}")
print(f"Realized PnL after fills and fees: ${realized_pnl:.2f}")
Buy cost: $83.50 (8.35 bps)
Sell cost: $63.50 (6.29 bps)
Round-trip implementation shortfall: $147.00
Paper PnL at benchmarks: $1000.00
Realized PnL after fills and fees: $853.00

The $147 difference reconciles exactly: $80 of buy shortfall, $60 of sell shortfall, and $7 of fees. This example has complete fills and no holding costs. A parent order that fills only partly needs a paper-versus-actual portfolio calculation to include the remaining quantity's opportunity cost.

Do not double-count spread and impact

Suppose a buy benchmark is the midpoint and the simulated fill is the ask. The fill-minus-midpoint shortfall already contains the crossed half-spread. Subtracting a separate half-spread again duplicates that cost. Conversely, a backtest based on last trades or bar closes may contain neither an executable spread nor the strategy's own impact.

Cost decomposition is benchmark-dependent. A fill shortfall against the arrival midpoint can combine spread, price movement during execution, and impact. Separating those components requires quotes, timestamps, routing, and a causal model. The total paper-versus-actual shortfall is often more defensible than a falsely precise attribution.

Passive orders need different accounting. A maker rebate does not guarantee a cheap fill. Queue position, non-fill probability, partial fills, and adverse selection after a fill can dominate the rebate. A marketable order pays for immediacy but reduces missed-trade risk. Compare complete policies, not fee labels alone.

Market impact and capacity

Impact is endogenous. The order changes the market it is trying to measure. Cost generally depends on:

  • parent-order size as a fraction of available or daily volume,
  • execution rate and schedule,
  • spread, volatility, depth, and recent order flow,
  • side, urgency, venue, auction or continuous session, and time of day,
  • cross-asset and portfolio crowding, and
  • whether impact decays or persists.

Robert Almgren, Chee Thum, Emmanuel Hauptmann, and Hong Li's empirical impact study estimated size, volatility, volume, and execution-rate relationships from institutional equity orders. Its fitted exponents and coefficients describe that dataset and period. They are not constants to paste into every asset class.

A square-root or power-law impact curve can be a useful stress model when calibrated to relevant orders. Fit it on historical parent orders, reserve later orders for validation, and report prediction intervals. Extrapolating far beyond observed participation rates is a capacity assumption, not a measurement.

Strategy capacity is the capital or order size at which net performance no longer meets the objective under executable constraints. Because impact is nonlinear, doubling capital need not double cost. Re-run sizing, fills, cash, and subsequent signals together rather than subtracting a constant from an already completed return series.

Match the model to the available data

Daily OHLCV can support a coarse next-bar price, spread proxy, fees, and participation cap. It cannot reveal quote-side liquidity, queue position, the intrabar path, or a counterfactual market response. Bar volume is not quantity offered at every price.

Quotes and trades support effective-spread, timing, and short-horizon response estimates. L2 books add aggregate depth. L3 data can show recorded order priority. Even full historical books describe the market without the simulated order. Large counterfactual orders would have changed that history.

Use the least complex model that captures the strategy's execution driver:

  • low-turnover allocation may need conservative next-session fills, calibrated spread, commissions, and holding costs,
  • intraday taker strategies need quote-side fills, latency, depth, and participation-sensitive impact,
  • passive strategies need queue, cancellation, partial-fill, adverse-selection, and non-fill models, and
  • derivatives and short strategies need contract multipliers, margin, funding, borrow, exercise, and liquidation rules.

Higher turnover increases exposure to one-way costs, but horizon alone does not determine materiality. A low-frequency rebalance can still move an illiquid market. A fast strategy trading tiny size in deep liquidity may have low impact but remain sensitive to spread and latency.

Calibrate and stress the assumptions

Collect decision timestamps, parent-order IDs, side, requested and filled quantity, arrival quotes, every fill, cancellation, venue, explicit fee, and subsequent prices. Segment realized shortfall by size or ADV, spread, volatility, urgency, time of day, and order type. Avoid learning a cost model on fills alone because missing orders are selected outcomes.

Use three layers:

  1. Base case: parameters estimated from comparable out-of-sample executions.
  2. Stress cases: worse spread, latency, volatility, depth, fill rate, financing, and impact supported by historical regimes or explicit shocks.
  3. Break-even case: the cost level that removes the strategy's net edge.

Report net results across capital and turnover levels. Preserve gross results only to show where costs enter. If the strategy was selected after trying multiple cost settings, that choice belongs in the research history.

What backtesting tools can model

Community VectorBT accepts percentage fees, fixed_fees, and a deterministic percentage slippage penalty in its portfolio orders. These inputs can be arrays, so a researcher can supply time- and asset-varying assumptions. The engine does not infer a spread or impact curve from OHLCV.

VectorBT PRO adds richer market, limit, stop, time-in-force, pending-order, broadcasting, chunking, and custom-order workflows. Supported portfolio simulations can use compiled Numba or native Rust execution, which makes large cost and capacity grids practical. The speed does not calibrate the inputs. Its percentage slippage remains a price penalty unless the researcher supplies a more detailed execution rule, and a touched limit is not proof of live queue priority.

NautilusTrader can replay bars, quotes, trades, and L1, L2, or L3 order books through a matching engine with configurable latency, fees, fill behavior, queue assumptions, and liquidity consumption. Its fill and matching documentation explains the model boundaries. Detailed replay can improve execution testing, but recorded books do not reveal how other participants would have reacted to the simulated orders.

The architecture does not choose realism. A vectorized backtest with calibrated order-level cost arrays can be more useful than an event engine running optimistic defaults. An event-driven book replay can be necessary when stateful execution determines the strategy. In either case, reconcile simulated shortfall with paper or live fills before treating net performance as credible.

Choose which optional services may run. You can change these settings at any time.