Backtesting and paper trading prove different things
A backtest tests a hypothesis about the market. Paper trading tests your software against reality. Skipping either one means shipping on an untested assumption.
Vizanix engineering · about the author
- SECTION
- Research and validation
- PUBLISHED
- 2026-08-28
- CHAPTERS
- 7
- READ NEXT
- 3
- LANGUAGE
- written in English
These two get treated as interchangeable stages of the same process — first the cheap simulation, then the expensive one. They are not. They answer different questions, and a system can pass one convincingly while failing the other.
What each one is for
| Backtest | Paper trading | |
|---|---|---|
| Question | Does this edge exist in history? | Does my software behave in the live market? |
| Fills | Modelled by you | Simulated against the real book |
| Catches | A hypothesis with no historical support | Latency, staleness, reconnects, sizing bugs |
| Misses | Everything about your infrastructure | Regimes not present in the test window |
| Cost | Compute | Time — real time, at market speed |
| Fails when | Costs are subtracted afterwards | It runs on a different code path |
The last row is the one that decides whether the exercise is worth anything at all.
Costs belong inside the loop
The most common way a backtest lies is subtracting fees from the final equity curve. It sounds equivalent and is not, because costs change which trades happen at all.
A strategy taking a hundred trades a day at an average edge of 0.08% and a round-trip cost of 0.11% is not “profitable minus fees”. It never should have taken those trades. Applied inside the loop, the cost filter removes them and the remaining sample is a different, much smaller strategy.
# Wrong: a profitable curve with a haircut at the end.
equity = simulate(strategy, data)
equity -= total_fees # trades that never made sense still happened
# Right: cost is part of the decision.
def on_signal(sig, book):
edge = expected_move(sig)
cost = fee_in + fee_out + slippage_estimate(book, sig.size)
if edge <= cost:
return skip("edge below cost") # this trade does not exist
return trade(sig)Our microstructure engine states this as a design rule: if out-of-sample expected value is not positive after fees, slippage and latency stress, the system does not trade — and that rule lives in code, not in a document. The break-even calculator is the one-line version of the same idea.
Grids, not point estimates
A single latency assumption and a single slippage assumption produce a single number that tells you almost nothing. Run the backtest across a grid — latency from optimistic to pessimistic, slippage from zero to a realistic tail — and look at the shape of the result.
A strategy whose profitability collapses between 50 ms and 150 ms of assumed latency is not a strategy, it is a bet on your infrastructure. Better to learn that from a grid than from production.
What paper trading catches
Paper trading places virtual orders against the live order book. It cannot tell you whether the edge is real, because you cannot run it long enough to matter. It tells you whether your software works — and the list of things it catches that no backtest can is long:
- Data arriving late, out of order, or not at all.
- The gap between your assumed fill price and where the book actually was.
- Reconnects, and whether the strategy resumes correctly after one.
- Sizing that passes in simulation and hits venue lot filters in reality.
- Rate limits under real message volume.
- Clock drift, and every other thing that only exists on a real host.
One code path, three modes
The requirement that makes both stages meaningful: backtest, paper and live must run the same strategy and risk code, differing only in the execution adapter. If the backtest has its own simplified order handling, you have validated something that will never run.
Our engines are built this way — collect, research, backtest, paper, live are phases of one system with a mode flag, not separate programs. The practical benefit is that a bug found in paper is a bug in the code that will trade, and a fix is verified by re-running the backtest.
Comparing the two is the actual test
The number worth watching is not the paper P&L. It is the divergence between paper and backtest over the same period. Same signals? Same fill prices, within the modelled slippage? Same trade count?
A divergence means one of them is wrong, and you now know to look. Convergence means your fill model is defensible — which is the only thing that makes the backtest's historical result worth anything.
The order, and why
- Backtest with costs inside and a latency grid. If the edge dies here, stop — cheaply.
- Paper trade on the same code path. Compare against the backtest, not against zero.
- Testnet for order lifecycle: rejections, partial fills, reconnect drills.
- Live with minimum size, with the kill switch tested first.
- Scale only after live matches paper qualitatively.
Every stage skipped moves a discovery later, where it is more expensive. That is the entire argument.
This article describes engineering practice. It is not investment advice. Vizanix develops software and does not promise trading returns.