rust_order_book/
book.rs

1//! Core module for the OrderBook engine.
2//!
3//! This module defines the [`OrderBook`] struct, which provides the main interface
4//! for submitting, canceling, modifying, and querying orders.
5//!
6//! Use [`OrderBookBuilder`](crate::OrderBookBuilder) to create a new instance.
7//!
8//! # Example
9//! ```rust
10//! use rust_order_book::{OrderBookBuilder, Side, MarketOrderOptions};
11//!
12//! let mut ob = OrderBookBuilder::new("BTCUSD").with_journaling(true).build();
13//!
14//! let result = ob.market(MarketOrderOptions {
15//!     side: Side::Buy,
16//!     quantity: 10_000,
17//! });
18//! ```
19use std::collections::{BTreeMap, HashMap};
20use std::fmt;
21
22use crate::enums::{JournalOp, OrderOptions};
23use crate::journal::Snapshot;
24use crate::order::{OrderId, Price, Quantity};
25use crate::report::ExecutionReportParams;
26use crate::utils::{current_timestamp_millis, safe_add, safe_sub};
27use crate::{
28    error::{make_error, ErrorType, Result},
29    journal::JournalLog,
30    order::{LimitOrder, LimitOrderOptions, MarketOrder, MarketOrderOptions},
31    {OrderStatus, OrderType, Side, TimeInForce},
32};
33use crate::{ExecutionReport, FillReport};
34use std::collections::VecDeque;
35
36/// Configuration options for initializing a new [`OrderBook`].
37///
38/// # Fields
39/// - `journaling`: If `true`, the order book will return a journal log for each operations.
40///   Defaults to `false`.
41/// - `snapshot`: A previously captured [`Snapshot`] representing the full state
42///   of an order book at a given point in time.
43/// - `replay_logs`: A vector of [`JournalLog`] entries to replay. Logs should ideally be in
44///   chronological order (`op_id` ascending), but `replay_logs` will sort them internally.
45#[derive(Debug, Clone, Default)]
46pub struct OrderBookOptions {
47    pub journaling: bool,
48    pub snapshot: Option<Snapshot>,
49    pub replay_logs: Option<Vec<JournalLog>>,
50}
51
52#[derive(Debug, PartialEq)]
53pub struct Depth {
54    pub asks: Vec<(Price, Quantity)>, // (price, volume)
55    pub bids: Vec<(Price, Quantity)>, // (price, volume)
56}
57
58/// A limit order book implementation with support for market orders,
59/// limit orders, cancellation, modification and real-time depth.
60///
61/// Use [`crate::OrderBookBuilder`] to create an instance with optional features
62/// like journaling or snapshot restoration.
63pub struct OrderBook {
64    pub(crate) last_op: u64,
65    pub(crate) symbol: String,
66    pub(crate) next_order_id: OrderId,
67    pub(crate) orders: HashMap<OrderId, LimitOrder>,
68    pub(crate) asks: BTreeMap<u64, VecDeque<OrderId>>,
69    pub(crate) bids: BTreeMap<u64, VecDeque<OrderId>>,
70    pub(crate) journaling: bool,
71}
72
73impl OrderBook {
74    /// Creates a new `OrderBook` instance with the given symbol and options.
75    ///
76    /// Prefer using [`crate::OrderBookBuilder`] for clarity and flexibility.
77    ///
78    /// # Parameters
79    /// - `symbol`: Market symbol (e.g., `"BTCUSD"`)
80    /// - `opts`: Configuration options (e.g., journaling, snapshot)
81    ///
82    /// # Example
83    /// ```
84    /// use rust_order_book::{OrderBook, OrderBookOptions};
85    /// let ob = OrderBook::new("BTCUSD", OrderBookOptions::default());
86    /// ```
87    pub fn new(symbol: &str, opts: OrderBookOptions) -> Self {
88        Self {
89            symbol: symbol.to_string(),
90            last_op: 0,
91            next_order_id: 0,
92            orders: HashMap::with_capacity(100_000),
93            asks: BTreeMap::new(),
94            bids: BTreeMap::new(),
95            journaling: opts.journaling,
96        }
97    }
98
99    /// Get the symbol of this order book
100    pub fn symbol(&self) -> &str {
101        &self.symbol
102    }
103
104    /// Executes a market order against the order book.
105    ///
106    /// The order will immediately match with the best available opposite orders
107    /// until the quantity is filled or the book is exhausted.
108    ///
109    /// # Parameters
110    /// - `options`: A [`MarketOrderOptions`] struct specifying the side and size.
111    ///
112    /// # Returns
113    /// An [`ExecutionReport`] with fill information and remaining quantity, if any.
114    ///
115    /// # Errors
116    /// Returns `Err` if the input is invalid (e.g., size is zero).
117    pub fn market(&mut self, options: MarketOrderOptions) -> Result<ExecutionReport> {
118        self.validate_market_order(&options)?;
119
120        let mut order = MarketOrder::new(self.new_order_id(), options);
121        let mut report = ExecutionReport::new(ExecutionReportParams {
122            id: order.id,
123            order_type: OrderType::Market,
124            side: order.side,
125            quantity: order.remaining_qty,
126            status: order.status,
127            time_in_force: None,
128            price: None,
129            post_only: false,
130        });
131
132        let mut fills = Vec::new();
133        order.remaining_qty = match order.side {
134            Side::Buy => self.match_with_asks(order.remaining_qty, &mut fills, None),
135            Side::Sell => self.match_with_bids(order.remaining_qty, &mut fills, None),
136        };
137        order.executed_qty = safe_sub(order.orig_qty, order.remaining_qty);
138        order.status = if order.remaining_qty > 0 {
139            OrderStatus::PartiallyFilled
140        } else {
141            OrderStatus::Filled
142        };
143
144        report.remaining_qty = order.remaining_qty;
145        report.executed_qty = order.executed_qty;
146        report.status = order.status;
147        report.taker_qty = order.executed_qty;
148
149        if self.journaling {
150            self.last_op = safe_add(self.last_op, 1);
151            report.log = Some(JournalLog {
152                op_id: self.last_op,
153                ts: current_timestamp_millis(),
154                op: JournalOp::Market,
155                o: OrderOptions::Market(options),
156            })
157        }
158
159        Ok(report)
160    }
161
162    /// Submits a new limit order to the order book.
163    ///
164    /// The order will be matched partially or fully if opposing liquidity exists,
165    /// otherwise it will rest in the book until matched or canceled.
166    ///
167    /// # Parameters
168    /// - `options`: A [`LimitOrderOptions`] with side, price, size, time-in-force and post_only.
169    ///
170    /// # Returns
171    /// An [`ExecutionReport`] with match information and resting status.
172    ///
173    /// # Errors
174    /// Returns `Err` if the input is invalid.
175    pub fn limit(&mut self, options: LimitOrderOptions) -> Result<ExecutionReport> {
176        self.validate_limit_order(&options)?;
177
178        let mut order = LimitOrder::new(self.new_order_id(), options);
179        let mut report = ExecutionReport::new(ExecutionReportParams {
180            id: order.id,
181            order_type: OrderType::Limit,
182            side: order.side,
183            quantity: order.orig_qty,
184            status: order.status,
185            time_in_force: Some(order.time_in_force),
186            price: Some(order.price), // here order price is Some because we have already validated in validate_limit_order
187            post_only: order.post_only,
188        });
189
190        let mut fills = Vec::new();
191        order.remaining_qty = match order.side {
192            Side::Buy => self.match_with_asks(order.remaining_qty, &mut fills, Some(order.price)),
193            Side::Sell => self.match_with_bids(order.remaining_qty, &mut fills, Some(order.price)),
194        };
195        order.executed_qty = safe_sub(order.orig_qty, order.remaining_qty);
196        order.taker_qty = safe_sub(order.orig_qty, order.remaining_qty);
197        order.maker_qty = order.remaining_qty;
198
199        if order.remaining_qty > 0 {
200            if order.time_in_force == TimeInForce::IOC {
201                // If IOC order was not matched completely so set as canceled
202                // and don't insert the order in the order book
203                order.status = OrderStatus::Canceled;
204            } else {
205                order.status = OrderStatus::PartiallyFilled;
206                self.orders.insert(order.id, order);
207                if order.side == Side::Buy {
208                    self.bids.entry(order.price).or_default().push_back(order.id);
209                } else {
210                    self.asks.entry(order.price).or_default().push_back(order.id);
211                }
212            }
213        } else {
214            order.status = OrderStatus::Filled;
215        }
216
217        report.remaining_qty = order.remaining_qty;
218        report.executed_qty = order.executed_qty;
219        report.taker_qty = order.taker_qty;
220        report.maker_qty = order.maker_qty;
221        report.status = order.status;
222
223        if self.journaling {
224            self.last_op = safe_add(self.last_op, 1);
225            report.log = Some(JournalLog {
226                op_id: self.last_op,
227                ts: current_timestamp_millis(),
228                op: JournalOp::Limit,
229                o: OrderOptions::Limit(options),
230            })
231        }
232
233        Ok(report)
234    }
235
236    /// Cancels an existing order by ID.
237    ///
238    /// # Parameters
239    /// - `id`: UUID of the order to cancel
240    ///
241    /// # Returns
242    /// An [`ExecutionReport`] with order info if successfully canceled.
243    ///
244    /// # Errors
245    /// Returns `Err` if the order is not found.
246    pub fn cancel(&mut self, id: OrderId) -> Result<ExecutionReport> {
247        let mut order = match self.orders.remove(&id) {
248            Some(o) => o,
249            None => return Err(make_error(ErrorType::OrderNotFound)),
250        };
251
252        let book_side = match order.side {
253            Side::Buy => &mut self.bids,
254            Side::Sell => &mut self.asks,
255        };
256
257        if let Some(queue) = book_side.get_mut(&order.price) {
258            if let Some(pos) = queue.iter().position(|x| *x == id) {
259                queue.remove(pos);
260            }
261            if queue.is_empty() {
262                book_side.remove(&order.price);
263            }
264        }
265
266        order.status = OrderStatus::Canceled;
267
268        let mut report = ExecutionReport {
269            order_id: order.id,
270            orig_qty: order.orig_qty,
271            executed_qty: order.executed_qty,
272            remaining_qty: order.remaining_qty,
273            taker_qty: order.taker_qty,
274            maker_qty: order.maker_qty,
275            order_type: order.order_type,
276            side: order.side,
277            price: order.price,
278            status: order.status,
279            time_in_force: order.time_in_force,
280            post_only: order.post_only,
281            fills: Vec::new(),
282            log: None,
283        };
284
285        if self.journaling {
286            self.last_op = safe_add(self.last_op, 1);
287            report.log = Some(JournalLog {
288                op_id: self.last_op,
289                ts: current_timestamp_millis(),
290                op: JournalOp::Cancel,
291                o: OrderOptions::Cancel(order.id),
292            })
293        }
294
295        Ok(report)
296    }
297
298    /// Modifies an existing order by cancelling it and submitting a new one.
299    ///
300    /// This function cancels the existing order with the given ID and replaces it
301    /// with a new one that has the updated price and/or quantity. The new order will
302    /// receive a **new unique ID** and will be placed at the end of the queue,
303    /// losing its original time priority.
304    ///
305    /// # Parameters
306    /// - `id`: UUID of the existing order to modify
307    /// - `price`: Optional new price
308    /// - `quantity`: Optional new quantity
309    ///
310    /// # Returns
311    /// An [`ExecutionReport`] describing the new order created.
312    ///
313    /// # Errors
314    /// Returns `Err` if the order is not found or if the modification parameters are invalid.
315    ///
316    /// # Note
317    /// This is a full replacement: time-priority is reset and the order ID changes.
318    pub fn modify(
319        &mut self,
320        id: OrderId,
321        price: Option<u64>,
322        quantity: Option<u64>,
323    ) -> Result<ExecutionReport> {
324        let old_journaling = self.journaling;
325        // Temporary disable journaling
326        self.journaling = false;
327        let order = match self.cancel(id) {
328            Ok(o) => o,
329            Err(e) => {
330                // Restore previous journaling value before returning
331                self.journaling = old_journaling;
332                return Err(e);
333            }
334        };
335
336        let mut report = match (price, quantity) {
337            (None, Some(quantity)) => self.limit(LimitOrderOptions {
338                side: order.side,
339                quantity,
340                price: order.price,
341                time_in_force: Some(order.time_in_force),
342                post_only: Some(order.post_only),
343            }),
344            (Some(price), None) => self.limit(LimitOrderOptions {
345                side: order.side,
346                quantity: order.remaining_qty,
347                price,
348                time_in_force: Some(order.time_in_force),
349                post_only: Some(order.post_only),
350            }),
351            (Some(price), Some(quantity)) => self.limit(LimitOrderOptions {
352                side: order.side,
353                quantity,
354                price,
355                time_in_force: Some(order.time_in_force),
356                post_only: Some(order.post_only),
357            }),
358            (None, None) => {
359                // Restore previous journaling value before returning
360                self.journaling = old_journaling;
361                return Err(make_error(ErrorType::InvalidPriceOrQuantity));
362            }
363        };
364
365        // Restore previous journaling value
366        self.journaling = old_journaling;
367
368        if let Ok(r) = report.as_mut() {
369            if self.journaling {
370                self.last_op = safe_add(self.last_op, 1);
371                r.log = Some(JournalLog {
372                    op_id: self.last_op,
373                    ts: current_timestamp_millis(),
374                    op: JournalOp::Modify,
375                    o: OrderOptions::Modify { id, price, quantity },
376                });
377            }
378        }
379        report
380    }
381
382    /// Get all orders at a specific price level
383    pub fn get_orders_at_price(&self, price: u64, side: Side) -> Vec<LimitOrder> {
384        let mut orders = Vec::new();
385        let queue = match side {
386            Side::Buy => self.bids.get(&price),
387            Side::Sell => self.asks.get(&price),
388        };
389
390        if let Some(q) = queue {
391            for id in q {
392                if let Some(order) = self.orders.get(id) {
393                    orders.push(*order);
394                }
395            }
396        }
397        orders
398    }
399
400    pub fn get_order(&self, id: OrderId) -> Result<LimitOrder> {
401        match self.orders.get(&id) {
402            Some(o) => Ok(*o),
403            None => Err(make_error(ErrorType::OrderNotFound)),
404        }
405    }
406
407    /// Get the best bid price, if any
408    pub fn best_bid(&self) -> Option<Price> {
409        self.bids.last_key_value().map(|(price, _)| *price)
410    }
411
412    /// Get the best ask price, if any
413    pub fn best_ask(&self) -> Option<Price> {
414        self.asks.first_key_value().map(|(price, _)| *price)
415    }
416
417    /// Get the mid price (average of best bid and best ask)
418    pub fn mid_price(&self) -> Option<Price> {
419        match (self.best_bid(), self.best_ask()) {
420            (Some(bid), Some(ask)) => Some(safe_add(bid, ask) / 2),
421            _ => None,
422        }
423    }
424
425    /// Get the spread (best ask - best bid)
426    pub fn spread(&self) -> Option<Price> {
427        match (self.best_bid(), self.best_ask()) {
428            (Some(bid), Some(ask)) => Some(safe_sub(ask, bid)),
429            _ => None,
430        }
431    }
432
433    /// Creates a complete snapshot of the current order book state.
434    ///
435    /// The snapshot includes all internal data necessary to fully restore the order book:
436    /// - `orders`: a mapping of `OrderId` to `LimitOrder`
437    /// - `bids` and `asks`: BTreeMaps representing the price levels and associated order IDs
438    /// - `last_op`: the ID of the last operation performed
439    /// - `next_order_id`: the next available order ID
440    /// - `ts`: a timestamp representing when the snapshot was taken
441    ///
442    /// This function **does not fail** and can be called at any time.
443    /// It returns a [`Snapshot`] struct, which can later be used with [`OrderBook::restore_snapshot`]
444    /// to recreate the order book state exactly as it was at the moment of the snapshot.
445    pub fn snapshot(&self) -> Snapshot {
446        Snapshot {
447            orders: self.orders.clone(),
448            bids: self.bids.clone(),
449            asks: self.asks.clone(),
450            last_op: self.last_op,
451            next_order_id: self.next_order_id,
452            ts: current_timestamp_millis(),
453        }
454    }
455
456    /// Restores the internal state of this [`OrderBook`] from a given [`Snapshot`].
457    ///
458    /// This replaces any existing orders and it is typically used when reconstructing
459    /// an order book from persistent storage.
460    ///
461    /// # Parameters
462    /// - `snapshot`: The snapshot to load into the order book.
463    pub fn restore_snapshot(&mut self, snapshot: Snapshot) {
464        self.orders = snapshot.orders;
465        self.bids = snapshot.bids;
466        self.asks = snapshot.asks;
467        self.last_op = snapshot.last_op;
468        self.next_order_id = snapshot.next_order_id;
469    }
470
471    /// Replays a sequence of journal logs to reconstruct the order book state.
472    ///
473    /// Each log entry represents a previously executed operation, such as a market order,
474    /// limit order, cancel, or modify. This function applies each operation in order.
475    ///
476    /// # Parameters
477    ///
478    /// - `logs`: A vector of [`JournalLog`] entries to be applied. Logs must be in chronological
479    ///   order to correctly reconstruct the state.
480    ///
481    /// # Returns
482    ///
483    /// Returns `Ok(())` if all operations are successfully applied.
484    /// Returns `Err(OrderBookError)` if any operation fails; the replay stops at the first error.
485    pub fn replay_logs(&mut self, mut logs: Vec<JournalLog>) -> Result<()> {
486        // sort logs by op_id ascending
487        logs.sort_by_key(|log| log.op_id);
488
489        for log in &logs {
490            match &log.o {
491                OrderOptions::Market(opts) => self.market(*opts)?,
492                OrderOptions::Limit(opts) => self.limit(*opts)?,
493                OrderOptions::Cancel(id) => self.cancel(*id)?,
494                OrderOptions::Modify { id, price, quantity } => {
495                    self.modify(*id, *price, *quantity)?
496                }
497            };
498        }
499        Ok(())
500    }
501
502    /// Returns the current depth of the order book.
503    ///
504    /// The depth includes aggregated quantities at each price level
505    /// for both the bid and ask sides.
506    ///
507    /// # Parameters
508    /// - `limit`: Optional maximum number of price levels per side
509    ///
510    /// # Returns
511    /// A [`Depth`] struct containing the order book snapshot.
512    pub fn depth(&self, limit: Option<usize>) -> Depth {
513        let levels = limit.unwrap_or(100);
514        Depth {
515            asks: self.get_asks_prices_and_volume(levels),
516            bids: self.get_bids_prices_and_volume(levels),
517        }
518    }
519
520    fn get_asks_prices_and_volume(&self, levels: usize) -> Vec<(Price, Quantity)> {
521        let mut asks = Vec::with_capacity(levels);
522        for (ask_price, queue) in self.asks.iter() {
523            let volume: u64 = queue
524                .iter()
525                .filter_map(|id| self.orders.get(id))
526                .map(|order| order.remaining_qty)
527                .sum();
528            asks.push((*ask_price, volume));
529        }
530        asks
531    }
532
533    fn get_bids_prices_and_volume(&self, levels: usize) -> Vec<(Price, Quantity)> {
534        let mut bids = Vec::with_capacity(levels);
535        for (bid_price, queue) in self.bids.iter().rev() {
536            let volume: u64 = queue
537                .iter()
538                .filter_map(|id| self.orders.get(id))
539                .map(|order| order.remaining_qty)
540                .sum();
541            bids.push((*bid_price, volume));
542        }
543        bids
544    }
545
546    fn match_with_asks(
547        &mut self,
548        quantity_to_fill: Quantity,
549        fills: &mut Vec<FillReport>,
550        limit_price: Option<Price>,
551    ) -> Quantity {
552        // Early exit if the side is empty
553        if self.asks.is_empty() {
554            return quantity_to_fill;
555        }
556        let mut remaining_qty = quantity_to_fill;
557        let mut filled_prices = Vec::new();
558        for (ask_price, queue) in self.asks.iter_mut() {
559            if remaining_qty == 0 {
560                break;
561            }
562            if let Some(limit_price) = limit_price {
563                if limit_price < *ask_price {
564                    break;
565                }
566            }
567            remaining_qty = Self::process_queue(&mut self.orders, queue, remaining_qty, fills);
568            if queue.is_empty() {
569                filled_prices.push(*ask_price);
570            }
571        }
572        for price in filled_prices {
573            self.asks.remove(&price);
574        }
575        remaining_qty
576    }
577
578    fn match_with_bids(
579        &mut self,
580        quantity_to_fill: Quantity,
581        fills: &mut Vec<FillReport>,
582        limit_price: Option<Price>,
583    ) -> Quantity {
584        // Early exit if the side is empty
585        if self.bids.is_empty() {
586            return quantity_to_fill;
587        }
588        let mut remaining_qty = quantity_to_fill;
589        let mut filled_prices = Vec::new();
590        for (bid_price, queue) in self.bids.iter_mut().rev() {
591            if remaining_qty == 0 {
592                break;
593            }
594            if let Some(limit_price) = limit_price {
595                if limit_price > *bid_price {
596                    break;
597                }
598            }
599            remaining_qty = Self::process_queue(&mut self.orders, queue, remaining_qty, fills);
600            if queue.is_empty() {
601                filled_prices.push(*bid_price);
602            }
603        }
604        for price in filled_prices {
605            self.bids.remove(&price);
606        }
607        remaining_qty
608    }
609
610    fn process_queue(
611        orders: &mut HashMap<OrderId, LimitOrder>,
612        order_queue: &mut VecDeque<OrderId>,
613        remaining_qty: Quantity,
614        fills: &mut Vec<FillReport>,
615    ) -> Quantity {
616        let mut quantity_left = remaining_qty;
617        while !order_queue.is_empty() && quantity_left > 0 {
618            let Some(head_order_uuid) = order_queue.front() else { break };
619            let Some(mut head_order) = orders.remove(head_order_uuid) else { break };
620
621            if quantity_left < head_order.remaining_qty {
622                head_order.remaining_qty = safe_sub(head_order.remaining_qty, quantity_left);
623                head_order.executed_qty = safe_add(head_order.executed_qty, quantity_left);
624                head_order.status = OrderStatus::PartiallyFilled;
625                fills.push(FillReport {
626                    order_id: head_order.id,
627                    price: head_order.price,
628                    quantity: quantity_left,
629                    status: head_order.status,
630                });
631                orders.insert(head_order.id, head_order);
632
633                quantity_left = 0;
634            } else {
635                order_queue.pop_front();
636                quantity_left = safe_sub(quantity_left, head_order.remaining_qty);
637
638                // let mut canceled_order = self.cancel_order(head_order_uuid, order_queue);
639                head_order.executed_qty =
640                    safe_add(head_order.executed_qty, head_order.remaining_qty);
641                head_order.remaining_qty = 0;
642                head_order.status = OrderStatus::Filled;
643                fills.push(FillReport {
644                    order_id: head_order.id,
645                    price: head_order.price,
646                    quantity: head_order.executed_qty,
647                    status: head_order.status,
648                });
649            }
650        }
651        quantity_left
652    }
653
654    fn validate_market_order(&self, options: &MarketOrderOptions) -> Result<()> {
655        if options.quantity == 0 {
656            return Err(make_error(ErrorType::InvalidQuantity));
657        }
658        if (options.side == Side::Buy && self.asks.is_empty())
659            || (options.side == Side::Sell && self.bids.is_empty())
660        {
661            return Err(make_error(ErrorType::OrderBookEmpty));
662        }
663        Ok(())
664    }
665
666    fn validate_limit_order(&self, options: &LimitOrderOptions) -> Result<()> {
667        if options.quantity == 0 {
668            return Err(make_error(ErrorType::InvalidQuantity));
669        }
670        if options.price == 0 {
671            return Err(make_error(ErrorType::InvalidPrice));
672        }
673        let time_in_force = options.time_in_force.unwrap_or(TimeInForce::GTC);
674        if time_in_force == TimeInForce::FOK
675            && !self.limit_order_is_fillable(options.side, options.quantity, options.price)
676        {
677            return Err(make_error(ErrorType::OrderFOK));
678        }
679        if options.post_only.unwrap_or(false) {
680            let crosses = match options.side {
681                Side::Buy => {
682                    if let Some((best_ask, _)) = self.asks.first_key_value() {
683                        options.price >= *best_ask
684                    } else {
685                        false
686                    }
687                }
688                Side::Sell => {
689                    if let Some((best_bid, _)) = self.bids.last_key_value() {
690                        options.price <= *best_bid
691                    } else {
692                        false
693                    }
694                }
695            };
696
697            if crosses {
698                return Err(make_error(ErrorType::OrderPostOnly));
699            }
700        }
701        Ok(())
702    }
703
704    fn limit_order_is_fillable(&self, side: Side, quantity: u64, price: u64) -> bool {
705        if side == Side::Buy {
706            self.limit_buy_order_is_fillable(quantity, price)
707        } else {
708            self.limit_sell_order_is_fillable(quantity, price)
709        }
710    }
711
712    fn limit_buy_order_is_fillable(&self, quantity: u64, price: u64) -> bool {
713        let mut cumulative_qty = 0;
714        for (ask_price, queue) in self.asks.iter() {
715            if price >= *ask_price && cumulative_qty < quantity {
716                for id in queue.iter() {
717                    if let Some(order) = self.orders.get(id) {
718                        cumulative_qty = safe_add(cumulative_qty, order.remaining_qty)
719                    }
720                }
721            } else {
722                break;
723            }
724        }
725        cumulative_qty >= quantity
726    }
727
728    fn limit_sell_order_is_fillable(&self, quantity: u64, price: u64) -> bool {
729        let mut cumulative_qty = 0;
730        for (bid_price, queue) in self.bids.iter().rev() {
731            if price <= *bid_price && cumulative_qty < quantity {
732                for id in queue.iter() {
733                    if let Some(order) = self.orders.get(id) {
734                        cumulative_qty = safe_add(cumulative_qty, order.remaining_qty)
735                    }
736                }
737            } else {
738                break;
739            }
740        }
741        cumulative_qty >= quantity
742    }
743
744    fn new_order_id(&mut self) -> OrderId {
745        let id = self.next_order_id;
746        self.next_order_id += 1;
747        id
748    }
749}
750
751impl fmt::Display for OrderBook {
752    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
753        // --- ASK (decrescente) ---
754        for (price, order_ids) in self.asks.iter().rev() {
755            let volume: u64 = order_ids
756                .iter()
757                .filter_map(|id| self.orders.get(id))
758                .map(|order| order.remaining_qty)
759                .sum();
760
761            writeln!(f, "{} -> {}", price, volume)?;
762        }
763
764        writeln!(f, "------------------------------------")?;
765
766        // --- BID (decrescente) ---
767        for (price, order_ids) in self.bids.iter().rev() {
768            let volume: u64 = order_ids
769                .iter()
770                .filter_map(|id| self.orders.get(id))
771                .map(|order| order.remaining_qty)
772                .sum();
773
774            writeln!(f, "{} -> {}", price, volume)?;
775        }
776
777        Ok(())
778    }
779}
780
781#[cfg(test)]
782mod tests;