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::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) {
278 self.increment(&order);
279 match order.side {
280 OrderSide::Buy => self.bids.add(order),
281 OrderSide::Sell => self.asks.add(order),
282 }
283 }
284
285 pub fn update(&mut self, order: OwnBookOrder) -> Result<(), OwnBookError> {
291 let result = match order.side {
292 OrderSide::Buy => self.bids.update(order),
293 OrderSide::Sell => self.asks.update(order),
294 };
295
296 if result.is_ok() {
297 self.increment(&order);
298 }
299
300 result
301 }
302
303 pub fn delete(&mut self, order: OwnBookOrder) -> Result<(), OwnBookError> {
309 let result = match order.side {
310 OrderSide::Buy => self.bids.delete(order),
311 OrderSide::Sell => self.asks.delete(order),
312 };
313
314 if result.is_ok() {
315 self.increment(&order);
316 }
317
318 result
319 }
320
321 pub fn clear(&mut self) {
323 self.bids.clear();
324 self.asks.clear();
325 }
326
327 pub fn bids(&self) -> impl Iterator<Item = &OwnBookLevel> {
329 self.bids.levels.values()
330 }
331
332 pub fn asks(&self) -> impl Iterator<Item = &OwnBookLevel> {
334 self.asks.levels.values()
335 }
336
337 #[must_use]
339 pub fn bid_client_order_ids(&self) -> Vec<ClientOrderId> {
340 self.bids.cache.keys().copied().collect()
341 }
342
343 #[must_use]
345 pub fn ask_client_order_ids(&self) -> Vec<ClientOrderId> {
346 self.asks.cache.keys().copied().collect()
347 }
348
349 #[must_use]
351 pub fn is_order_in_book(&self, client_order_id: &ClientOrderId) -> bool {
352 self.asks.cache.contains_key(client_order_id)
353 || self.bids.cache.contains_key(client_order_id)
354 }
355
356 #[must_use]
366 pub fn bids_as_map(
367 &self,
368 status: Option<&AHashSet<OrderStatus>>,
369 accepted_buffer_ns: Option<u64>,
370 ts_now: Option<u64>,
371 ) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
372 filter_orders(self.bids(), status, accepted_buffer_ns, ts_now)
373 }
374
375 #[must_use]
385 pub fn asks_as_map(
386 &self,
387 status: Option<&AHashSet<OrderStatus>>,
388 accepted_buffer_ns: Option<u64>,
389 ts_now: Option<u64>,
390 ) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
391 filter_orders(self.asks(), status, accepted_buffer_ns, ts_now)
392 }
393
394 #[must_use]
407 pub fn bid_quantity(
408 &self,
409 status: Option<&AHashSet<OrderStatus>>,
410 depth: Option<usize>,
411 group_size: Option<Decimal>,
412 accepted_buffer_ns: Option<u64>,
413 ts_now: Option<u64>,
414 ) -> IndexMap<Decimal, Decimal> {
415 let quantities = self
416 .bids_as_map(status, accepted_buffer_ns, ts_now)
417 .into_iter()
418 .map(|(price, orders)| (price, sum_order_sizes(orders.iter())))
419 .filter(|(_, quantity)| *quantity > Decimal::ZERO)
420 .collect::<IndexMap<Decimal, Decimal>>();
421
422 if let Some(group_size) = group_size {
423 group_quantities(quantities, group_size, depth, true)
424 } else if let Some(depth) = depth {
425 quantities.into_iter().take(depth).collect()
426 } else {
427 quantities
428 }
429 }
430
431 #[must_use]
444 pub fn ask_quantity(
445 &self,
446 status: Option<&AHashSet<OrderStatus>>,
447 depth: Option<usize>,
448 group_size: Option<Decimal>,
449 accepted_buffer_ns: Option<u64>,
450 ts_now: Option<u64>,
451 ) -> IndexMap<Decimal, Decimal> {
452 let quantities = self
453 .asks_as_map(status, accepted_buffer_ns, ts_now)
454 .into_iter()
455 .map(|(price, orders)| {
456 let quantity = sum_order_sizes(orders.iter());
457 (price, quantity)
458 })
459 .filter(|(_, quantity)| *quantity > Decimal::ZERO)
460 .collect::<IndexMap<Decimal, Decimal>>();
461
462 if let Some(group_size) = group_size {
463 group_quantities(quantities, group_size, depth, false)
464 } else if let Some(depth) = depth {
465 quantities.into_iter().take(depth).collect()
466 } else {
467 quantities
468 }
469 }
470
471 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_error(&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_error(&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_error(client_order_id: &ClientOrderId) {
549 log::error!(
550 "Audit error - {client_order_id} absent from valid order IDs, deleting from own book"
551 );
552}
553
554fn transform_opposite_order(order: OwnBookOrder, side: OrderSide) -> OwnBookOrder {
555 let parity_price = Price::from_decimal(Decimal::ONE - order.price.as_decimal())
556 .expect("Invalid parity transformed price for OwnOrderBook::combined_with_opposite");
557
558 OwnBookOrder::new(
559 order.trader_id,
560 order.client_order_id,
561 order.venue_order_id,
562 side,
563 parity_price,
564 order.size,
565 order.order_type,
566 order.time_in_force,
567 order.status,
568 order.ts_last,
569 order.ts_accepted,
570 order.ts_submitted,
571 order.ts_init,
572 )
573}
574
575pub(crate) fn validate_accepted_buffer(
581 accepted_buffer_ns: Option<u64>,
582 ts_now: Option<u64>,
583) -> Result<(), &'static str> {
584 if accepted_buffer_ns.is_some_and(|buffer| buffer > 0) && ts_now.is_none() {
585 Err("ts_now must be provided when accepted_buffer_ns > 0")
586 } else {
587 Ok(())
588 }
589}
590
591fn filter_orders<'a>(
604 levels: impl Iterator<Item = &'a OwnBookLevel>,
605 status: Option<&AHashSet<OrderStatus>>,
606 accepted_buffer_ns: Option<u64>,
607 ts_now: Option<u64>,
608) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
609 validate_accepted_buffer(accepted_buffer_ns, ts_now).unwrap_or_else(|e| panic!("{e}"));
610 let accepted_buffer_ns = accepted_buffer_ns.unwrap_or(0);
611
612 levels
613 .map(|level| {
614 let orders = level
615 .orders
616 .values()
617 .filter(|order| status.is_none_or(|f| f.contains(&order.status)))
618 .filter(|order| {
619 ts_now.is_none_or(|ts_now| {
620 order
621 .ts_accepted
622 .checked_add(accepted_buffer_ns)
623 .is_some_and(|eligible_at| eligible_at.as_u64() <= ts_now)
624 })
625 })
626 .copied()
627 .collect::<Vec<OwnBookOrder>>();
628
629 (level.price.value.as_decimal(), orders)
630 })
631 .filter(|(_, orders)| !orders.is_empty())
632 .collect::<IndexMap<Decimal, Vec<OwnBookOrder>>>()
633}
634
635fn group_quantities(
636 quantities: IndexMap<Decimal, Decimal>,
637 group_size: Decimal,
638 depth: Option<usize>,
639 is_bid: bool,
640) -> IndexMap<Decimal, Decimal> {
641 if group_size <= Decimal::ZERO {
642 log::warn!("Invalid group_size: {group_size}, must be positive; returning empty map");
643 return IndexMap::new();
644 }
645
646 let mut grouped = IndexMap::new();
647 let depth = depth.unwrap_or(usize::MAX);
648
649 for (price, size) in quantities {
650 let grouped_price = if is_bid {
651 (price / group_size).floor() * group_size
652 } else {
653 (price / group_size).ceil() * group_size
654 };
655
656 grouped
657 .entry(grouped_price)
658 .and_modify(|total| *total += size)
659 .or_insert(size);
660
661 if grouped.len() > depth {
662 if is_bid {
663 if let Some((lowest_price, _)) = grouped.iter().min_by_key(|(price, _)| *price) {
665 let lowest_price = *lowest_price;
666 grouped.shift_remove(&lowest_price);
667 }
668 } else {
669 if let Some((highest_price, _)) = grouped.iter().max_by_key(|(price, _)| *price) {
671 let highest_price = *highest_price;
672 grouped.shift_remove(&highest_price);
673 }
674 }
675 }
676 }
677
678 grouped
679}
680
681fn sum_order_sizes<'a, I>(orders: I) -> Decimal
682where
683 I: Iterator<Item = &'a OwnBookOrder>,
684{
685 orders.map(|order| order.size.as_decimal()).sum()
686}
687
688#[derive(Clone)]
690pub(crate) struct OwnBookLadder {
691 pub side: OrderSide,
692 pub levels: BTreeMap<BookPrice, OwnBookLevel>,
693 pub cache: IndexMap<ClientOrderId, BookPrice>,
694}
695
696impl OwnBookLadder {
697 #[must_use]
699 pub(crate) fn new(side: OrderSide) -> Self {
700 Self {
701 side,
702 levels: BTreeMap::new(),
703 cache: IndexMap::new(),
704 }
705 }
706
707 #[must_use]
709 #[allow(dead_code)]
710 pub(crate) fn len(&self) -> usize {
711 self.levels.len()
712 }
713
714 #[must_use]
716 #[allow(dead_code)]
717 pub(crate) fn is_empty(&self) -> bool {
718 self.levels.is_empty()
719 }
720
721 pub(crate) fn clear(&mut self) {
723 self.levels.clear();
724 self.cache.clear();
725 }
726
727 pub(crate) fn add(&mut self, order: OwnBookOrder) {
729 let book_price = order.to_book_price();
730 self.cache.insert(order.client_order_id, book_price);
731
732 if let Some(level) = self.levels.get_mut(&book_price) {
733 level.add(order);
734 } else {
735 let level = OwnBookLevel::from_order(order);
736 self.levels.insert(book_price, level);
737 }
738 }
739
740 pub(crate) fn update(&mut self, order: OwnBookOrder) -> Result<(), OwnBookError> {
746 let client_order_id = order.client_order_id;
747
748 let Some(price) = self.cache.get(&order.client_order_id).copied() else {
749 return Err(OwnBookError::OrderNotFoundInCache { client_order_id });
750 };
751
752 let Some(level) = self.levels.get_mut(&price) else {
753 return Err(OwnBookError::CachedLevelMissing {
754 client_order_id,
755 price,
756 });
757 };
758
759 if order.price == level.price.value {
760 level.update(order);
761 if order.size.is_zero() {
762 self.cache.shift_remove(&order.client_order_id);
763
764 if level.is_empty() {
765 self.levels.remove(&price);
766 }
767 }
768 return Ok(());
769 }
770
771 level.delete(&client_order_id)?;
772 self.cache.shift_remove(&order.client_order_id);
773
774 if level.is_empty() {
775 self.levels.remove(&price);
776 }
777
778 self.add(order);
779 Ok(())
780 }
781
782 pub(crate) fn delete(&mut self, order: OwnBookOrder) -> Result<(), OwnBookError> {
788 self.remove(&order.client_order_id)
789 }
790
791 pub(crate) fn remove(&mut self, client_order_id: &ClientOrderId) -> Result<(), OwnBookError> {
797 let Some(price) = self.cache.get(client_order_id).copied() else {
798 return Err(OwnBookError::OrderNotFoundInCache {
799 client_order_id: *client_order_id,
800 });
801 };
802
803 let Some(level) = self.levels.get_mut(&price) else {
804 return Err(OwnBookError::CachedLevelMissing {
805 client_order_id: *client_order_id,
806 price,
807 });
808 };
809
810 level.delete(client_order_id)?;
811
812 if level.is_empty() {
813 self.levels.remove(&price);
814 }
815 self.cache.shift_remove(client_order_id);
816
817 Ok(())
818 }
819
820 #[must_use]
822 #[allow(dead_code)]
823 pub(crate) fn sizes(&self) -> f64 {
824 self.levels.values().map(OwnBookLevel::size).sum()
825 }
826
827 #[must_use]
829 #[allow(dead_code)]
830 pub(crate) fn exposures(&self) -> f64 {
831 self.levels.values().map(OwnBookLevel::exposure).sum()
832 }
833
834 #[must_use]
836 #[allow(dead_code)]
837 pub(crate) fn top(&self) -> Option<&OwnBookLevel> {
838 self.levels.values().next()
839 }
840}
841
842impl Debug for OwnBookLadder {
843 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
844 f.debug_struct(stringify!(OwnBookLadder))
845 .field("side", &self.side)
846 .field("levels", &self.levels)
847 .field("cache", &self.cache)
848 .finish()
849 }
850}
851
852impl Display for OwnBookLadder {
853 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
854 writeln!(f, "{}(side={})", stringify!(OwnBookLadder), self.side)?;
855 for (price, level) in &self.levels {
856 writeln!(f, " {} -> {} orders", price, level.len())?;
857 }
858 Ok(())
859 }
860}
861
862#[derive(Clone, Debug)]
863pub struct OwnBookLevel {
864 pub price: BookPrice,
865 pub orders: IndexMap<ClientOrderId, OwnBookOrder>,
866}
867
868impl OwnBookLevel {
869 #[must_use]
871 pub fn new(price: BookPrice) -> Self {
872 Self {
873 price,
874 orders: IndexMap::new(),
875 }
876 }
877
878 #[must_use]
880 pub fn from_order(order: OwnBookOrder) -> Self {
881 let mut level = Self {
882 price: order.to_book_price(),
883 orders: IndexMap::new(),
884 };
885 level.orders.insert(order.client_order_id, order);
886 level
887 }
888
889 #[must_use]
891 pub fn len(&self) -> usize {
892 self.orders.len()
893 }
894
895 #[must_use]
897 pub fn is_empty(&self) -> bool {
898 self.orders.is_empty()
899 }
900
901 #[must_use]
903 pub fn first(&self) -> Option<&OwnBookOrder> {
904 self.orders.get_index(0).map(|(_key, order)| order)
905 }
906
907 pub fn iter(&self) -> impl Iterator<Item = &OwnBookOrder> {
909 self.orders.values()
910 }
911
912 #[must_use]
914 pub fn get_orders(&self) -> Vec<OwnBookOrder> {
915 self.orders.values().copied().collect()
916 }
917
918 #[must_use]
920 pub fn size(&self) -> f64 {
921 self.orders.values().map(|order| order.size.as_f64()).sum()
922 }
923
924 #[must_use]
926 pub fn size_decimal(&self) -> Decimal {
927 self.orders
928 .values()
929 .map(|order| order.size.as_decimal())
930 .sum()
931 }
932
933 #[must_use]
935 pub fn exposure(&self) -> f64 {
936 self.orders
937 .values()
938 .map(|order| order.price.as_f64() * order.size.as_f64())
939 .sum()
940 }
941
942 pub fn add_bulk(&mut self, orders: &[OwnBookOrder]) {
944 for order in orders {
945 self.add(*order);
946 }
947 }
948
949 pub fn add(&mut self, order: OwnBookOrder) {
951 debug_assert_eq!(order.price, self.price.value);
952
953 self.orders.insert(order.client_order_id, order);
954 }
955
956 pub fn update(&mut self, order: OwnBookOrder) {
959 debug_assert_eq!(order.price, self.price.value);
960
961 if order.size.is_zero() {
962 self.orders.shift_remove(&order.client_order_id);
963 } else {
964 self.orders.insert(order.client_order_id, order);
965 }
966 }
967
968 pub fn delete(&mut self, client_order_id: &ClientOrderId) -> Result<(), OwnBookError> {
974 if self.orders.shift_remove(client_order_id).is_none() {
975 return Err(OwnBookError::OrderNotFoundAtLevel {
976 client_order_id: *client_order_id,
977 price: self.price,
978 });
979 }
980 Ok(())
981 }
982}
983
984impl PartialEq for OwnBookLevel {
985 fn eq(&self, other: &Self) -> bool {
986 self.price == other.price
987 }
988}
989
990impl Eq for OwnBookLevel {}
991
992impl PartialOrd for OwnBookLevel {
993 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
994 Some(self.cmp(other))
995 }
996}
997
998impl Ord for OwnBookLevel {
999 fn cmp(&self, other: &Self) -> Ordering {
1000 self.price.cmp(&other.price)
1001 }
1002}
1003
1004#[must_use]
1005pub fn should_handle_own_book_order(order: &OrderAny) -> bool {
1006 order.has_price() && !matches!(order.time_in_force(), TimeInForce::Ioc | TimeInForce::Fok)
1007}