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:
#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.
#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.
| Section | Type | Parameters |
|---|---|---|
| stopLoss | percent | percent — distance from entry, e.g. 1.0 for 1% |
| stopLoss | atr | period, multiplier — stop distance = ATR(period) × multiplier |
| takeProfit | riskReward | multiple — take-profit distance = stop-loss distance × multiple |
| takeProfit | percent | percent — distance from entry, independent of the stop-loss |
"riskManagement": {
"stopLoss": { "type": "atr", "period": 14, "multiplier": 1.5 },
"takeProfit": { "type": "percent", "percent": 2.0 }
}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": {
"timeframe": "1H",
"direction": "short"
}direction only changes how the position is priced and scored, both mirrored around the entry price:
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:
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.
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": {
"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": {
"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.