rusty_backtest/
matching.rs

1//! L2 Matching Engine - Realistic order matching with queue position modeling
2//!
3//! Provides accurate L2 order matching simulation including:
4//! - FIFO queue position tracking
5//! - Partial fill support
6//! - Realistic latency simulation
7//! - Multiple queue models (RiskAverse, Probabilistic, L3FIFO)
8//!
9//! # Lock Ordering Convention
10//!
11//! IMPORTANT: To prevent deadlocks, always acquire locks in the following order:
12//! 1. order_map
13//! 2. bid_orders or ask_orders
14//!
15//! Never acquire bid_orders/ask_orders before order_map when both are needed.
16
17use 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/// Order information for matching
29///
30/// Cache-aligned for efficient processing in the matching engine
31#[repr(align(64))]
32#[derive(Debug, Clone)]
33pub struct Order {
34    /// Unique order identifier
35    pub id: u64,
36    /// Trading symbol/instrument
37    pub symbol: SmartString,
38    /// Order side (buy or sell)
39    pub side: OrderSide,
40    /// Type of order (market, limit, post-only)
41    pub order_type: OrderType,
42    /// Limit price (0 for market orders)
43    pub price: Decimal,
44    /// Original order quantity
45    pub quantity: Decimal,
46    /// Remaining unfilled quantity
47    pub remaining_quantity: Decimal,
48    /// Time in force constraint
49    pub time_in_force: TimeInForce,
50    /// Order creation timestamp in nanoseconds
51    pub timestamp_ns: u64,
52    /// Queue position model for execution priority
53    pub queue_position: QueuePosition,
54}
55
56/// Order side
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum OrderSide {
59    /// Buy order (bid side)
60    Buy,
61    /// Sell order (ask side)
62    Sell,
63}
64
65/// Order type
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum OrderType {
68    /// Market order - executes immediately at best available price
69    Market,
70    /// Limit order - executes at specified price or better
71    Limit,
72    /// Post-only order - only adds liquidity, rejected if it would cross
73    PostOnly,
74}
75
76/// Time in force
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum TimeInForce {
79    /// Good Till Cancel - remains active until filled or cancelled
80    GTC,
81    /// Good Till Crossing - post-only order cancelled if it would cross
82    GTX,
83    /// Immediate or Cancel - fill what's possible immediately, cancel remainder
84    IOC,
85    /// Fill or Kill - fill entire order immediately or cancel all
86    FOK,
87}
88
89/// Queue position information
90#[derive(Debug, Clone)]
91pub enum QueuePosition {
92    /// Always at front of queue (optimistic)
93    RiskAverse,
94    /// FIFO position with quantity ahead
95    FIFO {
96        /// Position index in the queue (0 = front)
97        position: usize,
98        /// Total quantity ahead in the queue
99        quantity_ahead: Decimal,
100    },
101    /// Probabilistic fill based on queue depth
102    Probabilistic {
103        /// Probability of execution (0.0 to 1.0)
104        probability: Decimal,
105        /// Ratio of order size to total depth
106        depth_ratio: Decimal,
107    },
108}
109
110/// Order execution result
111///
112/// Cache-aligned for efficient batch processing
113#[repr(align(64))]
114#[derive(Debug, Clone)]
115pub struct Execution {
116    /// ID of the executed order
117    pub order_id: u64,
118    /// Execution price
119    pub exec_price: Decimal,
120    /// Quantity executed in this fill
121    pub exec_quantity: Decimal,
122    /// Remaining quantity after this execution
123    pub remaining_quantity: Decimal,
124    /// Whether this order was a maker (added liquidity)
125    pub is_maker: bool,
126    /// Execution timestamp in nanoseconds
127    pub timestamp_ns: u64,
128}
129
130/// Information about a matched order during execution
131#[derive(Debug, Clone)]
132struct MatchInfo {
133    order_id: u64,
134    exec_quantity: Decimal,
135    remaining_quantity: Decimal,
136}
137
138/// L2 Matching Engine with const generic order queue sizing
139///
140/// # Type Parameters
141/// - `N`: Maximum number of orders per price level (default: 8)
142///   Optimized for typical HFT scenarios with multiple orders at each price level
143pub struct MatchingEngine<const N: usize = 8> {
144    /// Order book
145    book: Arc<OrderBook>,
146    /// Active limit orders by price level
147    bid_orders: Arc<RwLock<FxHashMap<i64, SmallVec<[Order; N]>>>>,
148    ask_orders: Arc<RwLock<FxHashMap<i64, SmallVec<[Order; N]>>>>,
149    /// Order lookup by ID
150    order_map: Arc<RwLock<FxHashMap<u64, Order>>>,
151    /// Queue model type
152    queue_model: QueueModel,
153    /// Allow partial fills
154    allow_partial_fills: bool,
155    /// Self-trade prevention
156    _self_trade_prevention: bool,
157    /// Market impact model
158    market_impact: MarketImpact,
159    /// Conservative execution parameters
160    conservative_params: ConservativeParams,
161    /// Cumulative market impact by symbol
162    cumulative_impact: Arc<RwLock<FxHashMap<SmartString, Decimal>>>,
163}
164
165/// Queue model types
166#[derive(Debug, Clone, Copy)]
167pub enum QueueModel {
168    /// Always at front (optimistic) - assumes immediate execution at the best price
169    RiskAverse,
170    /// True FIFO queue position - tracks actual position in the order queue
171    FIFO,
172    /// Probabilistic based on depth - execution probability based on order book depth
173    Probabilistic,
174}
175
176/// Market impact model for limit orders
177#[derive(Debug, Clone)]
178pub struct MarketImpact {
179    /// Linear impact coefficient (basis points per unit size)
180    pub linear_impact_bps: f64,
181    /// Temporary impact decay rate (per second)
182    pub temp_impact_decay: f64,
183    /// Permanent impact ratio (0.0 to 1.0)
184    pub permanent_impact_ratio: f64,
185    /// Impact asymmetry factor (>1.0 means sells impact more)
186    pub impact_asymmetry: f64,
187    /// Conservative spread widening factor
188    pub spread_widening_factor: f64,
189}
190
191impl Default for MarketImpact {
192    fn default() -> Self {
193        Self {
194            linear_impact_bps: 0.5,      // 0.5 bps per unit
195            temp_impact_decay: 0.1,      // 10% decay per second
196            permanent_impact_ratio: 0.3, // 30% of impact is permanent
197            impact_asymmetry: 1.2,       // Sells impact 20% more
198            spread_widening_factor: 1.1, // 10% wider spread on impact
199        }
200    }
201}
202
203/// Conservative execution parameters
204#[derive(Debug, Clone)]
205pub struct ConservativeParams {
206    /// Adverse selection probability (0.0 to 1.0)
207    pub adverse_selection_prob: f64,
208    /// Hidden liquidity ratio (fraction that trades before visible)
209    pub hidden_liquidity_ratio: f64,
210    /// Front-running probability for retail orders
211    pub front_run_probability: f64,
212    /// Worst-case queue position bias
213    pub queue_position_penalty: f64,
214    /// Slippage bias in basis points
215    pub slippage_bias_bps: f64,
216}
217
218impl Default for ConservativeParams {
219    fn default() -> Self {
220        Self {
221            adverse_selection_prob: 0.15, // 15% chance of adverse selection
222            hidden_liquidity_ratio: 0.2,  // 20% hidden liquidity
223            front_run_probability: 0.1,   // 10% chance of being front-run
224            queue_position_penalty: 0.3,  // 30% worse queue position
225            slippage_bias_bps: 0.2,       // 0.2 bps adverse slippage
226        }
227    }
228}
229
230impl ConservativeParams {
231    /// Create non-conservative params (all zeros)
232    #[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    /// Calculate queue position for an order based on the queue model
246    ///
247    /// This helper eliminates duplicate logic between Buy and Sell order processing
248    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        // Calculate quantity ahead for FIFO model
255        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, // Not needed for other models
260        };
261
262        // Set queue position based on model
263        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, // Will be updated when added
270                    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                    // Empty level - order has maximum probability of execution
280                    // since there's no queue ahead
281                    (Decimal::ZERO, Decimal::ONE)
282                };
283                order.queue_position = QueuePosition::Probabilistic {
284                    probability,
285                    depth_ratio,
286                };
287            }
288        }
289    }
290    /// Create a new L2 matching engine
291    #[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    /// Create with custom conservative parameters
313    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    /// Submit a new order
336    #[must_use]
337    pub fn submit_order(&self, mut order: Order) -> SmallExecutionVec<Execution> {
338        let mut executions = SmallExecutionVec::with_capacity(EXECUTION_CAPACITY);
339
340        // Basic validation: reject orders with invalid quantities
341        // For limit orders, also reject non-positive prices
342        if order.quantity <= Decimal::ZERO {
343            return executions; // Return empty executions for invalid orders
344        }
345
346        // Reject negative prices for limit orders (market orders can have price = 0)
347        if matches!(order.order_type, OrderType::Limit | OrderType::PostOnly)
348            && order.price <= Decimal::ZERO
349        {
350            return executions; // Return empty executions for invalid limit orders
351        }
352
353        // Calculate and apply market impact for limit orders
354        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                // Check if order crosses the spread
365                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                        // Post-only order would cross - reject it
385                        return executions;
386                    }
387                    // Process as taker
388                    executions = self.process_crossing_limit_order(&mut order);
389                }
390
391                // Add remaining quantity to book if any
392                if order.remaining_quantity > Decimal::ZERO {
393                    self.add_limit_order(order);
394                }
395            }
396        }
397
398        executions
399    }
400
401    /// Cancel an order
402    #[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                        // Calculate new total quantity for this price level
414                        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                            // Remove bid level from OrderBook
420                            self.book
421                                .update_bid(order.price, Decimal::ZERO, 0, order.timestamp_ns);
422                        } else {
423                            // Update OrderBook with new quantity
424                            self.book.update_bid(
425                                order.price,
426                                new_total_qty,
427                                orders.len() as u32,
428                                order.timestamp_ns,
429                            );
430
431                            // Update queue positions after cancellation
432                            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                        // Calculate new total quantity for this price level
453                        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                            // Remove ask level from OrderBook
459                            self.book
460                                .update_ask(order.price, Decimal::ZERO, 0, order.timestamp_ns);
461                        } else {
462                            // Update OrderBook with new quantity
463                            self.book.update_ask(
464                                order.price,
465                                new_total_qty,
466                                orders.len() as u32,
467                                order.timestamp_ns,
468                            );
469
470                            // Update queue positions after cancellation
471                            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    /// Process market order
494    fn process_market_order(&self, order: &mut Order) -> SmallExecutionVec<Execution> {
495        let mut executions = SmallExecutionVec::with_capacity(EXECUTION_CAPACITY);
496
497        // Calculate market impact for market orders
498        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                // Buy from asks
505                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                        // Revert if can't fill completely
525                        order.remaining_quantity = order.quantity;
526                        return SmallVec::new();
527                    }
528                }
529            }
530            OrderSide::Sell => {
531                // Sell to bids
532                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                    // Apply market impact to execution price for sell orders (price decreases)
542                    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                        // Revert if can't fill completely
556                        order.remaining_quantity = order.quantity;
557                        return SmallVec::new();
558                    }
559                }
560            }
561        }
562
563        // Check FOK constraint
564        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    /// Process limit order that crosses the spread
573    /// Create taker execution for incoming order
574    #[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    /// Create maker executions for matched orders
591    #[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    /// Process crossing limit order with price levels
608    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            // Create taker execution
629            executions.push(Self::create_taker_execution(order, level.price, exec_qty));
630
631            // Consume liquidity and create maker executions
632            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    /// Apply time-in-force constraints to order and executions
646    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                // Cancel remaining
653                order.remaining_quantity = Decimal::ZERO;
654                executions
655            }
656            TimeInForce::FOK => {
657                if order.remaining_quantity > Decimal::ZERO {
658                    // Can't fill completely - cancel all
659                    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    /// Consume liquidity from a price level due to execution
695    /// Returns information about which orders were matched
696    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        // IMPORTANT: Always acquire locks in consistent order to prevent deadlocks
706        // Order: order_map -> bid_orders/ask_orders
707        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    /// Helper function to consume liquidity from a specific price level
738    #[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            // Consume from orders in FIFO order
754            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                // Record the match
765                matches.push(MatchInfo {
766                    order_id: order.id,
767                    exec_quantity: consumed_from_order,
768                    remaining_quantity: order.remaining_quantity,
769                });
770
771                // Update order in order map
772                if let Some(map_order) = order_map.get_mut(&order.id) {
773                    map_order.remaining_quantity = order.remaining_quantity;
774                }
775
776                // Mark for removal if fully filled
777                if order.remaining_quantity <= Decimal::ZERO {
778                    orders_to_remove.push(idx);
779                }
780            }
781
782            // Remove fully filled orders (in reverse order to preserve indices)
783            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            // Update OrderBook level
789            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                // Remove level if no quantity remains
800                update_book(&self.book, price, Decimal::ZERO, 0, timestamp_ns);
801                orders_map.remove(&tick);
802            }
803        }
804        matches
805    }
806
807    /// Add limit order to book
808    fn add_limit_order(&self, mut order: Order) {
809        let tick = self.book.price_to_tick(order.price);
810
811        // Apply conservative queue position adjustments early
812        self.apply_conservative_queue_position(&mut order);
813
814        // IMPORTANT: Always acquire locks in consistent order to prevent deadlocks
815        // Order: order_map -> bid_orders/ask_orders
816        let mut order_map = self.order_map.write();
817
818        // Assign queue position based on model and add to appropriate order book
819        match order.side {
820            OrderSide::Buy => {
821                let mut bid_orders = self.bid_orders.write();
822
823                // Calculate queue position using helper function
824                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                // Add to order map first
832                order_map.insert(order.id, order.clone());
833
834                // Add to price level
835                let orders = bid_orders.entry(tick).or_default();
836
837                // Update FIFO position
838                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                // Update OrderBook level so crossing orders can see this liquidity
848                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                // Calculate queue position using helper function
860                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                // Add to order map first
868                order_map.insert(order.id, order.clone());
869
870                // Add to price level
871                let orders = ask_orders.entry(tick).or_default();
872
873                // Update FIFO position
874                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                // Update OrderBook level so crossing orders can see this liquidity
884                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    /// Process a trade event and check for fills
896    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        // IMPORTANT: Always acquire locks in consistent order to prevent deadlocks
907        // Order: order_map -> bid_orders/ask_orders
908        let mut order_map = self.order_map.write();
909
910        match side {
911            OrderSide::Buy => {
912                // Buy trade - check sell orders
913                let mut ask_orders = self.ask_orders.write();
914
915                // Process orders at or below trade price
916                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                        // Sort orders by queue position for FIFO processing
930                        if let QueueModel::FIFO = self.queue_model {
931                            // Sort by FIFO position for proper queue order
932                            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, // Process all orders in FIFO order after sorting
950                                QueuePosition::Probabilistic { probability, .. } => {
951                                    // Simple probability check
952                                    rand::random::<f64>() < probability.to_f64().unwrap_or(f64::NAN)
953                                }
954                            };
955
956                            if should_fill {
957                                // Check for adverse selection
958                                if self.check_adverse_selection() {
959                                    // Adverse selection - skip this fill
960                                    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                                // Apply slippage to execution price
968                                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                        // Remove filled orders
987                        for idx in filled_indices.into_iter().rev() {
988                            orders.remove(idx);
989                        }
990
991                        // Update queue positions
992                        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                // Sell trade - check buy orders
1013                let mut bid_orders = self.bid_orders.write();
1014
1015                // Process orders at or above trade price
1016                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                        // Sort orders by queue position for FIFO processing
1030                        if let QueueModel::FIFO = self.queue_model {
1031                            // Sort by FIFO position for proper queue order
1032                            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, // Process all orders in FIFO order after sorting
1050                                QueuePosition::Probabilistic { probability, .. } => {
1051                                    // Simple probability check
1052                                    rand::random::<f64>() < probability.to_f64().unwrap_or(f64::NAN)
1053                                }
1054                            };
1055
1056                            if should_fill {
1057                                // Check for adverse selection
1058                                if self.check_adverse_selection() {
1059                                    // Adverse selection - skip this fill
1060                                    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                                // Apply slippage to execution price
1068                                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                        // Remove filled orders
1087                        for idx in filled_indices.into_iter().rev() {
1088                            orders.remove(idx);
1089                        }
1090
1091                        // Update queue positions
1092                        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    /// Get active orders at a price level
1117    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    /// Clear all orders
1136    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    /// Calculate market impact for a limit order
1144    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) // Assume 100 unit baseline
1154        };
1155
1156        // Calculate base impact
1157        let mut impact_bps = self.market_impact.linear_impact_bps * order_size_ratio;
1158
1159        // Apply asymmetry for sells
1160        if matches!(order.side, OrderSide::Sell) {
1161            impact_bps *= self.market_impact.impact_asymmetry;
1162        }
1163
1164        // Add cumulative impact
1165        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; // 10% of cumulative impact
1173
1174        impact_bps
1175    }
1176
1177    /// Apply market impact to the order book
1178    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        // Update cumulative impact
1182        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        // Add permanent impact
1188        *cumulative +=
1189            Decimal::from_f64_retain(impact_bps * self.market_impact.permanent_impact_ratio)
1190                .unwrap_or(Decimal::ZERO);
1191
1192        // Apply temporary impact decay (simplified - in real system would be time-based)
1193        *cumulative *= Decimal::from_f64_retain(1.0 - self.market_impact.temp_impact_decay)
1194            .unwrap_or(Decimal::ONE);
1195
1196        // Widen the spread conservatively
1197        match order.side {
1198            OrderSide::Buy => {
1199                // Push ask prices up
1200                if let Some(best_ask) = self.book.best_ask() {
1201                    let _new_price = best_ask.price * (dec!(1) + impact_decimal);
1202                    // This is simplified - in reality would shift entire book
1203                }
1204            }
1205            OrderSide::Sell => {
1206                // Push bid prices down
1207                if let Some(best_bid) = self.book.best_bid() {
1208                    let _new_price = best_bid.price * (dec!(1) - impact_decimal);
1209                    // This is simplified - in reality would shift entire book
1210                }
1211            }
1212        }
1213    }
1214
1215    /// Apply conservative queue position adjustments
1216    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                // Apply worst-case penalty
1223                let penalty = self.conservative_params.queue_position_penalty;
1224                *quantity_ahead *= Decimal::from_f64_retain(1.0 + penalty).unwrap_or(Decimal::ONE);
1225
1226                // Account for hidden liquidity
1227                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                // Reduce fill probability conservatively
1232                *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                // Apply front-running penalty
1240                *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    /// Check for adverse selection on fill
1249    fn check_adverse_selection(&self) -> bool {
1250        rand::random::<f64>() < self.conservative_params.adverse_selection_prob
1251    }
1252
1253    /// Apply slippage to execution price
1254    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
1266// Type aliases for backward compatibility
1267/// Default matching engine with 8 orders per price level
1268pub type DefaultMatchingEngine = MatchingEngine<8>;
1269/// Matching engine with 4 orders per price level (low liquidity)
1270pub type MatchingEngine4 = MatchingEngine<4>;
1271/// Matching engine with 16 orders per price level (medium liquidity)
1272pub type MatchingEngine16 = MatchingEngine<16>;
1273/// Matching engine with 32 orders per price level (high liquidity)
1274pub 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        // Add some liquidity
1291        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        // Create engine with minimal market impact for predictable test results
1297        let mut engine =
1298            DefaultMatchingEngine::new(book.clone(), QueueModel::RiskAverse, true, false);
1299        engine.market_impact = MarketImpact {
1300            linear_impact_bps: 0.0, // No market impact for test
1301            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        // Market buy order
1308        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        // Add multiple limit orders at same price
1341        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        // Check queue positions
1359        let orders = engine.get_orders_at_level(OrderSide::Buy, dec!(50000));
1360        assert_eq!(orders.len(), 3);
1361
1362        // Verify FIFO positions
1363        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    // Property-based tests using proptest
1378
1379    // Strategy for generating valid prices
1380    fn price_strategy() -> impl Strategy<Value = Decimal> {
1381        (1u64..=1_000_000u64).prop_map(|v| Decimal::from(v) / dec!(100))
1382    }
1383
1384    // Strategy for generating valid quantities
1385    fn quantity_strategy() -> impl Strategy<Value = Decimal> {
1386        (1u64..=10_000_000u64).prop_map(|v| Decimal::from(v) / dec!(1000))
1387    }
1388
1389    // Strategy for generating order IDs
1390    fn order_id_strategy() -> impl Strategy<Value = u64> {
1391        1u64..=1_000_000u64
1392    }
1393
1394    // Strategy for generating timestamps
1395    fn timestamp_strategy() -> impl Strategy<Value = u64> {
1396        1_000_000_000u64..=10_000_000_000u64
1397    }
1398
1399    // Strategy for generating OrderSide
1400    fn order_side_strategy() -> impl Strategy<Value = OrderSide> {
1401        prop_oneof![Just(OrderSide::Buy), Just(OrderSide::Sell),]
1402    }
1403
1404    // Strategy for generating OrderType
1405    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    // Strategy for generating TimeInForce
1414    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    // Strategy for generating an Order
1424    fn order_strategy() -> impl Strategy<Value = Order> {
1425        (
1426            order_id_strategy(),
1427            "[A-Z]{3}-[A-Z]{3,4}", // symbol pattern
1428            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; // Ensure they can match
1479            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            // Submit orders
1488            let buy_execs = engine.submit_order(buy);
1489            let sell_execs = engine.submit_order(sell);
1490
1491            // Total executed should not exceed minimum of the two orders
1492            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); // Each side counts
1498
1499            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            // Add buy orders at different price levels
1517            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); // 3 price levels
1523                order.timestamp_ns = (i * 1000) as u64;
1524
1525                // Update book
1526                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            // Add a sell order to trigger matches
1533            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), // Large enough
1540                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            // Verify price priority: higher prices should execute first
1549            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            // Add limit orders
1572            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            // Submit market order
1591            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            // Properties to verify
1607            let total_executed: Decimal = executions.iter()
1608                .map(|e| e.exec_quantity)
1609                .sum();
1610
1611            // Market order should execute up to available liquidity
1612            prop_assert!(total_executed <= market_qty,
1613                "Cannot execute more than market order quantity");
1614
1615            // All executions should have valid prices
1616            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            // Add orders at same price level to test FIFO
1635            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; // Ensure clear time ordering
1645
1646                order_ids.push(order.id);
1647
1648                // Update book
1649                book.update_bid(order.price, order.quantity, 1, order.timestamp_ns);
1650
1651                let _ = engine.submit_order(order);
1652            }
1653
1654            // Submit sell order to match
1655            let total_buy_qty: Decimal = order_ids.iter()
1656                .take(order_count)
1657                .map(|_| dec!(100)) // Assuming a default quantity
1658                .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            // With FIFO, executions should follow submission order
1676            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(), // Add missing MarketImpact parameter
1707                conservative_params,
1708            );
1709
1710            // Add orders
1711            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            // Conservative mode should affect execution (though exact behavior is probabilistic)
1729            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        // Use the exact same values as the failing proptest
1741        let initial_qty = dec!(3.085);
1742
1743        // Place a buy order
1744        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        // Use the exact same calculation as the failing proptest
1780        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        // Validate execution results
1813        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        // Check that executions have valid quantities
1824        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        // Verify that each execution has the correct quantity (should be equal to fill_qty)
1836        // There are 2 executions: one for the sell order and one for the matched buy order
1837        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            // Assert that remaining quantity is correct (within tolerance for decimal precision)
1858            let tolerance = dec!(0.000002); // Tolerance for decimal precision differences
1859            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        // Create orders for partial fill scenario
1881        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        // Don't pre-populate the book - let orders create liquidity naturally
1911
1912        let engine = DefaultMatchingEngine::new(book, QueueModel::FIFO, true, false);
1913
1914        // Submit orders
1915        let buy_execs = engine.submit_order(buy_order);
1916        let sell_execs = engine.submit_order(sell_order);
1917
1918        // Test executes properly: both orders should match for 100 qty
1919
1920        // Collect all executions and group by order ID
1921        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) // Buy order ID
1928            .map(|e| e.exec_quantity)
1929            .sum();
1930        let total_sell_exec: Decimal = all_execs
1931            .iter()
1932            .filter(|e| e.order_id == 2) // Sell order ID
1933            .map(|e| e.exec_quantity)
1934            .sum();
1935
1936        // Conservation properties
1937        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        // Both orders should execute the same amount (conservation of quantity)
1947        assert_eq!(
1948            total_buy_exec, total_sell_exec,
1949            "Buy and sell execution quantities should match - this ensures conservation"
1950        );
1951
1952        // The executed amount should be the minimum of the two original quantities
1953        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}