1use std::{
21 cmp::Ordering,
22 collections::BTreeMap,
23 fmt::{Debug, Display},
24 hash::{Hash, Hasher},
25};
26
27use ahash::AHashSet;
28use indexmap::IndexMap;
29use nautilus_core::{DurationNanos, UnixNanos};
30use rust_decimal::Decimal;
31
32use super::{BookViewError, OwnBookError, display::pprint_own_book};
33use crate::{
34 enums::{OrderSide, OrderStatus, OrderType, TimeInForce},
35 identifiers::{ClientOrderId, InstrumentId, TraderId, VenueOrderId},
36 orderbook::BookPrice,
37 orders::{Order, OrderAny},
38 types::{Price, Quantity},
39};
40
41#[repr(C)]
46#[derive(Clone, Copy, Eq)]
47#[cfg_attr(
48 feature = "python",
49 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
50)]
51#[cfg_attr(
52 feature = "python",
53 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
54)]
55pub struct OwnBookOrder {
56 pub trader_id: TraderId,
58 pub client_order_id: ClientOrderId,
60 pub venue_order_id: Option<VenueOrderId>,
62 pub side: OrderSide,
64 pub price: Price,
66 pub size: Quantity,
68 pub order_type: OrderType,
70 pub time_in_force: TimeInForce,
72 pub status: OrderStatus,
74 pub ts_last: UnixNanos,
76 pub ts_accepted: UnixNanos,
78 pub ts_submitted: UnixNanos,
80 pub ts_init: UnixNanos,
82}
83
84impl OwnBookOrder {
85 #[must_use]
87 #[expect(clippy::too_many_arguments)]
88 pub fn new(
89 trader_id: TraderId,
90 client_order_id: ClientOrderId,
91 venue_order_id: Option<VenueOrderId>,
92 side: OrderSide,
93 price: Price,
94 size: Quantity,
95 order_type: OrderType,
96 time_in_force: TimeInForce,
97 status: OrderStatus,
98 ts_last: UnixNanos,
99 ts_accepted: UnixNanos,
100 ts_submitted: UnixNanos,
101 ts_init: UnixNanos,
102 ) -> Self {
103 Self {
104 trader_id,
105 client_order_id,
106 venue_order_id,
107 side,
108 price,
109 size,
110 order_type,
111 time_in_force,
112 status,
113 ts_last,
114 ts_accepted,
115 ts_submitted,
116 ts_init,
117 }
118 }
119
120 #[must_use]
122 pub fn to_book_price(&self) -> BookPrice {
123 BookPrice::new(self.price, self.side)
124 }
125
126 #[must_use]
128 pub fn exposure(&self) -> f64 {
129 self.price.as_f64() * self.size.as_f64()
130 }
131
132 #[must_use]
134 pub fn signed_size(&self) -> f64 {
135 match self.side {
136 OrderSide::Buy => self.size.as_f64(),
137 OrderSide::Sell => -(self.size.as_f64()),
138 }
139 }
140}
141
142impl Ord for OwnBookOrder {
143 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
144 self.client_order_id.cmp(&other.client_order_id)
145 }
146}
147
148impl PartialOrd for OwnBookOrder {
149 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
150 Some(self.cmp(other))
151 }
152}
153
154impl PartialEq for OwnBookOrder {
155 fn eq(&self, other: &Self) -> bool {
156 self.client_order_id == other.client_order_id
157 }
158}
159
160impl Hash for OwnBookOrder {
161 fn hash<H: Hasher>(&self, state: &mut H) {
162 self.client_order_id.hash(state);
163 }
164}
165
166impl Debug for OwnBookOrder {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 write!(
169 f,
170 "{}(trader_id={}, client_order_id={}, venue_order_id={:?}, side={}, price={}, size={}, order_type={}, time_in_force={}, status={}, ts_last={}, ts_accepted={}, ts_submitted={}, ts_init={})",
171 stringify!(OwnBookOrder),
172 self.trader_id,
173 self.client_order_id,
174 self.venue_order_id,
175 self.side,
176 self.price,
177 self.size,
178 self.order_type,
179 self.time_in_force,
180 self.status,
181 self.ts_last,
182 self.ts_accepted,
183 self.ts_submitted,
184 self.ts_init,
185 )
186 }
187}
188
189impl Display for OwnBookOrder {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 write!(
192 f,
193 "{},{},{:?},{},{},{},{},{},{},{},{},{},{}",
194 self.trader_id,
195 self.client_order_id,
196 self.venue_order_id,
197 self.side,
198 self.price,
199 self.size,
200 self.order_type,
201 self.time_in_force,
202 self.status,
203 self.ts_last,
204 self.ts_accepted,
205 self.ts_submitted,
206 self.ts_init,
207 )
208 }
209}
210
211#[derive(Clone, Debug)]
212#[cfg_attr(
213 feature = "python",
214 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
215)]
216#[cfg_attr(
217 feature = "python",
218 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
219)]
220pub struct OwnOrderBook {
221 pub instrument_id: InstrumentId,
223 pub ts_last: UnixNanos,
225 pub update_count: u64,
227 pub(crate) bids: OwnBookLadder,
228 pub(crate) asks: OwnBookLadder,
229}
230
231impl PartialEq for OwnOrderBook {
232 fn eq(&self, other: &Self) -> bool {
233 self.instrument_id == other.instrument_id
234 }
235}
236
237impl Display for OwnOrderBook {
238 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 write!(
240 f,
241 "{}(instrument_id={}, orders={}, update_count={})",
242 stringify!(OwnOrderBook),
243 self.instrument_id,
244 self.bids.cache.len() + self.asks.cache.len(),
245 self.update_count,
246 )
247 }
248}
249
250impl OwnOrderBook {
251 #[must_use]
253 pub fn new(instrument_id: InstrumentId) -> Self {
254 Self {
255 instrument_id,
256 ts_last: UnixNanos::default(),
257 update_count: 0,
258 bids: OwnBookLadder::new(OrderSide::Buy),
259 asks: OwnBookLadder::new(OrderSide::Sell),
260 }
261 }
262
263 fn increment(&mut self, order: &OwnBookOrder) {
264 self.ts_last = order.ts_last;
265 self.update_count += 1;
266 }
267
268 pub fn reset(&mut self) {
270 self.bids.clear();
271 self.asks.clear();
272 self.ts_last = UnixNanos::default();
273 self.update_count = 0;
274 }
275
276 pub fn add(&mut self, order: OwnBookOrder) {
279 self.increment(&order);
280 match order.side {
281 OrderSide::Buy => self.bids.add(order),
282 OrderSide::Sell => self.asks.add(order),
283 }
284 }
285
286 pub fn update(&mut self, order: OwnBookOrder) -> Result<(), OwnBookError> {
292 let result = match order.side {
293 OrderSide::Buy => self.bids.update(order),
294 OrderSide::Sell => self.asks.update(order),
295 };
296
297 if result.is_ok() {
298 self.increment(&order);
299 }
300
301 result
302 }
303
304 pub fn delete(&mut self, order: OwnBookOrder) -> Result<(), OwnBookError> {
310 let result = match order.side {
311 OrderSide::Buy => self.bids.delete(order),
312 OrderSide::Sell => self.asks.delete(order),
313 };
314
315 if result.is_ok() {
316 self.increment(&order);
317 }
318
319 result
320 }
321
322 pub fn clear(&mut self) {
324 self.bids.clear();
325 self.asks.clear();
326 }
327
328 pub fn bids(&self) -> impl Iterator<Item = &OwnBookLevel> {
330 self.bids.levels.values()
331 }
332
333 pub fn asks(&self) -> impl Iterator<Item = &OwnBookLevel> {
335 self.asks.levels.values()
336 }
337
338 #[must_use]
340 pub fn bid_client_order_ids(&self) -> Vec<ClientOrderId> {
341 self.bids.cache.keys().copied().collect()
342 }
343
344 #[must_use]
346 pub fn ask_client_order_ids(&self) -> Vec<ClientOrderId> {
347 self.asks.cache.keys().copied().collect()
348 }
349
350 #[must_use]
352 pub fn is_order_in_book(&self, client_order_id: &ClientOrderId) -> bool {
353 self.asks.cache.contains_key(client_order_id)
354 || self.bids.cache.contains_key(client_order_id)
355 }
356
357 #[must_use]
367 pub fn bids_as_map(
368 &self,
369 status: Option<&AHashSet<OrderStatus>>,
370 accepted_buffer_ns: Option<u64>,
371 ts_now: Option<u64>,
372 ) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
373 filter_orders(self.bids(), status, accepted_buffer_ns, ts_now)
374 }
375
376 #[must_use]
386 pub fn asks_as_map(
387 &self,
388 status: Option<&AHashSet<OrderStatus>>,
389 accepted_buffer_ns: Option<u64>,
390 ts_now: Option<u64>,
391 ) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
392 filter_orders(self.asks(), status, accepted_buffer_ns, ts_now)
393 }
394
395 #[must_use]
408 pub fn bid_quantity(
409 &self,
410 status: Option<&AHashSet<OrderStatus>>,
411 depth: Option<usize>,
412 group_size: Option<Decimal>,
413 accepted_buffer_ns: Option<u64>,
414 ts_now: Option<u64>,
415 ) -> IndexMap<Decimal, Decimal> {
416 let quantities = self
417 .bids_as_map(status, accepted_buffer_ns, ts_now)
418 .into_iter()
419 .map(|(price, orders)| (price, sum_order_sizes(orders.iter())))
420 .filter(|(_, quantity)| *quantity > Decimal::ZERO)
421 .collect::<IndexMap<Decimal, Decimal>>();
422
423 if let Some(group_size) = group_size {
424 group_quantities(quantities, group_size, depth, true)
425 } else if let Some(depth) = depth {
426 quantities.into_iter().take(depth).collect()
427 } else {
428 quantities
429 }
430 }
431
432 #[must_use]
445 pub fn ask_quantity(
446 &self,
447 status: Option<&AHashSet<OrderStatus>>,
448 depth: Option<usize>,
449 group_size: Option<Decimal>,
450 accepted_buffer_ns: Option<u64>,
451 ts_now: Option<u64>,
452 ) -> IndexMap<Decimal, Decimal> {
453 let quantities = self
454 .asks_as_map(status, accepted_buffer_ns, ts_now)
455 .into_iter()
456 .map(|(price, orders)| {
457 let quantity = sum_order_sizes(orders.iter());
458 (price, quantity)
459 })
460 .filter(|(_, quantity)| *quantity > Decimal::ZERO)
461 .collect::<IndexMap<Decimal, Decimal>>();
462
463 if let Some(group_size) = group_size {
464 group_quantities(quantities, group_size, depth, false)
465 } else if let Some(depth) = depth {
466 quantities.into_iter().take(depth).collect()
467 } else {
468 quantities
469 }
470 }
471
472 pub fn combined_with_opposite(&self, opposite: &Self) -> Result<Self, BookViewError> {
481 if self.instrument_id == opposite.instrument_id {
482 return Err(BookViewError::OppositeInstrumentMatch(
483 self.instrument_id,
484 opposite.instrument_id,
485 ));
486 }
487
488 let mut combined = self.clone();
489
490 for level in opposite.asks() {
491 for order in level.iter() {
492 combined.add(transform_opposite_order(*order, OrderSide::Buy));
493 }
494 }
495
496 for level in opposite.bids() {
497 for order in level.iter() {
498 combined.add(transform_opposite_order(*order, OrderSide::Sell));
499 }
500 }
501
502 Ok(combined)
503 }
504
505 #[must_use]
507 pub fn pprint(&self, num_levels: usize, group_size: Option<Decimal>) -> String {
508 pprint_own_book(self, num_levels, group_size)
509 }
510
511 pub fn audit_open_orders(&mut self, open_order_ids: &AHashSet<ClientOrderId>) {
512 log::debug!("Auditing {self}");
513
514 let bids_to_remove: Vec<ClientOrderId> = self
516 .bids
517 .cache
518 .keys()
519 .filter(|&key| !open_order_ids.contains(key))
520 .copied()
521 .collect();
522
523 let asks_to_remove: Vec<ClientOrderId> = self
525 .asks
526 .cache
527 .keys()
528 .filter(|&key| !open_order_ids.contains(key))
529 .copied()
530 .collect();
531
532 for client_order_id in bids_to_remove {
533 log_audit_removal(&client_order_id);
534 if let Err(e) = self.bids.remove(&client_order_id) {
535 log::error!("{e}");
536 }
537 }
538
539 for client_order_id in asks_to_remove {
540 log_audit_removal(&client_order_id);
541 if let Err(e) = self.asks.remove(&client_order_id) {
542 log::error!("{e}");
543 }
544 }
545 }
546}
547
548fn log_audit_removal(client_order_id: &ClientOrderId) {
549 log::warn!("Audit removing {client_order_id} from own book, absent from valid order IDs");
550}
551
552fn transform_opposite_order(order: OwnBookOrder, side: OrderSide) -> OwnBookOrder {
553 let parity_price = Price::from_decimal(Decimal::ONE - order.price.as_decimal())
554 .expect("Invalid parity transformed price for OwnOrderBook::combined_with_opposite");
555
556 OwnBookOrder::new(
557 order.trader_id,
558 order.client_order_id,
559 order.venue_order_id,
560 side,
561 parity_price,
562 order.size,
563 order.order_type,
564 order.time_in_force,
565 order.status,
566 order.ts_last,
567 order.ts_accepted,
568 order.ts_submitted,
569 order.ts_init,
570 )
571}
572
573pub(crate) fn validate_accepted_buffer(
579 accepted_buffer_ns: Option<u64>,
580 ts_now: Option<u64>,
581) -> Result<(), &'static str> {
582 if accepted_buffer_ns.is_some_and(|buffer| buffer > 0) && ts_now.is_none() {
583 Err("ts_now must be provided when accepted_buffer_ns > 0")
584 } else {
585 Ok(())
586 }
587}
588
589fn filter_orders<'a>(
602 levels: impl Iterator<Item = &'a OwnBookLevel>,
603 status: Option<&AHashSet<OrderStatus>>,
604 accepted_buffer_ns: Option<u64>,
605 ts_now: Option<u64>,
606) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
607 validate_accepted_buffer(accepted_buffer_ns, ts_now).unwrap_or_else(|e| panic!("{e}"));
608 let accepted_buffer_ns = accepted_buffer_ns.unwrap_or(0);
609
610 levels
611 .map(|level| {
612 let orders = level
613 .orders
614 .values()
615 .filter(|order| status.is_none_or(|f| f.contains(&order.status)))
616 .filter(|order| {
617 ts_now.is_none_or(|ts_now| {
618 order
619 .ts_accepted
620 .checked_add(DurationNanos::new(accepted_buffer_ns))
621 .is_some_and(|eligible_at| eligible_at.as_u64() <= ts_now)
622 })
623 })
624 .copied()
625 .collect::<Vec<OwnBookOrder>>();
626
627 (level.price.value.as_decimal(), orders)
628 })
629 .filter(|(_, orders)| !orders.is_empty())
630 .collect::<IndexMap<Decimal, Vec<OwnBookOrder>>>()
631}
632
633fn group_quantities(
634 quantities: IndexMap<Decimal, Decimal>,
635 group_size: Decimal,
636 depth: Option<usize>,
637 is_bid: bool,
638) -> IndexMap<Decimal, Decimal> {
639 if group_size <= Decimal::ZERO {
640 log::warn!("Invalid group_size: {group_size}, must be positive; returning empty map");
641 return IndexMap::new();
642 }
643
644 let mut grouped = IndexMap::new();
645 let depth = depth.unwrap_or(usize::MAX);
646
647 for (price, size) in quantities {
648 let grouped_price = if is_bid {
649 (price / group_size).floor() * group_size
650 } else {
651 (price / group_size).ceil() * group_size
652 };
653
654 grouped
655 .entry(grouped_price)
656 .and_modify(|total| *total += size)
657 .or_insert(size);
658
659 if grouped.len() > depth {
660 if is_bid {
661 if let Some((lowest_price, _)) = grouped.iter().min_by_key(|(price, _)| *price) {
663 let lowest_price = *lowest_price;
664 grouped.shift_remove(&lowest_price);
665 }
666 } else {
667 if let Some((highest_price, _)) = grouped.iter().max_by_key(|(price, _)| *price) {
669 let highest_price = *highest_price;
670 grouped.shift_remove(&highest_price);
671 }
672 }
673 }
674 }
675
676 grouped
677}
678
679fn sum_order_sizes<'a, I>(orders: I) -> Decimal
680where
681 I: Iterator<Item = &'a OwnBookOrder>,
682{
683 orders.map(|order| order.size.as_decimal()).sum()
684}
685
686#[derive(Clone)]
688pub(crate) struct OwnBookLadder {
689 pub side: OrderSide,
690 pub levels: BTreeMap<BookPrice, OwnBookLevel>,
691 pub cache: IndexMap<ClientOrderId, BookPrice>,
692}
693
694impl OwnBookLadder {
695 #[must_use]
697 pub(crate) fn new(side: OrderSide) -> Self {
698 Self {
699 side,
700 levels: BTreeMap::new(),
701 cache: IndexMap::new(),
702 }
703 }
704
705 #[must_use]
707 #[allow(dead_code)]
708 pub(crate) fn len(&self) -> usize {
709 self.levels.len()
710 }
711
712 #[must_use]
714 #[allow(dead_code)]
715 pub(crate) fn is_empty(&self) -> bool {
716 self.levels.is_empty()
717 }
718
719 pub(crate) fn clear(&mut self) {
721 self.levels.clear();
722 self.cache.clear();
723 }
724
725 pub(crate) fn add(&mut self, order: OwnBookOrder) {
730 let book_price = order.to_book_price();
731
732 if self
733 .cache
734 .get(&order.client_order_id)
735 .is_some_and(|price| *price != book_price)
736 && let Err(e) = self.remove(&order.client_order_id)
737 {
738 log::error!("{e}");
739 }
740
741 self.cache.insert(order.client_order_id, book_price);
742
743 if let Some(level) = self.levels.get_mut(&book_price) {
744 level.add(order);
745 } else {
746 let level = OwnBookLevel::from_order(order);
747 self.levels.insert(book_price, level);
748 }
749 }
750
751 pub(crate) fn update(&mut self, order: OwnBookOrder) -> Result<(), OwnBookError> {
758 let client_order_id = order.client_order_id;
759
760 let Some(price) = self.cache.get(&order.client_order_id).copied() else {
761 return Err(OwnBookError::OrderNotFoundInCache { client_order_id });
762 };
763
764 let Some(level) = self.levels.get_mut(&price) else {
765 return Err(OwnBookError::CachedLevelMissing {
766 client_order_id,
767 price,
768 });
769 };
770
771 if order.price == level.price.value {
772 level.update(order);
773 if order.size.is_zero() {
774 self.cache.shift_remove(&order.client_order_id);
775
776 if level.is_empty() {
777 self.levels.remove(&price);
778 }
779 }
780 return Ok(());
781 }
782
783 level.delete(&client_order_id)?;
784 self.cache.shift_remove(&order.client_order_id);
785
786 if level.is_empty() {
787 self.levels.remove(&price);
788 }
789
790 if !order.size.is_zero() {
791 self.add(order);
792 }
793
794 Ok(())
795 }
796
797 pub(crate) fn delete(&mut self, order: OwnBookOrder) -> Result<(), OwnBookError> {
803 self.remove(&order.client_order_id)
804 }
805
806 pub(crate) fn remove(&mut self, client_order_id: &ClientOrderId) -> Result<(), OwnBookError> {
812 let Some(price) = self.cache.get(client_order_id).copied() else {
813 return Err(OwnBookError::OrderNotFoundInCache {
814 client_order_id: *client_order_id,
815 });
816 };
817
818 let Some(level) = self.levels.get_mut(&price) else {
819 return Err(OwnBookError::CachedLevelMissing {
820 client_order_id: *client_order_id,
821 price,
822 });
823 };
824
825 level.delete(client_order_id)?;
826
827 if level.is_empty() {
828 self.levels.remove(&price);
829 }
830 self.cache.shift_remove(client_order_id);
831
832 Ok(())
833 }
834
835 #[must_use]
837 #[allow(dead_code)]
838 pub(crate) fn sizes(&self) -> f64 {
839 self.levels.values().map(OwnBookLevel::size).sum()
840 }
841
842 #[must_use]
844 #[allow(dead_code)]
845 pub(crate) fn exposures(&self) -> f64 {
846 self.levels.values().map(OwnBookLevel::exposure).sum()
847 }
848
849 #[must_use]
851 #[allow(dead_code)]
852 pub(crate) fn top(&self) -> Option<&OwnBookLevel> {
853 self.levels.values().next()
854 }
855}
856
857impl Debug for OwnBookLadder {
858 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
859 f.debug_struct(stringify!(OwnBookLadder))
860 .field("side", &self.side)
861 .field("levels", &self.levels)
862 .field("cache", &self.cache)
863 .finish()
864 }
865}
866
867impl Display for OwnBookLadder {
868 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
869 writeln!(f, "{}(side={})", stringify!(OwnBookLadder), self.side)?;
870 for (price, level) in &self.levels {
871 writeln!(f, " {} -> {} orders", price, level.len())?;
872 }
873 Ok(())
874 }
875}
876
877#[derive(Clone, Debug)]
878pub struct OwnBookLevel {
879 pub price: BookPrice,
880 pub orders: IndexMap<ClientOrderId, OwnBookOrder>,
881}
882
883impl OwnBookLevel {
884 #[must_use]
886 pub fn new(price: BookPrice) -> Self {
887 Self {
888 price,
889 orders: IndexMap::new(),
890 }
891 }
892
893 #[must_use]
895 pub fn from_order(order: OwnBookOrder) -> Self {
896 let mut level = Self {
897 price: order.to_book_price(),
898 orders: IndexMap::new(),
899 };
900 level.orders.insert(order.client_order_id, order);
901 level
902 }
903
904 #[must_use]
906 pub fn len(&self) -> usize {
907 self.orders.len()
908 }
909
910 #[must_use]
912 pub fn is_empty(&self) -> bool {
913 self.orders.is_empty()
914 }
915
916 #[must_use]
918 pub fn first(&self) -> Option<&OwnBookOrder> {
919 self.orders.get_index(0).map(|(_key, order)| order)
920 }
921
922 pub fn iter(&self) -> impl Iterator<Item = &OwnBookOrder> {
924 self.orders.values()
925 }
926
927 #[must_use]
929 pub fn get_orders(&self) -> Vec<OwnBookOrder> {
930 self.orders.values().copied().collect()
931 }
932
933 #[must_use]
935 pub fn size(&self) -> f64 {
936 self.orders.values().map(|order| order.size.as_f64()).sum()
937 }
938
939 #[must_use]
941 pub fn size_decimal(&self) -> Decimal {
942 self.orders
943 .values()
944 .map(|order| order.size.as_decimal())
945 .sum()
946 }
947
948 #[must_use]
950 pub fn exposure(&self) -> f64 {
951 self.orders
952 .values()
953 .map(|order| order.price.as_f64() * order.size.as_f64())
954 .sum()
955 }
956
957 pub fn add_bulk(&mut self, orders: &[OwnBookOrder]) {
959 for order in orders {
960 self.add(*order);
961 }
962 }
963
964 pub fn add(&mut self, order: OwnBookOrder) {
966 debug_assert_eq!(order.price, self.price.value);
967
968 self.orders.insert(order.client_order_id, order);
969 }
970
971 pub fn update(&mut self, order: OwnBookOrder) {
974 debug_assert_eq!(order.price, self.price.value);
975
976 if order.size.is_zero() {
977 self.orders.shift_remove(&order.client_order_id);
978 } else {
979 self.orders.insert(order.client_order_id, order);
980 }
981 }
982
983 pub fn delete(&mut self, client_order_id: &ClientOrderId) -> Result<(), OwnBookError> {
989 if self.orders.shift_remove(client_order_id).is_none() {
990 return Err(OwnBookError::OrderNotFoundAtLevel {
991 client_order_id: *client_order_id,
992 price: self.price,
993 });
994 }
995 Ok(())
996 }
997}
998
999impl PartialEq for OwnBookLevel {
1000 fn eq(&self, other: &Self) -> bool {
1001 self.price == other.price
1002 }
1003}
1004
1005impl Eq for OwnBookLevel {}
1006
1007impl PartialOrd for OwnBookLevel {
1008 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1009 Some(self.cmp(other))
1010 }
1011}
1012
1013impl Ord for OwnBookLevel {
1014 fn cmp(&self, other: &Self) -> Ordering {
1015 self.price.cmp(&other.price)
1016 }
1017}
1018
1019#[must_use]
1025pub fn should_handle_own_book_order(order: &OrderAny) -> bool {
1026 order.has_price()
1027 && !matches!(order.time_in_force(), TimeInForce::Ioc | TimeInForce::Fok)
1028 && order.emulation_trigger().is_none()
1029 && !order.is_quote_quantity()
1030}