A high-performance backtesting system implemented in Rust, designed to process and analyze Kraken's complete trading history with support for custom strategies and sophisticated execution simulation.
This is a Rust workspace containing:
crates/backtest/- The main backtesting library cratecrates/xmr_swing/- XMR/USD swing trading strategy implementation
This project uses Kraken's historical market data for backtesting. Here's how to set up the data:
-
Download
Kraken_Trading_History.zipfrom Kraken's official support page: https://support.kraken.com/hc/en-us/articles/360047543791-Downloadable-historical-market-data-time-and-sales- -
Place the downloaded
Kraken_Trading_History.zipin thedata/directory. -
Run the unzip script:
cd data ./unzip_data.shThis will extract the data into a
data/Trading_Historydirectory.
After running the unzip script, you'll have:
data/Kraken_Trading_History.zip- The original downloaded filedata/Trading_History/- Directory containing the extracted CSV files
Note: Both the zip file and extracted directory are gitignored to prevent committing large data files to the repository.
The dataset contains complete time and sales (trade) data for all trading pairs on Kraken's platform. Each CSV file follows this format:
timestamp,price,volume,buysell
1568062578.0428,10084.9,0.00113146,s
Fields:
timestamp: Unix timestamp with microsecond precisionprice: The price at which the trade executedvolume: The volume of the tradebuysell: Trade direction ('b' for buy, 's' for sell)
Files are organized by trading pair and year, e.g., XBT_USD_trades_2019.csv for Bitcoin-USD trades from 2019.
To run the XMR swing trading strategy:
# From the workspace root
cargo run -p xmr_swing- Trade data parser and validator
- OHLCV candle generator
- Flexible timeframe support
- Data cleaning and validation
- Stream processing interface
pub trait DataSource {
fn next_trade(&mut self) -> Option<Result<Trade>>;
fn seek_time(&mut self, timestamp: f64) -> Result<()>;
}
pub struct CandleBuilder {
fn new(period_length: f64) -> Self;
fn update(&mut self, trade: &Trade) -> Option<Candle>;
fn finish(&mut self) -> Option<Candle>;
}- Order book simulation with synthetic depth
- Price impact modeling
- Market depth tracking
- Tick-by-tick price updates
pub trait MarketSimulator {
fn process_trade(&mut self, trade: Trade);
fn current_price(&self) -> Option<Price>;
fn get_depth(&self, levels: usize) -> OrderBookDepth;
}
// Example usage:
let mut sim = SimpleMarketSimulator::new(
100, // Keep 100 trades of history
0.1, // 0.1% spread
5, // Maintain 5 levels of depth
2.0, // Each level has 2x avg trade size
);
// Process some trades
sim.process_trade(trade);
// Get current price and market depth
if let Some(price) = sim.current_price() {
println!("Current price: {}", price.raw());
}
let depth = sim.get_depth(5);
println!("Best bid: {}, Best ask: {}",
depth.bids[0].price.raw(),
depth.asks[0].price.raw());- Order types (Market, Limit, Stop, Take-Profit)
- Position tracking
- Leverage and margin handling
- Liquidation mechanics
- Fee calculation
// Example usage of the ExecutionEngine
let mut engine = ExecutionEngine::new(
100_000.0, // Initial cash balance
10.0, // Maximum leverage
10.0, // Maximum position size
);
// Place a leveraged long order
let order = Order::new(Side::Buy, OrderType::Market, 1.0)?
.with_leverage(5.0)?;
let order_id = engine.place_order(order, &mut market)?;
// Check order status
if let Some(status) = engine.get_order_status(order_id) {
match status {
OrderStatus::Filled { size, avg_price } => {
println!("Filled {} units at {}", size, avg_price.raw());
}
OrderStatus::PartiallyFilled { filled_size, avg_price } => {
println!("Partially filled {} units at {}", filled_size, avg_price.raw());
}
_ => println!("Order is {:?}", status),
}
}
// Monitor positions and check for liquidations
engine.update_positions(&market);
let liquidations = engine.check_liquidations(&market);
for (symbol, position) in liquidations {
println!("Position {} liquidated at {}", symbol.0, position.entry_price.raw());
}- Strategy interface
- Signal generation
- Position sizing
- Risk management rules
pub trait Strategy {
fn on_trade(&mut self, trade: &Trade);
fn on_candle(&mut self, candle: &Candle);
fn generate_signals(&self) -> Vec<Signal>;
fn analyze_market(&self, market: &dyn MarketSimulator) -> Vec<String>;
fn analyze_positions(&self, positions: &[Position]) -> Vec<String>;
}
pub trait RiskManager {
fn analyze_risk(
&self,
portfolio: &dyn Portfolio,
positions: &[Position],
market: &dyn MarketSimulator,
) -> RiskAnalysis;
fn validate_signal(
&self,
signal: &Signal,
portfolio: &dyn Portfolio,
positions: &[Position],
) -> bool;
}- Technical indicators
- SMA - Simple Moving Average
- EMA - Exponential Moving Average
- MACD - Moving Average Convergence Divergence
- RSI - Relative Strength Index
- BB - Bollinger Bands
- Custom indicator framework
- Window-based calculations
- Performance metrics
// Trading metrics
let mut stats = TradeStats::new();
stats.update(100.0, 1.0); // Add a trade with P&L and fees
println!("Win rate: {:.2}%", stats.win_rate() * 100.0);
println!("Profit factor: {:.2}", stats.profit_factor());
// Position analysis
let mut metrics = PositionMetrics::new();
metrics.update(&position, intended_entry, intended_exit);
println!("Avg leverage: {:.2}x", metrics.avg_leverage);
println!("Slippage: {:.2}%", metrics.entry_slippage * 100.0);
// Portfolio risk metrics
let mut portfolio = PortfolioMetrics::new();
portfolio.update_exposure(symbol, exposure);
portfolio.calculate_metrics();
println!("Diversity: {:.2}", portfolio.diversity_score);
pub trait Indicator {
/// Update indicator with a new candle
fn update(&mut self, candle: &Candle);
/// Get current indicator value
fn value(&self) -> Option<f64>;
}
/// Example usage of technical analysis:
let mut analysis = TechnicalAnalysis::new();
analysis.update(&candle);
// Access indicator values
if let Some(sma) = analysis.sma20() {
println!("SMA-20: {}", sma);
}
if let Some(rsi) = analysis.rsi() {
println!("RSI-14: {}", rsi);
}
if let Some((middle, upper, lower)) = analysis.bollinger_bands() {
println!("Bollinger Bands - Middle: {}, Upper: {}, Lower: {}", middle, upper, lower);
}
// Get comprehensive analysis
let analysis_report = analysis.analyze();
for insight in analysis_report {
println!("{}", insight);
}- Multi-asset tracking
- Balance management
- Exposure calculation
- Portfolio metrics
pub trait Portfolio {
fn init_balance(&self) -> f64;
fn current_balance(&self) -> f64;
fn total_value(&self) -> f64;
fn max_drawdown(&self) -> f64;
}
pub struct BasicPortfolio {
fn new(init_balance: f64) -> Self;
fn update_position(&mut self, position: Position);
fn close_position(&mut self, symbol: &Symbol, exit_price: Price) -> f64;
fn available_margin(&self) -> f64;
fn margin_usage_ratio(&self) -> f64;
fn unrealized_pnl(&self) -> f64;
fn asset_exposure(&self, symbol: &Symbol) -> f64;
fn max_drawdown_ratio(&self) -> f64;
}- Strategy parameters
- Risk limits
- Execution settings
- Fee structures
// Risk Configuration
pub struct RiskConfig {
pub max_drawdown: f64, // % as 0.0 to 1.0
pub max_leverage: f64, // e.g., 3.0 for 3x
pub max_position_size: f64, // % as 0.0 to 1.0
pub strict_enforcement: bool,
}
// Fee Configuration
pub struct FeeConfig {
pub maker_fee: f64, // e.g., 0.0002 for 0.02%
pub taker_fee: f64, // e.g., 0.0005 for 0.05%
pub custom_fees: HashMap<Symbol, (f64, f64)>,
pub include_fees: bool,
}
// Strategy Parameters
pub struct StrategyParams {
pub fn new() -> Self;
pub fn with_param(self, name: &str, value: f64) -> Self;
pub fn get(&self, name: &str) -> Option<f64>;
pub fn set(&mut self, name: &str, value: f64);
}- Trade logs
- Performance reports
- Data export
- Visualization tools
use backtest::{
report::{
TradeLog, TradeRecord, PerformanceReport,
ChartData, ChartFormat, ChartOptions,
generate_equity_curve, generate_position_chart,
},
};
// Create a trade log with file output
let mut log = TradeLog::with_log_file("trades.log")?;
// Record trades
log.record(TradeRecord::new(
timestamp,
symbol,
"buy",
1.0,
price,
100.0, // P&L
1.0, // Fees
))?;
// Generate performance report
let report = PerformanceReport::new(
stats,
position_metrics,
portfolio_metrics,
positions,
);
// Export report to JSON
report.export_json("performance.json")?;
// Print formatted report
println!("{}", report.format());
// Generate and export charts
let mut chart_options = ChartOptions::default();
chart_options.format = ChartFormat::TradingView;
// Generate equity curve chart
let equity_chart = generate_equity_curve(&portfolio_metrics, &chart_options);
equity_chart.export("equity_curve.pine", &chart_options)?;
// Generate position visualization with candles and trades
let position_chart = generate_position_chart(&positions, &candles, &chart_options);
position_chart.export("trades.pine", &chart_options)?;
// Export data in other formats
chart_options.format = ChartFormat::Csv;
position_chart.export("trades.csv", &chart_options)?;
chart_options.format = ChartFormat::Json;
position_chart.export("trades.json", &chart_options)?;- Time handling (timestamps, durations, time series)
- Data structures (ring buffers, time series buffers)
- Statistical functions (core stats, financial metrics)
- Logging and error handling
// Time handling
let t1 = Timestamp::from_unix_secs(1600000000.0);
let d = Duration::from_secs(60.0);
let t2 = t1 + d;
assert_eq!(t2.as_unix_secs(), 1600000060.0);
// Data structures
let mut buffer = RingBuffer::new(100);
buffer.push(1.0);
let time_series = TimeSeriesBuffer::new(100);
time_series.push(t1.as_unix_secs(), 1.0);
// Statistical functions
let mut stats = Statistics::new();
stats.update(100.0);
println!("Mean: {}, StdDev: {}", stats.mean(), stats.std_dev());
let mut metrics = FinancialMetrics::new(100);
metrics.update(100.0);
println!("Sharpe: {}, Sortino: {}",
metrics.sharpe_ratio(0.02),
metrics.sortino_ratio(0.02));
// Logging
info!("Processing trade", "execution");
error!("Invalid order", "validation");
debug!("Position updated", "portfolio");- Process 1M trades per second on standard hardware
// Benchmark results: Data Processing/process_100k_trades: 264.04 μs // = 378M trades/sec for basic processing Data Processing/process_100k_trades_with_market: 10.972 ms // = 9M trades/sec with market simulation Portfolio/update_100_positions: 2.726 μs // = 36.6M position updates/sec
- Memory-efficient data structures (ring buffers, time series buffers)
- Parallel processing support (sync collections, thread-safe logging)
- Efficient data storage and retrieval (CSV parsing, indexed access)
- Unit tests for data processing components
- Unit tests for all other components (87 tests total)
- Integration tests for full system
// Integration test categories:
test_trading_workflow // End-to-end trading workflow
test_risk_management // Portfolio risk limits
test_market_behavior // Market simulation realism
test_analysis_signals // Technical indicators
test_market_impact // Order execution & liquidity
test_market_correlation // Multi-asset correlation
test_portfolio_risk // Portfolio risk metrics- Benchmark suite
cargo bench # Run all benchmarks
cargo bench data # Run only data processing benchmarks
cargo bench portfolio # Run only portfolio benchmarks- Known strategy validation tests
// XMR Swing Strategy validation tests:
#[test]
fn test_strategy_signals() {
// Validates signal generation in oversold/overbought conditions
let mut strategy = XmrSwingStrategy::new(70.0, 30.0, 1.0);
// Generate test data and verify signal triggers
// ...
}
#[test]
fn test_strategy_workflow() {
// Tests complete strategy execution workflow
let mut strategy = XmrSwingStrategy::default();
let mut market = SimpleMarketSimulator::new();
let mut portfolio = BasicPortfolio::new(100_000.0);
// Run backtest simulation and verify results
// ...
}
#[test]
fn test_strategy_risk_management() {
// Validates position sizing and risk limits
let mut strategy = XmrSwingStrategy::new(70.0, 30.0, 2.0);
let mut engine = ExecutionEngine::new(100_000.0, 2.0, 5.0);
// Test position size limits and risk management
// ...
}
#[test]
fn test_market_correlation() {
// Tests alignment of signals with technical indicators
let mut strategy = XmrSwingStrategy::default();
let mut technical = TechnicalAnalysis::new();
// Generate price trends and verify signal correlation
// ...
}Currently under development. The data processing module is ready for use:
use backtest::{
data::{DataSource, CandleBuilder},
Candle, Trade,
};
// Create a data source
let mut source = KrakenCsvDataSource::new("data/Trading_History/XBT_USD_trades_2019.csv")?;
// Create a candle builder for 1-minute candles
let mut builder = CandleBuilder::new(60.0);
// Process trades and generate candles
while let Some(Ok(trade)) = source.next_trade() {
if let Some(candle) = builder.update(&trade) {
// Process completed candle
println!("Completed candle: {:?}", candle);
}
}
// Get the last incomplete candle if any
if let Some(last_candle) = builder.finish() {
println!("Final incomplete candle: {:?}", last_candle);
}