Skip to main content

Overview

The metrics module provides comprehensive performance tracking for backtests, including P&L metrics, trade statistics, drawdown analysis, and risk-adjusted returns. Source: nano-backtest/src/metrics.rs

BacktestMetrics

Core performance metrics collected during backtest execution.
pub struct BacktestMetrics {
    pub total_pnl: f64,
    pub realized_pnl: f64,
    pub unrealized_pnl: f64,
    pub total_fees: f64,
    pub num_trades: u32,
    pub winning_trades: u32,
    pub losing_trades: u32,
    pub gross_profit: f64,
    pub gross_loss: f64,
    pub max_drawdown_pct: f64,
    pub max_drawdown_abs: f64,
    pub peak_pnl: f64,
    pub total_volume: u64,
    pub buy_fills: u32,
    pub sell_fills: u32,
    pub maker_fills: u32,
    pub taker_fills: u32,
    pub avg_fill_latency_ns: f64,
    pub start_time: Option<Timestamp>,
    pub end_time: Option<Timestamp>,
}

Key Metrics

P&L Metrics

FieldDescription
total_pnlTotal P&L (realized + unrealized)
realized_pnlP&L from closed positions
unrealized_pnlP&L from open positions
total_feesTotal fees paid

Trade Statistics

FieldDescription
num_tradesTotal number of round-trip trades
winning_tradesNumber of profitable trades
losing_tradesNumber of losing trades
gross_profitSum of all winning trade P&Ls
gross_lossSum of all losing trade P&Ls (absolute)

Volume Statistics

FieldDescription
total_volumeTotal contracts traded
buy_fillsNumber of buy-side fills
sell_fillsNumber of sell-side fills
maker_fillsNumber of maker fills (liquidity providing)
taker_fillsNumber of taker fills (liquidity taking)

Drawdown Metrics

FieldDescription
max_drawdown_pctMaximum drawdown as percentage of peak
max_drawdown_absMaximum drawdown in absolute dollars
peak_pnlHighest P&L reached during backtest

Constructor

new

Creates a new empty metrics instance.
pub fn new() -> Self

Calculated Metrics

win_rate

Calculates the win rate as a percentage.
pub fn win_rate(&self) -> f64
Returns: Win rate between 0.0 and 1.0 Formula: winning_trades / num_trades Example:
let metrics = engine.metrics();
println!("Win Rate: {:.2}%", metrics.win_rate() * 100.0);

profit_factor

Calculates the profit factor (gross profit / gross loss).
pub fn profit_factor(&self) -> f64
Returns: Profit factor (infinity if no losing trades) Interpretation:
  • 1.0: Profitable strategy
  • < 1.0: Losing strategy
  • 2.0+: Strong strategy

avg_trade_pnl

Calculates average P&L per trade.
pub fn avg_trade_pnl(&self) -> f64

avg_winning_trade

Calculates average winning trade P&L.
pub fn avg_winning_trade(&self) -> f64

avg_losing_trade

Calculates average losing trade P&L.
pub fn avg_losing_trade(&self) -> f64

maker_ratio

Calculates the ratio of maker fills to total fills.
pub fn maker_ratio(&self) -> f64
Returns: Maker ratio between 0.0 and 1.0 Interpretation: Higher values indicate better liquidity provision (lower fees)

Recording Methods

record_fill

Records a fill for metrics tracking.
pub fn record_fill(&mut self, fill: &Fill)

record_trade

Records a completed round-trip trade.
pub fn record_trade(&mut self, pnl: f64)

update_pnl

Updates P&L tracking and drawdown calculation.
pub fn update_pnl(&mut self, total_pnl: f64, realized: f64, unrealized: f64)

Time Methods

duration_secs

Returns the backtest duration in seconds.
pub fn duration_secs(&self) -> f64

PerformanceStats

Detailed performance statistics including risk-adjusted returns.
pub struct PerformanceStats {
    pub daily_returns: Vec<f64>,
    pub equity_curve: Vec<f64>,
    pub equity_timestamps: Vec<i64>,
    pub trade_pnls: Vec<f64>,
    pub sharpe_ratio: f64,
    pub sortino_ratio: f64,
    pub calmar_ratio: f64,
    pub max_consecutive_wins: u32,
    pub max_consecutive_losses: u32,
    pub recovery_factor: f64,
}

Fields

FieldDescription
daily_returnsDaily return series
equity_curveCumulative P&L over time
equity_timestampsTimestamps for equity curve points
trade_pnlsP&L for each individual trade
sharpe_ratioAnnualized Sharpe ratio
sortino_ratioAnnualized Sortino ratio (downside-only)
calmar_ratioAnnual return / max drawdown
max_consecutive_winsLongest winning streak
max_consecutive_lossesLongest losing streak
recovery_factorTotal return / max drawdown

Constructor

new

Creates a new empty statistics instance.
pub fn new() -> Self

Data Recording

add_equity_point

Adds a point to the equity curve.
pub fn add_equity_point(&mut self, timestamp: i64, pnl: f64)

add_daily_return

Adds a daily return observation.
pub fn add_daily_return(&mut self, ret: f64)

add_trade_pnl

Adds a trade P&L observation.
pub fn add_trade_pnl(&mut self, pnl: f64)

Calculation

calculate

Calculates all statistics from recorded data.
pub fn calculate(&mut self, initial_capital: f64, max_drawdown: f64)
Parameters:
  • initial_capital: Starting capital for return calculations
  • max_drawdown: Maximum drawdown percentage from metrics
Calculates:
  • Sharpe ratio (annualized, assuming 252 trading days)
  • Sortino ratio (using downside deviation only)
  • Calmar ratio (annual return / max drawdown)
  • Consecutive win/loss streaks
  • Recovery factor

Risk-Adjusted Returns

Sharpe Ratio

Measures excess return per unit of total volatility. Formula: (mean_daily_return / std_daily_return) * sqrt(252) Interpretation:
  • < 1.0: Poor risk-adjusted returns
  • 1.0-2.0: Good
  • 2.0-3.0: Very good
  • 3.0: Excellent (rare)

Sortino Ratio

Measures excess return per unit of downside volatility (only negative returns). Formula: (mean_daily_return / downside_std) * sqrt(252) Interpretation: Similar to Sharpe but more relevant for asymmetric strategies

Calmar Ratio

Measures annualized return relative to maximum drawdown. Formula: annual_return / max_drawdown_pct Interpretation:
  • 1.0: Return exceeds max drawdown
  • 3.0: Strong risk-adjusted performance

RollingStats

Efficient rolling window statistics calculator.
pub struct RollingStats {
    window_size: usize,
    values: VecDeque<f64>,
    sum: f64,
    sum_sq: f64,
}

Constructor

new

Creates a new rolling statistics calculator.
pub fn new(window_size: usize) -> Self

Methods

add

Adds a value to the rolling window.
pub fn add(&mut self, value: f64)

mean

Returns the rolling mean.
pub fn mean(&self) -> f64

variance

Returns the rolling variance.
pub fn variance(&self) -> f64

std_dev

Returns the rolling standard deviation.
pub fn std_dev(&self) -> f64

sharpe

Returns the rolling Sharpe ratio (not annualized).
pub fn sharpe(&self) -> f64

Usage Example

use nano_backtest::metrics::RollingStats;

let mut rolling = RollingStats::new(20);

for ret in daily_returns {
    rolling.add(ret);
    
    if rolling.is_full() {
        println!("Rolling Sharpe: {:.2}", rolling.sharpe());
    }
}

Complete Example

use nano_backtest::{BacktestConfig, BacktestEngine};

// Run backtest
let config = BacktestConfig::default();
let mut engine = BacktestEngine::new(config);
// ... register instruments and run ...
engine.run(&mut strategy);

// Access metrics
let metrics = engine.metrics();
let stats = engine.stats();

// Basic metrics
println!("=== Performance Summary ===");
println!("Total P&L: ${:.2}", metrics.total_pnl);
println!("Realized P&L: ${:.2}", metrics.realized_pnl);
println!("Total Fees: ${:.2}", metrics.total_fees);
println!("Net P&L: ${:.2}", metrics.realized_pnl - metrics.total_fees);

// Trade statistics
println!("\n=== Trade Statistics ===");
println!("Total Trades: {}", metrics.num_trades);
println!("Win Rate: {:.2}%", metrics.win_rate() * 100.0);
println!("Profit Factor: {:.2}", metrics.profit_factor());
println!("Avg Trade: ${:.2}", metrics.avg_trade_pnl());
println!("Avg Winner: ${:.2}", metrics.avg_winning_trade());
println!("Avg Loser: ${:.2}", metrics.avg_losing_trade());

// Risk metrics
println!("\n=== Risk Metrics ===");
println!("Max Drawdown: {:.2}%", metrics.max_drawdown_pct * 100.0);
println!("Max Drawdown ($): ${:.2}", metrics.max_drawdown_abs);
println!("Peak P&L: ${:.2}", metrics.peak_pnl);

// Risk-adjusted returns
println!("\n=== Risk-Adjusted Returns ===");
println!("Sharpe Ratio: {:.2}", stats.sharpe_ratio);
println!("Sortino Ratio: {:.2}", stats.sortino_ratio);
println!("Calmar Ratio: {:.2}", stats.calmar_ratio);
println!("Recovery Factor: {:.2}", stats.recovery_factor);

// Execution quality
println!("\n=== Execution Quality ===");
println!("Total Volume: {} contracts", metrics.total_volume);
println!("Maker Ratio: {:.2}%", metrics.maker_ratio() * 100.0);
println!("Buy Fills: {}", metrics.buy_fills);
println!("Sell Fills: {}", metrics.sell_fills);

// Streaks
println!("\n=== Streaks ===");
println!("Max Consecutive Wins: {}", stats.max_consecutive_wins);
println!("Max Consecutive Losses: {}", stats.max_consecutive_losses);

See Also