DocsBacktestsReading backtest results
BacktestsReference

Reading backtest results

A finished run returns four things: an equity curve, a margin curve, a per-trade ledger, and a summary-stats object. This page is the field-by-field reference for all four, for what each close reason means, and for the commonly-requested metrics that a run derives rather than stores. For the terse per-metric lookup, see the metrics glossary. For how a run gets to this point, see the backtest lifecycle.

Updated Jul 20263 min read

Every dollar figure on this page is struck at mid marks: open positions mark at mid, forced exits fill at bid/ask, and the per-contract commission applies as configured. See backtest assumptions and realism and fills, marks and slippage for what that does and does not capture.

The three frames

A run returns three data frames plus the summary object.

Equity curve: one row per snapshot.

Column
Type
Meaning
timestamp
datetime
Snapshot time.
nlv_dollars
float
Net liquidating value at that snapshot, in dollars.

Margin curve: one row per snapshot, carrying both dollar and percent-of-NLV columns so you can chart either axis without rederiving the ratio. The two *_pct_nlv fields are unit fractions (0.30 = 30%) and are 0 when NLV is at or below zero. For how these numbers are computed, see margin and auto-decomposition.

Column
Type
Meaning
timestamp
datetime
Snapshot time.
initial_margin_dollars
float
Initial margin requirement, in dollars.
maintenance_margin_dollars
float
Maintenance margin requirement, in dollars.
excess_liquidity_dollars
float
NLV minus maintenance margin, in dollars.
maintenance_margin_pct_nlv
float
Maintenance margin as a fraction of NLV. 0 when NLV ≤ 0.
excess_liquidity_pct_nlv
float
Excess liquidity as a fraction of NLV. 0 when NLV ≤ 0.

Trade ledger: one row per closed position (see below).

The trade ledger row

Each row is one closed position.

Column
Type
Meaning
open_timestamp
datetime
When the position opened.
close_timestamp
datetime
When the position closed.
holding_minutes
int
Wall-clock minutes held, close_timestamp − open_timestamp.
realized_pnl_dollars
float
Realized P&L for the position, in dollars.
legs_at_open
int
Leg count at open.
legs_at_close
int
Leg count at close, after any legs added or removed by adjustments.
lots_at_open
int
Lots at open.
lots_at_close
int
Lots at close.
close_reason
string
Why the position closed. See Close reasons.
adjustment_count
int
Accepted adjustments applied to this position.

Positions still open at the end of the run are not in the ledger. They appear in the run's positions list instead, and their mid mark counts in final NLV.

Summary metrics

The summary object carries every stat below. Return, P&L, and margin figures are dollars or unit fractions as noted; percentages that read as 12.5 are already scaled by 100.

Field
What it is
Formula or basis
total_return_pct
Total return, percent
(final NLV − starting capital) / starting capital × 100
compound_annual_growth_rate
Annualized growth, unit fraction
(1 + total_return_pct/100) ^ (annualization_periods / trading_days) − 1
max_drawdown_pct
Worst peak-to-trough decline, percent
Peak-to-trough over snapshot NLV, in stream order
max_drawdown_dollars
Worst peak-to-trough decline, dollars
Same peak-to-trough, in dollars
sharpe_ratio
Annualized Sharpe
mean(r) / std(r) × sqrt(annualization_periods) on per-day log returns, risk-free rate 0, sample std
sortino_ratio
Annualized Sortino
Same numerator as Sharpe, downside-only denominator
annualized_volatility
Annualized volatility, unit fraction
std(daily log returns) × sqrt(annualization_periods)
trade_count
Closed positions
Count of closed positions
win_rate
Win rate, unit fraction
win_count / trade_count
win_count
Winning trades
Closed positions with P&L > 0
loss_count
Losing trades
Closed positions with P&L < 0
average_pnl_per_trade_dollars
Mean P&L per trade
total_realized_pnl_dollars / trade_count
average_winning_pnl_dollars
Mean P&L of winners
Mean over trades with P&L > 0
average_losing_pnl_dollars
Mean P&L of losers
Mean over trades with P&L < 0
average_holding_period_minutes
Mean hold time
Mean of per-trade holding minutes
total_realized_pnl_dollars
Realized P&L across closed trades
Sum of ledger realized_pnl_dollars
final_nlv_dollars
Ending NLV, dollars
NLV at the last snapshot
max_concurrent_positions
Peak open positions
Max open-position count across snapshots
avg_margin_utilization_pct
Mean margin usage, unit fraction
Mean of maintenance margin / NLV over snapshots with NLV > 0
adjustment_count
Accepted adjustments across the run
Count of accepted adjustment actions

The risk ratios run on the end-of-day NLV series: the latest snapshot's NLV per date, with starting capital as day 0. Per-day returns are log returns, ln(NLV_t / NLV_{t-1}), so the summed log returns equal ln(final / start) exactly. Sharpe and Sortino return 0 when there are fewer than two return observations, and Sortino returns 0 when there are fewer than two downside observations.

Note

win_count counts trades with P&L strictly above zero and loss_count counts trades strictly below, so a breakeven trade (P&L exactly 0) falls in neither. win_rate divides by trade_count, not by win_count + loss_count, so breakeven trades lower it.

Annualization

annualization_periods is the number of periods per year used to scale Sharpe, Sortino, annualized volatility, and CAGR. It defaults to 252 trading days. Set it to 365 for a crypto-style calendar or 260 for a 5-day-a-week market.

A run scaled at 252 is not comparable to one scaled at 365; compare runs only when they share the same setting.

Close reasons

close_reason on a ledger row is one of six values.

Value
Meaning
exit_signal
The position's exit signal returned truthy.
expiration
The position's contracts reached expiration and were force-closed.
adjustment_full_close
An adjustment action closed the last remaining leg.
soft_trim
The risk layer trimmed the position when excess liquidity fell below its soft threshold.
forced_liquidation
The risk layer closed the position at bid/ask to cure a hard margin deficit (excess liquidity below zero).
equity_halt
The run halted with equity at or below zero and closed everything at bid/ask.

margin_insufficient is not a close reason. It is a no-action entry-rejection reason: the engine records it when a proposed entry or adjustment would breach margin and declines to act, leaving the existing position untouched.

Risk-mechanism counters and halt fields

The summary object also carries counters for the risk layer and three fields describing a halt.

Field
Type
Meaning
soft_trim_count
int
Positions closed by soft trim.
forced_liquidation_count
int
Positions closed by forced liquidation.
margin_warning_count
int
Margin-warning events raised during the run.
liquidation_exhausted_count
int
Times forced liquidation ran out of positions to close without curing the deficit.
greedy_fallback_position_count
int
Positions whose latest margin computation fell back to the greedy decomposition instead of the exhaustive one.
halted
bool
Whether the run halted early.
halt_timestamp
datetime or null
When the halt fired. Null if the run did not halt.
halt_reason
string or null
Why it halted. Today the only value is equity_below_zero. Null if the run did not halt.

Metrics you might expect but won't find

Quarterly P&L is not stored. P&L lives per-trade in the ledger and per-snapshot in the equity curve, so any quarterly, monthly, or yearly view is derived by bucketing those rows.

The per-run benchmark

Every backtest run carries a benchmark it is read against, so results always sit next to a reference return instead of standing alone. The default reference is S&P 500 total return. Custom benchmarks, including another saved backtest as the reference, are under consideration for the future. The benchmark is a reference return drawn alongside the strategy's results; it does not change any of the fields above.

Charts and the results view

A run produces the curve and ledger data described above. The Output section renders it after a run completes, in three parts:

  • Equity curve: a line chart of NLV over the full historical period, with the run's benchmark return drawn alongside it. Multiple curves appear at once when a parameter is being swept.
  • Metrics panel: the summary stats, annualized return, Sharpe, max drawdown, win rate, average winning and losing trade size, average hold time.
  • Trade log: a table of every closed trade, one row per position, drawn from the trade ledger.

The curve, ledger, and summary data that feed these panels are produced by every run.

Was this page helpful?