Articles ·
Backtest audit, part 2: the data a backtest is given
Part 1 rebuilt how the engine walks a bar, how indicators start and how a strategy warms up. This part is about what the engine is given: a backtest replays exactly the bars, funding and market rules it receives, so it must refuse what an exchange could never have produced and say plainly what the history lacks. This matters most for the API, where the data comes from your own code.
Every candle is checked
Full rules: What a Metrand backtest accepts as market data.
- Prices finite and above zero, the high and low enclosing the open and close, volume not negative.
- Open times in milliseconds, forward in time, once each, all on one timeframe. Timestamps in seconds and close-time stamps are recognised and refused with a message that says so.
- An empty list of bars is refused instead of quietly replaying the synthetic demo series.
- Bars the exchange's history lacks are no longer skipped in silence: the report counts them and names the longest gap, and the app shows it. Five years of 15-minute futures history for ten major coins were all sound, except one three-day hole in LTC, XLM and XRP that earlier reports had crossed without a word.
Funding must be whole
A funding series sent with the bars must hold every settlement of the traded span: no stretch longer than 8½ hours without one. A partial series used to charge part of the window and leave the rest free. No funding at all is still allowed, and the report says it was not applied.
Other markets on the same timeframe
A strategy that reads other markets gets their bars on its own timeframe; a longer bar of another market would have reached the strategy before it closed, so such a request is refused. When two markets' bars open together, the traded market's comes first, as a live run receives them.
Maker orders rest where a live run rests them
A post-only entry, and every post-only rung of a ladder, now rests two price steps of the market off the touch — the
same steps a live run uses — instead of 2 basis points. On BTCUSDT at 100,000 that is 0.2 USDT instead of 20: the old
rule rested maker orders a hundred times further away than a live run, so a backtest filled fewer of them and at better
prices than live would. Where the price step is not known, 2 basis points stand in, and the result says which rule was
used (report.config.maker_offset).
The traded market's own lot rules
Orders are sized to the lot step, minimum quantity and minimum order of the market the backtest replays. When the
futures market's rules could not be read, the backtest used to borrow the spot market's and still call them the
venue's. Now it uses only the traded market's rules; without them it sizes on generic ones and the result says so
(report.config.lot_rules: "generic").
A window ends at its last closed bar
An end time in the middle of a bar ends the window at the bar before it. The bar containing the end time used to be included, and with it prices from after the moment asked for.
A ladder that runs short says so
A ladder's orders are sized and checked one by one against the market's rules, as a live run checks them: a rung under
the market's minimum order or lot, or past the plan's declared total, is not placed. The backtest used to leave such
rungs out without a word, and a ladder of six could quietly trade as four. The report now counts them
(plan_legs_unplaced), and the app says how many orders could not be placed and why.
Margin by the market's own schedule
Liquidation used to be priced on one maintenance margin, BTCUSDT's 0.4%, on every market and at every size. Smaller
markets keep 2.5% and more, and every market keeps more as a position grows, so a leveraged backtest on them liquidated
too late. A backtest now uses its own market's schedule, read from the exchange every day: the maintenance margin by
position size, and the largest position each leverage allows. An entry past that size is not opened, as the exchange
rejects it, and a leverage above the market's maximum is refused before it is charged. The result names the schedule
and the day it was read (report.config.margin_tiers). How it is computed:
How a Metrand backtest prices liquidation and the largest position.
Next
The next part is about how results are shown: totals, baskets of markets, and the optimizer's held-out check.