Quant Terminal
D4-03D4·intermediate·~20 min

Transaction cost modeling — the edge that costs eat

backtestingtransaction-costsspreadslippageturnovermarket-impact

▸ Pretest — guess, even if you don't know

A strategy earns 10 percent per year gross (before costs) and does 250 round trips per year (a round trip is a buy plus its later sell). At 5 bps of cost per round trip (bps = basis points; 1 bp is 0.01 percent), roughly what is its net annual return?

The cost stack, with numbers

Recall D4-01's list, now as a model you can compute with. First the vocabulary, then the numbers. Spread — the gap between the best bid (highest price anyone will pay) and the best ask (lowest price anyone will sell for); crossing it costs you money. Slippage — the difference between the price you expected and the price your order actually filled at. Borrow fees — the rent you pay to borrow shares for a short position. Market impact — your own order pushing the price against you as it fills. For a retail-scale trader in liquid US large-caps:

ComponentTypical sizeScales with
Spread1–10 bps quoted; you pay the half-spread per sideevery trade
Slippage5–20 bps for medium ordersorder size, urgency
Commission~0 at most retail brokers todayper trade
Borrow fees30–300 bps annualizedshort positions held
Market impactgrows roughly like the square root of order size / ADVsize relative to liquidity

The half-spread convention: suppose the quote is bid 100.00 / ask 100.04 — a 4 bps spread, since 0.04 on a price of 100 is four hundredths of a percent. A marketable buy (an order that crosses the spread to fill immediately) pays about 2 bps above mid — the midpoint between bid and ask. The later sell collects about 2 bps below mid. So you pay half the spread per side, the full spread per round trip. Market impact is the one that grows with you. ADV is average daily volume — how many shares typically trade in a day. Doubling your order size raises per-share impact by roughly the square root of two, about 1.4× (Almgren et al. 2005). That is why backtests of small accounts do not scale to large ones.

Cost-adjusted returns

From D4-02, turnover — the amount of trading you do — at time tt is τt=ptpt1\tau_t = |p_t - p_{t-1}|. Read that as: "tau sub t" (τ is the Greek letter tau) equals the absolute value of today's position minus yesterday's position. Let cc be the one-way cost per unit of turnover, written as a decimal — for example, 5 bps = 0.0005. Then:

rtnet=rtgrosscτtr^{\text{net}}_t = r^{\text{gross}}_t - c \cdot \tau_t

In words: the net return at time t equals the gross return minus c times that day's turnover. The superscripts "net" and "gross" are just labels — net means after costs, gross means before. Concrete numbers: you go from flat to fully long (turnover 1) on a day the strategy grosses 0.30%; at c=0.0005c = 0.0005 the net is 0.00300.0005=0.00250.0030 - 0.0005 = 0.0025, or 0.25%.

That's the whole model. Its honesty lives entirely in the value of cc — which is why cc must be stated, justified, and stress-tested, never defaulted to zero.

The sensitivity table

Never report one net number. Report net performance as a function of assumed cost. For a strategy earning 10% gross with 250 round trips per year:

Round-trip costAnnual cost dragNet return
2 bps250 × 2 bps = 5.0%+5.0%
5 bps250 × 5 bps = 12.5%−2.5%
10 bps250 × 10 bps = 25.0%−15.0%

The same backtest is a good strategy, a coin flip, or a disaster — depending on one assumption. A strategy whose sign flips inside the plausible cost range is not a strategy. It is a bet on your cost model. Contrast a 10%-gross strategy doing only 5 round trips per year: even at 10 bps, it loses just 0.5% to costs.

Document or it didn't happen

Any backtest claim must state three things: the assumed cost per unit of turnover, where that number came from (measured spreads? broker fill data? a guess?), and the sensitivity table. This is the "realistic transaction costs" checkbox from the D4-01 checklist made concrete. An undocumented cost assumption is indistinguishable from an optimistic one.

Try it

▮ EXERCISE · d4-03-ex1

Implement net_returns(gross_returns, positions, cost_bps): turnover_t = abs(positions_t - positions_t-minus-1) with the position before the first day equal to 0, and net_t = gross_t - turnover_t * cost_bps / 10000. cost_bps is the one-way cost per unit of turnover, in basis points. Return a numpy array.

⧉ Review card
What is the half-spread convention for transaction costs?
A marketable order pays about half the quoted spread relative to mid, per side. A full round trip (buy then sell) pays the full spread. Quoted spread 4 bps means roughly 2 bps per side.
⧉ Review card
How do you compute cost-adjusted returns from a position series?
turnover_t = abs(p_t - p_t-minus-1), with position 0 before the start. net_t = gross_t - c * turnover_t, where c is the one-way cost per unit of turnover as a decimal.
⧉ Review card
How does market impact scale with order size?
Roughly with the square root of order size divided by average daily volume (Almgren et al. 2005). Doubling size raises per-share impact by about 1.4x — small-account backtests do not scale to large capital.
⧉ Review card
Why report a cost sensitivity table instead of one net return?
Because the net result can flip sign within the plausible cost range: 10 percent gross at 250 round trips per year is +5 percent at 2 bps, -2.5 percent at 5 bps, -15 percent at 10 bps. A strategy whose sign depends on the cost guess is a bet on the cost model.

Explain it

Your generative activity: explain out loud, to an imaginary friend who just showed you a backtest with Sharpe 2 and daily trading, why their result might be an artifact of a zero-cost assumption. Use the 10-percent-gross, 250-round-trip example, and tell them exactly what two numbers you'd ask for (turnover and assumed cost per round trip).

Predict before the next lesson

D4-04 covers walk-forward testing and out-of-sample discipline. Predict:

◈ Calibration check

Could you cost-adjust a backtest from its position series and defend your cost assumption with a sensitivity table?

1 = guessing · 5 = could teach it

⏻ End of lesson

Mark it read to book its 4 review cards into your deck.

Sources & further reading