Building a slippage model that does not flatter your backtest
Assuming you fill at the touch is the most common way a backtest lies. A crude but measured slippage model is worth more than a sophisticated guess.
Vizanix engineering · about the author
- SECTION
- Research and validation
- PUBLISHED
- 2026-08-31
- CHAPTERS
- 5
- READ NEXT
- 3
- LANGUAGE
- written in English
Every backtest contains a fill model, whether or not anyone wrote one. The default — you fill at the price you asked for — is a model, and it is wrong in a direction that always favours the strategy.
The three levels of honesty
Level 0: no slippage. Fill at the touch. Fine for a first sanity check, worthless for a go-live decision. Any strategy with a small edge is entirely fictional at this level.
Level 1: a fixed haircut. Assume N basis points against you on every market order. Crude, but it puts the cost in the right direction and forces the edge to clear a bar. If you do nothing else, do this.
Level 2: depth-based. Walk the recorded order book and compute what your size would actually have consumed. This is the honest version, and it requires having recorded the book — which is why the collect phase comes first.
def walk_book(levels, qty):
"""What this size actually costs against recorded depth."""
filled = cost = 0.0
for price, size in levels:
take = min(size, qty - filled)
cost += take * price
filled += take
if filled >= qty:
return cost / filled # volume-weighted fill
return None # not enough depth — the honest answer is 'you could not'That None return matters. A backtest that silently fills a size the book could not support is testing a market that did not exist. Returning nothing and skipping the trade is more truthful than extrapolating.
Measure it live, then calibrate
Any model is a guess until it is compared against reality. The measurement is simple and most operations do not do it:
# Log on every fill. This is the only real slippage number you will ever have.
slip_bps = (fill_price - decision_mid) / decision_mid * 10_000
if side == "sell":
slip_bps = -slip_bps
metrics.observe("slippage_bps", slip_bps,
tags={"symbol": symbol, "order_type": order_type, "size_bucket": bucket(qty)})After a few thousand fills you have a distribution. Compare its median and its 95th percentile against what your backtest assumed. If the backtest used one basis point and reality is four, every historical result needs re-reading.
What drives it
| Factor | Effect |
|---|---|
| Order size relative to depth | Dominant. Everything else is secondary. |
| Volatility at the moment of the order | Strong — the mid moves while you are in flight |
| Urgency | Market orders pay; patient limits do not, but risk not filling |
| Time of day | Thinner books in quiet hours |
| Whether the market is moving against you | Adverse selection: worse exactly when it matters |
The last row is why average slippage understates the cost. Your worst fills cluster in your worst moments, so the mean flatters and the tail is what hurts. Model the 95th percentile, not the median.
Limit orders need a different model
For a limit order the question is not price — you get your price or nothing. The question is whether it fills, and a backtest that fills every limit order whose price was touched is optimistic in a specific way: it ignores queue position.
A reasonable approximation: require the price to trade through your level, not merely touch it, before counting a fill. It is conservative and it removes the bulk of the fantasy. If you have full book data, model queue position properly.
The practical minimum
- Apply a fixed conservative haircut from day one. Never test at zero.
- Log realised slippage on every live fill, bucketed by size.
- Calibrate the backtest's assumption against the measured distribution quarterly.
- Use the 95th percentile for risk decisions and the median for expectations.
- Run the backtest across a slippage grid — if profitability dies between 1 and 5 basis points, you have learned something important cheaply.
This article describes engineering practice. It is not investment advice. Vizanix develops software and does not promise trading returns.