Result Anatomy
client.backtest() and client.backtest_signals() return a Result — a thin wrapper over the engine response that exposes the pieces you need as pandas and Python objects.
Properties
| Property | Type | Description |
|---|---|---|
result.stats |
dict |
Performance metrics, keyed by metric id (see below). |
result.trades |
list[dict] |
The full trade log. |
result.strategy_equity |
pd.Series |
Strategy equity curve, datetime-indexed. |
result.benchmark_equity |
pd.Series |
Benchmark equity curve; empty Series when no benchmark was supplied. |
result.returns |
pd.Series |
Net-of-cost log-return series. |
result.signals |
pd.Series |
Signal series with values in {-1, 0, 1}. |
result.markers |
dict |
Warmup boundary and first/last trade positions (see below). |
result.data_quality |
dict |
The engine's assessment of the input data. |
result.raw |
dict |
The full engine response, for any field not exposed above. |
result.summary() is a method, not a property. It prints a compact overview — Total Return, Annualized Return, Annualized Std Dev, and Sharpe — to stdout and returns None:
result.summary()
# Performance Summary
# ─────────────────────────────
# Total Return 8.3%
# Annualized Return 12.1%
# Annualized Std Dev 18.4%
# Sharpe Ratio 1.42
How stats are keyed
By default result.stats is keyed by stable metric id — snake_case strings that do not change between releases. This is what every example in these docs uses:
result.stats["sharpe"] # 1.42
result.stats["max_drawdown"] # -0.42
Pass stats_keys="labels" to the request method to key by human-readable display label instead (result.stats["Sharpe"]). Prefer ids in code — labels can change; ids are the contract.
Common metrics
| id | label | Meaning |
|---|---|---|
total_return |
Total Return | Cumulative return over the period. |
cagr |
CAGR | Compound annual growth rate. |
vol_ann |
Vol (Ann) | Annualized return volatility. |
sharpe |
Sharpe | Annualized Sharpe ratio. |
sortino |
Sortino | Sortino ratio (downside deviation). |
calmar |
Calmar | CAGR divided by max drawdown. |
max_drawdown |
Max Drawdown | Worst peak-to-trough drawdown. |
win_rate |
Win Rate | Fraction of positive days. |
profit_factor |
Profit Factor | Gross profit divided by gross loss (trade-based). |
avg_trade_pnl |
Avg Trade P&L | Average net trade return. |
total_return is a real metric id in result.stats, distinct from the Total Return line that summary() derives from the equity curve.
!!! warning "win_rate is not the trade win rate"
win_rate measures the fraction of positive days. For the fraction of winning trades, use trade_win_rate. (A related signal_win_rate exists for signal-level analysis.) Reach for the one your question actually means — they routinely differ.
Benchmark metrics
When you pass a benchmark, the engine computes benchmark-relative metrics and the client surfaces them on result.relative — a dict keyed the same way as result.stats (they are deliberately not mixed into result.stats).
| id | label |
|---|---|
alpha |
Alpha |
beta |
Beta |
information_ratio |
Information Ratio |
tracking_error |
Tracking Error |
up_capture |
Up Capture |
down_capture |
Down Capture |
capture_ratio |
Capture Ratio |
result = client.backtest(strategy, df, benchmark=spy)
print(result.relative["alpha"], result.relative["beta"])
result.relative is an empty dict when no benchmark was supplied.
The full catalog
result.stats carries the headline metric set available to your plan (roughly two dozen on the free tier). The engine's complete catalog — every id, its label, and a description — is served by GET /api/sections. Do not assume a fixed count in code; iterate over what the run returned:
for metric_id, value in sorted(result.stats.items()):
print(f"{metric_id:24s} {value}")
Trade log fields
Each entry in result.trades is a dict:
{
"entry_date": "2023-01-15",
"exit_date": "2023-02-03",
"direction": 1, # 1 = long, -1 = short
"entry_price": 16800.0,
"exit_price": 23400.0,
"return_gross": 0.0392,
"return_net": 0.0387, # after costs
"fee_cost": -0.0002,
"slippage_cost": -0.0003,
"bars_held": 19,
"stop_hit": False,
}
Markers
result.markers marks the run's boundaries for chart annotation. It is not a per-bar event stream — it reports the warmup boundary and the first/last trade positions:
| Field | Meaning |
|---|---|
warmup_bars |
Bars consumed before the strategy could trade. |
warmup_end_index / warmup_end_date |
Where the warmup period ends. |
first_trade_index / first_trade_date |
The first trade's entry. |
last_trade_exit_index / last_trade_exit_date |
The last trade's exit. |
Individual fields are None when they do not apply (for example, the trade fields on a run that never traded), and markers is an empty dict when the engine response carries none.
Data quality
result.data_quality reports issues the engine found while preparing the run — bad prices, missing bars, and warnings. It is an empty dict when the response carries no data-quality block. Inspect it when results look surprising; a gap in the input often explains a gap in the output.
See also
- Client Reference — the full
ResultAPI. - Your first backtest — a
Resultread end to end.