1use crate::orderbook::{Level, OrderBook};
18use parking_lot::RwLock;
19use rust_decimal::{Decimal, prelude::*};
20use rust_decimal_macros::dec;
21use rusty_common::SmartString;
22use rusty_common::collections::{
23 EXECUTION_CAPACITY, FxHashMap, MATCH_CAPACITY, SmallExecutionVec, SmallMatchVec,
24};
25use smallvec::SmallVec;
26use std::sync::Arc;
27
28#[repr(align(64))]
32#[derive(Debug, Clone)]
33pub struct Order {
34 pub id: u64,
36 pub symbol: SmartString,
38 pub side: OrderSide,
40 pub order_type: OrderType,
42 pub price: Decimal,
44 pub quantity: Decimal,
46 pub remaining_quantity: Decimal,
48 pub time_in_force: TimeInForce,
50 pub timestamp_ns: u64,
52 pub queue_position: QueuePosition,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum OrderSide {
59 Buy,
61 Sell,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum OrderType {
68 Market,
70 Limit,
72 PostOnly,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum TimeInForce {
79 GTC,
81 GTX,
83 IOC,
85 FOK,
87}
88
89#[derive(Debug, Clone)]
91pub enum QueuePosition {
92 RiskAverse,
94 FIFO {
96 position: usize,
98 quantity_ahead: Decimal,
100 },
101 Probabilistic {
103 probability: Decimal,
105 depth_ratio: Decimal,
107 },
108}
109
110#[repr(align(64))]
114#[derive(Debug, Clone)]
115pub struct Execution {
116 pub order_id: u64,
118 pub exec_price: Decimal,
120 pub exec_quantity: Decimal,
122 pub remaining_quantity: Decimal,
124 pub is_maker: bool,
126 pub timestamp_ns: u64,
128}
129
130#[derive(Debug, Clone)]
132struct MatchInfo {
133 order_id: u64,
134 exec_quantity: Decimal,
135 remaining_quantity: Decimal,
136}
137
138pub struct MatchingEngine<const N: usize = 8> {
144 book: Arc<OrderBook>,
146 bid_orders: Arc<RwLock<FxHashMap<i64, SmallVec<[Order; N]>>>>,
148 ask_orders: Arc<RwLock<FxHashMap<i64, SmallVec<[Order; N]>>>>,
149 order_map: Arc<RwLock<FxHashMap<u64, Order>>>,
151 queue_model: QueueModel,
153 allow_partial_fills: bool,
155 _self_trade_prevention: bool,
157 market_impact: MarketImpact,
159 conservative_params: ConservativeParams,
161 cumulative_impact: Arc<RwLock<FxHashMap<SmartString, Decimal>>>,
163}
164
165#[derive(Debug, Clone, Copy)]
167pub enum QueueModel {
168 RiskAverse,
170 FIFO,
172 Probabilistic,
174}
175
176#[derive(Debug, Clone)]
178pub struct MarketImpact {
179 pub linear_impact_bps: f64,
181 pub temp_impact_decay: f64,
183 pub permanent_impact_ratio: f64,
185 pub impact_asymmetry: f64,
187 pub spread_widening_factor: f64,
189}
190
191impl Default for MarketImpact {
192 fn default() -> Self {
193 Self {
194 linear_impact_bps: 0.5, temp_impact_decay: 0.1, permanent_impact_ratio: 0.3, impact_asymmetry: 1.2, spread_widening_factor: 1.1, }
200 }
201}
202
203#[derive(Debug, Clone)]
205pub struct ConservativeParams {
206 pub adverse_selection_prob: f64,
208 pub hidden_liquidity_ratio: f64,
210 pub front_run_probability: f64,
212 pub queue_position_penalty: f64,
214 pub slippage_bias_bps: f64,
216}
217
218impl Default for ConservativeParams {
219 fn default() -> Self {
220 Self {
221 adverse_selection_prob: 0.15, hidden_liquidity_ratio: 0.2, front_run_probability: 0.1, queue_position_penalty: 0.3, slippage_bias_bps: 0.2, }
227 }
228}
229
230impl ConservativeParams {
231 #[must_use]
233 pub const fn non_conservative() -> Self {
234 Self {
235 adverse_selection_prob: 0.0,
236 hidden_liquidity_ratio: 0.0,
237 front_run_probability: 0.0,
238 queue_position_penalty: 0.0,
239 slippage_bias_bps: 0.0,
240 }
241 }
242}
243
244impl<const N: usize> MatchingEngine<N> {
245 fn calculate_queue_position(
249 queue_model: QueueModel,
250 order: &mut Order,
251 existing_orders: Option<&SmallVec<[Order; N]>>,
252 level_quantity: Decimal,
253 ) {
254 let quantity_ahead = match queue_model {
256 QueueModel::FIFO => existing_orders
257 .map(|orders| orders.iter().map(|o| o.remaining_quantity).sum::<Decimal>())
258 .unwrap_or(Decimal::ZERO),
259 _ => Decimal::ZERO, };
261
262 match queue_model {
264 QueueModel::RiskAverse => {
265 order.queue_position = QueuePosition::RiskAverse;
266 }
267 QueueModel::FIFO => {
268 order.queue_position = QueuePosition::FIFO {
269 position: 0, quantity_ahead,
271 };
272 }
273 QueueModel::Probabilistic => {
274 let (depth_ratio, probability) = if level_quantity > Decimal::ZERO {
275 let ratio = order.remaining_quantity / level_quantity;
276 let prob = (Decimal::ONE - ratio).max(Decimal::ZERO);
277 (ratio, prob)
278 } else {
279 (Decimal::ZERO, Decimal::ONE)
282 };
283 order.queue_position = QueuePosition::Probabilistic {
284 probability,
285 depth_ratio,
286 };
287 }
288 }
289 }
290 #[must_use]
292 pub fn new(
293 book: Arc<OrderBook>,
294 queue_model: QueueModel,
295 allow_partial_fills: bool,
296 _self_trade_prevention: bool,
297 ) -> Self {
298 Self {
299 book,
300 bid_orders: Arc::new(RwLock::new(FxHashMap::default())),
301 ask_orders: Arc::new(RwLock::new(FxHashMap::default())),
302 order_map: Arc::new(RwLock::new(FxHashMap::default())),
303 queue_model,
304 allow_partial_fills,
305 _self_trade_prevention,
306 market_impact: MarketImpact::default(),
307 conservative_params: ConservativeParams::non_conservative(),
308 cumulative_impact: Arc::new(RwLock::new(FxHashMap::default())),
309 }
310 }
311
312 pub fn with_conservative_params(
314 book: Arc<OrderBook>,
315 queue_model: QueueModel,
316 allow_partial_fills: bool,
317 _self_trade_prevention: bool,
318 market_impact: MarketImpact,
319 conservative_params: ConservativeParams,
320 ) -> Self {
321 Self {
322 book,
323 bid_orders: Arc::new(RwLock::new(FxHashMap::default())),
324 ask_orders: Arc::new(RwLock::new(FxHashMap::default())),
325 order_map: Arc::new(RwLock::new(FxHashMap::default())),
326 queue_model,
327 allow_partial_fills,
328 _self_trade_prevention,
329 market_impact,
330 conservative_params,
331 cumulative_impact: Arc::new(RwLock::new(FxHashMap::default())),
332 }
333 }
334
335 #[must_use]
337 pub fn submit_order(&self, mut order: Order) -> SmallExecutionVec<Execution> {
338 let mut executions = SmallExecutionVec::with_capacity(EXECUTION_CAPACITY);
339
340 if order.quantity <= Decimal::ZERO {
343 return executions; }
345
346 if matches!(order.order_type, OrderType::Limit | OrderType::PostOnly)
348 && order.price <= Decimal::ZERO
349 {
350 return executions; }
352
353 if matches!(order.order_type, OrderType::Limit | OrderType::PostOnly) {
355 let impact_bps = self.calculate_market_impact(&order);
356 self.apply_market_impact(&order, impact_bps);
357 }
358
359 match order.order_type {
360 OrderType::Market => {
361 executions = self.process_market_order(&mut order);
362 }
363 OrderType::Limit | OrderType::PostOnly => {
364 let crosses = match order.side {
366 OrderSide::Buy => {
367 if let Some(best_ask) = self.book.best_ask() {
368 order.price >= best_ask.price
369 } else {
370 false
371 }
372 }
373 OrderSide::Sell => {
374 if let Some(best_bid) = self.book.best_bid() {
375 order.price <= best_bid.price
376 } else {
377 false
378 }
379 }
380 };
381
382 if crosses {
383 if order.order_type == OrderType::PostOnly {
384 return executions;
386 }
387 executions = self.process_crossing_limit_order(&mut order);
389 }
390
391 if order.remaining_quantity > Decimal::ZERO {
393 self.add_limit_order(order);
394 }
395 }
396 }
397
398 executions
399 }
400
401 #[must_use]
403 pub fn cancel_order(&self, order_id: u64) -> bool {
404 let mut order_map = self.order_map.write();
405 if let Some(order) = order_map.remove(&order_id) {
406 let tick = self.book.price_to_tick(order.price);
407 match order.side {
408 OrderSide::Buy => {
409 let mut bid_orders = self.bid_orders.write();
410 if let Some(orders) = bid_orders.get_mut(&tick) {
411 orders.retain(|o| o.id != order_id);
412
413 let new_total_qty: Decimal =
415 orders.iter().map(|o| o.remaining_quantity).sum();
416
417 if orders.is_empty() {
418 bid_orders.remove(&tick);
419 self.book
421 .update_bid(order.price, Decimal::ZERO, 0, order.timestamp_ns);
422 } else {
423 self.book.update_bid(
425 order.price,
426 new_total_qty,
427 orders.len() as u32,
428 order.timestamp_ns,
429 );
430
431 let mut total_quantity_ahead = Decimal::ZERO;
433 for (idx, queue_order) in orders.iter_mut().enumerate() {
434 if let QueuePosition::FIFO {
435 position,
436 quantity_ahead,
437 } = &mut queue_order.queue_position
438 {
439 *position = idx;
440 *quantity_ahead = total_quantity_ahead;
441 total_quantity_ahead += queue_order.remaining_quantity;
442 }
443 }
444 }
445 }
446 }
447 OrderSide::Sell => {
448 let mut ask_orders = self.ask_orders.write();
449 if let Some(orders) = ask_orders.get_mut(&tick) {
450 orders.retain(|o| o.id != order_id);
451
452 let new_total_qty: Decimal =
454 orders.iter().map(|o| o.remaining_quantity).sum();
455
456 if orders.is_empty() {
457 ask_orders.remove(&tick);
458 self.book
460 .update_ask(order.price, Decimal::ZERO, 0, order.timestamp_ns);
461 } else {
462 self.book.update_ask(
464 order.price,
465 new_total_qty,
466 orders.len() as u32,
467 order.timestamp_ns,
468 );
469
470 let mut total_quantity_ahead = Decimal::ZERO;
472 for (idx, queue_order) in orders.iter_mut().enumerate() {
473 if let QueuePosition::FIFO {
474 position,
475 quantity_ahead,
476 } = &mut queue_order.queue_position
477 {
478 *position = idx;
479 *quantity_ahead = total_quantity_ahead;
480 total_quantity_ahead += queue_order.remaining_quantity;
481 }
482 }
483 }
484 }
485 }
486 }
487 true
488 } else {
489 false
490 }
491 }
492
493 fn process_market_order(&self, order: &mut Order) -> SmallExecutionVec<Execution> {
495 let mut executions = SmallExecutionVec::with_capacity(EXECUTION_CAPACITY);
496
497 let impact_bps = self.calculate_market_impact(order);
499 let impact_multiplier =
500 Decimal::from_f64_retain(1.0 + impact_bps / 10000.0).unwrap_or(dec!(1));
501
502 match order.side {
503 OrderSide::Buy => {
504 let asks = self.book.top_asks(20);
506 for level in asks {
507 if order.remaining_quantity <= Decimal::ZERO {
508 break;
509 }
510
511 let exec_qty = order.remaining_quantity.min(level.quantity);
512 order.remaining_quantity -= exec_qty;
513
514 executions.push(Execution {
515 order_id: order.id,
516 exec_price: level.price * impact_multiplier,
517 exec_quantity: exec_qty,
518 remaining_quantity: order.remaining_quantity,
519 is_maker: false,
520 timestamp_ns: order.timestamp_ns,
521 });
522
523 if !self.allow_partial_fills && order.remaining_quantity > Decimal::ZERO {
524 order.remaining_quantity = order.quantity;
526 return SmallVec::new();
527 }
528 }
529 }
530 OrderSide::Sell => {
531 let bids = self.book.top_bids(20);
533 for level in bids {
534 if order.remaining_quantity <= Decimal::ZERO {
535 break;
536 }
537
538 let exec_qty = order.remaining_quantity.min(level.quantity);
539 order.remaining_quantity -= exec_qty;
540
541 let impact_multiplier_sell =
543 Decimal::from_f64_retain(1.0 - impact_bps / 10000.0).unwrap_or(dec!(1));
544
545 executions.push(Execution {
546 order_id: order.id,
547 exec_price: level.price * impact_multiplier_sell,
548 exec_quantity: exec_qty,
549 remaining_quantity: order.remaining_quantity,
550 is_maker: false,
551 timestamp_ns: order.timestamp_ns,
552 });
553
554 if !self.allow_partial_fills && order.remaining_quantity > Decimal::ZERO {
555 order.remaining_quantity = order.quantity;
557 return SmallVec::new();
558 }
559 }
560 }
561 }
562
563 if order.time_in_force == TimeInForce::FOK && order.remaining_quantity > Decimal::ZERO {
565 order.remaining_quantity = order.quantity;
566 SmallVec::with_capacity(0)
567 } else {
568 executions
569 }
570 }
571
572 #[inline(always)]
575 const fn create_taker_execution(
576 order: &Order,
577 level_price: Decimal,
578 exec_qty: Decimal,
579 ) -> Execution {
580 Execution {
581 order_id: order.id,
582 exec_price: level_price,
583 exec_quantity: exec_qty,
584 remaining_quantity: order.remaining_quantity,
585 is_maker: false,
586 timestamp_ns: order.timestamp_ns,
587 }
588 }
589
590 #[inline(always)]
592 fn create_maker_executions(
593 matches: SmallMatchVec<MatchInfo>,
594 level_price: Decimal,
595 timestamp_ns: u64,
596 ) -> impl Iterator<Item = Execution> {
597 matches.into_iter().map(move |match_info| Execution {
598 order_id: match_info.order_id,
599 exec_price: level_price,
600 exec_quantity: match_info.exec_quantity,
601 remaining_quantity: match_info.remaining_quantity,
602 is_maker: true,
603 timestamp_ns,
604 })
605 }
606
607 fn process_crossing_levels<F>(
609 &self,
610 order: &mut Order,
611 levels: SmallVec<[Level; 10]>,
612 price_check: F,
613 opposite_side: OrderSide,
614 ) -> SmallExecutionVec<Execution>
615 where
616 F: Fn(Decimal, Decimal) -> bool,
617 {
618 let mut executions = SmallExecutionVec::with_capacity(EXECUTION_CAPACITY);
619
620 for level in levels {
621 if !price_check(level.price, order.price) || order.remaining_quantity <= Decimal::ZERO {
622 break;
623 }
624
625 let exec_qty = order.remaining_quantity.min(level.quantity);
626 order.remaining_quantity -= exec_qty;
627
628 executions.push(Self::create_taker_execution(order, level.price, exec_qty));
630
631 let matches =
633 self.consume_liquidity(opposite_side, level.price, exec_qty, order.timestamp_ns);
634
635 executions.extend(Self::create_maker_executions(
636 matches,
637 level.price,
638 order.timestamp_ns,
639 ));
640 }
641
642 executions
643 }
644
645 fn apply_time_in_force_constraints(
647 order: &mut Order,
648 executions: SmallExecutionVec<Execution>,
649 ) -> SmallExecutionVec<Execution> {
650 match order.time_in_force {
651 TimeInForce::IOC => {
652 order.remaining_quantity = Decimal::ZERO;
654 executions
655 }
656 TimeInForce::FOK => {
657 if order.remaining_quantity > Decimal::ZERO {
658 order.remaining_quantity = order.quantity;
660 SmallVec::with_capacity(0)
661 } else {
662 executions
663 }
664 }
665 _ => executions,
666 }
667 }
668
669 fn process_crossing_limit_order(&self, order: &mut Order) -> SmallExecutionVec<Execution> {
670 let executions = match order.side {
671 OrderSide::Buy => {
672 let asks = self.book.top_asks(20);
673 self.process_crossing_levels(
674 order,
675 asks,
676 |level_price, order_price| level_price <= order_price,
677 OrderSide::Sell,
678 )
679 }
680 OrderSide::Sell => {
681 let bids = self.book.top_bids(20);
682 self.process_crossing_levels(
683 order,
684 bids,
685 |level_price, order_price| level_price >= order_price,
686 OrderSide::Buy,
687 )
688 }
689 };
690
691 Self::apply_time_in_force_constraints(order, executions)
692 }
693
694 fn consume_liquidity(
697 &self,
698 side: OrderSide,
699 price: Decimal,
700 consumed_qty: Decimal,
701 timestamp_ns: u64,
702 ) -> SmallMatchVec<MatchInfo> {
703 let tick = self.book.price_to_tick(price);
704
705 let mut order_map = self.order_map.write();
708
709 match side {
710 OrderSide::Buy => {
711 let mut bid_orders = self.bid_orders.write();
712 self.consume_liquidity_from_level(
713 &mut bid_orders,
714 &mut order_map,
715 tick,
716 price,
717 consumed_qty,
718 timestamp_ns,
719 |book, price, qty, count, ts| book.update_bid(price, qty, count, ts),
720 )
721 }
722 OrderSide::Sell => {
723 let mut ask_orders = self.ask_orders.write();
724 self.consume_liquidity_from_level(
725 &mut ask_orders,
726 &mut order_map,
727 tick,
728 price,
729 consumed_qty,
730 timestamp_ns,
731 |book, price, qty, count, ts| book.update_ask(price, qty, count, ts),
732 )
733 }
734 }
735 }
736
737 #[allow(clippy::too_many_arguments)]
739 fn consume_liquidity_from_level(
740 &self,
741 orders_map: &mut FxHashMap<i64, SmallVec<[Order; N]>>,
742 order_map: &mut FxHashMap<u64, Order>,
743 tick: i64,
744 price: Decimal,
745 consumed_qty: Decimal,
746 timestamp_ns: u64,
747 update_book: impl Fn(&OrderBook, Decimal, Decimal, u32, u64),
748 ) -> SmallMatchVec<MatchInfo> {
749 let mut matches = SmallMatchVec::with_capacity(MATCH_CAPACITY);
750 let mut remaining_to_consume = consumed_qty;
751
752 if let Some(orders) = orders_map.get_mut(&tick) {
753 let mut orders_to_remove = SmallVec::<[usize; 8]>::with_capacity(8);
755 for (idx, order) in orders.iter_mut().enumerate() {
756 if remaining_to_consume <= Decimal::ZERO {
757 break;
758 }
759
760 let consumed_from_order = remaining_to_consume.min(order.remaining_quantity);
761 order.remaining_quantity -= consumed_from_order;
762 remaining_to_consume -= consumed_from_order;
763
764 matches.push(MatchInfo {
766 order_id: order.id,
767 exec_quantity: consumed_from_order,
768 remaining_quantity: order.remaining_quantity,
769 });
770
771 if let Some(map_order) = order_map.get_mut(&order.id) {
773 map_order.remaining_quantity = order.remaining_quantity;
774 }
775
776 if order.remaining_quantity <= Decimal::ZERO {
778 orders_to_remove.push(idx);
779 }
780 }
781
782 for &idx in orders_to_remove.iter().rev() {
784 let removed_order = orders.remove(idx);
785 order_map.remove(&removed_order.id);
786 }
787
788 let total_quantity: Decimal = orders.iter().map(|o| o.remaining_quantity).sum();
790 if total_quantity > Decimal::ZERO {
791 update_book(
792 &self.book,
793 price,
794 total_quantity,
795 orders.len() as u32,
796 timestamp_ns,
797 );
798 } else {
799 update_book(&self.book, price, Decimal::ZERO, 0, timestamp_ns);
801 orders_map.remove(&tick);
802 }
803 }
804 matches
805 }
806
807 fn add_limit_order(&self, mut order: Order) {
809 let tick = self.book.price_to_tick(order.price);
810
811 self.apply_conservative_queue_position(&mut order);
813
814 let mut order_map = self.order_map.write();
817
818 match order.side {
820 OrderSide::Buy => {
821 let mut bid_orders = self.bid_orders.write();
822
823 Self::calculate_queue_position(
825 self.queue_model,
826 &mut order,
827 bid_orders.get(&tick),
828 self.book.bid_qty_at_tick(tick),
829 );
830
831 order_map.insert(order.id, order.clone());
833
834 let orders = bid_orders.entry(tick).or_default();
836
837 if let QueuePosition::FIFO {
839 ref mut position, ..
840 } = order.queue_position
841 {
842 *position = orders.len();
843 }
844
845 orders.push(order.clone());
846
847 let total_quantity: Decimal = orders.iter().map(|o| o.remaining_quantity).sum();
849 self.book.update_bid(
850 order.price,
851 total_quantity,
852 orders.len() as u32,
853 order.timestamp_ns,
854 );
855 }
856 OrderSide::Sell => {
857 let mut ask_orders = self.ask_orders.write();
858
859 Self::calculate_queue_position(
861 self.queue_model,
862 &mut order,
863 ask_orders.get(&tick),
864 self.book.ask_qty_at_tick(tick),
865 );
866
867 order_map.insert(order.id, order.clone());
869
870 let orders = ask_orders.entry(tick).or_default();
872
873 if let QueuePosition::FIFO {
875 ref mut position, ..
876 } = order.queue_position
877 {
878 *position = orders.len();
879 }
880
881 orders.push(order.clone());
882
883 let total_quantity: Decimal = orders.iter().map(|o| o.remaining_quantity).sum();
885 self.book.update_ask(
886 order.price,
887 total_quantity,
888 orders.len() as u32,
889 order.timestamp_ns,
890 );
891 }
892 }
893 }
894
895 pub fn process_trade(
897 &self,
898 side: OrderSide,
899 price: Decimal,
900 quantity: Decimal,
901 timestamp_ns: u64,
902 ) -> SmallExecutionVec<Execution> {
903 let mut executions = SmallExecutionVec::with_capacity(EXECUTION_CAPACITY);
904 let _tick = self.book.price_to_tick(price);
905
906 let mut order_map = self.order_map.write();
909
910 match side {
911 OrderSide::Buy => {
912 let mut ask_orders = self.ask_orders.write();
914
915 let mut ticks_to_check = SmallVec::<[i64; 10]>::with_capacity(10);
917 ticks_to_check.extend(
918 ask_orders
919 .keys()
920 .filter(|&&t| self.book.tick_to_price(t) <= price)
921 .cloned(),
922 );
923
924 for check_tick in ticks_to_check {
925 if let Some(orders) = ask_orders.get_mut(&check_tick) {
926 let mut remaining_qty = quantity;
927 let mut filled_indices = SmallVec::<[usize; 8]>::with_capacity(8);
928
929 if let QueueModel::FIFO = self.queue_model {
931 orders.sort_by_key(|order| {
933 if let QueuePosition::FIFO { position, .. } = &order.queue_position
934 {
935 *position
936 } else {
937 0
938 }
939 });
940 }
941
942 for (idx, order) in orders.iter_mut().enumerate() {
943 if remaining_qty <= Decimal::ZERO {
944 break;
945 }
946
947 let should_fill = match &order.queue_position {
948 QueuePosition::RiskAverse => true,
949 QueuePosition::FIFO { .. } => true, QueuePosition::Probabilistic { probability, .. } => {
951 rand::random::<f64>() < probability.to_f64().unwrap_or(f64::NAN)
953 }
954 };
955
956 if should_fill {
957 if self.check_adverse_selection() {
959 continue;
961 }
962
963 let fill_qty = order.remaining_quantity.min(remaining_qty);
964 order.remaining_quantity -= fill_qty;
965 remaining_qty -= fill_qty;
966
967 let exec_price = self.apply_slippage(order.price, order.side);
969
970 executions.push(Execution {
971 order_id: order.id,
972 exec_price,
973 exec_quantity: fill_qty,
974 remaining_quantity: order.remaining_quantity,
975 is_maker: true,
976 timestamp_ns,
977 });
978
979 if order.remaining_quantity <= Decimal::ZERO {
980 filled_indices.push(idx);
981 order_map.remove(&order.id);
982 }
983 }
984 }
985
986 for idx in filled_indices.into_iter().rev() {
988 orders.remove(idx);
989 }
990
991 let mut total_quantity_ahead = Decimal::ZERO;
993 for (idx, order) in orders.iter_mut().enumerate() {
994 if let QueuePosition::FIFO {
995 position,
996 quantity_ahead,
997 } = &mut order.queue_position
998 {
999 *position = idx;
1000 *quantity_ahead = total_quantity_ahead;
1001 total_quantity_ahead += order.remaining_quantity;
1002 }
1003 }
1004
1005 if orders.is_empty() {
1006 ask_orders.remove(&check_tick);
1007 }
1008 }
1009 }
1010 }
1011 OrderSide::Sell => {
1012 let mut bid_orders = self.bid_orders.write();
1014
1015 let mut ticks_to_check = SmallVec::<[i64; 10]>::with_capacity(10);
1017 ticks_to_check.extend(
1018 bid_orders
1019 .keys()
1020 .filter(|&&t| self.book.tick_to_price(t) >= price)
1021 .cloned(),
1022 );
1023
1024 for check_tick in ticks_to_check {
1025 if let Some(orders) = bid_orders.get_mut(&check_tick) {
1026 let mut remaining_qty = quantity;
1027 let mut filled_indices = SmallVec::<[usize; 8]>::with_capacity(8);
1028
1029 if let QueueModel::FIFO = self.queue_model {
1031 orders.sort_by_key(|order| {
1033 if let QueuePosition::FIFO { position, .. } = &order.queue_position
1034 {
1035 *position
1036 } else {
1037 0
1038 }
1039 });
1040 }
1041
1042 for (idx, order) in orders.iter_mut().enumerate() {
1043 if remaining_qty <= Decimal::ZERO {
1044 break;
1045 }
1046
1047 let should_fill = match &order.queue_position {
1048 QueuePosition::RiskAverse => true,
1049 QueuePosition::FIFO { .. } => true, QueuePosition::Probabilistic { probability, .. } => {
1051 rand::random::<f64>() < probability.to_f64().unwrap_or(f64::NAN)
1053 }
1054 };
1055
1056 if should_fill {
1057 if self.check_adverse_selection() {
1059 continue;
1061 }
1062
1063 let fill_qty = order.remaining_quantity.min(remaining_qty);
1064 order.remaining_quantity -= fill_qty;
1065 remaining_qty -= fill_qty;
1066
1067 let exec_price = self.apply_slippage(order.price, order.side);
1069
1070 executions.push(Execution {
1071 order_id: order.id,
1072 exec_price,
1073 exec_quantity: fill_qty,
1074 remaining_quantity: order.remaining_quantity,
1075 is_maker: true,
1076 timestamp_ns,
1077 });
1078
1079 if order.remaining_quantity <= Decimal::ZERO {
1080 filled_indices.push(idx);
1081 order_map.remove(&order.id);
1082 }
1083 }
1084 }
1085
1086 for idx in filled_indices.into_iter().rev() {
1088 orders.remove(idx);
1089 }
1090
1091 let mut total_quantity_ahead = Decimal::ZERO;
1093 for (idx, order) in orders.iter_mut().enumerate() {
1094 if let QueuePosition::FIFO {
1095 position,
1096 quantity_ahead,
1097 } = &mut order.queue_position
1098 {
1099 *position = idx;
1100 *quantity_ahead = total_quantity_ahead;
1101 total_quantity_ahead += order.remaining_quantity;
1102 }
1103 }
1104
1105 if orders.is_empty() {
1106 bid_orders.remove(&check_tick);
1107 }
1108 }
1109 }
1110 }
1111 }
1112
1113 executions
1114 }
1115
1116 pub fn get_orders_at_level(&self, side: OrderSide, price: Decimal) -> SmallVec<[Order; N]> {
1118 let tick = self.book.price_to_tick(price);
1119 match side {
1120 OrderSide::Buy => self
1121 .bid_orders
1122 .read()
1123 .get(&tick)
1124 .cloned()
1125 .unwrap_or_default(),
1126 OrderSide::Sell => self
1127 .ask_orders
1128 .read()
1129 .get(&tick)
1130 .cloned()
1131 .unwrap_or_default(),
1132 }
1133 }
1134
1135 pub fn clear(&self) {
1137 self.bid_orders.write().clear();
1138 self.ask_orders.write().clear();
1139 self.order_map.write().clear();
1140 self.cumulative_impact.write().clear();
1141 }
1142
1143 fn calculate_market_impact(&self, order: &Order) -> f64 {
1145 let symbol_volume = self.book.total_volume();
1146 let order_size_ratio = if symbol_volume > Decimal::ZERO {
1147 (order.quantity / symbol_volume)
1148 .to_f64()
1149 .unwrap_or(f64::NAN)
1150 } else {
1151 (order.quantity / Decimal::from(100))
1152 .to_f64()
1153 .unwrap_or(0.0) };
1155
1156 let mut impact_bps = self.market_impact.linear_impact_bps * order_size_ratio;
1158
1159 if matches!(order.side, OrderSide::Sell) {
1161 impact_bps *= self.market_impact.impact_asymmetry;
1162 }
1163
1164 let cumulative = self
1166 .cumulative_impact
1167 .read()
1168 .get(&order.symbol)
1169 .copied()
1170 .unwrap_or(Decimal::ZERO);
1171
1172 impact_bps += cumulative.to_f64().unwrap_or(f64::NAN) * 0.1; impact_bps
1175 }
1176
1177 fn apply_market_impact(&self, order: &Order, impact_bps: f64) {
1179 let impact_decimal = Decimal::from_f64_retain(impact_bps / 10000.0).unwrap_or(dec!(0));
1180
1181 let mut cumulative_impact = self.cumulative_impact.write();
1183 let cumulative = cumulative_impact
1184 .entry(order.symbol.clone())
1185 .or_insert(Decimal::ZERO);
1186
1187 *cumulative +=
1189 Decimal::from_f64_retain(impact_bps * self.market_impact.permanent_impact_ratio)
1190 .unwrap_or(Decimal::ZERO);
1191
1192 *cumulative *= Decimal::from_f64_retain(1.0 - self.market_impact.temp_impact_decay)
1194 .unwrap_or(Decimal::ONE);
1195
1196 match order.side {
1198 OrderSide::Buy => {
1199 if let Some(best_ask) = self.book.best_ask() {
1201 let _new_price = best_ask.price * (dec!(1) + impact_decimal);
1202 }
1204 }
1205 OrderSide::Sell => {
1206 if let Some(best_bid) = self.book.best_bid() {
1208 let _new_price = best_bid.price * (dec!(1) - impact_decimal);
1209 }
1211 }
1212 }
1213 }
1214
1215 fn apply_conservative_queue_position(&self, order: &mut Order) {
1217 match &mut order.queue_position {
1218 QueuePosition::FIFO {
1219 position: _position,
1220 quantity_ahead,
1221 } => {
1222 let penalty = self.conservative_params.queue_position_penalty;
1224 *quantity_ahead *= Decimal::from_f64_retain(1.0 + penalty).unwrap_or(Decimal::ONE);
1225
1226 let hidden = self.conservative_params.hidden_liquidity_ratio;
1228 *quantity_ahead *= Decimal::from_f64_retain(1.0 + hidden).unwrap_or(Decimal::ONE);
1229 }
1230 QueuePosition::Probabilistic { probability, .. } => {
1231 *probability *=
1233 Decimal::from_f64_retain(1.0 - self.conservative_params.queue_position_penalty)
1234 .unwrap_or(Decimal::ONE);
1235 *probability *=
1236 Decimal::from_f64_retain(1.0 - self.conservative_params.hidden_liquidity_ratio)
1237 .unwrap_or(Decimal::ONE);
1238
1239 *probability *=
1241 Decimal::from_f64_retain(1.0 - self.conservative_params.front_run_probability)
1242 .unwrap_or(Decimal::ONE);
1243 }
1244 _ => {}
1245 }
1246 }
1247
1248 fn check_adverse_selection(&self) -> bool {
1250 rand::random::<f64>() < self.conservative_params.adverse_selection_prob
1251 }
1252
1253 fn apply_slippage(&self, price: Decimal, side: OrderSide) -> Decimal {
1255 let slippage =
1256 Decimal::from_f64_retain(self.conservative_params.slippage_bias_bps / 10000.0)
1257 .unwrap_or(dec!(0));
1258
1259 match side {
1260 OrderSide::Buy => price * (dec!(1) + slippage),
1261 OrderSide::Sell => price * (dec!(1) - slippage),
1262 }
1263 }
1264}
1265
1266pub type DefaultMatchingEngine = MatchingEngine<8>;
1269pub type MatchingEngine4 = MatchingEngine<4>;
1271pub type MatchingEngine16 = MatchingEngine<16>;
1273pub type MatchingEngine32 = MatchingEngine<32>;
1275
1276#[cfg(test)]
1277mod tests {
1278 use super::*;
1279 use proptest::prelude::*;
1280 use rust_decimal_macros::dec;
1281
1282 #[test]
1283 fn test_market_order_execution() {
1284 let book = Arc::new(OrderBook::new(
1285 SmartString::from("BTC-USD"),
1286 dec!(0.01),
1287 dec!(0.001),
1288 ));
1289
1290 book.update_ask(dec!(50001), dec!(1.0), 2, 100);
1292 book.update_ask(dec!(50002), dec!(2.0), 3, 101);
1293 book.update_bid(dec!(50000), dec!(1.5), 2, 102);
1294 book.update_bid(dec!(49999), dec!(2.5), 4, 103);
1295
1296 let mut engine =
1298 DefaultMatchingEngine::new(book.clone(), QueueModel::RiskAverse, true, false);
1299 engine.market_impact = MarketImpact {
1300 linear_impact_bps: 0.0, temp_impact_decay: 0.0,
1302 permanent_impact_ratio: 0.0,
1303 impact_asymmetry: 1.0,
1304 spread_widening_factor: 1.0,
1305 };
1306
1307 let order = Order {
1309 id: 1,
1310 symbol: SmartString::from("BTC-USD"),
1311 side: OrderSide::Buy,
1312 order_type: OrderType::Market,
1313 price: dec!(0),
1314 quantity: dec!(2.5),
1315 remaining_quantity: dec!(2.5),
1316 time_in_force: TimeInForce::IOC,
1317 timestamp_ns: 1000,
1318 queue_position: QueuePosition::RiskAverse,
1319 };
1320
1321 let executions = engine.submit_order(order);
1322
1323 assert_eq!(executions.len(), 2);
1324 assert_eq!(executions[0].exec_price, dec!(50001));
1325 assert_eq!(executions[0].exec_quantity, dec!(1.0));
1326 assert_eq!(executions[1].exec_price, dec!(50002));
1327 assert_eq!(executions[1].exec_quantity, dec!(1.5));
1328 }
1329
1330 #[test]
1331 fn test_limit_order_queue() {
1332 let book = Arc::new(OrderBook::new(
1333 SmartString::from("BTC-USD"),
1334 dec!(0.01),
1335 dec!(0.001),
1336 ));
1337
1338 let engine = DefaultMatchingEngine::new(book.clone(), QueueModel::FIFO, true, false);
1339
1340 for i in 0..3 {
1342 let order = Order {
1343 id: i + 1,
1344 symbol: SmartString::from("BTC-USD"),
1345 side: OrderSide::Buy,
1346 order_type: OrderType::Limit,
1347 price: dec!(50000),
1348 quantity: dec!(1.0),
1349 remaining_quantity: dec!(1.0),
1350 time_in_force: TimeInForce::GTC,
1351 timestamp_ns: 1000 + i,
1352 queue_position: QueuePosition::RiskAverse,
1353 };
1354
1355 let _ = engine.submit_order(order);
1356 }
1357
1358 let orders = engine.get_orders_at_level(OrderSide::Buy, dec!(50000));
1360 assert_eq!(orders.len(), 3);
1361
1362 let mut expected_quantity_ahead = Decimal::ZERO;
1364 for (idx, order) in orders.iter().enumerate() {
1365 if let QueuePosition::FIFO {
1366 position,
1367 quantity_ahead,
1368 } = &order.queue_position
1369 {
1370 assert_eq!(*position, idx);
1371 assert_eq!(*quantity_ahead, expected_quantity_ahead);
1372 expected_quantity_ahead += order.remaining_quantity;
1373 }
1374 }
1375 }
1376
1377 fn price_strategy() -> impl Strategy<Value = Decimal> {
1381 (1u64..=1_000_000u64).prop_map(|v| Decimal::from(v) / dec!(100))
1382 }
1383
1384 fn quantity_strategy() -> impl Strategy<Value = Decimal> {
1386 (1u64..=10_000_000u64).prop_map(|v| Decimal::from(v) / dec!(1000))
1387 }
1388
1389 fn order_id_strategy() -> impl Strategy<Value = u64> {
1391 1u64..=1_000_000u64
1392 }
1393
1394 fn timestamp_strategy() -> impl Strategy<Value = u64> {
1396 1_000_000_000u64..=10_000_000_000u64
1397 }
1398
1399 fn order_side_strategy() -> impl Strategy<Value = OrderSide> {
1401 prop_oneof![Just(OrderSide::Buy), Just(OrderSide::Sell),]
1402 }
1403
1404 fn order_type_strategy() -> impl Strategy<Value = OrderType> {
1406 prop_oneof![
1407 Just(OrderType::Limit),
1408 Just(OrderType::Market),
1409 Just(OrderType::PostOnly),
1410 ]
1411 }
1412
1413 fn time_in_force_strategy() -> impl Strategy<Value = TimeInForce> {
1415 prop_oneof![
1416 Just(TimeInForce::GTC),
1417 Just(TimeInForce::GTX),
1418 Just(TimeInForce::IOC),
1419 Just(TimeInForce::FOK),
1420 ]
1421 }
1422
1423 fn order_strategy() -> impl Strategy<Value = Order> {
1425 (
1426 order_id_strategy(),
1427 "[A-Z]{3}-[A-Z]{3,4}", order_side_strategy(),
1429 order_type_strategy(),
1430 price_strategy(),
1431 quantity_strategy(),
1432 time_in_force_strategy(),
1433 timestamp_strategy(),
1434 )
1435 .prop_map(
1436 |(id, symbol, side, order_type, price, quantity, time_in_force, timestamp_ns)| {
1437 let price = match order_type {
1438 OrderType::Market => Decimal::ZERO,
1439 _ => price,
1440 };
1441
1442 Order {
1443 id,
1444 symbol: SmartString::from(symbol),
1445 side,
1446 order_type,
1447 price,
1448 quantity,
1449 remaining_quantity: quantity,
1450 time_in_force,
1451 timestamp_ns,
1452 queue_position: QueuePosition::RiskAverse,
1453 }
1454 },
1455 )
1456 }
1457
1458 proptest! {
1459 #[test]
1460 fn test_order_quantity_conservation(
1461 buy_order in order_strategy(),
1462 sell_order in order_strategy(),
1463 ) {
1464 let book = Arc::new(OrderBook::new(
1465 SmartString::from("TEST-USD"),
1466 dec!(0.01),
1467 dec!(0.001),
1468 ));
1469
1470 let mut buy = buy_order.clone();
1471 buy.side = OrderSide::Buy;
1472 buy.order_type = OrderType::Limit;
1473 buy.symbol = SmartString::from("TEST-USD");
1474
1475 let mut sell = sell_order.clone();
1476 sell.side = OrderSide::Sell;
1477 sell.order_type = OrderType::Limit;
1478 sell.price = buy.price; sell.symbol = SmartString::from("TEST-USD");
1480
1481 let mut engine = DefaultMatchingEngine::new(book, QueueModel::FIFO, true, false);
1482 engine.market_impact = MarketImpact::default();
1483
1484 let initial_buy_qty = buy.quantity;
1485 let initial_sell_qty = sell.quantity;
1486
1487 let buy_execs = engine.submit_order(buy);
1489 let sell_execs = engine.submit_order(sell);
1490
1491 let total_executed: Decimal = buy_execs.iter()
1493 .chain(sell_execs.iter())
1494 .map(|e| e.exec_quantity)
1495 .sum();
1496
1497 let max_possible = initial_buy_qty.min(initial_sell_qty) * dec!(2); prop_assert!(total_executed <= max_possible,
1500 "Total executed ({}) cannot exceed maximum possible ({})",
1501 total_executed, max_possible);
1502 }
1503
1504 #[test]
1505 fn test_price_time_priority_invariant(
1506 orders in prop::collection::vec(order_strategy(), 5..15)
1507 ) {
1508 let book = Arc::new(OrderBook::new(
1509 SmartString::from("TEST-USD"),
1510 dec!(0.01),
1511 dec!(0.001),
1512 ));
1513
1514 let engine = DefaultMatchingEngine::new(book.clone(), QueueModel::FIFO, true, false);
1515
1516 let mut buy_orders = Vec::new();
1518 for (i, mut order) in orders.into_iter().enumerate() {
1519 order.side = OrderSide::Buy;
1520 order.order_type = OrderType::Limit;
1521 order.symbol = "TEST-USD".into();
1522 order.price = dec!(100) + Decimal::from(i % 3); order.timestamp_ns = (i * 1000) as u64;
1524
1525 book.update_bid(order.price, order.quantity, 1, order.timestamp_ns);
1527
1528 buy_orders.push(order.clone());
1529 let _ = engine.submit_order(order);
1530 }
1531
1532 let sell = Order {
1534 id: 999999,
1535 symbol: "TEST-USD".into(),
1536 side: OrderSide::Sell,
1537 order_type: OrderType::Market,
1538 price: Decimal::ZERO,
1539 quantity: dec!(10000), remaining_quantity: dec!(10000),
1541 time_in_force: TimeInForce::IOC,
1542 timestamp_ns: 1_000_000,
1543 queue_position: QueuePosition::RiskAverse,
1544 };
1545
1546 let executions = engine.submit_order(sell);
1547
1548 for i in 1..executions.len() {
1550 let prev_price = executions[i-1].exec_price;
1551 let curr_price = executions[i].exec_price;
1552
1553 prop_assert!(curr_price <= prev_price,
1554 "Price priority violated: {} > {}", curr_price, prev_price);
1555 }
1556 }
1557
1558 #[test]
1559 fn test_market_order_properties(
1560 limit_orders in prop::collection::vec(order_strategy(), 1..10),
1561 market_qty in quantity_strategy()
1562 ) {
1563 let book = Arc::new(OrderBook::new(
1564 SmartString::from("TEST-USD"),
1565 dec!(0.01),
1566 dec!(0.001),
1567 ));
1568
1569 let engine = DefaultMatchingEngine::new(book.clone(), QueueModel::FIFO, true, false);
1570
1571 let base_price = dec!(100);
1573 for (i, mut order) in limit_orders.into_iter().enumerate() {
1574 order.order_type = OrderType::Limit;
1575 order.symbol = "TEST-USD".into();
1576
1577 if i.is_multiple_of(2) {
1578 order.side = OrderSide::Buy;
1579 order.price = base_price - Decimal::from(i);
1580 book.update_bid(order.price, order.quantity, 1, order.timestamp_ns);
1581 } else {
1582 order.side = OrderSide::Sell;
1583 order.price = base_price + Decimal::from(i);
1584 book.update_ask(order.price, order.quantity, 1, order.timestamp_ns);
1585 }
1586
1587 let _ = engine.submit_order(order);
1588 }
1589
1590 let market_order = Order {
1592 id: 999999,
1593 symbol: "TEST-USD".into(),
1594 side: OrderSide::Buy,
1595 order_type: OrderType::Market,
1596 price: Decimal::ZERO,
1597 quantity: market_qty,
1598 remaining_quantity: market_qty,
1599 time_in_force: TimeInForce::IOC,
1600 timestamp_ns: 1_000_000,
1601 queue_position: QueuePosition::RiskAverse,
1602 };
1603
1604 let executions = engine.submit_order(market_order);
1605
1606 let total_executed: Decimal = executions.iter()
1608 .map(|e| e.exec_quantity)
1609 .sum();
1610
1611 prop_assert!(total_executed <= market_qty,
1613 "Cannot execute more than market order quantity");
1614
1615 for exec in &executions {
1617 prop_assert!(exec.exec_price > Decimal::ZERO,
1618 "Market order execution must have valid price");
1619 }
1620 }
1621
1622 #[test]
1623 fn test_queue_position_fifo_ordering(
1624 orders in prop::collection::vec(order_strategy(), 5..20)
1625 ) {
1626 let book = Arc::new(OrderBook::new(
1627 SmartString::from("TEST-USD"),
1628 dec!(0.01),
1629 dec!(0.001),
1630 ));
1631
1632 let engine = DefaultMatchingEngine::new(book.clone(), QueueModel::FIFO, true, false);
1633
1634 let fixed_price = dec!(100);
1636 let mut order_ids = Vec::new();
1637 let order_count = orders.len();
1638
1639 for (i, mut order) in orders.into_iter().enumerate() {
1640 order.side = OrderSide::Buy;
1641 order.order_type = OrderType::Limit;
1642 order.price = fixed_price;
1643 order.symbol = "TEST-USD".into();
1644 order.timestamp_ns = i as u64 * 1000; order_ids.push(order.id);
1647
1648 book.update_bid(order.price, order.quantity, 1, order.timestamp_ns);
1650
1651 let _ = engine.submit_order(order);
1652 }
1653
1654 let total_buy_qty: Decimal = order_ids.iter()
1656 .take(order_count)
1657 .map(|_| dec!(100)) .sum();
1659
1660 let sell = Order {
1661 id: 999999,
1662 symbol: "TEST-USD".into(),
1663 side: OrderSide::Sell,
1664 order_type: OrderType::Limit,
1665 price: fixed_price,
1666 quantity: total_buy_qty,
1667 remaining_quantity: total_buy_qty,
1668 time_in_force: TimeInForce::IOC,
1669 timestamp_ns: 1_000_000,
1670 queue_position: QueuePosition::RiskAverse,
1671 };
1672
1673 let executions = engine.submit_order(sell);
1674
1675 prop_assert!(!executions.is_empty(),
1677 "Should have executions when orders can match");
1678 }
1679
1680
1681 #[test]
1682 fn test_conservative_mode_properties(
1683 orders in prop::collection::vec(order_strategy(), 10..30),
1684 adverse_prob in 0.0f64..0.5,
1685 hidden_ratio in 0.0f64..0.3,
1686 ) {
1687 let book = Arc::new(OrderBook::new(
1688 SmartString::from("TEST-USD"),
1689 dec!(0.01),
1690 dec!(0.001),
1691 ));
1692
1693 let conservative_params = ConservativeParams {
1694 adverse_selection_prob: adverse_prob,
1695 hidden_liquidity_ratio: hidden_ratio,
1696 front_run_probability: 0.1,
1697 queue_position_penalty: 0.2,
1698 slippage_bias_bps: 0.1,
1699 };
1700
1701 let engine = DefaultMatchingEngine::with_conservative_params(
1702 book.clone(),
1703 QueueModel::FIFO,
1704 true,
1705 false,
1706 MarketImpact::default(), conservative_params,
1708 );
1709
1710 let fixed_price = dec!(100);
1712 for (i, mut order) in orders.into_iter().enumerate() {
1713 order.symbol = "TEST-USD".into();
1714 order.order_type = OrderType::Limit;
1715 order.price = fixed_price;
1716
1717 if i.is_multiple_of(2) {
1718 order.side = OrderSide::Buy;
1719 book.update_bid(order.price, order.quantity, 1, order.timestamp_ns);
1720 } else {
1721 order.side = OrderSide::Sell;
1722 book.update_ask(order.price, order.quantity, 1, order.timestamp_ns);
1723 }
1724
1725 let _ = engine.submit_order(order);
1726 }
1727
1728 prop_assert!(true, "Conservative mode parameters accepted");
1730 }
1731 }
1732
1733 #[test]
1734 fn debug_partial_fill_simple() {
1735 let orderbook = Arc::new(OrderBook::new("BTC/USDT".into(), dec!(0.01), dec!(0.00001)));
1736 let engine = DefaultMatchingEngine::new(orderbook.clone(), QueueModel::FIFO, true, true);
1737
1738 let price = dec!(100);
1739
1740 let initial_qty = dec!(3.085);
1742
1743 let buy_order = Order {
1745 id: 1,
1746 symbol: "BTC/USDT".into(),
1747 side: OrderSide::Buy,
1748 order_type: OrderType::Limit,
1749 price,
1750 quantity: initial_qty,
1751 remaining_quantity: initial_qty,
1752 time_in_force: TimeInForce::GTC,
1753 timestamp_ns: 1000,
1754 queue_position: QueuePosition::RiskAverse,
1755 };
1756
1757 println!("=== BEFORE PLACING BUY ORDER ===");
1758 if let Some(best_bid) = orderbook.best_bid() {
1759 println!(
1760 "Best bid before: price={}, qty={}",
1761 best_bid.price, best_bid.quantity
1762 );
1763 } else {
1764 println!("No best bid before");
1765 }
1766
1767 let _ = engine.submit_order(buy_order);
1768
1769 println!("=== AFTER PLACING BUY ORDER ===");
1770 if let Some(best_bid) = orderbook.best_bid() {
1771 println!(
1772 "Best bid after buy: price={}, qty={}",
1773 best_bid.price, best_bid.quantity
1774 );
1775 } else {
1776 println!("No best bid after buy");
1777 }
1778
1779 let fill_ratio = 0.12979220016395918;
1781 let fill_qty = initial_qty * Decimal::from_f64_retain(fill_ratio).unwrap();
1782
1783 println!("Calculated fill_qty: {fill_qty}");
1784 println!("Fill ratio used: {fill_ratio}");
1785 let sell_order = Order {
1786 id: 2,
1787 symbol: "BTC/USDT".into(),
1788 side: OrderSide::Sell,
1789 order_type: OrderType::Limit,
1790 price,
1791 quantity: fill_qty,
1792 remaining_quantity: fill_qty,
1793 time_in_force: TimeInForce::GTC,
1794 timestamp_ns: 2000,
1795 queue_position: QueuePosition::RiskAverse,
1796 };
1797
1798 println!("=== BEFORE PLACING SELL ORDER ===");
1799 println!("Sell order qty: {fill_qty}");
1800
1801 let executions = engine.submit_order(sell_order);
1802
1803 println!("=== AFTER PLACING SELL ORDER ===");
1804 println!("Number of executions: {}", executions.len());
1805 for (i, exec) in executions.iter().enumerate() {
1806 println!(
1807 "Execution {}: order_id={}, qty={}, price={}, remaining={}",
1808 i, exec.order_id, exec.exec_quantity, exec.exec_price, exec.remaining_quantity
1809 );
1810 }
1811
1812 assert!(
1814 !executions.is_empty(),
1815 "Should have at least one execution from partial fill"
1816 );
1817 assert_eq!(
1818 executions.len(),
1819 2,
1820 "Should have exactly 2 executions: one for each order"
1821 );
1822
1823 for exec in &executions {
1825 assert!(
1826 exec.exec_quantity > Decimal::ZERO,
1827 "Execution quantity should be positive"
1828 );
1829 assert_eq!(
1830 exec.exec_price, price,
1831 "Execution price should match order price"
1832 );
1833 }
1834
1835 for exec in &executions {
1838 assert_eq!(
1839 exec.exec_quantity, fill_qty,
1840 "Each execution quantity should equal the traded amount"
1841 );
1842 }
1843
1844 if let Some(best_bid) = orderbook.best_bid() {
1845 println!(
1846 "Best bid after sell: price={}, qty={}",
1847 best_bid.price, best_bid.quantity
1848 );
1849 let expected_remaining = dec!(3.085) - fill_qty;
1850 println!("Expected remaining: {expected_remaining}");
1851 println!("Actual remaining: {}", best_bid.quantity);
1852 println!(
1853 "Difference: {}",
1854 (best_bid.quantity - expected_remaining).abs()
1855 );
1856
1857 let tolerance = dec!(0.000002); assert!(
1860 (best_bid.quantity - expected_remaining).abs() <= tolerance,
1861 "Remaining quantity should be approximately correct. Expected: {}, Actual: {}, Diff: {}",
1862 expected_remaining,
1863 best_bid.quantity,
1864 (best_bid.quantity - expected_remaining).abs()
1865 );
1866
1867 assert_eq!(
1868 best_bid.price, price,
1869 "Best bid price should match original order price"
1870 );
1871 } else {
1872 panic!("Should still have a best bid after partial fill");
1873 }
1874 }
1875
1876 #[test]
1877 fn test_partial_fill_conservation() {
1878 let book = Arc::new(OrderBook::new("TEST-USD".into(), dec!(0.01), dec!(0.001)));
1879
1880 let buy_order = Order {
1882 id: 1,
1883 symbol: "TEST-USD".into(),
1884 side: OrderSide::Buy,
1885 order_type: OrderType::Limit,
1886 price: dec!(100),
1887 quantity: dec!(150),
1888 remaining_quantity: dec!(150),
1889 time_in_force: TimeInForce::GTC,
1890 timestamp_ns: 1000000000,
1891 queue_position: QueuePosition::RiskAverse,
1892 };
1893
1894 let sell_order = Order {
1895 id: 2,
1896 symbol: "TEST-USD".into(),
1897 side: OrderSide::Sell,
1898 order_type: OrderType::Limit,
1899 price: dec!(100),
1900 quantity: dec!(100),
1901 remaining_quantity: dec!(100),
1902 time_in_force: TimeInForce::GTC,
1903 timestamp_ns: 1000000000,
1904 queue_position: QueuePosition::RiskAverse,
1905 };
1906
1907 let original_buy_qty = buy_order.quantity;
1908 let original_sell_qty = sell_order.quantity;
1909
1910 let engine = DefaultMatchingEngine::new(book, QueueModel::FIFO, true, false);
1913
1914 let buy_execs = engine.submit_order(buy_order);
1916 let sell_execs = engine.submit_order(sell_order);
1917
1918 let mut all_execs = Vec::new();
1922 all_execs.extend(buy_execs.iter());
1923 all_execs.extend(sell_execs.iter());
1924
1925 let total_buy_exec: Decimal = all_execs
1926 .iter()
1927 .filter(|e| e.order_id == 1) .map(|e| e.exec_quantity)
1929 .sum();
1930 let total_sell_exec: Decimal = all_execs
1931 .iter()
1932 .filter(|e| e.order_id == 2) .map(|e| e.exec_quantity)
1934 .sum();
1935
1936 assert!(
1938 total_buy_exec <= original_buy_qty,
1939 "Buy executions cannot exceed original quantity"
1940 );
1941 assert!(
1942 total_sell_exec <= original_sell_qty,
1943 "Sell executions cannot exceed original quantity"
1944 );
1945
1946 assert_eq!(
1948 total_buy_exec, total_sell_exec,
1949 "Buy and sell execution quantities should match - this ensures conservation"
1950 );
1951
1952 let expected_execution = original_buy_qty.min(original_sell_qty);
1954 assert_eq!(
1955 total_buy_exec, expected_execution,
1956 "Total execution should equal minimum of buy/sell quantities"
1957 );
1958 }
1959}