---
title: Stats Reference
description: Every performance stat the Strategy Tester reports, its exact formula, and its edge cases. All formulas are locked by engine tests.
---

> **Since engine 3.0.61; verified against engine 3.0.61.** Every example on this page is an executed probe against that build.

Every number below is computed by the engine with a locked formula (pinned by tests, byte-identical across reruns) and delivered on the run result under `output.strategy.stats`. Percent-scaled fields end in `Pct` and are already multiplied by 100.

## Returns

| Stat | Definition |
| --- | --- |
| `netProfit` | Sum of realized trade PnL minus all fees paid. `netProfitPct` is relative to `initialCapital`. |
| `grossProfit` / `grossLoss` | Sum of winning trades' PnL and (absolute) sum of losing trades' PnL, respectively. |
| `profitFactor` | `grossProfit / grossLoss`. Reported as `null` when there are no losses (division by zero is not a number worth printing). |
| `buyHoldReturnPct` | `(lastClose / firstTradableClose - 1) * 100`: what doing nothing would have returned over the same window. If your strategy does not beat this after costs, the market did the work, not the rules. |

## Trades

| Stat | Definition |
| --- | --- |
| `totalTrades`, `winTrades`, `lossTrades` | Closed trades, and the win/loss split. `winRatePct` is `winTrades / totalTrades * 100`. |
| `avgTrade`, `avgWin`, `avgLoss` | Mean PnL per closed trade, per winner, per loser. |
| `payoffRatio` | `avgWin / abs(avgLoss)`. `null` when there are no losers. Read it WITH the win rate: 40% winners at a 3.0 payoff is profitable, 70% winners at 0.3 is not. |
| `largestWin`, `largestLoss` | The single best and worst closed trades. If `largestWin` dominates `netProfit`, one trade made the backtest. |
| `avgBarsInTrade` | Mean holding time in bars. |
| `rejectedOrders` | Orders the broker refused (pyramiding cap, invalid sizing, non-positive equity, NaN bars). Rejections are counted, never silently dropped. |

## Risk

| Stat | Definition |
| --- | --- |
| `maxDrawdown` | Largest peak-to-trough equity decline over the run, in money. `maxDrawdownPct` is relative to the equity peak it fell from. |
| `maxRunup` | Mirror of drawdown: the largest trough-to-peak climb. |
| `sharpe` | `mean(r) / sampleStd(r) * sqrt(barsPerYear)`, where `r` are per-bar equity returns over confirmed bars and `barsPerYear = 31,536,000,000 / intervalMs`. Zero when the return series has no variance. Annualized from YOUR chart interval, so compare Sharpe across runs at the same interval. |
| `sortino` | Same as Sharpe with downside deviation (only returns below zero) in the denominator. Punishes losing volatility, forgives winning volatility. |
| `exposurePct` | Bars with a nonzero position divided by all confirmed bars, times 100. A strategy in the market 4% of the time with the same net profit as one exposed 90% of the time is taking far less risk per unit of return. |

## Costs

| Stat | Definition |
| --- | --- |
| `feesPaid` | Total commission across all fills (`commissionPercent` of each fill's notional). Already subtracted from equity and `netProfit`. |

Slippage is not a separate stat line because it is baked into every fill price; the [slippage disclosure](slippage-and-costs.md#reading-the-slippage-disclosure) in the run details reports how it was priced.

## Long / short splits

`stats.long` and `stats.short` carry `netProfit`, `totalTrades`, and `winRatePct` per direction. A "market-neutral" idea whose entire profit sits in `stats.long` during a bull-market window is a long-only idea with extra steps.

## Simulation-quality stats

These describe how the result was produced, not how the strategy performed. They power the disclosures in the run-details popover:

| Stat | Definition |
| --- | --- |
| `ambiguousFillCount` | Intrabar ordering decisions settled by the declared `fillModel` assumption rather than data. Details in [fill simulation](fill-simulation.md). |
| `fineResolvedCount` | Ordering questions settled by walking finer-interval bars (no assumption involved). |
| `fineFillCoveragePct` | Walked ordering questions over all ordering questions, times 100. `null` when the run posed none (zero of zero is not zero percent). |
| `fillResolutionByLane` | Walked questions per finer-data lane, e.g. `{ "1m": 12, "5m": 3 }`. |
| `bookSlippageFillCount` | Fills priced from recorded order-book depth (`slippageModel="bookEstimate"` only). |
| `bookSlippageUnavailableCount` | bookEstimate fills that fell back to the declared rate (no usable book for that bar or size). |
| `bookSlippageAvgBps` | Mean applied book impact in basis points; `null` when nothing was book-priced. |

## Equity accounting, precisely

Equity on every bar `i` is cash plus the open position marked at `close[i]`, minus all fees paid so far, recorded on flat bars too. Per-bar returns for Sharpe and Sortino are `r_i = equity_i / equity_{i-1} - 1` over confirmed bars with positive prior equity. On the live edge, strategy scripts fully recompute on every tick; the forming bar can shift pending-order and stat values once between the initial render and the first tick, and settles when the bar confirms.
