---
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.
verified:
  engine: 3.0.67
---

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; on perps also insufficient margin and post-halt entries). 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 fees across all fills, already subtracted from equity and `netProfit` (funding is excluded; it has its own stats). Spot: `commissionPercent` of each fill's notional. Perps: exactly `makerFeesPaid + takerFeesPaid` (observed `1.0625991428568167 = 0.7247578990925553 + 0.3378412437642613`). |
| `makerFeesPaid` / `takerFeesPaid` | Perps fee split by fill type: maker for limit-priced fills, taker for market-crossing fills (including liquidation fills). Both `0` on spot runs, even with maker/taker rates declared (they are ignored with a diagnostic; see [Slippage and Costs](slippage-and-costs.md#one-fee-schedule-per-instrument)). |
| `fundingPaid` | Net funding settled against open perps positions, signed from the strategy's side: positive means the strategy paid funding, negative means it received it (a probed long under a positive rate paid: `0.36904136169411894`). `0` when `funding="off"` or on spot. |
| `fundingEventsApplied` | Funding settlements applied to an open position. Events with no open position do not count (38 events in the window, 36 applied to a position opened at bar 10). |
| `fundingUnavailableCount` | Bars on which an open perps position had no covering funding data under `funding="data"`: a funding window not fully covered, invalid OHLC for funding, or settlement events dropped in a data gap (observed `289` for a position held from bar 10 through a 300-bar run with no funding data). The disclosure counter: no rate is ever invented. |

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.

## Perps

Populated by `instrument="perps"` runs; spot runs report them as `0`/`false`. Mechanics on the [Perps page](perps.md).

| Stat | Definition |
| --- | --- |
| `liquidationCount` | Trades force-closed by the broker's implicit liquidation stop. Those trades carry `exitReason: "liquidation"` in the trades list (observed: 7 of 7 trades at `leverage=25` under `onLiquidation="continue"`). One liquidation event can close multiple open trade records in the same net position. |
| `liquidationHalted` | `true` when `onLiquidation="halt"` stopped the run at the first liquidation; every subsequent entry is then rejected and counted (observed: `liquidationCount: 1`, `rejectedOrders: 6`). Always `false` under `"continue"` and on spot. |
| `bankruptcyDeficit` | Shortfall beyond the position's committed margin left by liquidation fills (the amount by which equity would have gone below zero before the engine floored it). `0` in every probed run: each observed liquidation loss (`3.853208734751064`) settled inside its committed margin (`4.381648789802639`). |

Perps runs also add two bar-aligned output series next to the stats, `committedMarginSeries` and `fundingPaidSeries` (cumulative, ending at `stats.fundingPaid`); both are omitted on spot runs. See [Perps output series](perps.md#perps-output-series).


## 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.
