Quant Buffet API
Metrics API
compute_metrics: every output field, the exact formulas, and benchmark-relative stats.
`backtest.metrics.compute_metrics` turns an equity curve into the statistics shown in the lab UI.
Signature
python
metrics = compute_metrics(
result.equity, # pd.Series, required, positional
benchmark=spy_buy_and_hold, # optional pd.Series, reindexed + ffilled for you
risk_free=0.0, # annualised, subtracted before Sharpe
trades_count=len(result.trades),
)
# keys: start, end, years, start_equity, end_equity, total_return, cagr,
# volatility, sharpe, sortino, max_drawdown, calmar, daily_win_rate,
# trades (+ benchmark_total_return, benchmark_cagr, alpha, beta)| Parameter | Type | Default | Notes |
|---|---|---|---|
equity | pd.Series | — | Positional. NaNs dropped, cast to float. |
benchmark | pd.Series | None | None | Keyword-only. Reindexed onto equity and forward-filled. |
risk_free | float | 0.0 | Keyword-only. Annualised; de-annualised internally. |
trades_count | int | 0 | Keyword-only. Passed straight through to output. |
Core output fields
| Key | Type | How it is computed |
|---|---|---|
start, end | str | First and last equity dates as YYYY-MM-DD. |
years | float | Calendar span in days / 365.25, rounded to 2 dp. |
start_equity, end_equity | float | First and last equity values. |
total_return | float | end / start − 1, not annualised. |
cagr | float | (end / start) ** (1 / years) − 1. |
volatility | float | Daily stdev (ddof=1) × √(annualisation factor). |
sharpe | float | Mean excess return / stdev of excess return × √factor. |
sortino | float | Annualised mean return / annualised downside deviation. |
max_drawdown | float | Minimum of equity / equity.cummax() − 1; negative. |
calmar | float | cagr / abs(max_drawdown), or 0.0 if never in drawdown. |
daily_win_rate | float | Share of days with a positive return. |
trades | int | Echo of trades_count. |
The annualisation factor
Rather than hardcoding 252, the module infers the factor from your own index: len(index) / (span in days / 365.25). A pure ETF panel yields roughly 252; a crypto panel with weekend rows yields roughly 365. This keeps Sharpe comparable across asset types, but it also means a panel mixing both lands somewhere in between.
| Panel content | Approx. factor |
|---|---|
| US ETFs only | ≈ 252 |
| Crypto only | ≈ 365 |
| ETFs + crypto mixed | ≈ 365 (crypto rows dominate the calendar) |
| Fewer than 2 rows | 252 (fallback) |
Benchmark fields
Supplied only when benchmark is given and at least 6 aligned rows survive the join:
| Key | How it is computed |
|---|---|
benchmark_total_return | Benchmark end / start − 1. |
benchmark_cagr | Annualised with your years, for a like-for-like comparison. |
beta | cov(strategy, benchmark) / var(benchmark) on daily returns. |
alpha | (mean(strategy) − beta × mean(benchmark)) × factor — annualised Jensen's alpha. |
python
# How the lab builds its benchmark: buy-and-hold, scaled to your starting equity
bench_sym = "SPY" if "SPY" in prices.columns else ASSETS[0]
spy = prices[bench_sym].reindex(result.equity.index).ffill()
buy_hold = float(result.equity.iloc[0]) * (spy / spy.iloc[0])
metrics = compute_metrics(result.equity, benchmark=buy_hold,
trades_count=len(result.trades))Edge cases
| Situation | Result |
|---|---|
| Fewer than 5 equity points | {"error": "insufficient equity points"} and nothing else |
| Zero-variance returns (never traded) | sharpe and sortino return 0.0 rather than dividing by zero |
| No losing days | sortino is 0.0, because downside deviation is zero |
| Equity never below its peak | max_drawdown is 0.0 and calmar is 0.0 |
| Benchmark with under 6 aligned rows | Benchmark keys are omitted entirely |
How the UI formats these
| UI label | Metric key | Formatting |
|---|---|---|
| Annualised return | cagr | Percentage, 2 dp |
| Volatility | volatility | Percentage, 2 dp |
| Sharpe ratio | sharpe | Number, 2 dp |
| Sortino ratio | sortino | Number, 2 dp |
| Max drawdown | max_drawdown | Percentage, 2 dp (negative) |
| Beta | beta | Number, 2 dp |
| Alpha | alpha | Percentage, 2 dp |
| Win rate | daily_win_rate | Percentage, 2 dp |