1use std::collections::HashMap;
10use std::time::Instant;
11
12use async_trait::async_trait;
13
14use crate::messages::{
15 BatchOrderInput, BatchOrderOutput, CancelOrderInput, CancelOrderOutput, GetSnapshotInput,
16 GetSnapshotOutput, ModifyOrderInput, ModifyOrderOutput, SubmitOrderInput, SubmitOrderOutput,
17};
18use crate::types::{
19 EngineConfig, L2Snapshot, MatchResult, Order, OrderBook, OrderStatus, OrderType, Price,
20 PriceLevel, Quantity, Side, TimeInForce, Trade,
21};
22use rustkernel_core::{
23 domain::Domain,
24 error::Result as KernelResult,
25 kernel::KernelMetadata,
26 traits::{BatchKernel, GpuKernel},
27};
28
29#[derive(Debug)]
37pub struct OrderMatchingEngine {
38 metadata: KernelMetadata,
39 books: HashMap<u32, OrderBook>,
41 orders: HashMap<u64, Order>,
43 next_trade_id: u64,
45 config: EngineConfig,
47}
48
49impl Default for OrderMatchingEngine {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl OrderMatchingEngine {
56 #[must_use]
58 pub fn new() -> Self {
59 Self::with_config(EngineConfig::default())
60 }
61
62 #[must_use]
64 pub fn with_config(config: EngineConfig) -> Self {
65 Self {
66 metadata: KernelMetadata::ring("orderbook/matching", Domain::OrderMatching)
67 .with_description("Price-time priority order matching")
68 .with_throughput(100_000)
69 .with_latency_us(10.0)
70 .with_gpu_native(true),
71 books: HashMap::new(),
72 orders: HashMap::new(),
73 next_trade_id: 1,
74 config,
75 }
76 }
77
78 pub fn submit_order(&mut self, mut order: Order) -> MatchResult {
80 if let Err(status) = self.validate_order(&order) {
82 order.status = status;
83 let mut result = MatchResult::no_match(order.id, order.remaining);
84 result.status = status;
85 self.orders.insert(order.id, order);
86 return result;
87 }
88
89 self.books
91 .entry(order.symbol_id)
92 .or_insert_with(|| OrderBook::new(order.symbol_id));
93
94 let mut result = self.match_order_internal(&mut order);
96
97 if !order.is_filled() {
99 match (order.order_type, order.tif) {
100 (OrderType::Market, _) | (_, TimeInForce::IOC) => {
101 order.status = if result.trades.is_empty() {
103 OrderStatus::Canceled
104 } else {
105 OrderStatus::PartiallyFilled
106 };
107 }
108 (_, TimeInForce::FOK) => {
109 if !result.trades.is_empty() {
111 order.status = OrderStatus::PartiallyFilled;
113 } else {
114 order.status = OrderStatus::Canceled;
115 }
116 }
117 _ => {
118 self.add_to_book(&order);
120 order.status = if result.trades.is_empty() {
121 OrderStatus::New
122 } else {
123 OrderStatus::PartiallyFilled
124 };
125 }
126 }
127 } else {
128 order.status = OrderStatus::Filled;
129 }
130
131 result.status = order.status;
133
134 self.orders.insert(order.id, order);
136
137 result
138 }
139
140 pub fn cancel_order(&mut self, order_id: u64) -> Option<Order> {
142 {
144 let order = self.orders.get(&order_id)?;
145 if !order.is_active() {
146 return None;
147 }
148 }
149
150 self.remove_from_book(order_id);
152
153 let order = self.orders.get_mut(&order_id)?;
155 order.status = OrderStatus::Canceled;
156 Some(order.clone())
157 }
158
159 pub fn modify_order(
161 &mut self,
162 order_id: u64,
163 new_price: Option<Price>,
164 new_quantity: Option<Quantity>,
165 ) -> Option<MatchResult> {
166 let old_order = self.orders.get(&order_id)?.clone();
167
168 if !old_order.is_active() {
169 return None;
170 }
171
172 self.cancel_order(order_id)?;
174
175 let new_order = Order {
177 id: order_id, price: new_price.unwrap_or(old_order.price),
179 quantity: new_quantity.unwrap_or(old_order.remaining),
180 remaining: new_quantity.unwrap_or(old_order.remaining),
181 status: OrderStatus::New,
182 ..old_order
183 };
184
185 Some(self.submit_order(new_order))
186 }
187
188 pub fn get_order(&self, order_id: u64) -> Option<&Order> {
190 self.orders.get(&order_id)
191 }
192
193 pub fn get_snapshot(&self, symbol_id: u32, depth: usize) -> Option<L2Snapshot> {
195 let book = self.books.get(&symbol_id)?;
196 Some(L2Snapshot::from_book(
197 book,
198 depth,
199 std::time::SystemTime::now()
200 .duration_since(std::time::UNIX_EPOCH)
201 .unwrap()
202 .as_nanos() as u64,
203 ))
204 }
205
206 pub fn get_book(&self, symbol_id: u32) -> Option<&OrderBook> {
208 self.books.get(&symbol_id)
209 }
210
211 fn validate_order(&self, order: &Order) -> Result<(), OrderStatus> {
213 if order.quantity.0 < self.config.min_order_size.0 {
215 return Err(OrderStatus::Rejected);
216 }
217 if order.quantity.0 > self.config.max_order_size.0 {
218 return Err(OrderStatus::Rejected);
219 }
220
221 if order.order_type == OrderType::Limit
223 && self.config.tick_size.0 > 0
224 && order.price.0 % self.config.tick_size.0 != 0
225 {
226 return Err(OrderStatus::Rejected);
227 }
228
229 Ok(())
230 }
231
232 fn match_order_internal(&mut self, order: &mut Order) -> MatchResult {
234 let book = self.books.get_mut(&order.symbol_id).unwrap();
235 let mut trades = Vec::new();
236
237 let opposite_side = match order.side {
238 Side::Buy => &mut book.asks,
239 Side::Sell => &mut book.bids,
240 };
241
242 let prices_to_match: Vec<Price> = match order.side {
244 Side::Buy => opposite_side.keys().copied().collect(),
245 Side::Sell => opposite_side.keys().rev().copied().collect(),
246 };
247
248 let mut levels_to_remove = Vec::new();
249
250 for price in prices_to_match {
251 let can_match = match order.order_type {
253 OrderType::Market => true,
254 OrderType::Limit => match order.side {
255 Side::Buy => price <= order.price,
256 Side::Sell => price >= order.price,
257 },
258 _ => false,
259 };
260
261 if !can_match {
262 break;
263 }
264
265 let level = match opposite_side.get_mut(&price) {
267 Some(l) => l,
268 None => continue,
269 };
270
271 let mut orders_to_remove = Vec::new();
273
274 for &resting_order_id in &level.orders {
275 if order.remaining.0 == 0 {
276 break;
277 }
278
279 let resting_order = match self.orders.get_mut(&resting_order_id) {
280 Some(o) => o,
281 None => continue,
282 };
283
284 if self.config.self_trade_prevention && order.trader_id == resting_order.trader_id {
286 continue;
287 }
288
289 let fill_qty = Quantity(order.remaining.0.min(resting_order.remaining.0));
291
292 let trade = Trade {
294 id: self.next_trade_id,
295 symbol_id: order.symbol_id,
296 buy_order_id: if order.side == Side::Buy {
297 order.id
298 } else {
299 resting_order_id
300 },
301 sell_order_id: if order.side == Side::Sell {
302 order.id
303 } else {
304 resting_order_id
305 },
306 price,
307 quantity: fill_qty,
308 timestamp: order.timestamp,
309 aggressor: order.side,
310 };
311
312 self.next_trade_id += 1;
313 trades.push(trade);
314
315 order.remaining.0 -= fill_qty.0;
317 resting_order.remaining.0 -= fill_qty.0;
318
319 if resting_order.remaining.0 == 0 {
321 resting_order.status = OrderStatus::Filled;
322 orders_to_remove.push(resting_order_id);
323 } else {
324 resting_order.status = OrderStatus::PartiallyFilled;
325 }
326
327 book.volume_24h.0 += fill_qty.0;
329 book.last_price = Some(price);
330 }
331
332 for order_id in &orders_to_remove {
334 if let Some(resting) = self.orders.get(order_id) {
335 level.remove_order(*order_id, resting.quantity);
336 }
337 }
338
339 if level.is_empty() {
341 levels_to_remove.push(price);
342 }
343
344 if order.remaining.0 == 0 {
345 break;
346 }
347 }
348
349 for price in levels_to_remove {
351 opposite_side.remove(&price);
352 }
353
354 if trades.is_empty() {
356 MatchResult::no_match(order.id, order.remaining)
357 } else if order.remaining.0 == 0 {
358 MatchResult::full_fill(order.id, trades)
359 } else {
360 MatchResult::partial_fill(order.id, trades, order.remaining)
361 }
362 }
363
364 fn add_to_book(&mut self, order: &Order) {
366 let book = self
367 .books
368 .entry(order.symbol_id)
369 .or_insert_with(|| OrderBook::new(order.symbol_id));
370
371 let levels = match order.side {
372 Side::Buy => &mut book.bids,
373 Side::Sell => &mut book.asks,
374 };
375
376 let level = levels
377 .entry(order.price)
378 .or_insert_with(|| PriceLevel::new(order.price));
379
380 level.add_order(order.id, order.remaining);
381 }
382
383 fn remove_from_book(&mut self, order_id: u64) -> bool {
385 let order = match self.orders.get(&order_id) {
386 Some(o) => o.clone(),
387 None => return false,
388 };
389
390 let book = match self.books.get_mut(&order.symbol_id) {
391 Some(b) => b,
392 None => return false,
393 };
394
395 let levels = match order.side {
396 Side::Buy => &mut book.bids,
397 Side::Sell => &mut book.asks,
398 };
399
400 if let Some(level) = levels.get_mut(&order.price) {
401 level.remove_order(order_id, order.remaining);
402 if level.is_empty() {
403 levels.remove(&order.price);
404 }
405 true
406 } else {
407 false
408 }
409 }
410
411 pub fn process_batch(&mut self, orders: Vec<Order>) -> Vec<MatchResult> {
413 orders.into_iter().map(|o| self.submit_order(o)).collect()
414 }
415
416 pub fn clear(&mut self) {
418 self.books.clear();
419 self.orders.clear();
420 self.next_trade_id = 1;
421 }
422}
423
424impl Clone for OrderMatchingEngine {
425 fn clone(&self) -> Self {
426 Self {
427 metadata: self.metadata.clone(),
428 books: self.books.clone(),
429 orders: self.orders.clone(),
430 next_trade_id: self.next_trade_id,
431 config: self.config.clone(),
432 }
433 }
434}
435
436impl GpuKernel for OrderMatchingEngine {
437 fn metadata(&self) -> &KernelMetadata {
438 &self.metadata
439 }
440}
441
442#[async_trait]
443impl BatchKernel<SubmitOrderInput, SubmitOrderOutput> for OrderMatchingEngine {
444 async fn execute(&self, input: SubmitOrderInput) -> KernelResult<SubmitOrderOutput> {
445 let start = Instant::now();
446 let mut engine = self.clone();
450 let result = engine.submit_order(input.order);
451 Ok(SubmitOrderOutput {
452 result,
453 compute_time_us: start.elapsed().as_micros() as u64,
454 })
455 }
456}
457
458#[async_trait]
459impl BatchKernel<BatchOrderInput, BatchOrderOutput> for OrderMatchingEngine {
460 async fn execute(&self, input: BatchOrderInput) -> KernelResult<BatchOrderOutput> {
461 let start = Instant::now();
462 let mut engine = self.clone();
463 let results = engine.process_batch(input.orders);
464 let total_trades: usize = results.iter().map(|r| r.trades.len()).sum();
465 Ok(BatchOrderOutput {
466 results,
467 total_trades,
468 compute_time_us: start.elapsed().as_micros() as u64,
469 })
470 }
471}
472
473#[async_trait]
474impl BatchKernel<CancelOrderInput, CancelOrderOutput> for OrderMatchingEngine {
475 async fn execute(&self, input: CancelOrderInput) -> KernelResult<CancelOrderOutput> {
476 let start = Instant::now();
477 let mut engine = self.clone();
478 let canceled_order = engine.cancel_order(input.order_id);
479 Ok(CancelOrderOutput {
480 success: canceled_order.is_some(),
481 canceled_order,
482 compute_time_us: start.elapsed().as_micros() as u64,
483 })
484 }
485}
486
487#[async_trait]
488impl BatchKernel<ModifyOrderInput, ModifyOrderOutput> for OrderMatchingEngine {
489 async fn execute(&self, input: ModifyOrderInput) -> KernelResult<ModifyOrderOutput> {
490 let start = Instant::now();
491 let mut engine = self.clone();
492 let result = engine.modify_order(input.order_id, input.new_price, input.new_quantity);
493 Ok(ModifyOrderOutput {
494 success: result.is_some(),
495 result,
496 compute_time_us: start.elapsed().as_micros() as u64,
497 })
498 }
499}
500
501#[async_trait]
502impl BatchKernel<GetSnapshotInput, GetSnapshotOutput> for OrderMatchingEngine {
503 async fn execute(&self, input: GetSnapshotInput) -> KernelResult<GetSnapshotOutput> {
504 let start = Instant::now();
505 let snapshot = self.get_snapshot(input.symbol_id, input.depth);
506 Ok(GetSnapshotOutput {
507 snapshot,
508 compute_time_us: start.elapsed().as_micros() as u64,
509 })
510 }
511}
512
513use crate::ring_messages::{
518 CancelOrderResponse, CancelOrderRing, QueryBookResponse, QueryBookRing, RingOrderStatus,
519 RingString, SubmitOrderResponse, SubmitOrderRing,
520};
521use ringkernel_core::RingContext;
522use rustkernel_core::traits::RingKernelHandler;
523
524#[async_trait]
526impl RingKernelHandler<SubmitOrderRing, SubmitOrderResponse> for OrderMatchingEngine {
527 async fn handle(
528 &self,
529 _ctx: &mut RingContext,
530 msg: SubmitOrderRing,
531 ) -> KernelResult<SubmitOrderResponse> {
532 let order = msg.to_order();
534
535 let mut engine = self.clone();
537 let result = engine.submit_order(order);
538
539 let status = match result.status {
541 OrderStatus::New => RingOrderStatus::New,
542 OrderStatus::PartiallyFilled => RingOrderStatus::PartiallyFilled,
543 OrderStatus::Filled => RingOrderStatus::Filled,
544 OrderStatus::Canceled => RingOrderStatus::Canceled,
545 OrderStatus::Rejected | OrderStatus::Expired => RingOrderStatus::Rejected,
546 };
547
548 Ok(SubmitOrderResponse {
549 correlation_id: msg.correlation_id,
550 order_id: msg.order_id,
551 status,
552 filled_quantity: result.filled_quantity.0,
553 avg_price: result.avg_price.0,
554 remaining: result.remaining.0,
555 trade_count: result.trades.len() as u32,
556 error: RingString::empty(),
557 })
558 }
559}
560
561#[async_trait]
563impl RingKernelHandler<CancelOrderRing, CancelOrderResponse> for OrderMatchingEngine {
564 async fn handle(
565 &self,
566 _ctx: &mut RingContext,
567 msg: CancelOrderRing,
568 ) -> KernelResult<CancelOrderResponse> {
569 let mut engine = self.clone();
570 let canceled = engine.cancel_order(msg.order_id);
571
572 Ok(CancelOrderResponse {
573 correlation_id: msg.correlation_id,
574 order_id: msg.order_id,
575 success: canceled.is_some(),
576 remaining: canceled.map_or(0, |o| o.remaining.0),
577 })
578 }
579}
580
581#[async_trait]
583impl RingKernelHandler<QueryBookRing, QueryBookResponse> for OrderMatchingEngine {
584 async fn handle(
585 &self,
586 _ctx: &mut RingContext,
587 msg: QueryBookRing,
588 ) -> KernelResult<QueryBookResponse> {
589 let book = self.get_book(msg.symbol_id);
590
591 let (best_bid, best_ask, bid_depth, ask_depth, spread, mid_price) = match book {
592 Some(b) => {
593 let (bid_d, ask_d) = b.depth_at_levels(msg.depth as usize);
594 (
595 b.best_bid().map_or(0, |p| p.0),
596 b.best_ask().map_or(0, |p| p.0),
597 bid_d.0,
598 ask_d.0,
599 b.spread().map_or(0, |p| p.0),
600 b.mid_price().map_or(0, |p| p.0),
601 )
602 }
603 None => (0, 0, 0, 0, 0, 0),
604 };
605
606 Ok(QueryBookResponse {
607 correlation_id: msg.correlation_id,
608 symbol_id: msg.symbol_id,
609 best_bid,
610 best_ask,
611 bid_depth,
612 ask_depth,
613 spread,
614 mid_price,
615 })
616 }
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 fn create_engine() -> OrderMatchingEngine {
624 OrderMatchingEngine::new()
625 }
626
627 #[test]
628 fn test_engine_metadata() {
629 let engine = create_engine();
630 assert_eq!(engine.metadata().id, "orderbook/matching");
631 assert_eq!(engine.metadata().domain, Domain::OrderMatching);
632 }
633
634 #[test]
635 fn test_limit_order_submission() {
636 let mut engine = create_engine();
637
638 let order = Order::limit(
639 1,
640 100,
641 Side::Buy,
642 Price::from_f64(100.0),
643 Quantity::from_f64(10.0),
644 1000,
645 0,
646 );
647
648 let result = engine.submit_order(order);
649
650 assert_eq!(result.order_id, 1);
651 assert_eq!(result.status, OrderStatus::New);
652 assert!(result.trades.is_empty());
653 }
654
655 #[test]
656 fn test_market_order_no_liquidity() {
657 let mut engine = create_engine();
658
659 let order = Order::market(1, 100, Side::Buy, Quantity::from_f64(10.0), 1000, 0);
660
661 let result = engine.submit_order(order);
662
663 assert_eq!(result.status, OrderStatus::Canceled);
665 assert!(result.trades.is_empty());
666 }
667
668 #[test]
669 fn test_simple_match() {
670 let mut engine = create_engine();
671
672 let sell = Order::limit(
674 1,
675 100,
676 Side::Sell,
677 Price::from_f64(100.0),
678 Quantity::from_f64(10.0),
679 1000,
680 0,
681 );
682 engine.submit_order(sell);
683
684 let buy = Order::limit(
686 2,
687 100,
688 Side::Buy,
689 Price::from_f64(100.0),
690 Quantity::from_f64(10.0),
691 2000,
692 1,
693 );
694 let result = engine.submit_order(buy);
695
696 assert_eq!(result.status, OrderStatus::Filled);
697 assert_eq!(result.trades.len(), 1);
698 assert_eq!(result.filled_quantity.0, Quantity::from_f64(10.0).0);
699 }
700
701 #[test]
702 fn test_partial_fill() {
703 let mut engine = create_engine();
704
705 let sell = Order::limit(
707 1,
708 100,
709 Side::Sell,
710 Price::from_f64(100.0),
711 Quantity::from_f64(5.0),
712 1000,
713 0,
714 );
715 engine.submit_order(sell);
716
717 let buy = Order::limit(
719 2,
720 100,
721 Side::Buy,
722 Price::from_f64(100.0),
723 Quantity::from_f64(10.0),
724 2000,
725 1,
726 );
727 let result = engine.submit_order(buy);
728
729 assert_eq!(result.status, OrderStatus::PartiallyFilled);
730 assert_eq!(result.trades.len(), 1);
731 assert_eq!(result.filled_quantity.0, Quantity::from_f64(5.0).0);
732 assert_eq!(result.remaining.0, Quantity::from_f64(5.0).0);
733 }
734
735 #[test]
736 fn test_price_priority() {
737 let mut engine = create_engine();
738
739 let sell1 = Order::limit(
741 1,
742 100,
743 Side::Sell,
744 Price::from_f64(101.0),
745 Quantity::from_f64(10.0),
746 1000,
747 0,
748 );
749 engine.submit_order(sell1);
750
751 let sell2 = Order::limit(
752 2,
753 100,
754 Side::Sell,
755 Price::from_f64(100.0),
756 Quantity::from_f64(10.0),
757 1000,
758 1,
759 );
760 engine.submit_order(sell2);
761
762 let buy = Order::limit(
764 3,
765 100,
766 Side::Buy,
767 Price::from_f64(101.0),
768 Quantity::from_f64(10.0),
769 2000,
770 2,
771 );
772 let result = engine.submit_order(buy);
773
774 assert_eq!(result.status, OrderStatus::Filled);
775 assert_eq!(result.trades[0].price.to_f64(), 100.0);
776 assert_eq!(result.trades[0].sell_order_id, 2); }
778
779 #[test]
780 fn test_time_priority() {
781 let mut engine = create_engine();
782
783 let sell1 = Order::limit(
785 1,
786 100,
787 Side::Sell,
788 Price::from_f64(100.0),
789 Quantity::from_f64(5.0),
790 1000,
791 0, );
793 engine.submit_order(sell1);
794
795 let sell2 = Order::limit(
796 2,
797 100,
798 Side::Sell,
799 Price::from_f64(100.0),
800 Quantity::from_f64(5.0),
801 1000,
802 1, );
804 engine.submit_order(sell2);
805
806 let buy = Order::limit(
808 3,
809 100,
810 Side::Buy,
811 Price::from_f64(100.0),
812 Quantity::from_f64(5.0),
813 2000,
814 2,
815 );
816 let result = engine.submit_order(buy);
817
818 assert_eq!(result.trades[0].sell_order_id, 1); }
820
821 #[test]
822 fn test_cancel_order() {
823 let mut engine = create_engine();
824
825 let order = Order::limit(
826 1,
827 100,
828 Side::Buy,
829 Price::from_f64(100.0),
830 Quantity::from_f64(10.0),
831 1000,
832 0,
833 );
834 engine.submit_order(order);
835
836 let canceled = engine.cancel_order(1);
837
838 assert!(canceled.is_some());
839 assert_eq!(canceled.unwrap().status, OrderStatus::Canceled);
840 }
841
842 #[test]
843 fn test_order_book_snapshot() {
844 let mut engine = create_engine();
845
846 for i in 0..5 {
848 let order = Order::limit(
849 i,
850 100,
851 Side::Buy,
852 Price::from_f64(100.0 - i as f64),
853 Quantity::from_f64(10.0),
854 1000,
855 i,
856 );
857 engine.submit_order(order);
858 }
859
860 for i in 5..10 {
862 let order = Order::limit(
863 i,
864 100,
865 Side::Sell,
866 Price::from_f64(101.0 + (i - 5) as f64),
867 Quantity::from_f64(10.0),
868 1000,
869 i,
870 );
871 engine.submit_order(order);
872 }
873
874 let snapshot = engine.get_snapshot(100, 3).unwrap();
875
876 assert_eq!(snapshot.bids.len(), 3);
877 assert_eq!(snapshot.asks.len(), 3);
878
879 assert_eq!(snapshot.bids[0].0.to_f64(), 100.0);
881 assert_eq!(snapshot.asks[0].0.to_f64(), 101.0);
883 }
884
885 #[test]
886 fn test_self_trade_prevention() {
887 let mut engine = create_engine();
888
889 let sell = Order::limit(
891 1,
892 100,
893 Side::Sell,
894 Price::from_f64(100.0),
895 Quantity::from_f64(10.0),
896 1000, 0,
898 );
899 engine.submit_order(sell);
900
901 let buy = Order::limit(
903 2,
904 100,
905 Side::Buy,
906 Price::from_f64(100.0),
907 Quantity::from_f64(10.0),
908 1000, 1,
910 );
911 let result = engine.submit_order(buy);
912
913 assert!(result.trades.is_empty());
915 }
916
917 #[test]
918 fn test_market_order_fill() {
919 let mut engine = create_engine();
920
921 let sell = Order::limit(
923 1,
924 100,
925 Side::Sell,
926 Price::from_f64(100.0),
927 Quantity::from_f64(10.0),
928 1000,
929 0,
930 );
931 engine.submit_order(sell);
932
933 let buy = Order::market(2, 100, Side::Buy, Quantity::from_f64(5.0), 2000, 1);
935 let result = engine.submit_order(buy);
936
937 assert_eq!(result.status, OrderStatus::Filled);
938 assert_eq!(result.trades.len(), 1);
939 }
940
941 #[test]
942 fn test_book_depth() {
943 let mut engine = create_engine();
944
945 let buy = Order::limit(
947 1,
948 100,
949 Side::Buy,
950 Price::from_f64(100.0),
951 Quantity::from_f64(10.0),
952 1000,
953 0,
954 );
955 engine.submit_order(buy);
956
957 let sell = Order::limit(
959 2,
960 100,
961 Side::Sell,
962 Price::from_f64(101.0),
963 Quantity::from_f64(20.0),
964 1000,
965 1,
966 );
967 engine.submit_order(sell);
968
969 let book = engine.get_book(100).unwrap();
970
971 assert_eq!(book.best_bid().unwrap().to_f64(), 100.0);
972 assert_eq!(book.best_ask().unwrap().to_f64(), 101.0);
973 assert_eq!(book.bid_depth().to_f64(), 10.0);
974 assert_eq!(book.ask_depth().to_f64(), 20.0);
975 }
976
977 #[test]
978 fn test_modify_order() {
979 let mut engine = create_engine();
980
981 let order = Order::limit(
983 1,
984 100,
985 Side::Buy,
986 Price::from_f64(100.0),
987 Quantity::from_f64(10.0),
988 1000,
989 0,
990 );
991 engine.submit_order(order);
992
993 let result = engine.modify_order(1, Some(Price::from_f64(99.0)), None);
995
996 assert!(result.is_some());
997 let order = engine.get_order(1).unwrap();
998 assert_eq!(order.price.to_f64(), 99.0);
999 }
1000
1001 #[test]
1002 fn test_batch_processing() {
1003 let mut engine = create_engine();
1004
1005 let orders = vec![
1006 Order::limit(
1007 1,
1008 100,
1009 Side::Sell,
1010 Price::from_f64(100.0),
1011 Quantity::from_f64(10.0),
1012 1000,
1013 0,
1014 ),
1015 Order::limit(
1016 2,
1017 100,
1018 Side::Buy,
1019 Price::from_f64(100.0),
1020 Quantity::from_f64(5.0),
1021 2000,
1022 1,
1023 ),
1024 Order::limit(
1025 3,
1026 100,
1027 Side::Buy,
1028 Price::from_f64(100.0),
1029 Quantity::from_f64(5.0),
1030 2000,
1031 2,
1032 ),
1033 ];
1034
1035 let results = engine.process_batch(orders);
1036
1037 assert_eq!(results.len(), 3);
1038 assert_eq!(results[0].status, OrderStatus::New);
1040 assert_eq!(results[1].status, OrderStatus::Filled);
1042 assert_eq!(results[2].status, OrderStatus::Filled);
1044 }
1045
1046 #[test]
1047 fn test_multiple_symbols() {
1048 let mut engine = create_engine();
1049
1050 let order1 = Order::limit(
1052 1,
1053 100,
1054 Side::Buy,
1055 Price::from_f64(100.0),
1056 Quantity::from_f64(10.0),
1057 1000,
1058 0,
1059 );
1060 engine.submit_order(order1);
1061
1062 let order2 = Order::limit(
1064 2,
1065 200,
1066 Side::Sell,
1067 Price::from_f64(50.0),
1068 Quantity::from_f64(20.0),
1069 1000,
1070 1,
1071 );
1072 engine.submit_order(order2);
1073
1074 assert!(engine.get_book(100).is_some());
1075 assert!(engine.get_book(200).is_some());
1076 assert!(engine.get_book(300).is_none());
1077 }
1078
1079 #[test]
1080 fn test_order_validation() {
1081 let mut engine = create_engine();
1082
1083 let small_order = Order::limit(
1085 1,
1086 100,
1087 Side::Buy,
1088 Price::from_f64(100.0),
1089 Quantity(1), 1000,
1091 0,
1092 );
1093 let result = engine.submit_order(small_order);
1094 assert_eq!(result.status, OrderStatus::Rejected);
1095
1096 let large_order = Order::limit(
1098 2,
1099 100,
1100 Side::Buy,
1101 Price::from_f64(100.0),
1102 Quantity::from_f64(10_000_000.0), 1000,
1104 1,
1105 );
1106 let result = engine.submit_order(large_order);
1107 assert_eq!(result.status, OrderStatus::Rejected);
1108 }
1109}