rusty_backtest/
engine.rs

1//! L2 Backtesting Engine - Simple, accurate, and fastAdd commentMore actions
2//!
3//! The main L2 backtesting engine that orchestrates market data replay,
4//! order matching, and strategy execution with realistic latency simulation.
5
6use crate::latency::LatencyModel;
7use crate::matching::{Execution, MatchingEngine, Order, OrderSide, QueueModel};
8use crate::orderbook::OrderBook;
9use parking_lot::{Mutex, RwLock};
10use rust_decimal::Decimal;
11use rust_decimal_macros::dec;
12use rusty_common::SmartString;
13use rusty_common::collections::{FxHashMap, SmallOrderVec, SmallStrategyVec, SmallSymbolVec};
14use std::cmp::Ordering;
15use std::collections::BinaryHeap;
16use std::sync::Arc;
17
18/// Maximum order book depth to clear when processing depth clear events (default)
19const DEFAULT_MAX_BOOK_DEPTH: usize = 100;
20
21/// Default tick size for symbols without explicit configuration
22const DEFAULT_TICK_SIZE: Decimal = dec!(0.01);
23
24/// Event types in the backtesting engine
25#[derive(Debug, Clone)]
26pub enum Event {
27    /// Market data update
28    MarketData {
29        /// Timestamp of the market data event in nanoseconds
30        timestamp_ns: u64,
31        /// Symbol that the market data is for
32        symbol: SmartString,
33        /// Type of market data event (trade, depth update, etc.)
34        event_type: MarketDataEvent,
35    },
36    /// Order event (submission, response)
37    Order {
38        /// Timestamp of the order event in nanoseconds
39        timestamp_ns: u64,
40        /// The order event details (submit, cancel, response)
41        order_event: OrderEvent,
42    },
43    /// Timer event for strategies
44    Timer {
45        /// Timestamp when the timer fires in nanoseconds
46        timestamp_ns: u64,
47        /// Unique identifier for the timer
48        timer_id: u64,
49    },
50}
51
52/// Market data event types
53#[derive(Debug, Clone)]
54pub enum MarketDataEvent {
55    /// Trade occurred
56    Trade {
57        /// Side of the aggressor (buyer or seller)
58        side: OrderSide,
59        /// Trade execution price
60        price: Decimal,
61        /// Trade quantity/volume
62        quantity: Decimal,
63    },
64    /// Order book level update
65    DepthUpdate {
66        /// Side of the order book (bid or ask)
67        side: OrderSide,
68        /// Price level being updated
69        price: Decimal,
70        /// New quantity at this price level
71        quantity: Decimal,
72        /// Number of orders at this level
73        order_count: u32,
74    },
75    /// Clear all levels for a side
76    DepthClear {
77        /// Side to clear (None = clear both sides)
78        side: Option<OrderSide>,
79    },
80}
81
82/// Order events
83#[derive(Debug, Clone)]
84pub enum OrderEvent {
85    /// Order submission request
86    Submit(Order),
87    /// Order cancel request
88    Cancel {
89        /// ID of the order to cancel
90        order_id: u64,
91    },
92    /// Order response (accept/reject/fill)
93    Response(OrderResponse),
94}
95
96/// Order response types
97#[derive(Debug, Clone)]
98pub struct OrderResponse {
99    /// Unique identifier of the order this response is for
100    pub order_id: u64,
101    /// Type of response (accepted, rejected, filled, cancelled)
102    pub response_type: ResponseType,
103    /// Timestamp when the response was generated in nanoseconds
104    pub timestamp_ns: u64,
105}
106
107/// Types of responses that can be received for an order
108#[derive(Debug, Clone)]
109pub enum ResponseType {
110    /// Order was accepted by the exchange
111    Accepted,
112    /// Order was rejected with a reason
113    Rejected {
114        /// Reason for rejection (e.g., "Insufficient funds", "Invalid price")
115        reason: SmartString,
116    },
117    /// Order was filled (partially or fully)
118    Filled(Execution),
119    /// Order was successfully cancelled
120    Cancelled,
121}
122
123/// Strategy interface for L2 backtesting
124pub trait Strategy: Send + Sync {
125    /// Called on market data update
126    fn on_market_data(&mut self, symbol: &str, event: &MarketDataEvent, book: &OrderBook);
127
128    /// Called on order response
129    fn on_order_response(&mut self, response: &OrderResponse);
130
131    /// Called on timer event
132    fn on_timer(&mut self, timer_id: u64);
133
134    /// Get pending order submissions
135    fn get_orders(&mut self) -> SmallOrderVec<Order>;
136
137    /// Get pending order cancellations
138    fn get_cancels(&mut self) -> SmallOrderVec<u64>;
139}
140
141/// Event with timestamp for priority queue ordering
142#[derive(Debug, Clone)]
143struct TimedEvent {
144    timestamp_ns: u64,
145    sequence: u64, // For stable ordering of same-time events
146    event: Event,
147}
148
149impl Ord for TimedEvent {
150    fn cmp(&self, other: &Self) -> Ordering {
151        // Reverse order for min-heap
152        other
153            .timestamp_ns
154            .cmp(&self.timestamp_ns)
155            .then_with(|| other.sequence.cmp(&self.sequence))
156    }
157}
158
159impl PartialOrd for TimedEvent {
160    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
161        Some(self.cmp(other))
162    }
163}
164
165impl PartialEq for TimedEvent {
166    fn eq(&self, other: &Self) -> bool {
167        self.timestamp_ns == other.timestamp_ns && self.sequence == other.sequence
168    }
169}
170
171impl Eq for TimedEvent {}
172
173/// Configuration for L2 backtesting
174pub struct BacktestConfig {
175    /// Start time of the backtest in nanoseconds
176    pub start_time_ns: u64,
177    /// End time of the backtest in nanoseconds
178    pub end_time_ns: u64,
179    /// List of symbols to include in the backtest
180    pub symbols: SmallSymbolVec<SmartString>,
181    /// Minimum price increment per symbol (e.g., 0.01 for 2 decimal places)
182    pub tick_sizes: FxHashMap<SmartString, Decimal>,
183    /// Minimum order quantity increment per symbol
184    pub lot_sizes: FxHashMap<SmartString, Decimal>,
185    /// Queue position model (FIFO, ProRata, etc.)
186    pub queue_model: QueueModel,
187    /// Whether to allow orders to be partially filled
188    pub allow_partial_fills: bool,
189    /// Latency model for order submission and responses
190    pub order_latency: Box<dyn LatencyModel>,
191    /// Latency model for market data delivery
192    pub market_data_latency: Box<dyn LatencyModel>,
193    /// Enable conservative execution assumptions
194    pub conservative_mode: bool,
195    /// Optional custom market impact parameters
196    pub market_impact: Option<crate::matching::MarketImpact>,
197    /// Optional custom conservative parameters
198    pub conservative_params: Option<crate::matching::ConservativeParams>,
199}
200
201/// L2 Backtesting Engine with const generic book depth
202pub struct BacktestEngine<const DEPTH: usize = DEFAULT_MAX_BOOK_DEPTH> {
203    /// Configuration
204    config: BacktestConfig,
205    /// Current simulation time
206    current_time: Arc<RwLock<u64>>,
207    /// Order books by symbol
208    order_books: Arc<RwLock<FxHashMap<SmartString, Arc<OrderBook>>>>,
209    /// Matching engines by symbol
210    matching_engines: Arc<RwLock<FxHashMap<SmartString, Arc<MatchingEngine>>>>,
211    /// Event queue
212    event_queue: Arc<Mutex<BinaryHeap<TimedEvent>>>,
213    /// Event sequence counter
214    sequence_counter: Arc<Mutex<u64>>,
215    /// Strategies
216    strategies: Arc<RwLock<SmallStrategyVec<Box<dyn Strategy>>>>,
217    /// Performance statistics
218    stats: Arc<Mutex<BacktestStats>>,
219}
220
221/// Backtesting statistics
222#[derive(Debug, Default, Clone)]
223pub struct BacktestStats {
224    /// Total number of events processed during the backtest
225    pub events_processed: u64,
226    /// Number of orders submitted by strategies
227    pub orders_submitted: u64,
228    /// Number of orders that were filled (partially or fully)
229    pub orders_filled: u64,
230    /// Number of orders that were cancelled
231    pub orders_cancelled: u64,
232    /// Total traded volume across all fills
233    pub total_volume: Decimal,
234    /// Number of market data events processed
235    pub market_data_events: u64,
236}
237
238impl<const DEPTH: usize> BacktestEngine<DEPTH> {
239    /// Create a new L2 backtesting engine
240    #[must_use]
241    pub fn new(config: BacktestConfig) -> Self {
242        let mut order_books = FxHashMap::default();
243        let mut matching_engines = FxHashMap::default();
244
245        // Initialize order books and matching engines for each symbol
246        for symbol in &config.symbols {
247            let tick_size = config
248                .tick_sizes
249                .get(symbol)
250                .copied()
251                .unwrap_or(DEFAULT_TICK_SIZE);
252            let lot_size = config
253                .lot_sizes
254                .get(symbol)
255                .copied()
256                .unwrap_or(Decimal::new(1, 3)); // 0.001
257
258            let book = Arc::new(OrderBook::new(symbol.clone(), tick_size, lot_size));
259
260            let engine = if config.conservative_mode {
261                // Use conservative matching engine
262                let market_impact = config.market_impact.clone().unwrap_or_default();
263                let conservative_params = config.conservative_params.clone().unwrap_or_default();
264
265                Arc::new(MatchingEngine::with_conservative_params(
266                    book.clone(),
267                    config.queue_model,
268                    config.allow_partial_fills,
269                    false, // No self-trade prevention in backtest
270                    market_impact,
271                    conservative_params,
272                ))
273            } else {
274                Arc::new(MatchingEngine::new(
275                    book.clone(),
276                    config.queue_model,
277                    config.allow_partial_fills,
278                    false, // No self-trade prevention in backtest
279                ))
280            };
281
282            order_books.insert(symbol.clone(), book);
283            matching_engines.insert(symbol.clone(), engine);
284        }
285
286        Self {
287            config,
288            current_time: Arc::new(RwLock::new(0)),
289            order_books: Arc::new(RwLock::new(order_books)),
290            matching_engines: Arc::new(RwLock::new(matching_engines)),
291            event_queue: Arc::new(Mutex::new(BinaryHeap::new())),
292            sequence_counter: Arc::new(Mutex::new(0)),
293            strategies: Arc::new(RwLock::new(SmallStrategyVec::new())),
294            stats: Arc::new(Mutex::new(BacktestStats::default())),
295        }
296    }
297
298    /// Add a strategy
299    pub fn add_strategy(&self, strategy: Box<dyn Strategy>) {
300        self.strategies.write().push(strategy);
301    }
302
303    /// Add market data event
304    pub fn add_market_data(&self, timestamp_ns: u64, symbol: SmartString, event: MarketDataEvent) {
305        let timed_event = TimedEvent {
306            timestamp_ns,
307            sequence: self.next_sequence(),
308            event: Event::MarketData {
309                timestamp_ns,
310                symbol,
311                event_type: event,
312            },
313        };
314
315        self.event_queue.lock().push(timed_event);
316    }
317
318    /// Add timer event
319    pub fn add_timer(&self, timestamp_ns: u64, timer_id: u64) {
320        let timed_event = TimedEvent {
321            timestamp_ns,
322            sequence: self.next_sequence(),
323            event: Event::Timer {
324                timestamp_ns,
325                timer_id,
326            },
327        };
328
329        self.event_queue.lock().push(timed_event);
330    }
331
332    /// Run the backtest
333    #[must_use]
334    pub fn run(&self) -> BacktestStats {
335        *self.current_time.write() = self.config.start_time_ns;
336
337        loop {
338            // Pop event and immediately release the lock to avoid deadlock
339            let event = {
340                let mut queue = self.event_queue.lock();
341                queue.pop()
342            };
343
344            let Some(event) = event else {
345                break;
346            };
347
348            if event.timestamp_ns > self.config.end_time_ns {
349                break;
350            }
351
352            *self.current_time.write() = event.timestamp_ns;
353            self.process_event(event.event);
354        }
355
356        self.stats.lock().clone()
357    }
358
359    /// Process a single event
360    fn process_event(&self, event: Event) {
361        match event {
362            Event::MarketData {
363                timestamp_ns,
364                symbol,
365                event_type,
366            } => {
367                self.process_market_data(timestamp_ns, &symbol, event_type);
368            }
369            Event::Order {
370                timestamp_ns,
371                order_event,
372            } => {
373                self.process_order_event(timestamp_ns, order_event);
374            }
375            Event::Timer { timer_id, .. } => {
376                self.process_timer(timer_id);
377            }
378        }
379
380        self.stats.lock().events_processed += 1;
381    }
382
383    /// Process market data event
384    fn process_market_data(&self, timestamp_ns: u64, symbol: &str, event: MarketDataEvent) {
385        // Update order book
386        let books = self.order_books.read();
387        if let Some(book) = books.get(symbol) {
388            match &event {
389                MarketDataEvent::Trade {
390                    side,
391                    price,
392                    quantity,
393                } => {
394                    book.update_last_price(*price, timestamp_ns);
395
396                    // Check for order fills
397                    let engines = self.matching_engines.read();
398                    if let Some(engine) = engines.get(symbol) {
399                        let executions =
400                            engine.process_trade(*side, *price, *quantity, timestamp_ns);
401                        for exec in executions {
402                            self.process_execution(exec);
403                        }
404                    }
405                }
406                MarketDataEvent::DepthUpdate {
407                    side,
408                    price,
409                    quantity,
410                    order_count,
411                } => match side {
412                    OrderSide::Buy => {
413                        book.update_bid(*price, *quantity, *order_count, timestamp_ns)
414                    }
415                    OrderSide::Sell => {
416                        book.update_ask(*price, *quantity, *order_count, timestamp_ns)
417                    }
418                },
419                MarketDataEvent::DepthClear { side } => {
420                    match side {
421                        Some(OrderSide::Buy) => {
422                            // Clear all bid levels using const generic depth
423                            for i in 0..DEPTH {
424                                book.update_bid(Decimal::from(i), Decimal::ZERO, 0, timestamp_ns);
425                            }
426                        }
427                        Some(OrderSide::Sell) => {
428                            // Clear all ask levels using const generic depth
429                            for i in 0..DEPTH {
430                                book.update_ask(Decimal::from(i), Decimal::ZERO, 0, timestamp_ns);
431                            }
432                        }
433                        None => book.clear(),
434                    }
435                }
436            }
437
438            // Notify strategies
439            let mut strategies = self.strategies.write();
440            for strategy in strategies.iter_mut() {
441                strategy.on_market_data(symbol, &event, book);
442            }
443            drop(strategies); // Drop the lock before calling process_strategy_orders
444
445            // Process any new orders from strategies
446            self.process_strategy_orders();
447        }
448
449        self.stats.lock().market_data_events += 1;
450    }
451
452    /// Process order event
453    fn process_order_event(&self, timestamp_ns: u64, event: OrderEvent) {
454        match event {
455            OrderEvent::Submit(order) => {
456                // Add latency for order submission
457                let latency = self.config.order_latency.get_latency_ns();
458                let exec_time = timestamp_ns + latency;
459
460                let timed_event = TimedEvent {
461                    timestamp_ns: exec_time,
462                    sequence: self.next_sequence(),
463                    event: Event::Order {
464                        timestamp_ns: exec_time,
465                        order_event: OrderEvent::Response(OrderResponse {
466                            order_id: order.id,
467                            response_type: ResponseType::Accepted,
468                            timestamp_ns: exec_time,
469                        }),
470                    },
471                };
472
473                self.event_queue.lock().push(timed_event);
474
475                // Process order in matching engine
476                let engines = self.matching_engines.read();
477                if let Some(engine) = engines.get(&order.symbol) {
478                    let executions = engine.submit_order(order);
479                    for exec in executions {
480                        self.process_execution(exec);
481                    }
482                }
483
484                self.stats.lock().orders_submitted += 1;
485            }
486            OrderEvent::Cancel { order_id } => {
487                // Add latency for order cancellation
488                let latency = self.config.order_latency.get_latency_ns();
489                let exec_time = timestamp_ns + latency;
490
491                // Find and cancel order
492                let mut cancelled = false;
493                let engines = self.matching_engines.read();
494                for engine in engines.values() {
495                    if engine.cancel_order(order_id) {
496                        cancelled = true;
497                        break;
498                    }
499                }
500
501                if cancelled {
502                    let response = OrderResponse {
503                        order_id,
504                        response_type: ResponseType::Cancelled,
505                        timestamp_ns: exec_time,
506                    };
507
508                    let timed_event = TimedEvent {
509                        timestamp_ns: exec_time,
510                        sequence: self.next_sequence(),
511                        event: Event::Order {
512                            timestamp_ns: exec_time,
513                            order_event: OrderEvent::Response(response),
514                        },
515                    };
516
517                    self.event_queue.lock().push(timed_event);
518                    self.stats.lock().orders_cancelled += 1;
519                }
520            }
521            OrderEvent::Response(response) => {
522                // Deliver response to strategies
523                let mut strategies = self.strategies.write();
524                for strategy in strategies.iter_mut() {
525                    strategy.on_order_response(&response);
526                }
527            }
528        }
529    }
530
531    /// Process timer event
532    fn process_timer(&self, timer_id: u64) {
533        let mut strategies = self.strategies.write();
534        for strategy in strategies.iter_mut() {
535            strategy.on_timer(timer_id);
536        }
537
538        // Process any new orders from strategies
539        drop(strategies);
540        self.process_strategy_orders();
541    }
542
543    /// Process execution
544    fn process_execution(&self, exec: Execution) {
545        let response = OrderResponse {
546            order_id: exec.order_id,
547            response_type: ResponseType::Filled(exec.clone()),
548            timestamp_ns: exec.timestamp_ns,
549        };
550
551        let timed_event = TimedEvent {
552            timestamp_ns: exec.timestamp_ns,
553            sequence: self.next_sequence(),
554            event: Event::Order {
555                timestamp_ns: exec.timestamp_ns,
556                order_event: OrderEvent::Response(response),
557            },
558        };
559
560        self.event_queue.lock().push(timed_event);
561
562        let mut stats = self.stats.lock();
563        stats.orders_filled += 1;
564        stats.total_volume += exec.exec_quantity;
565    }
566
567    /// Process orders from strategies
568    fn process_strategy_orders(&self) {
569        let current_time = *self.current_time.read();
570        let mut strategies = self.strategies.write();
571
572        for strategy in strategies.iter_mut() {
573            // Get new orders
574            let orders = strategy.get_orders();
575            for order in orders {
576                let timed_event = TimedEvent {
577                    timestamp_ns: current_time,
578                    sequence: self.next_sequence(),
579                    event: Event::Order {
580                        timestamp_ns: current_time,
581                        order_event: OrderEvent::Submit(order),
582                    },
583                };
584
585                self.event_queue.lock().push(timed_event);
586            }
587
588            // Get cancellations
589            let cancels = strategy.get_cancels();
590            for order_id in cancels {
591                let timed_event = TimedEvent {
592                    timestamp_ns: current_time,
593                    sequence: self.next_sequence(),
594                    event: Event::Order {
595                        timestamp_ns: current_time,
596                        order_event: OrderEvent::Cancel { order_id },
597                    },
598                };
599
600                self.event_queue.lock().push(timed_event);
601            }
602        }
603    }
604
605    /// Get next event sequence number
606    fn next_sequence(&self) -> u64 {
607        let mut counter = self.sequence_counter.lock();
608        let seq = *counter;
609        *counter += 1;
610        seq
611    }
612
613    /// Get order book for a symbol
614    pub fn get_order_book(&self, symbol: &str) -> Option<Arc<OrderBook>> {
615        self.order_books.read().get(symbol).cloned()
616    }
617}
618
619/// Type alias for BacktestEngine with 100 levels of depth
620pub type BacktestEngine100 = BacktestEngine<100>;
621/// Type alias for BacktestEngine with 50 levels of depth
622pub type BacktestEngine50 = BacktestEngine<50>;
623/// Type alias for BacktestEngine with 200 levels of depth
624pub type BacktestEngine200 = BacktestEngine<200>;
625
626/// Default BacktestEngine type alias for seamless migration (100 levels)
627pub use BacktestEngine100 as DefaultBacktestEngine;
628
629#[cfg(test)]
630mod tests {
631    use super::*;
632    use crate::latency::FixedLatency;
633    use crate::matching::{OrderType, TimeInForce};
634    use rust_decimal_macros::dec;
635
636    struct TestStrategy {
637        orders: SmallOrderVec<Order>,
638        cancels: SmallOrderVec<u64>,
639        fills: SmallOrderVec<OrderResponse>,
640    }
641
642    impl Strategy for TestStrategy {
643        fn on_market_data(&mut self, _symbol: &str, _event: &MarketDataEvent, _book: &OrderBook) {
644            // No action
645        }
646
647        fn on_order_response(&mut self, response: &OrderResponse) {
648            if matches!(response.response_type, ResponseType::Filled(_)) {
649                self.fills.push(response.clone());
650            }
651        }
652
653        fn on_timer(&mut self, _timer_id: u64) {
654            // No action
655        }
656
657        fn get_orders(&mut self) -> SmallOrderVec<Order> {
658            std::mem::take(&mut self.orders)
659        }
660
661        fn get_cancels(&mut self) -> SmallOrderVec<u64> {
662            std::mem::take(&mut self.cancels)
663        }
664    }
665
666    #[test]
667    fn test_l2_backtest_engine() {
668        let mut config = BacktestConfig {
669            start_time_ns: 0,
670            end_time_ns: 10_000_000_000, // 10 seconds
671            symbols: smallvec::smallvec!["BTC-USD".into()],
672            tick_sizes: FxHashMap::default(),
673            lot_sizes: FxHashMap::default(),
674            queue_model: QueueModel::FIFO,
675            allow_partial_fills: true,
676            order_latency: Box::new(FixedLatency::new(100_000)), // 100μs
677            market_data_latency: Box::new(FixedLatency::new(50_000)), // 50μs
678            conservative_mode: false,
679            market_impact: None,
680            conservative_params: None,
681        };
682
683        config
684            .tick_sizes
685            .insert(SmartString::from("BTC-USD"), dec!(0.01));
686        config
687            .lot_sizes
688            .insert(SmartString::from("BTC-USD"), Decimal::new(1, 3)); // 0.001
689
690        let engine = DefaultBacktestEngine::new(config);
691
692        // Add test strategy
693        let strategy = TestStrategy {
694            orders: SmallOrderVec::from_vec(vec![Order {
695                id: 1,
696                symbol: SmartString::from("BTC-USD"),
697                side: OrderSide::Buy,
698                order_type: OrderType::Limit,
699                price: dec!(50000),
700                quantity: Decimal::ONE,
701                remaining_quantity: Decimal::ONE,
702                time_in_force: TimeInForce::GTC,
703                timestamp_ns: 1_000_000,
704                queue_position: crate::matching::QueuePosition::RiskAverse,
705            }]),
706            cancels: SmallOrderVec::new(),
707            fills: SmallOrderVec::new(),
708        };
709
710        engine.add_strategy(Box::new(strategy));
711
712        // Add market data
713        engine.add_market_data(
714            1_000_000,
715            SmartString::from("BTC-USD"),
716            MarketDataEvent::DepthUpdate {
717                side: OrderSide::Sell,
718                price: dec!(50001),
719                quantity: Decimal::TWO,
720                order_count: 1,
721            },
722        );
723
724        engine.add_market_data(
725            2_000_000,
726            SmartString::from("BTC-USD"),
727            MarketDataEvent::Trade {
728                side: OrderSide::Sell,
729                price: dec!(50000),
730                quantity: Decimal::new(5, 1), // 0.5
731            },
732        );
733
734        // Run backtest
735        let stats = engine.run();
736
737        assert_eq!(stats.events_processed, 5); // 2 market data + 1 order submit + 1 order accepted + 1 fill
738        assert_eq!(stats.orders_submitted, 1);
739        assert_eq!(stats.orders_filled, 1);
740        assert_eq!(stats.total_volume, Decimal::new(5, 1)); // 0.5
741    }
742}