1use 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
18const DEFAULT_MAX_BOOK_DEPTH: usize = 100;
20
21const DEFAULT_TICK_SIZE: Decimal = dec!(0.01);
23
24#[derive(Debug, Clone)]
26pub enum Event {
27 MarketData {
29 timestamp_ns: u64,
31 symbol: SmartString,
33 event_type: MarketDataEvent,
35 },
36 Order {
38 timestamp_ns: u64,
40 order_event: OrderEvent,
42 },
43 Timer {
45 timestamp_ns: u64,
47 timer_id: u64,
49 },
50}
51
52#[derive(Debug, Clone)]
54pub enum MarketDataEvent {
55 Trade {
57 side: OrderSide,
59 price: Decimal,
61 quantity: Decimal,
63 },
64 DepthUpdate {
66 side: OrderSide,
68 price: Decimal,
70 quantity: Decimal,
72 order_count: u32,
74 },
75 DepthClear {
77 side: Option<OrderSide>,
79 },
80}
81
82#[derive(Debug, Clone)]
84pub enum OrderEvent {
85 Submit(Order),
87 Cancel {
89 order_id: u64,
91 },
92 Response(OrderResponse),
94}
95
96#[derive(Debug, Clone)]
98pub struct OrderResponse {
99 pub order_id: u64,
101 pub response_type: ResponseType,
103 pub timestamp_ns: u64,
105}
106
107#[derive(Debug, Clone)]
109pub enum ResponseType {
110 Accepted,
112 Rejected {
114 reason: SmartString,
116 },
117 Filled(Execution),
119 Cancelled,
121}
122
123pub trait Strategy: Send + Sync {
125 fn on_market_data(&mut self, symbol: &str, event: &MarketDataEvent, book: &OrderBook);
127
128 fn on_order_response(&mut self, response: &OrderResponse);
130
131 fn on_timer(&mut self, timer_id: u64);
133
134 fn get_orders(&mut self) -> SmallOrderVec<Order>;
136
137 fn get_cancels(&mut self) -> SmallOrderVec<u64>;
139}
140
141#[derive(Debug, Clone)]
143struct TimedEvent {
144 timestamp_ns: u64,
145 sequence: u64, event: Event,
147}
148
149impl Ord for TimedEvent {
150 fn cmp(&self, other: &Self) -> Ordering {
151 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
173pub struct BacktestConfig {
175 pub start_time_ns: u64,
177 pub end_time_ns: u64,
179 pub symbols: SmallSymbolVec<SmartString>,
181 pub tick_sizes: FxHashMap<SmartString, Decimal>,
183 pub lot_sizes: FxHashMap<SmartString, Decimal>,
185 pub queue_model: QueueModel,
187 pub allow_partial_fills: bool,
189 pub order_latency: Box<dyn LatencyModel>,
191 pub market_data_latency: Box<dyn LatencyModel>,
193 pub conservative_mode: bool,
195 pub market_impact: Option<crate::matching::MarketImpact>,
197 pub conservative_params: Option<crate::matching::ConservativeParams>,
199}
200
201pub struct BacktestEngine<const DEPTH: usize = DEFAULT_MAX_BOOK_DEPTH> {
203 config: BacktestConfig,
205 current_time: Arc<RwLock<u64>>,
207 order_books: Arc<RwLock<FxHashMap<SmartString, Arc<OrderBook>>>>,
209 matching_engines: Arc<RwLock<FxHashMap<SmartString, Arc<MatchingEngine>>>>,
211 event_queue: Arc<Mutex<BinaryHeap<TimedEvent>>>,
213 sequence_counter: Arc<Mutex<u64>>,
215 strategies: Arc<RwLock<SmallStrategyVec<Box<dyn Strategy>>>>,
217 stats: Arc<Mutex<BacktestStats>>,
219}
220
221#[derive(Debug, Default, Clone)]
223pub struct BacktestStats {
224 pub events_processed: u64,
226 pub orders_submitted: u64,
228 pub orders_filled: u64,
230 pub orders_cancelled: u64,
232 pub total_volume: Decimal,
234 pub market_data_events: u64,
236}
237
238impl<const DEPTH: usize> BacktestEngine<DEPTH> {
239 #[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 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)); let book = Arc::new(OrderBook::new(symbol.clone(), tick_size, lot_size));
259
260 let engine = if config.conservative_mode {
261 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, 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, ))
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 pub fn add_strategy(&self, strategy: Box<dyn Strategy>) {
300 self.strategies.write().push(strategy);
301 }
302
303 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 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 #[must_use]
334 pub fn run(&self) -> BacktestStats {
335 *self.current_time.write() = self.config.start_time_ns;
336
337 loop {
338 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 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 fn process_market_data(&self, timestamp_ns: u64, symbol: &str, event: MarketDataEvent) {
385 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 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 for i in 0..DEPTH {
424 book.update_bid(Decimal::from(i), Decimal::ZERO, 0, timestamp_ns);
425 }
426 }
427 Some(OrderSide::Sell) => {
428 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 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); self.process_strategy_orders();
447 }
448
449 self.stats.lock().market_data_events += 1;
450 }
451
452 fn process_order_event(&self, timestamp_ns: u64, event: OrderEvent) {
454 match event {
455 OrderEvent::Submit(order) => {
456 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 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 let latency = self.config.order_latency.get_latency_ns();
489 let exec_time = timestamp_ns + latency;
490
491 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 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 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 drop(strategies);
540 self.process_strategy_orders();
541 }
542
543 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 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 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 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 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 pub fn get_order_book(&self, symbol: &str) -> Option<Arc<OrderBook>> {
615 self.order_books.read().get(symbol).cloned()
616 }
617}
618
619pub type BacktestEngine100 = BacktestEngine<100>;
621pub type BacktestEngine50 = BacktestEngine<50>;
623pub type BacktestEngine200 = BacktestEngine<200>;
625
626pub 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 }
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 }
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, 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)), market_data_latency: Box::new(FixedLatency::new(50_000)), 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)); let engine = DefaultBacktestEngine::new(config);
691
692 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 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), },
732 );
733
734 let stats = engine.run();
736
737 assert_eq!(stats.events_processed, 5); assert_eq!(stats.orders_submitted, 1);
739 assert_eq!(stats.orders_filled, 1);
740 assert_eq!(stats.total_volume, Decimal::new(5, 1)); }
742}