Documentation

Execution Engine

How the backtest engine evaluates strategies — entry logic, the configurable risk model, position management, and determinism rules.

#Overview

The backtest engine evaluates your strategy against historical OHLCV data candle by candle, in chronological order. For each candle, it runs the full evaluation pipeline: compute inputs, evaluate conditions, sum scores, check the decision rule, and open a trade if the rule is met.

The engine is deterministic. Given the same strategy, asset pair, and date range, it always produces the same result.

#Evaluation Pipeline

For each candle, the engine runs in this order:

1
Compute inputsAll input expressions are evaluated using current and past candle data. Indicators are computed with the full lookback required.
2
Evaluate conditionsEach condition is evaluated as a boolean using the computed inputs and comparison operators.
3
Sum scoreThe scores of all true conditions are summed to produce the candle's total score.
4
Check decisionThe entry and exit expressions are evaluated. If entry resolves to true, a new trade is opened.
5
Update open positionsAll currently open trades are checked against the high and low of the candle to see if SL or TP was hit, then against the exit signal.

#Entry Execution

Entries are executed at the close of the candle where the decision rule evaluates to true. There is no look-ahead — the signal is generated using data up to and including the current close, and the entry price is that close.

Entry on close means the strategy cannot react faster than one candle interval. On a 1H strategy, the minimum response time to a market event is one hour.

#Risk Model (R-Units)

Every trade's stop-loss and take-profit are calculated at entry by the strategy'sriskManagement configuration. It is optional — if a strategy omits it, the engine falls back to a default:

  • Stop-loss1% from entry price (default).
  • Take-profit3% from entry price — 1:3 risk-reward ratio (default).
  • Win (in R)+3.0R per winning trade (default).
  • Loss (in R)-1.0R per losing trade (default).

Results are measured in R, not in currency amounts. This makes strategy results comparable regardless of account size, asset price, or position sizing method.

Configuring riskManagement

To override the default, add a riskManagement object to the strategy with a stopLoss and/or takeProfit block.

SectionTypeParameters
stopLosspercentpercent — distance from entry, e.g. 1.0 for 1%
stopLossatrperiod, multiplier — stop distance = ATR(period) × multiplier
takeProfitriskRewardmultiple — take-profit distance = stop-loss distance × multiple
takeProfitpercentpercent — distance from entry, independent of the stop-loss
riskManagement
"riskManagement": {
  "stopLoss":   { "type": "atr", "period": 14, "multiplier": 1.5 },
  "takeProfit": { "type": "percent", "percent": 2.0 }
}
With a custom riskManagement, win/loss R magnitudes are no longer always exactly +3.0R / -1.0R — an ATR-based stop-loss, for example, varies the risk distance (and therefore the R value of each trade) based on volatility at entry time.

Why R-units?

R-units remove the illusion of dollar profits. A strategy withpnlR: +50 returned 50 units of risk across the backtest period. Whether each unit was $10 or $1,000 is a position sizing decision made separately. This is the correct way to evaluate statistical edge.

#Direction

By default every strategy is long — it buys on entry and profits when price rises. Set configuration.direction to "short" to trade the other side instead: the strategy sells on entry and profits when price falls. A single backtest run is one direction or the other, never both — decision.entry/exit,conditions, and score keep their exact same meaning either way.

configuration
"configuration": {
  "timeframe": "1H",
  "direction": "short"
}

direction only changes how the position is priced and scored, both mirrored around the entry price:

Stop-lossPlaced above entry instead of below — a rising price stops the trade out.
Take-profitPlaced below entry instead of above — a falling price takes profit.
PnL signProfit when the exit price is below entry; loss when it's above — the mirror image of long.
Every stop-loss/take-profit type (percent, atr, riskReward) and the same-candle checking order described in Exits below are unaffected by direction — only the price placement and PnL sign mirror.

The result's top-level direction field ("long" or "short") records which side the run traded, so results are self-describing without cross-referencing the input strategy.

#Exits

A position can close three ways: stop-loss, take-profit, or a signal exit (decision.exit, see Strategy System). Each open position is checked against all three, in this order, every candle:

Stop-lossCandle low reaches the stop price.
Take-profitCandle high reaches the take price.
Signal exit"decision.exit" evaluates to true.
The first of these three checks that triggers on a candle closes the position — the others are not evaluated for that position on that candle. Concretely: if a candle's stop-loss (or take-profit) is hit on the same candle decision.exitalso turns true, the stop-loss/take-profit wins. A signal exit fills at the candle's close, same as entry; stop-loss/take-profit fill at their respective price levels.

Each closed trade's exitReason field in the results records which of the three closed it: 0 (stop-loss),1 (take-profit), or 2 (signal exit).decision.exit is optional — strategies that omit it are unaffected and only ever close via stop-loss or take-profit, exactly as before.

#Multiple Positions

By default, the engine supports multiple open positions simultaneously. Every candle where the decision rule evaluates to true opens a new, independent trade — even if other trades are already open.

There is no position sizing or capital allocation simulation. Each trade is fully independent. If 5 trades are open simultaneously and all hit SL, the result is -5R. The engine does not simulate compounding or account depletion.

This is intentional: the goal is to evaluate the statistical edge of the strategy signal itself, not the portfolio behavior under any particular position sizing scheme.

Capping concurrent positions

Set configuration.maxOpenPositions to cap how many trades can be open at once. When the cap is reached, new entry signals are skipped until a position closes. Omit it (or leave it null) for unlimited concurrent positions — the default, unchanged behavior.

configuration
"configuration": {
  "timeframe": "1H",
  "maxOpenPositions": 1
}

maxOpenPositions: 1 gives single-position mode — the closest approximation to how a trader managing one position at a time would run the strategy.

#Same-Candle Ambiguity

When both the stop-loss and take-profit levels are reached within the same candle (i.e., the candle's low is below SL and its high is above TP), the engine conservatively assumes the stop-loss triggered first.

The count of these ambiguous trades is reported in the bothHitfield of the results. A high bothHit count relative to total trades may indicate the strategy is being used on a timeframe that is too coarse for the intended entry precision.

#Warmup Period

Indicators require a minimum number of candles to compute (the lookback period). For example, ema(close, 21) requires at least 21 candles of history before its value is meaningful — before that, it is still converging (or, for window-based indicators like rsi and atr, undefined).

The engine does not infer this automatically. Set configuration.warmupBars to the largest lookback period used by any indicator in your strategy. Candles within the warmup window are still included in the results (so you can inspect indicator values as they stabilize), butentry is forced to false andscore to 0 for that range — no trade can open during warmup.

configuration
"configuration": {
  "timeframe": "1H",
  "warmupBars": 21
}
warmupBars defaults to 0. If your strategy uses an indicator with a longer lookback than the warmup you configured (or you omit it entirely), early candles may generate signals off of indicator values that have not fully stabilized yet.

#Strategy Diagnostics

Beyond performance metrics, the engine tracks two diagnostic datasets that are critical for strategy quality analysis:

conditionsDistributionPct

For each condition in the strategy, the fraction of all evaluated candles where it was true. A condition that is true on 95% of candles adds almost no discriminating power and may be wasting score weight.

scoreDistributionPct

For each possible score value, the fraction of candles that reached that score. This shows how often your strategy is "close to triggering" vs. fully aligned, and helps tune your entry threshold.

Use these diagnostics to identify overfitting risk. If your entry threshold is barely above the most common score, you may be triggering too frequently on marginal signals.

#Limitations

  • No slippage simulation. Entries and exits are at exact close/SL/TP prices.
  • No trading fees or commissions are deducted from results.
  • No capital compounding. Each trade is sized independently at 1R.
  • A single backtest trades one direction (long or short via configuration.direction) — a strategy cannot hold both long and short positions in the same run.
  • Data is limited to supported asset pairs and their available historical range.
  • The engine processes candles sequentially — there is no intra-candle execution simulation.
These limitations are by design. The engine is built to evaluate statistical edge, not to simulate live trading. The Live Execution API(beta) runs the same strategy against real, live market data — no simulation, real prices — but doesn't execute orders on your behalf; it emits Entry/Exit signals only.