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.
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.
timestampnlv_dollarsMargin 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.
timestampinitial_margin_dollarsmaintenance_margin_dollarsexcess_liquidity_dollarsmaintenance_margin_pct_nlv0 when NLV ≤ 0.excess_liquidity_pct_nlv0 when NLV ≤ 0.Trade ledger: one row per closed position (see below).
The trade ledger row
Each row is one closed position.
open_timestampclose_timestampholding_minutesclose_timestamp − open_timestamp.realized_pnl_dollarslegs_at_openlegs_at_closelots_at_openlots_at_closePositions 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.
total_return_pct(final NLV − starting capital) / starting capital × 100compound_annual_growth_rate(1 + total_return_pct/100) ^ (annualization_periods / trading_days) − 1max_drawdown_pctmax_drawdown_dollarssharpe_ratiomean(r) / std(r) × sqrt(annualization_periods) on per-day log returns, risk-free rate 0, sample stdsortino_ratioannualized_volatilitystd(daily log returns) × sqrt(annualization_periods)trade_countwin_ratewin_count / trade_countwin_countloss_countaverage_pnl_per_trade_dollarstotal_realized_pnl_dollars / trade_countaverage_winning_pnl_dollarsaverage_losing_pnl_dollarsaverage_holding_period_minutestotal_realized_pnl_dollarsrealized_pnl_dollarsfinal_nlv_dollarsmax_concurrent_positionsavg_margin_utilization_pctadjustment_countThe 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.
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.
exit_signalexpirationadjustment_full_closesoft_trimforced_liquidationequity_haltmargin_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.
soft_trim_countforced_liquidation_countmargin_warning_countliquidation_exhausted_countgreedy_fallback_position_counthaltedhalt_timestamphalt_reasonequity_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.