Documentation Index
Fetch the complete documentation index at: https://mintlify.com/dhir1007/nanoARB/llms.txt
Use this file to discover all available pages before exploring further.
The backtesting engine provides comprehensive performance metrics and statistics to evaluate strategy quality.
BacktestMetrics
The primary metrics structure is defined in nano-backtest/src/metrics.rs:10-52.
Core Metrics
pub struct BacktestMetrics {
pub total_pnl: f64, // Total profit/loss
pub realized_pnl: f64, // Realized P&L from closed trades
pub unrealized_pnl: f64, // Mark-to-market unrealized P&L
pub total_fees: f64, // Total fees paid
pub num_trades: u32, // Number of round-trip trades
pub winning_trades: u32, // Number of profitable trades
pub losing_trades: u32, // Number of losing trades
pub gross_profit: f64, // Sum of all winning trades
pub gross_loss: f64, // Sum of all losing trades
pub max_drawdown_pct: f64, // Maximum drawdown percentage
pub max_drawdown_abs: f64, // Maximum drawdown in dollars
pub peak_pnl: f64, // Peak P&L reached
pub total_volume: u64, // Total contracts traded
// ... additional metrics
}
Accessing Metrics
From engine.rs:325-329:
let metrics = engine.metrics();
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.total_pnl - metrics.total_fees);
Win Rate
Percentage of profitable trades (metrics.rs:62-68):
let win_rate = metrics.win_rate();
println!("Win Rate: {:.1}%", win_rate * 100.0);
// Calculation
win_rate = winning_trades / total_trades
Interpretation:
- 50%+: Strategy has edge (for equal-sized trades)
- 40-50%: Acceptable if winners > losers
- <40%: Likely unprofitable unless avg winner >> avg loser
Profit Factor
Ratio of gross profit to gross loss (metrics.rs:71-77):
let profit_factor = metrics.profit_factor();
println!("Profit Factor: {:.2}", profit_factor);
// Calculation
profit_factor = gross_profit / abs(gross_loss)
Interpretation:
- >2.0: Excellent strategy
- 1.5-2.0: Good strategy
- 1.0-1.5: Marginal, needs improvement
- <1.0: Unprofitable
Average Trade P&L
Mean profit per trade (metrics.rs:80-86):
let avg_pnl = metrics.avg_trade_pnl();
let avg_winner = metrics.avg_winning_trade();
let avg_loser = metrics.avg_losing_trade();
println!("Avg Trade: ${:.2}", avg_pnl);
println!("Avg Winner: ${:.2}", avg_winner);
println!("Avg Loser: ${:.2}", avg_loser);
Interpretation:
- Average trade should be significantly positive after fees
- For HFT: Even 1−5 per round-trip can be profitable at scale
- Avg winner should typically be larger than avg loser
Maker Ratio
Percentage of fills that added liquidity (metrics.rs:107-114):
let maker_ratio = metrics.maker_ratio();
println!("Maker Ratio: {:.1}%", maker_ratio * 100.0);
// Calculation
maker_ratio = maker_fills / (maker_fills + taker_fills)
Interpretation:
- >80%: Excellent for fee optimization
- 60-80%: Good maker-taker balance
- <50%: High taker fees may erode profits
For CME futures, maker rebates vs taker fees can be $0.60+ difference per contract.
Maximum Drawdown
Drawdown Calculation
From metrics.rs:146-167:
// Percentage drawdown
let max_dd_pct = metrics.max_drawdown_pct;
println!("Max Drawdown: {:.2}%", max_dd_pct * 100.0);
// Absolute drawdown
let max_dd_abs = metrics.max_drawdown_abs;
println!("Max Drawdown: ${:.2}", max_dd_abs);
// Calculation
drawdown = (peak_pnl - current_pnl) / peak_pnl
Drawdown Tracking
The engine continuously updates drawdown (metrics.rs:156-166):
if total_pnl > peak_pnl {
peak_pnl = total_pnl; // New peak
}
let drawdown = peak_pnl - total_pnl;
let drawdown_pct = drawdown / peak_pnl;
if drawdown_pct > max_drawdown_pct {
max_drawdown_pct = drawdown_pct; // New max drawdown
}
Interpretation:
- <5%: Excellent risk management
- 5-10%: Good for HFT strategies
- 10-20%: Acceptable for lower-frequency strategies
- >20%: High risk, review strategy and risk limits
High drawdowns can trigger the kill switch if max_drawdown_pct is exceeded in the risk configuration.
Advanced Statistics
The PerformanceStats struct (metrics.rs:186-208) provides deeper analytics.
Sharpe Ratio
Risk-adjusted return metric (metrics.rs:260-275):
let sharpe = stats.sharpe_ratio;
println!("Sharpe Ratio: {:.2}", sharpe);
// Calculation (annualized)
mean_daily_return = mean(daily_returns)
std_daily_return = std_dev(daily_returns)
sharpe = (mean / std) * sqrt(252) // 252 trading days
Interpretation:
- >3.0: Excellent (rare for real strategies)
- 2.0-3.0: Very good
- 1.0-2.0: Good
- <1.0: Poor risk-adjusted returns
HFT strategies often achieve Sharpe ratios of 2-4 due to low volatility and consistent returns.
Sortino Ratio
Downside risk-adjusted return (metrics.rs:278-299):
let sortino = stats.sortino_ratio;
println!("Sortino Ratio: {:.2}", sortino);
// Calculation (annualized)
mean_return = mean(daily_returns)
downside_std = std_dev(negative_returns_only)
sortino = (mean / downside_std) * sqrt(252)
Interpretation:
- Similar to Sharpe, but only penalizes downside volatility
- Better metric for asymmetric return distributions
- Higher Sortino than Sharpe indicates positive skew
Calmar Ratio
Return relative to maximum drawdown (metrics.rs:301-309):
let calmar = stats.calmar_ratio;
println!("Calmar Ratio: {:.2}", calmar);
// Calculation
annual_return = mean(daily_returns) * 252
calmar = annual_return / max_drawdown_pct
Interpretation:
- >3.0: Excellent return/drawdown profile
- 1.0-3.0: Good
- <1.0: Returns don’t justify drawdown risk
Consecutive Wins/Losses
Track winning and losing streaks (metrics.rs:311-332):
let max_wins = stats.max_consecutive_wins;
let max_losses = stats.max_consecutive_losses;
println!("Max Consecutive Wins: {}", max_wins);
println!("Max Consecutive Losses: {}", max_losses);
Interpretation:
- Long losing streaks indicate strategy may need adjustment
- Very long winning streaks may indicate overfitting
- Ratio should be reasonable (e.g., max_wins/max_losses ≈ 2-3)
Recovery Factor
Ability to recover from drawdowns (metrics.rs:334-342):
let recovery = stats.recovery_factor;
println!("Recovery Factor: {:.2}", recovery);
// Calculation
total_return_pct = total_pnl / initial_capital
recovery_factor = total_return_pct / max_drawdown_pct
Interpretation:
- >5.0: Strong recovery capability
- 2.0-5.0: Moderate recovery
- <2.0: Slow recovery from drawdowns
Equity Curve Analysis
The equity curve tracks cumulative P&L over time (metrics.rs:235-238):
let equity_curve = stats.equity_curve;
let timestamps = stats.equity_timestamps;
// Plot or analyze equity progression
for (i, (×tamp, &pnl)) in timestamps.iter().zip(equity_curve.iter()).enumerate() {
println!("[{}] {}: ${:.2}", i, timestamp, pnl);
}
Equity Curve Characteristics
Smooth Upward Trend:
Ideal - consistent positive returns with low volatility.
Volatile but Positive:
Positive but risky - high variance in returns.
Drawdown Pattern:
Periods of losses - analyze what caused drawdown.
Volume and Fill Analysis
From metrics.rs:36-50:
// Trading volume
let total_volume = metrics.total_volume;
let buy_fills = metrics.buy_fills;
let sell_fills = metrics.sell_fills;
println!("Total Volume: {} contracts", total_volume);
println!("Buy Fills: {}", buy_fills);
println!("Sell Fills: {}", sell_fills);
// Liquidity provision
let maker_fills = metrics.maker_fills;
let taker_fills = metrics.taker_fills;
println!("Maker Fills: {}", maker_fills);
println!("Taker Fills: {}", taker_fills);
println!("Maker Ratio: {:.1}%", metrics.maker_ratio() * 100.0);
Volume Analysis:
- High volume strategies can profit with small edge per trade
- Unbalanced buy/sell may indicate directional bias
- High maker ratio reduces total trading costs
Time-Based Metrics
From metrics.rs:170-182:
// Backtest duration
let duration_secs = metrics.duration_secs();
let duration_hours = duration_secs / 3600.0;
let duration_days = duration_hours / 24.0;
println!("Backtest Duration: {:.2} hours", duration_hours);
// Trades per hour
let trades_per_hour = metrics.num_trades as f64 / duration_hours;
println!("Trade Frequency: {:.1} trades/hour", trades_per_hour);
// Average P&L per hour
let pnl_per_hour = metrics.total_pnl / duration_hours;
println!("P&L Rate: ${:.2}/hour", pnl_per_hour);
Rolling Statistics
The RollingStats calculator (metrics.rs:346-442) tracks windowed metrics:
use nano_backtest::metrics::RollingStats;
// Create 100-trade rolling window
let mut rolling = RollingStats::new(100);
for trade_pnl in trade_pnls {
rolling.add(trade_pnl);
if rolling.is_full() {
let mean = rolling.mean();
let std = rolling.std_dev();
let sharpe = rolling.sharpe();
println!("Rolling Sharpe (100 trades): {:.2}", sharpe);
}
}
Use Cases:
- Detect strategy degradation over time
- Monitor consistency of returns
- Adaptive risk management based on recent performance
Interpreting Results
Example Good Strategy
Total P&L: $127,450.00
Realized P&L: $125,320.00
Total Fees: $18,250.00
Net P&L: $109,200.00
Num Trades: 1,247
Winning Trades: 789 (63.3%)
Losing Trades: 458 (36.7%)
Profit Factor: 2.31
Avg Trade: $87.56
Avg Winner: $203.45
Avg Loser: -$89.32
Max Drawdown: 3.24%
Sharpe Ratio: 2.87
Sortino Ratio: 3.45
Calmar Ratio: 4.12
Maker Ratio: 76.3%
Total Volume: 12,470 contracts
Analysis:
- Strong win rate (63%)
- Excellent profit factor (2.31)
- Low drawdown (3.24%)
- High Sharpe ratio (2.87)
- Good maker ratio (76%)
- Avg winner > Avg loser
Example Problematic Strategy
Total P&L: $12,340.00
Realized P&L: $18,250.00
Total Fees: $22,100.00
Net P&L: -$9,760.00 ⚠️
Num Trades: 3,142
Winning Trades: 1,257 (40.0%) ⚠️
Losing Trades: 1,885 (60.0%)
Profit Factor: 0.87 ⚠️
Avg Trade: -$3.11
Avg Winner: $45.23
Avg Loser: -$68.92 ⚠️
Max Drawdown: 18.45% ⚠️
Sharpe Ratio: 0.34 ⚠️
Sortino Ratio: 0.42
Maker Ratio: 23.1% ⚠️
Issues:
- Unprofitable after fees (profit factor < 1.0)
- Low win rate with larger losers
- High drawdown (18%)
- Poor Sharpe ratio (0.34)
- Too many taker fills (expensive)
- Over-trading (fees > gross profit)
Metrics Summary Table
| Metric | Good | Acceptable | Poor |
|---|
| Profit Factor | >2.0 | 1.5-2.0 | <1.0 |
| Sharpe Ratio | >2.0 | 1.0-2.0 | <1.0 |
| Win Rate | >55% | 45-55% | <40% |
| Max Drawdown | <5% | 5-10% | >15% |
| Maker Ratio (HFT) | >75% | 60-75% | <50% |
| Calmar Ratio | >3.0 | 1.5-3.0 | <1.0 |
Exporting Results
use serde_json;
use std::fs::File;
// Export metrics to JSON
let metrics_json = serde_json::to_string_pretty(&metrics)?;
std::fs::write("backtest_metrics.json", metrics_json)?;
// Export equity curve to CSV
let mut writer = csv::Writer::from_path("equity_curve.csv")?;
writer.write_record(&["timestamp", "pnl"])?;
for (&ts, &pnl) in stats.equity_timestamps.iter().zip(stats.equity_curve.iter()) {
writer.write_record(&[ts.to_string(), pnl.to_string()])?;
}
writer.flush()?;