Tektii just went live. We're shipping fixes daily — spot a bug? Let us know

Backtest Execution & Costs

When your strategy sends an order, the backtest engine fills it against the market data for that instant. Every fill crosses the bid/ask spread — the same spread a live broker would quote. On top of the spread you can optionally model commission, slippage, and funding. All three default to zero, so unless you configure them a backtest pays only the spread.

How orders fill

Every order crosses the spread at the prevailing quote:

  • A buy fills at the ask (the higher quote).
  • A sell fills at the bid (the lower quote).

This is what "realistic execution" means here: you pay to cross the book on every fill, never getting the mid-price for free.

Where the spread comes from

The spread is taken from real historical market data, not a fixed assumption, so it widens and narrows through the session just as the live market did. How the bid and ask are sourced depends on the asset class.

  • Forex — native broker bid/ask quotes. The historical feed already carries both sides of the book, so your fills use the actual quoted spread for each instant.
  • Crypto — synthetic bid/ask. Crypto venues publish trades rather than a continuous top-of-book quote, so Tektii derives a bid and an ask around the traded price. Your fills cross this synthetic spread the same way forex fills cross the native one.

Configurable costs

Beyond the spread, three optional costs can be layered on through the costs object in your scenario config file. Every cost defaults to zero — omit costs (or leave a field at its default) and the backtest pays only the spread: nothing extra is deducted.

  • Commissioncosts.commission. Charge basis points of notional per side (entry and exit) with { "model": "bps_of_notional", "fee_rate_bps": "10" }. The default, { "model": "zero" }, charges nothing.
  • Slippagecosts.slippage. Adds adverse price movement on top of the spread, in basis points — base_slippage_bps, plus a size-impact term and a cap. Defaults to zero.
  • Fundingcosts.funding. Models overnight financing on positions held across the rollover, e.g. { "model": "daily", "long_rate": "-0.03", "short_rate": "-0.01" }. The default, { "model": "disabled" }, charges nothing.

Example — charge 10 bps commission per side and leave slippage and funding off:

{
  "strategyVersionId": "b2c3d4e5-f6a7-4b2c-8d3e-4f5a6b7c8d9e",
  "subscriptions": [{ "instrument": "F:EURUSD", "events": ["candle_1m"] }],
  "startTime": "2024-03-04T13:30:00Z",
  "endTime": "2024-03-08T21:00:00Z",
  "costs": {
    "commission": { "model": "bps_of_notional", "fee_rate_bps": "10" }
  }
}

The top-level config keys are camelCase, but costs and its nested fields use snake_case (fee_rate_bps, base_slippage_bps, long_rate) on every surface — see Scenario Configuration for the full config file.

Gross vs net results

Backtest results report both a net and a gross P&L so you can see the impact of commission at a glance:

  • netPnl — your equity change after every cost the engine applied: spread, commission, slippage, and funding.
  • grossPnlnetPnl with commission added back: grossPnl = netPnl + totalCommission.
  • totalCommission — the sum of entry and exit commission across all trades.

For the full definition of every reported metric and the schema of the downloadable result files, see the Results Reference.