nautilus_model/orderbook/
own.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! An `OwnBookOrder` for use with tracking own/user orders in L3 order books.
17//! It organizes orders into bid and ask ladders, maintains timestamps for state changes,
18//! and provides various methods for adding, updating, deleting, and querying orders.
19
20use std::{
21    cmp::Ordering,
22    collections::{BTreeMap, HashMap},
23    fmt::{Debug, Display},
24    hash::{Hash, Hasher},
25};
26
27use indexmap::IndexMap;
28use nautilus_core::UnixNanos;
29use rust_decimal::Decimal;
30
31use super::display::pprint_own_book;
32use crate::{
33    enums::{OrderSideSpecified, OrderStatus, OrderType, TimeInForce},
34    identifiers::{ClientOrderId, InstrumentId},
35    orderbook::BookPrice,
36    types::{Price, Quantity},
37};
38
39/// Represents an own/user order for a book.
40///
41/// This struct models an order that may be in-flight to the trading venue or actively working,
42/// depending on the value of the `status` field.
43#[repr(C)]
44#[derive(Clone, Copy, Eq)]
45#[cfg_attr(
46    feature = "python",
47    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
48)]
49pub struct OwnBookOrder {
50    /// The client order ID.
51    pub client_order_id: ClientOrderId,
52    /// The specified order side (BUY or SELL).
53    pub side: OrderSideSpecified,
54    /// The order price.
55    pub price: Price,
56    /// The order size.
57    pub size: Quantity,
58    /// The order type.
59    pub order_type: OrderType,
60    /// The order time in force.
61    pub time_in_force: TimeInForce,
62    /// The current order status (SUBMITTED/ACCEPTED/CANCELED/FILLED).
63    pub status: OrderStatus,
64    /// UNIX timestamp (nanoseconds) when the last event occurred for this order.
65    pub ts_last: UnixNanos,
66    /// UNIX timestamp (nanoseconds) when the order was initialized.
67    pub ts_init: UnixNanos,
68}
69
70impl OwnBookOrder {
71    /// Creates a new [`OwnBookOrder`] instance.
72    #[must_use]
73    #[allow(clippy::too_many_arguments)]
74    pub fn new(
75        client_order_id: ClientOrderId,
76        side: OrderSideSpecified,
77        price: Price,
78        size: Quantity,
79        order_type: OrderType,
80        time_in_force: TimeInForce,
81        status: OrderStatus,
82        ts_last: UnixNanos,
83        ts_init: UnixNanos,
84    ) -> Self {
85        Self {
86            client_order_id,
87            side,
88            price,
89            size,
90            order_type,
91            time_in_force,
92            status,
93            ts_last,
94            ts_init,
95        }
96    }
97
98    /// Returns a [`BookPrice`] from this order.
99    #[must_use]
100    pub fn to_book_price(&self) -> BookPrice {
101        BookPrice::new(self.price, self.side)
102    }
103
104    /// Returns the order exposure as an `f64`.
105    #[must_use]
106    pub fn exposure(&self) -> f64 {
107        self.price.as_f64() * self.size.as_f64()
108    }
109
110    /// Returns the signed order exposure as an `f64`.
111    #[must_use]
112    pub fn signed_size(&self) -> f64 {
113        match self.side {
114            OrderSideSpecified::Buy => self.size.as_f64(),
115            OrderSideSpecified::Sell => -(self.size.as_f64()),
116        }
117    }
118}
119
120impl Ord for OwnBookOrder {
121    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
122        // Compare solely based on ts_init.
123        self.ts_init.cmp(&other.ts_init)
124    }
125}
126
127impl PartialOrd for OwnBookOrder {
128    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
129        Some(self.cmp(other))
130    }
131}
132
133impl PartialEq for OwnBookOrder {
134    fn eq(&self, other: &Self) -> bool {
135        self.client_order_id == other.client_order_id && self.ts_init == other.ts_init
136    }
137}
138
139impl Hash for OwnBookOrder {
140    fn hash<H: Hasher>(&self, state: &mut H) {
141        self.client_order_id.hash(state);
142    }
143}
144
145impl Debug for OwnBookOrder {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        write!(
148            f,
149            "{}(client_order_id={}, side={}, price={}, size={}, order_type={}, time_in_force={}, ts_init={})",
150            stringify!(OwnBookOrder),
151            self.client_order_id,
152            self.side,
153            self.price,
154            self.size,
155            self.order_type,
156            self.time_in_force,
157            self.ts_init,
158        )
159    }
160}
161
162impl Display for OwnBookOrder {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        write!(
165            f,
166            "{},{},{},{},{},{},{}",
167            self.client_order_id,
168            self.side,
169            self.price,
170            self.size,
171            self.order_type,
172            self.time_in_force,
173            self.ts_init,
174        )
175    }
176}
177
178#[derive(Debug)]
179#[cfg_attr(
180    feature = "python",
181    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
182)]
183pub struct OwnOrderBook {
184    /// The instrument ID for the order book.
185    pub instrument_id: InstrumentId,
186    /// The timestamp of the last event applied to the order book.
187    pub ts_last: UnixNanos,
188    /// The current count of events applied to the order book.
189    pub event_count: u64,
190    pub(crate) bids: OwnBookLadder,
191    pub(crate) asks: OwnBookLadder,
192}
193
194impl PartialEq for OwnOrderBook {
195    fn eq(&self, other: &Self) -> bool {
196        self.instrument_id == other.instrument_id
197    }
198}
199
200impl Display for OwnOrderBook {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        write!(
203            f,
204            "{}(instrument_id={}, event_count={})",
205            stringify!(OwnOrderBook),
206            self.instrument_id,
207            self.event_count,
208        )
209    }
210}
211
212impl OwnOrderBook {
213    /// Creates a new [`OwnOrderBook`] instance.
214    #[must_use]
215    pub fn new(instrument_id: InstrumentId) -> Self {
216        Self {
217            instrument_id,
218            ts_last: UnixNanos::default(),
219            event_count: 0,
220            bids: OwnBookLadder::new(OrderSideSpecified::Buy),
221            asks: OwnBookLadder::new(OrderSideSpecified::Sell),
222        }
223    }
224
225    fn increment(&mut self, order: &OwnBookOrder) {
226        self.ts_last = order.ts_last;
227        self.event_count += 1;
228    }
229
230    /// Resets the order book to its initial empty state.
231    pub fn reset(&mut self) {
232        self.bids.clear();
233        self.asks.clear();
234        self.ts_last = UnixNanos::default();
235        self.event_count = 0;
236    }
237
238    /// Adds an own order to the book.
239    pub fn add(&mut self, order: OwnBookOrder) {
240        self.increment(&order);
241        match order.side {
242            OrderSideSpecified::Buy => self.bids.add(order),
243            OrderSideSpecified::Sell => self.asks.add(order),
244        }
245    }
246
247    /// Updates an existing own order in the book.
248    pub fn update(&mut self, order: OwnBookOrder) -> anyhow::Result<()> {
249        self.increment(&order);
250        match order.side {
251            OrderSideSpecified::Buy => self.bids.update(order),
252            OrderSideSpecified::Sell => self.asks.update(order),
253        }
254    }
255
256    /// Deletes an own order from the book.
257    pub fn delete(&mut self, order: OwnBookOrder) -> anyhow::Result<()> {
258        self.increment(&order);
259        match order.side {
260            OrderSideSpecified::Buy => self.bids.delete(order),
261            OrderSideSpecified::Sell => self.asks.delete(order),
262        }
263    }
264
265    /// Clears all orders from both sides of the book.
266    pub fn clear(&mut self) {
267        self.bids.clear();
268        self.asks.clear();
269    }
270
271    /// Returns an iterator over bid price levels.
272    pub fn bids(&self) -> impl Iterator<Item = &OwnBookLevel> {
273        self.bids.levels.values()
274    }
275
276    /// Returns an iterator over ask price levels.
277    pub fn asks(&self) -> impl Iterator<Item = &OwnBookLevel> {
278        self.asks.levels.values()
279    }
280
281    /// Returns bid price levels as a map of level price to order list at that level.
282    pub fn bids_as_map(&self) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
283        self.bids()
284            .map(|level| {
285                (
286                    level.price.value.as_decimal(),
287                    level.orders.values().cloned().collect(),
288                )
289            })
290            .collect()
291    }
292
293    /// Returns ask price levels as a map of level price to order list at that level.
294    pub fn asks_as_map(&self) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
295        self.asks()
296            .map(|level| {
297                (
298                    level.price.value.as_decimal(),
299                    level.orders.values().cloned().collect(),
300                )
301            })
302            .collect()
303    }
304
305    /// Returns the aggregated own bid quantity at each price level.
306    pub fn bid_quantity(&self) -> IndexMap<Decimal, Decimal> {
307        self.bids()
308            .map(|level| {
309                (
310                    level.price.value.as_decimal(),
311                    level.orders.values().fold(Decimal::ZERO, |total, order| {
312                        total + order.size.as_decimal()
313                    }),
314                )
315            })
316            .collect()
317    }
318
319    /// Returns the aggregated own ask quantity at each price level.
320    pub fn ask_quantity(&self) -> IndexMap<Decimal, Decimal> {
321        self.asks()
322            .map(|level| {
323                (
324                    level.price.value.as_decimal(),
325                    level.orders.values().fold(Decimal::ZERO, |total, order| {
326                        total + order.size.as_decimal()
327                    }),
328                )
329            })
330            .collect()
331    }
332
333    /// Return a formatted string representation of the order book.
334    #[must_use]
335    pub fn pprint(&self, num_levels: usize) -> String {
336        pprint_own_book(&self.bids, &self.asks, num_levels)
337    }
338}
339
340/// Represents a ladder of price levels for one side of an order book.
341pub(crate) struct OwnBookLadder {
342    pub side: OrderSideSpecified,
343    pub levels: BTreeMap<BookPrice, OwnBookLevel>,
344    pub cache: HashMap<ClientOrderId, BookPrice>,
345}
346
347impl OwnBookLadder {
348    /// Creates a new [`OwnBookLadder`] instance.
349    #[must_use]
350    pub fn new(side: OrderSideSpecified) -> Self {
351        Self {
352            side,
353            levels: BTreeMap::new(),
354            cache: HashMap::new(),
355        }
356    }
357
358    /// Returns the number of price levels in the ladder.
359    #[must_use]
360    #[allow(dead_code)] // Used in tests
361    pub fn len(&self) -> usize {
362        self.levels.len()
363    }
364
365    /// Returns true if the ladder has no price levels.
366    #[must_use]
367    #[allow(dead_code)] // Used in tests
368    pub fn is_empty(&self) -> bool {
369        self.levels.is_empty()
370    }
371
372    /// Removes all orders and price levels from the ladder.
373    pub fn clear(&mut self) {
374        self.levels.clear();
375        self.cache.clear();
376    }
377
378    /// Adds an order to the ladder at its price level.
379    pub fn add(&mut self, order: OwnBookOrder) {
380        let book_price = order.to_book_price();
381        self.cache.insert(order.client_order_id, book_price);
382
383        match self.levels.get_mut(&book_price) {
384            Some(level) => {
385                level.add(order);
386            }
387            None => {
388                let level = OwnBookLevel::from_order(order);
389                self.levels.insert(book_price, level);
390            }
391        }
392    }
393
394    /// Updates an existing order in the ladder, moving it to a new price level if needed.
395    pub fn update(&mut self, order: OwnBookOrder) -> anyhow::Result<()> {
396        let price = self.cache.get(&order.client_order_id).copied();
397        if let Some(price) = price {
398            if let Some(level) = self.levels.get_mut(&price) {
399                if order.price == level.price.value {
400                    // Update at current price level
401                    level.update(order);
402                    return Ok(());
403                }
404
405                // Price update: delete and insert at new level
406                self.cache.remove(&order.client_order_id);
407                level.delete(&order.client_order_id)?;
408                if level.is_empty() {
409                    self.levels.remove(&price);
410                }
411            }
412        }
413
414        self.add(order);
415        Ok(())
416    }
417
418    /// Deletes an order from the ladder.
419    pub fn delete(&mut self, order: OwnBookOrder) -> anyhow::Result<()> {
420        self.remove(order.client_order_id)
421    }
422
423    /// Removes an order by its ID from the ladder.
424    pub fn remove(&mut self, client_order_id: ClientOrderId) -> anyhow::Result<()> {
425        if let Some(price) = self.cache.remove(&client_order_id) {
426            if let Some(level) = self.levels.get_mut(&price) {
427                level.delete(&client_order_id)?;
428                if level.is_empty() {
429                    self.levels.remove(&price);
430                }
431            }
432        }
433
434        Ok(())
435    }
436
437    /// Returns the total size of all orders in the ladder.
438    #[must_use]
439    #[allow(dead_code)] // Used in tests
440    pub fn sizes(&self) -> f64 {
441        self.levels.values().map(OwnBookLevel::size).sum()
442    }
443
444    /// Returns the total value exposure (price * size) of all orders in the ladder.
445    #[must_use]
446    #[allow(dead_code)] // Used in tests
447    pub fn exposures(&self) -> f64 {
448        self.levels.values().map(OwnBookLevel::exposure).sum()
449    }
450
451    /// Returns the best price level in the ladder.
452    #[must_use]
453    #[allow(dead_code)] // Used in tests
454    pub fn top(&self) -> Option<&OwnBookLevel> {
455        match self.levels.iter().next() {
456            Some((_, l)) => Option::Some(l),
457            None => Option::None,
458        }
459    }
460}
461
462impl Debug for OwnBookLadder {
463    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
464        f.debug_struct(stringify!(OwnBookLadder))
465            .field("side", &self.side)
466            .field("levels", &self.levels)
467            .finish()
468    }
469}
470
471impl Display for OwnBookLadder {
472    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473        writeln!(f, "{}(side={})", stringify!(OwnBookLadder), self.side)?;
474        for (price, level) in &self.levels {
475            writeln!(f, "  {} -> {} orders", price, level.len())?;
476        }
477        Ok(())
478    }
479}
480
481#[derive(Clone, Debug)]
482pub struct OwnBookLevel {
483    pub price: BookPrice,
484    pub orders: IndexMap<ClientOrderId, OwnBookOrder>,
485}
486
487impl OwnBookLevel {
488    /// Creates a new [`OwnBookLevel`] instance.
489    #[must_use]
490    pub fn new(price: BookPrice) -> Self {
491        Self {
492            price,
493            orders: IndexMap::new(),
494        }
495    }
496
497    /// Creates a new [`OwnBookLevel`] from an order, using the order's price and side.
498    #[must_use]
499    pub fn from_order(order: OwnBookOrder) -> Self {
500        let mut level = Self {
501            price: order.to_book_price(),
502            orders: IndexMap::new(),
503        };
504        level.orders.insert(order.client_order_id, order);
505        level
506    }
507
508    /// Returns the number of orders at this price level.
509    #[must_use]
510    pub fn len(&self) -> usize {
511        self.orders.len()
512    }
513
514    /// Returns true if this price level has no orders.
515    #[must_use]
516    pub fn is_empty(&self) -> bool {
517        self.orders.is_empty()
518    }
519
520    /// Returns a reference to the first order at this price level in FIFO order.
521    #[must_use]
522    pub fn first(&self) -> Option<&OwnBookOrder> {
523        self.orders.get_index(0).map(|(_key, order)| order)
524    }
525
526    /// Returns an iterator over the orders at this price level in FIFO order.
527    pub fn iter(&self) -> impl Iterator<Item = &OwnBookOrder> {
528        self.orders.values()
529    }
530
531    /// Returns all orders at this price level in FIFO insertion order.
532    #[must_use]
533    pub fn get_orders(&self) -> Vec<OwnBookOrder> {
534        self.orders.values().copied().collect()
535    }
536
537    /// Returns the total size of all orders at this price level as a float.
538    #[must_use]
539    pub fn size(&self) -> f64 {
540        self.orders.iter().map(|(_, o)| o.size.as_f64()).sum()
541    }
542
543    /// Returns the total size of all orders at this price level as a decimal.
544    #[must_use]
545    pub fn size_decimal(&self) -> Decimal {
546        self.orders.iter().map(|(_, o)| o.size.as_decimal()).sum()
547    }
548
549    /// Returns the total exposure (price * size) of all orders at this price level as a float.
550    #[must_use]
551    pub fn exposure(&self) -> f64 {
552        self.orders
553            .iter()
554            .map(|(_, o)| o.price.as_f64() * o.size.as_f64())
555            .sum()
556    }
557
558    /// Adds multiple orders to this price level in FIFO order. Orders must match the level's price.
559    pub fn add_bulk(&mut self, orders: Vec<OwnBookOrder>) {
560        for order in orders {
561            self.add(order);
562        }
563    }
564
565    /// Adds an order to this price level. Order must match the level's price.
566    pub fn add(&mut self, order: OwnBookOrder) {
567        debug_assert_eq!(order.price, self.price.value);
568
569        self.orders.insert(order.client_order_id, order);
570    }
571
572    /// Updates an existing order at this price level. Updated order must match the level's price.
573    /// Removes the order if size becomes zero.
574    pub fn update(&mut self, order: OwnBookOrder) {
575        debug_assert_eq!(order.price, self.price.value);
576
577        self.orders[&order.client_order_id] = order;
578    }
579
580    /// Deletes an order from this price level.
581    pub fn delete(&mut self, client_order_id: &ClientOrderId) -> anyhow::Result<()> {
582        if self.orders.shift_remove(client_order_id).is_none() {
583            // TODO: Use a generic anyhow result for now pending specific error types
584            anyhow::bail!("Order {client_order_id} not found for delete");
585        };
586        Ok(())
587    }
588}
589
590impl PartialEq for OwnBookLevel {
591    fn eq(&self, other: &Self) -> bool {
592        self.price == other.price
593    }
594}
595
596impl Eq for OwnBookLevel {}
597
598impl PartialOrd for OwnBookLevel {
599    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
600        Some(self.cmp(other))
601    }
602}
603
604impl Ord for OwnBookLevel {
605    fn cmp(&self, other: &Self) -> Ordering {
606        self.price.cmp(&other.price)
607    }
608}
609
610////////////////////////////////////////////////////////////////////////////////
611// Tests
612////////////////////////////////////////////////////////////////////////////////
613
614#[cfg(test)]
615mod tests {
616    use nautilus_core::UnixNanos;
617    use rstest::{fixture, rstest};
618    use rust_decimal_macros::dec;
619
620    use super::*;
621
622    #[fixture]
623    fn own_order() -> OwnBookOrder {
624        let client_order_id = ClientOrderId::from("O-123456789");
625        let side = OrderSideSpecified::Buy;
626        let price = Price::from("100.00");
627        let size = Quantity::from("10");
628        let order_type = OrderType::Limit;
629        let time_in_force = TimeInForce::Gtc;
630        let status = OrderStatus::Submitted;
631        let ts_last = UnixNanos::default();
632        let ts_init = UnixNanos::default();
633
634        OwnBookOrder::new(
635            client_order_id,
636            side,
637            price,
638            size,
639            order_type,
640            time_in_force,
641            status,
642            ts_last,
643            ts_init,
644        )
645    }
646
647    #[rstest]
648    fn test_to_book_price(own_order: OwnBookOrder) {
649        let book_price = own_order.to_book_price();
650        assert_eq!(book_price.value, Price::from("100.00"));
651        assert_eq!(book_price.side, OrderSideSpecified::Buy);
652    }
653
654    #[rstest]
655    fn test_exposure(own_order: OwnBookOrder) {
656        let exposure = own_order.exposure();
657        assert_eq!(exposure, 1000.0);
658    }
659
660    #[rstest]
661    fn test_signed_size(own_order: OwnBookOrder) {
662        let own_order_buy = own_order;
663        let own_order_sell = OwnBookOrder::new(
664            ClientOrderId::from("O-123456789"),
665            OrderSideSpecified::Sell,
666            Price::from("101.0"),
667            Quantity::from("10"),
668            OrderType::Limit,
669            TimeInForce::Gtc,
670            OrderStatus::Accepted,
671            UnixNanos::default(),
672            UnixNanos::default(),
673        );
674
675        assert_eq!(own_order_buy.signed_size(), 10.0);
676        assert_eq!(own_order_sell.signed_size(), -10.0);
677    }
678
679    #[rstest]
680    fn test_debug(own_order: OwnBookOrder) {
681        assert_eq!(
682            format!("{own_order:?}"),
683            "OwnBookOrder(client_order_id=O-123456789, side=BUY, price=100.00, size=10, order_type=LIMIT, time_in_force=GTC, ts_init=0)"
684        );
685    }
686
687    #[rstest]
688    fn test_display(own_order: OwnBookOrder) {
689        assert_eq!(
690            own_order.to_string(),
691            "O-123456789,BUY,100.00,10,LIMIT,GTC,0".to_string()
692        );
693    }
694
695    #[rstest]
696    fn test_own_book_level_size_and_exposure() {
697        let mut level = OwnBookLevel::new(BookPrice::new(
698            Price::from("100.00"),
699            OrderSideSpecified::Buy,
700        ));
701        let order1 = OwnBookOrder::new(
702            ClientOrderId::from("O-1"),
703            OrderSideSpecified::Buy,
704            Price::from("100.00"),
705            Quantity::from("10"),
706            OrderType::Limit,
707            TimeInForce::Gtc,
708            OrderStatus::Accepted,
709            UnixNanos::default(),
710            UnixNanos::default(),
711        );
712        let order2 = OwnBookOrder::new(
713            ClientOrderId::from("O-2"),
714            OrderSideSpecified::Buy,
715            Price::from("100.00"),
716            Quantity::from("20"),
717            OrderType::Limit,
718            TimeInForce::Gtc,
719            OrderStatus::Accepted,
720            UnixNanos::default(),
721            UnixNanos::default(),
722        );
723        level.add(order1);
724        level.add(order2);
725
726        assert_eq!(level.len(), 2);
727        assert_eq!(level.size(), 30.0);
728        assert_eq!(level.exposure(), 3000.0);
729    }
730
731    #[rstest]
732    fn test_own_book_level_add_update_delete() {
733        let mut level = OwnBookLevel::new(BookPrice::new(
734            Price::from("100.00"),
735            OrderSideSpecified::Buy,
736        ));
737        let order = OwnBookOrder::new(
738            ClientOrderId::from("O-1"),
739            OrderSideSpecified::Buy,
740            Price::from("100.00"),
741            Quantity::from("10"),
742            OrderType::Limit,
743            TimeInForce::Gtc,
744            OrderStatus::Accepted,
745            UnixNanos::default(),
746            UnixNanos::default(),
747        );
748        level.add(order);
749        assert_eq!(level.len(), 1);
750
751        // Update the order to a new size
752        let order_updated = OwnBookOrder::new(
753            ClientOrderId::from("O-1"),
754            OrderSideSpecified::Buy,
755            Price::from("100.00"),
756            Quantity::from("15"),
757            OrderType::Limit,
758            TimeInForce::Gtc,
759            OrderStatus::Accepted,
760            UnixNanos::default(),
761            UnixNanos::default(),
762        );
763        level.update(order_updated);
764        let orders = level.get_orders();
765        assert_eq!(orders[0].size, Quantity::from("15"));
766
767        // Delete the order
768        level.delete(&ClientOrderId::from("O-1")).unwrap();
769        assert!(level.is_empty());
770    }
771
772    #[rstest]
773    fn test_own_book_ladder_add_update_delete() {
774        let mut ladder = OwnBookLadder::new(OrderSideSpecified::Buy);
775        let order1 = OwnBookOrder::new(
776            ClientOrderId::from("O-1"),
777            OrderSideSpecified::Buy,
778            Price::from("100.00"),
779            Quantity::from("10"),
780            OrderType::Limit,
781            TimeInForce::Gtc,
782            OrderStatus::Accepted,
783            UnixNanos::default(),
784            UnixNanos::default(),
785        );
786        let order2 = OwnBookOrder::new(
787            ClientOrderId::from("O-2"),
788            OrderSideSpecified::Buy,
789            Price::from("100.00"),
790            Quantity::from("20"),
791            OrderType::Limit,
792            TimeInForce::Gtc,
793            OrderStatus::Accepted,
794            UnixNanos::default(),
795            UnixNanos::default(),
796        );
797        ladder.add(order1);
798        ladder.add(order2);
799        assert_eq!(ladder.len(), 1);
800        assert_eq!(ladder.sizes(), 30.0);
801
802        // Update order2 to a larger size
803        let order2_updated = OwnBookOrder::new(
804            ClientOrderId::from("O-2"),
805            OrderSideSpecified::Buy,
806            Price::from("100.00"),
807            Quantity::from("25"),
808            OrderType::Limit,
809            TimeInForce::Gtc,
810            OrderStatus::Accepted,
811            UnixNanos::default(),
812            UnixNanos::default(),
813        );
814        ladder.update(order2_updated).unwrap();
815        assert_eq!(ladder.sizes(), 35.0);
816
817        // Delete order1
818        ladder.delete(order1).unwrap();
819        assert_eq!(ladder.sizes(), 25.0);
820    }
821
822    #[rstest]
823    fn test_own_order_book_add_update_delete_clear() {
824        let instrument_id = InstrumentId::from("AAPL.XNAS");
825        let mut book = OwnOrderBook::new(instrument_id);
826        let order_buy = OwnBookOrder::new(
827            ClientOrderId::from("O-1"),
828            OrderSideSpecified::Buy,
829            Price::from("100.00"),
830            Quantity::from("10"),
831            OrderType::Limit,
832            TimeInForce::Gtc,
833            OrderStatus::Accepted,
834            UnixNanos::default(),
835            UnixNanos::default(),
836        );
837        let order_sell = OwnBookOrder::new(
838            ClientOrderId::from("O-2"),
839            OrderSideSpecified::Sell,
840            Price::from("101.00"),
841            Quantity::from("20"),
842            OrderType::Limit,
843            TimeInForce::Gtc,
844            OrderStatus::Accepted,
845            UnixNanos::default(),
846            UnixNanos::default(),
847        );
848
849        // Add orders to respective ladders
850        book.add(order_buy);
851        book.add(order_sell);
852        assert!(!book.bids.is_empty());
853        assert!(!book.asks.is_empty());
854
855        // Update buy order
856        let order_buy_updated = OwnBookOrder::new(
857            ClientOrderId::from("O-1"),
858            OrderSideSpecified::Buy,
859            Price::from("100.00"),
860            Quantity::from("15"),
861            OrderType::Limit,
862            TimeInForce::Gtc,
863            OrderStatus::Accepted,
864            UnixNanos::default(),
865            UnixNanos::default(),
866        );
867        book.update(order_buy_updated).unwrap();
868        book.delete(order_sell).unwrap();
869
870        assert_eq!(book.bids.sizes(), 15.0);
871        assert!(book.asks.is_empty());
872
873        // Clear the book
874        book.clear();
875        assert!(book.bids.is_empty());
876        assert!(book.asks.is_empty());
877    }
878
879    #[rstest]
880    fn test_own_order_book_bids_and_asks_as_map() {
881        let instrument_id = InstrumentId::from("AAPL.XNAS");
882        let mut book = OwnOrderBook::new(instrument_id);
883        let order1 = OwnBookOrder::new(
884            ClientOrderId::from("O-1"),
885            OrderSideSpecified::Buy,
886            Price::from("100.00"),
887            Quantity::from("10"),
888            OrderType::Limit,
889            TimeInForce::Gtc,
890            OrderStatus::Accepted,
891            UnixNanos::default(),
892            UnixNanos::default(),
893        );
894        let order2 = OwnBookOrder::new(
895            ClientOrderId::from("O-2"),
896            OrderSideSpecified::Sell,
897            Price::from("101.00"),
898            Quantity::from("20"),
899            OrderType::Limit,
900            TimeInForce::Gtc,
901            OrderStatus::Accepted,
902            UnixNanos::default(),
903            UnixNanos::default(),
904        );
905        book.add(order1);
906        book.add(order2);
907        let bids_map = book.bids_as_map();
908        let asks_map = book.asks_as_map();
909
910        assert_eq!(bids_map.len(), 1);
911        let bid_price = Price::from("100.00").as_decimal();
912        let bid_orders = bids_map.get(&bid_price).unwrap();
913        assert_eq!(bid_orders.len(), 1);
914        assert_eq!(bid_orders[0], order1);
915
916        assert_eq!(asks_map.len(), 1);
917        let ask_price = Price::from("101.00").as_decimal();
918        let ask_orders = asks_map.get(&ask_price).unwrap();
919        assert_eq!(ask_orders.len(), 1);
920        assert_eq!(ask_orders[0], order2);
921    }
922
923    #[rstest]
924    fn test_own_order_book_quantity_empty_levels() {
925        let instrument_id = InstrumentId::from("AAPL.XNAS");
926        let book = OwnOrderBook::new(instrument_id);
927
928        let bid_quantities = book.bid_quantity();
929        let ask_quantities = book.ask_quantity();
930
931        assert!(bid_quantities.is_empty());
932        assert!(ask_quantities.is_empty());
933    }
934
935    #[rstest]
936    fn test_own_order_book_bid_ask_quantity() {
937        let instrument_id = InstrumentId::from("AAPL.XNAS");
938        let mut book = OwnOrderBook::new(instrument_id);
939
940        // Add multiple orders at the same price level (bids)
941        let bid_order1 = OwnBookOrder::new(
942            ClientOrderId::from("O-1"),
943            OrderSideSpecified::Buy,
944            Price::from("100.00"),
945            Quantity::from("10"),
946            OrderType::Limit,
947            TimeInForce::Gtc,
948            OrderStatus::Accepted,
949            UnixNanos::default(),
950            UnixNanos::default(),
951        );
952        let bid_order2 = OwnBookOrder::new(
953            ClientOrderId::from("O-2"),
954            OrderSideSpecified::Buy,
955            Price::from("100.00"),
956            Quantity::from("15"),
957            OrderType::Limit,
958            TimeInForce::Gtc,
959            OrderStatus::Accepted,
960            UnixNanos::default(),
961            UnixNanos::default(),
962        );
963        // Add an order at a different price level (bids)
964        let bid_order3 = OwnBookOrder::new(
965            ClientOrderId::from("O-3"),
966            OrderSideSpecified::Buy,
967            Price::from("99.50"),
968            Quantity::from("20"),
969            OrderType::Limit,
970            TimeInForce::Gtc,
971            OrderStatus::Accepted,
972            UnixNanos::default(),
973            UnixNanos::default(),
974        );
975
976        // Add orders at different price levels (asks)
977        let ask_order1 = OwnBookOrder::new(
978            ClientOrderId::from("O-4"),
979            OrderSideSpecified::Sell,
980            Price::from("101.00"),
981            Quantity::from("12"),
982            OrderType::Limit,
983            TimeInForce::Gtc,
984            OrderStatus::Accepted,
985            UnixNanos::default(),
986            UnixNanos::default(),
987        );
988        let ask_order2 = OwnBookOrder::new(
989            ClientOrderId::from("O-5"),
990            OrderSideSpecified::Sell,
991            Price::from("101.00"),
992            Quantity::from("8"),
993            OrderType::Limit,
994            TimeInForce::Gtc,
995            OrderStatus::Accepted,
996            UnixNanos::default(),
997            UnixNanos::default(),
998        );
999
1000        book.add(bid_order1);
1001        book.add(bid_order2);
1002        book.add(bid_order3);
1003        book.add(ask_order1);
1004        book.add(ask_order2);
1005
1006        let bid_quantities = book.bid_quantity();
1007        assert_eq!(bid_quantities.len(), 2);
1008        assert_eq!(bid_quantities.get(&dec!(100.00)), Some(&dec!(25)));
1009        assert_eq!(bid_quantities.get(&dec!(99.50)), Some(&dec!(20)));
1010
1011        let ask_quantities = book.ask_quantity();
1012        assert_eq!(ask_quantities.len(), 1);
1013        assert_eq!(ask_quantities.get(&dec!(101.00)), Some(&dec!(20)));
1014    }
1015}