The backtesting engine uses BacktestConfig to control all aspects of simulation realism, from network latency to fill probabilities.
BacktestConfig Structure
The main configuration is defined in nano-backtest/src/config.rs:11-29:
pub struct BacktestConfig {
pub initial_capital: f64,
pub latency: LatencyConfig,
pub fees: FeeConfig,
pub risk: RiskConfig,
pub execution: ExecutionConfig,
pub output: OutputConfig,
}
Initial Capital
Starting capital in USD for the backtest.
initial_capital: 1_000_000.0 // $1M default
Latency Configuration
Realistic latency simulation is critical for HFT backtesting. The LatencyConfig struct (config.rs:104-132) models network and exchange delays:
pub struct LatencyConfig {
pub order_latency_ns: u64, // Order submission latency
pub market_data_latency_ns: u64, // Market data reception latency
pub ack_latency_ns: u64, // Order acknowledgment latency
pub jitter_ns: u64, // Latency jitter (std dev)
pub use_random_jitter: bool, // Enable random jitter
}
Latency Components
| Component | Description | Typical Range |
|---|
order_latency_ns | Time from order submission to exchange receipt | 5,000 - 200,000 ns |
market_data_latency_ns | Time from exchange update to strategy receipt | 2,500 - 100,000 ns |
ack_latency_ns | Time from exchange ack to strategy receipt | 5,000 - 250,000 ns |
jitter_ns | Standard deviation of latency variance | 1,000 - 50,000 ns |
Jitter Models
The latency simulator supports multiple jitter models (latency.rs:26-52):
- None: Deterministic latency (no variance)
- Uniform: Random jitter in range
[-max_jitter, +max_jitter]
- Normal: Gaussian distribution with configurable std dev
- LogNormal: Realistic network latency distribution
- Empirical: Based on historical percentile data
[p50, p75, p90, p95, p99]
Preset Latency Profiles
Aurora (CME Primary Colo) - latency.rs:215-220:
ColoLatencyModel::aurora() // 5μs to exchange, 2μs processing
Aggressive HFT - config.rs:47-56:
order_latency_ns = 50_000 # 50 microseconds
market_data_latency_ns = 10_000 # 10 microseconds
ack_latency_ns = 60_000 # 60 microseconds
jitter_ns = 5_000 # 5 microseconds jitter
use_random_jitter = true
Conservative - config.rs:76-82:
order_latency_ns = 200_000 # 200 microseconds
market_data_latency_ns = 50_000 # 50 microseconds
ack_latency_ns = 250_000 # 250 microseconds
jitter_ns = 50_000 # 50 microseconds jitter
use_random_jitter = true
Fee Configuration
The FeeConfig struct (config.rs:134-172) models exchange and clearing fees:
pub struct FeeConfig {
pub maker_fee: f64, // Fee per contract for adding liquidity
pub taker_fee: f64, // Fee per contract for taking liquidity
pub exchange_fee: f64, // Exchange fee per contract
pub clearing_fee: f64, // Clearing house fee per contract
}
CME Default Fees
FeeConfig::default() // Uses CME futures fees:
// maker_fee: CME_MAKER_FEE
// taker_fee: CME_TAKER_FEE
// exchange_fee: CME_EXCHANGE_FEE
// clearing_fee: CME_CLEARING_FEE
Fee Calculation
Total fee per trade:
fee = (maker_or_taker_fee + exchange_fee + clearing_fee) * quantity
Example for a 10-contract maker trade:
fees.calculate_fee(10, true) // is_maker = true
Risk Configuration
The RiskConfig struct (config.rs:174-207) defines position limits and risk controls:
pub struct RiskConfig {
pub max_position: i64, // Maximum net position (contracts)
pub max_order_size: u32, // Maximum single order size
pub max_drawdown_pct: f64, // Kill switch drawdown threshold
pub max_daily_loss: f64, // Maximum daily loss in dollars
pub max_open_orders: usize, // Maximum concurrent open orders
pub enable_kill_switch: bool, // Enable automatic shutdown
}
Default Risk Limits
max_position = 100 # DEFAULT_MAX_INVENTORY
max_order_size = 20 # contracts
max_drawdown_pct = 0.05 # 5% drawdown limit
max_daily_loss = 100_000.0 # $100k daily loss limit
max_open_orders = 20 # concurrent orders
enable_kill_switch = true # auto-stop on breach
Risk Presets
Aggressive HFT:
max_position = 50
max_order_size = 10
max_drawdown_pct = 0.04 # 4%
max_daily_loss = 50_000.0
Conservative:
max_position = 20
max_order_size = 5
max_drawdown_pct = 0.03 # 3%
max_daily_loss = 30_000.0
Execution Configuration
The ExecutionConfig struct (config.rs:209-242) controls fill simulation realism:
pub struct ExecutionConfig {
pub track_queue_position: bool, // Track position in order queue
pub fill_probability_decay: f64, // Fill prob decay with queue depth
pub partial_fill_probability: f64, // Probability of partial fills
pub min_partial_fill: u32, // Minimum partial fill size
pub simulate_adverse_selection: bool, // Model adverse selection
pub adverse_selection_prob: f64, // Adverse selection probability
}
Fill Simulation
Queue Position Tracking (execution.rs:63-68):
- Tracks order position in the limit order book queue
- Fill probability decays exponentially:
decay^queue_position
- Default decay: 0.9 (90% probability at front, 81% at position 2, etc.)
Partial Fills (execution.rs:185-198):
- Simulates realistic partial execution
- Default 20% probability of partial fill
- Fill size randomly chosen between
min_partial_fill and remaining quantity
Adverse Selection (execution.rs:171-177):
- Models the tendency to get filled when market moves against you
- Default 10% probability that fill doesn’t occur due to adverse selection
- Critical for realistic maker strategy backtesting
Default Execution Settings
track_queue_position = true
fill_probability_decay = 0.9
partial_fill_probability = 0.2
min_partial_fill = 1
simulate_adverse_selection = true
adverse_selection_prob = 0.1
Conservative Preset (config.rs:93-97):
fill_probability_decay = 0.7 # More conservative fills
partial_fill_probability = 0.4 # More partial fills
Output Configuration
The OutputConfig struct (config.rs:244-273) controls logging and data recording:
pub struct OutputConfig {
pub verbosity: u8, // Log level (0-3)
pub record_tick_pnl: bool, // Record P&L at every tick
pub record_fills: bool, // Record all fill events
pub record_orders: bool, // Record all order events
pub snapshot_interval: usize, // Metrics snapshot frequency
}
Default Output Settings
verbosity = 1 # Basic logging
record_tick_pnl = false # Disabled for performance
record_fills = true # Keep fill history
record_orders = true # Keep order history
snapshot_interval = 10000 # Snapshot every 10k events
Enabling record_tick_pnl = true can significantly impact performance and memory usage for high-frequency strategies.
Example Configuration
Create a backtest_config.toml file:
initial_capital = 1_000_000.0
[latency]
order_latency_ns = 100_000
market_data_latency_ns = 50_000
ack_latency_ns = 120_000
jitter_ns = 10_000
use_random_jitter = true
[fees]
maker_fee = 0.25
taker_fee = 0.85
exchange_fee = 0.10
clearing_fee = 0.02
[risk]
max_position = 50
max_order_size = 10
max_drawdown_pct = 0.05
max_daily_loss = 100_000.0
max_open_orders = 20
enable_kill_switch = true
[execution]
track_queue_position = true
fill_probability_decay = 0.9
partial_fill_probability = 0.2
min_partial_fill = 1
simulate_adverse_selection = true
adverse_selection_prob = 0.1
[output]
verbosity = 1
record_tick_pnl = false
record_fills = true
record_orders = true
snapshot_interval = 10000
Rust API
use nano_backtest::BacktestConfig;
// Use default configuration
let config = BacktestConfig::default();
// Use preset
let config = BacktestConfig::aggressive_hft();
let config = BacktestConfig::conservative();
// Custom configuration
let config = BacktestConfig {
initial_capital: 500_000.0,
latency: LatencyConfig {
order_latency_ns: 75_000,
market_data_latency_ns: 25_000,
ack_latency_ns: 100_000,
jitter_ns: 15_000,
use_random_jitter: true,
},
fees: FeeConfig::default(),
risk: RiskConfig {
max_position: 30,
max_drawdown_pct: 0.03,
..Default::default()
},
execution: ExecutionConfig::default(),
output: OutputConfig::default(),
};
Configuration Presets
Two built-in presets are available:
Aggressive HFT (config.rs:45-69)
Optimized for sub-millisecond strategies:
- Ultra-low latency (50μs orders)
- Tight risk limits (50 max position)
- Realistic fill simulation
let config = BacktestConfig::aggressive_hft();
Conservative (config.rs:71-100)
Optimized for strategy validation:
- Higher latency (200μs orders)
- Buffered fees (1.2x CME rates)
- Conservative fill model (70% decay)
- Tighter drawdown limit (3%)
let config = BacktestConfig::conservative();