Backtest assumptions and realism
Every backtest result rests on a stack of defaults: how each fill is priced, what the equity curve marks at between trades, which quote a fill reads, what execution costs apply, and what the engine does not model. This page collects all of them so you never read a number without knowing what produced it. Before you trust a run, know how it fills, what it marks at, and what it leaves out.
How fills are priced
Each run picks one fill mode, a required choice with no silent default, and prices every entry and exit under it.
midfills every side at the quoted mid. You pay no spread cost. This is the optimistic bound, and it flatters a strategy that trades often.bid_askcrosses the full quoted spread: you buy at the ask and sell at the bid. This is the pessimistic bound, and it overstates cost on a strategy that mostly holds.- The Backtest.ai fill model books a calibrated estimate of where between mid and the touch the order actually fills. Paper and live trading always price with the fill model, so this is the mode to run when you want the backtest to match forward trading.
The mode decides the price booked for each side of a trade:
midbid_askIn bid_ask, a round trip pays the full spread twice. In mid, a round trip pays nothing to the spread. Under the fill model, each side pays a calibrated fraction of it. The two raw modes bracket a strategy; the fill model estimates it. Read a P&L number against the mode that produced it. On top of the modeled price, a size-dependent market-impact penalty applies in every mode, and a lot clamp caps order size against available liquidity; see fills, marks and slippage for the mechanism, and for the per-contract commission every run charges.
The equity curve is marked at mid
Between trades, the run marks every open position at mid. Margin, net liquidation value, and unrealized P&L all use the mid column, whatever the run's fill mode. Only the entry and exit transactions themselves pay spread cost. The curve you watch move minute to minute is a mid-priced curve in every mode; the spread cost shows up when a position opens or closes, and a position looks worth its mid value right up until you close it and pay the spread.
Forced liquidations always cross the spread
Two engine-driven closes ignore the run's fill mode and price at marketable bid/ask, even in a mid run:
- A margin-deficit forced close, when excess liquidity goes negative and the engine liquidates positions to restore it.
- An equity-halt force-close, when NLV drops to zero or below and the run closes everything and stops.
Both book at the bid when selling and the ask when buying, so a stressed run pays real spread cost on the way out even if its normal fills were priced at mid. This penalizes a stressed run harder than a clean one, on purpose: assuming a mid exit on a forced liquidation would understate the damage. A run that never hits a deficit is never penalized this way. The one exception is a user-threshold soft-trim close, which the engine drives on a minimum-excess-liquidity threshold you set: that close uses the run's own fill mode, not forced bid/ask.
Point-in-time, no look-ahead
A fill reads the most recent quote with timestamp ≤ t, the backward as-of rule from the execution model. No fill ever reads a quote stamped after the fill minute. This is the same point-in-time guarantee the rest of the engine holds to.
The cost of that rule sits in gappy data. A minute with no fresh quote carries the prior quote forward, so on a stale or thin stream you can fill against a quote that is minutes old. On a liquid, tightly-quoted contract this is close to reality. On a stale or gappy stream it understates true slippage, because your fill price is a quote the market has already moved past.
You don't trade the open or the close
The run does not fill at the session open or the session close. The spine is trimmed by a 5-minute open offset and a 5-minute close-before-expiration offset, so the first fill of a normal day is 09:35 and the last is 15:55. On a half-day the last fill is 12:55. A strategy whose logic assumes execution at the opening print or the closing print will not match a run here, because the run never offers those minutes as fill points. If your edge lives in the first or last five minutes of the session, the backtest cannot express it. For how the spine is built and trimmed, see the backtest lifecycle.
Every run carries a benchmark
Every run is read against a benchmark, S&P 500 total return by default, so results sit next to a reference return instead of standing alone. Custom benchmarks, including another saved backtest as the reference, are under consideration for the future. The reading backtest results page describes where the benchmark surfaces in the results view.
What the engine does not model
A run applies the assumptions above and nothing else. The table below lists what the engine leaves out. Read a result knowing these are absent from it.
On margin: the strategy-based model computes each position's requirement independently and sums them, so it nets only within a position's own legs. The portfolio-margin model nets across every position on the same underlying under a set of price shocks, so a hedged book can require less than the sum of its legs. Which model applies is a run-level choice; see margin and auto-decomposition.
Precision
All cash and strike math runs on exact fixed-point arithmetic at micro-dollar resolution (one part in 1,000,000), with banker's rounding at every step. There is no float drift accumulating across a long run: a P&L figure is exact to the micro-dollar, not a floating-point approximation of one.