QTQuant 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 and does 250 round trips per year. At 5 bps of cost per round trip, 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. 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: if the quote is bid 100.00 / ask 100.04 (4 bps spread), a marketable buy pays about 2 bps above mid, and the later sell pays 2 bps below mid — half the spread per side, the full spread per round trip. Market impact is the one that grows with you: doubling order size raises per-share impact by roughly the square root of two (Almgren et al. 2005), which is why backtests of small accounts do not scale to large ones.

Cost-adjusted returns

From D4-02, turnover at time tt is τt=ptpt1\tau_t = |p_t - p_{t-1}|. With a one-way cost of cc (as a decimal, e.g. 5 bps = 0.0005) per unit of turnover:

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

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 5 round trips per year: even at 10 bps it loses only 0.5% to costs.

Document or it didn't happen

Any backtest claim must state: the assumed cost per unit turnover, where that number came from (measured spreads? broker fill data? 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