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