Articles ·

Backtest audit, part 2: the data a backtest is given

Backtest audit, part 2: candles, funding and where maker orders rest

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.

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.