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, time::nanos_since_unix_epoch};
30use rust_decimal::Decimal;
31
32use super::{BookViewError, display::pprint_own_book};
33use crate::{
34 enums::{OrderSideSpecified, 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.core.nautilus_pyo3.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: OrderSideSpecified,
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: OrderSideSpecified,
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 OrderSideSpecified::Buy => self.size.as_f64(),
137 OrderSideSpecified::Sell => -(self.size.as_f64()),
138 }
139 }
140}
141
142impl Ord for OwnBookOrder {
143 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
144 self.ts_init
145 .cmp(&other.ts_init)
146 .then_with(|| self.client_order_id.cmp(&other.client_order_id))
147 }
148}
149
150impl PartialOrd for OwnBookOrder {
151 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
152 Some(self.cmp(other))
153 }
154}
155
156impl PartialEq for OwnBookOrder {
157 fn eq(&self, other: &Self) -> bool {
158 self.client_order_id == other.client_order_id
159 }
160}
161
162impl Hash for OwnBookOrder {
163 fn hash<H: Hasher>(&self, state: &mut H) {
164 self.client_order_id.hash(state);
165 }
166}
167
168impl Debug for OwnBookOrder {
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 write!(
171 f,
172 "{}(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={})",
173 stringify!(OwnBookOrder),
174 self.trader_id,
175 self.client_order_id,
176 self.venue_order_id,
177 self.side,
178 self.price,
179 self.size,
180 self.order_type,
181 self.time_in_force,
182 self.status,
183 self.ts_last,
184 self.ts_accepted,
185 self.ts_submitted,
186 self.ts_init,
187 )
188 }
189}
190
191impl Display for OwnBookOrder {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 write!(
194 f,
195 "{},{},{:?},{},{},{},{},{},{},{},{},{},{}",
196 self.trader_id,
197 self.client_order_id,
198 self.venue_order_id,
199 self.side,
200 self.price,
201 self.size,
202 self.order_type,
203 self.time_in_force,
204 self.status,
205 self.ts_last,
206 self.ts_accepted,
207 self.ts_submitted,
208 self.ts_init,
209 )
210 }
211}
212
213#[derive(Clone, Debug)]
214#[cfg_attr(
215 feature = "python",
216 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
217)]
218#[cfg_attr(
219 feature = "python",
220 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
221)]
222pub struct OwnOrderBook {
223 pub instrument_id: InstrumentId,
225 pub ts_last: UnixNanos,
227 pub update_count: u64,
229 pub(crate) bids: OwnBookLadder,
230 pub(crate) asks: OwnBookLadder,
231}
232
233impl PartialEq for OwnOrderBook {
234 fn eq(&self, other: &Self) -> bool {
235 self.instrument_id == other.instrument_id
236 }
237}
238
239impl Display for OwnOrderBook {
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 write!(
242 f,
243 "{}(instrument_id={}, orders={}, update_count={})",
244 stringify!(OwnOrderBook),
245 self.instrument_id,
246 self.bids.cache.len() + self.asks.cache.len(),
247 self.update_count,
248 )
249 }
250}
251
252impl OwnOrderBook {
253 #[must_use]
255 pub fn new(instrument_id: InstrumentId) -> Self {
256 Self {
257 instrument_id,
258 ts_last: UnixNanos::default(),
259 update_count: 0,
260 bids: OwnBookLadder::new(OrderSideSpecified::Buy),
261 asks: OwnBookLadder::new(OrderSideSpecified::Sell),
262 }
263 }
264
265 fn increment(&mut self, order: &OwnBookOrder) {
266 self.ts_last = order.ts_last;
267 self.update_count += 1;
268 }
269
270 pub fn reset(&mut self) {
272 self.bids.clear();
273 self.asks.clear();
274 self.ts_last = UnixNanos::default();
275 self.update_count = 0;
276 }
277
278 pub fn add(&mut self, order: OwnBookOrder) {
280 self.increment(&order);
281 match order.side {
282 OrderSideSpecified::Buy => self.bids.add(order),
283 OrderSideSpecified::Sell => self.asks.add(order),
284 }
285 }
286
287 pub fn update(&mut self, order: OwnBookOrder) -> anyhow::Result<()> {
293 let result = match order.side {
294 OrderSideSpecified::Buy => self.bids.update(order),
295 OrderSideSpecified::Sell => self.asks.update(order),
296 };
297
298 if result.is_ok() {
299 self.increment(&order);
300 }
301
302 result
303 }
304
305 pub fn delete(&mut self, order: OwnBookOrder) -> anyhow::Result<()> {
311 let result = match order.side {
312 OrderSideSpecified::Buy => self.bids.delete(order),
313 OrderSideSpecified::Sell => self.asks.delete(order),
314 };
315
316 if result.is_ok() {
317 self.increment(&order);
318 }
319
320 result
321 }
322
323 pub fn clear(&mut self) {
325 self.bids.clear();
326 self.asks.clear();
327 }
328
329 pub fn bids(&self) -> impl Iterator<Item = &OwnBookLevel> {
331 self.bids.levels.values()
332 }
333
334 pub fn asks(&self) -> impl Iterator<Item = &OwnBookLevel> {
336 self.asks.levels.values()
337 }
338
339 #[must_use]
341 pub fn bid_client_order_ids(&self) -> Vec<ClientOrderId> {
342 self.bids.cache.keys().copied().collect()
343 }
344
345 #[must_use]
347 pub fn ask_client_order_ids(&self) -> Vec<ClientOrderId> {
348 self.asks.cache.keys().copied().collect()
349 }
350
351 #[must_use]
353 pub fn is_order_in_book(&self, client_order_id: &ClientOrderId) -> bool {
354 self.asks.cache.contains_key(client_order_id)
355 || self.bids.cache.contains_key(client_order_id)
356 }
357
358 #[must_use]
363 pub fn bids_as_map(
364 &self,
365 status: Option<&AHashSet<OrderStatus>>,
366 accepted_buffer_ns: Option<u64>,
367 ts_now: Option<u64>,
368 ) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
369 filter_orders(self.bids(), status, accepted_buffer_ns, ts_now)
370 }
371
372 #[must_use]
377 pub fn asks_as_map(
378 &self,
379 status: Option<&AHashSet<OrderStatus>>,
380 accepted_buffer_ns: Option<u64>,
381 ts_now: Option<u64>,
382 ) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
383 filter_orders(self.asks(), status, accepted_buffer_ns, ts_now)
384 }
385
386 #[must_use]
394 pub fn bid_quantity(
395 &self,
396 status: Option<&AHashSet<OrderStatus>>,
397 depth: Option<usize>,
398 group_size: Option<Decimal>,
399 accepted_buffer_ns: Option<u64>,
400 ts_now: Option<u64>,
401 ) -> IndexMap<Decimal, Decimal> {
402 let quantities = self
403 .bids_as_map(status, accepted_buffer_ns, ts_now)
404 .into_iter()
405 .map(|(price, orders)| (price, sum_order_sizes(orders.iter())))
406 .filter(|(_, quantity)| *quantity > Decimal::ZERO)
407 .collect::<IndexMap<Decimal, Decimal>>();
408
409 if let Some(group_size) = group_size {
410 group_quantities(quantities, group_size, depth, true)
411 } else if let Some(depth) = depth {
412 quantities.into_iter().take(depth).collect()
413 } else {
414 quantities
415 }
416 }
417
418 #[must_use]
426 pub fn ask_quantity(
427 &self,
428 status: Option<&AHashSet<OrderStatus>>,
429 depth: Option<usize>,
430 group_size: Option<Decimal>,
431 accepted_buffer_ns: Option<u64>,
432 ts_now: Option<u64>,
433 ) -> IndexMap<Decimal, Decimal> {
434 let quantities = self
435 .asks_as_map(status, accepted_buffer_ns, ts_now)
436 .into_iter()
437 .map(|(price, orders)| {
438 let quantity = sum_order_sizes(orders.iter());
439 (price, quantity)
440 })
441 .filter(|(_, quantity)| *quantity > Decimal::ZERO)
442 .collect::<IndexMap<Decimal, Decimal>>();
443
444 if let Some(group_size) = group_size {
445 group_quantities(quantities, group_size, depth, false)
446 } else if let Some(depth) = depth {
447 quantities.into_iter().take(depth).collect()
448 } else {
449 quantities
450 }
451 }
452
453 pub fn combined_with_opposite(&self, opposite: &Self) -> Result<Self, BookViewError> {
463 if self.instrument_id == opposite.instrument_id {
464 return Err(BookViewError::OppositeInstrumentMatch(
465 self.instrument_id,
466 opposite.instrument_id,
467 ));
468 }
469
470 let mut combined = self.clone();
471
472 for level in opposite.asks() {
473 for order in level.iter() {
474 combined.add(transform_opposite_order(*order, OrderSideSpecified::Buy));
475 }
476 }
477
478 for level in opposite.bids() {
479 for order in level.iter() {
480 combined.add(transform_opposite_order(*order, OrderSideSpecified::Sell));
481 }
482 }
483
484 Ok(combined)
485 }
486
487 #[must_use]
489 pub fn pprint(&self, num_levels: usize, group_size: Option<Decimal>) -> String {
490 pprint_own_book(self, num_levels, group_size)
491 }
492
493 pub fn audit_open_orders(&mut self, open_order_ids: &AHashSet<ClientOrderId>) {
494 log::debug!("Auditing {self}");
495
496 let bids_to_remove: Vec<ClientOrderId> = self
498 .bids
499 .cache
500 .keys()
501 .filter(|&key| !open_order_ids.contains(key))
502 .copied()
503 .collect();
504
505 let asks_to_remove: Vec<ClientOrderId> = self
507 .asks
508 .cache
509 .keys()
510 .filter(|&key| !open_order_ids.contains(key))
511 .copied()
512 .collect();
513
514 for client_order_id in bids_to_remove {
515 log_audit_error(&client_order_id);
516 if let Err(e) = self.bids.remove(&client_order_id) {
517 log::error!("{e}");
518 }
519 }
520
521 for client_order_id in asks_to_remove {
522 log_audit_error(&client_order_id);
523 if let Err(e) = self.asks.remove(&client_order_id) {
524 log::error!("{e}");
525 }
526 }
527 }
528}
529
530fn log_audit_error(client_order_id: &ClientOrderId) {
531 log::error!(
532 "Audit error - {client_order_id} cached order already closed, deleting from own book"
533 );
534}
535
536fn transform_opposite_order(order: OwnBookOrder, side: OrderSideSpecified) -> OwnBookOrder {
537 let parity_price = Price::from_decimal(Decimal::ONE - order.price.as_decimal())
538 .expect("Invalid parity transformed price for OwnOrderBook::combined_with_opposite");
539
540 OwnBookOrder::new(
541 order.trader_id,
542 order.client_order_id,
543 order.venue_order_id,
544 side,
545 parity_price,
546 order.size,
547 order.order_type,
548 order.time_in_force,
549 order.status,
550 order.ts_last,
551 order.ts_accepted,
552 order.ts_submitted,
553 order.ts_init,
554 )
555}
556
557fn filter_orders<'a>(
566 levels: impl Iterator<Item = &'a OwnBookLevel>,
567 status: Option<&AHashSet<OrderStatus>>,
568 accepted_buffer_ns: Option<u64>,
569 ts_now: Option<u64>,
570) -> IndexMap<Decimal, Vec<OwnBookOrder>> {
571 let accepted_buffer_ns = accepted_buffer_ns.unwrap_or(0);
572 let ts_now = ts_now.unwrap_or_else(nanos_since_unix_epoch);
573 levels
574 .map(|level| {
575 let orders = level
576 .orders
577 .values()
578 .filter(|order| status.is_none_or(|f| f.contains(&order.status)))
579 .filter(|order| order.ts_accepted + accepted_buffer_ns <= ts_now)
580 .copied()
581 .collect::<Vec<OwnBookOrder>>();
582
583 (level.price.value.as_decimal(), orders)
584 })
585 .filter(|(_, orders)| !orders.is_empty())
586 .collect::<IndexMap<Decimal, Vec<OwnBookOrder>>>()
587}
588
589fn group_quantities(
590 quantities: IndexMap<Decimal, Decimal>,
591 group_size: Decimal,
592 depth: Option<usize>,
593 is_bid: bool,
594) -> IndexMap<Decimal, Decimal> {
595 if group_size <= Decimal::ZERO {
596 log::error!("Invalid group_size: {group_size}, must be positive; returning empty map");
597 return IndexMap::new();
598 }
599
600 let mut grouped = IndexMap::new();
601 let depth = depth.unwrap_or(usize::MAX);
602
603 for (price, size) in quantities {
604 let grouped_price = if is_bid {
605 (price / group_size).floor() * group_size
606 } else {
607 (price / group_size).ceil() * group_size
608 };
609
610 grouped
611 .entry(grouped_price)
612 .and_modify(|total| *total += size)
613 .or_insert(size);
614
615 if grouped.len() > depth {
616 if is_bid {
617 if let Some((lowest_price, _)) = grouped.iter().min_by_key(|(price, _)| *price) {
619 let lowest_price = *lowest_price;
620 grouped.shift_remove(&lowest_price);
621 }
622 } else {
623 if let Some((highest_price, _)) = grouped.iter().max_by_key(|(price, _)| *price) {
625 let highest_price = *highest_price;
626 grouped.shift_remove(&highest_price);
627 }
628 }
629 }
630 }
631
632 grouped
633}
634
635fn sum_order_sizes<'a, I>(orders: I) -> Decimal
636where
637 I: Iterator<Item = &'a OwnBookOrder>,
638{
639 orders.fold(Decimal::ZERO, |total, order| {
640 total + order.size.as_decimal()
641 })
642}
643
644#[derive(Clone)]
646pub(crate) struct OwnBookLadder {
647 pub side: OrderSideSpecified,
648 pub levels: BTreeMap<BookPrice, OwnBookLevel>,
649 pub cache: IndexMap<ClientOrderId, BookPrice>,
650}
651
652impl OwnBookLadder {
653 #[must_use]
655 pub fn new(side: OrderSideSpecified) -> Self {
656 Self {
657 side,
658 levels: BTreeMap::new(),
659 cache: IndexMap::new(),
660 }
661 }
662
663 #[must_use]
665 #[allow(dead_code)]
666 pub fn len(&self) -> usize {
667 self.levels.len()
668 }
669
670 #[must_use]
672 #[allow(dead_code)]
673 pub fn is_empty(&self) -> bool {
674 self.levels.is_empty()
675 }
676
677 pub fn clear(&mut self) {
679 self.levels.clear();
680 self.cache.clear();
681 }
682
683 pub fn add(&mut self, order: OwnBookOrder) {
685 let book_price = order.to_book_price();
686 self.cache.insert(order.client_order_id, book_price);
687
688 if let Some(level) = self.levels.get_mut(&book_price) {
689 level.add(order);
690 } else {
691 let level = OwnBookLevel::from_order(order);
692 self.levels.insert(book_price, level);
693 }
694 }
695
696 pub fn update(&mut self, order: OwnBookOrder) -> anyhow::Result<()> {
702 let Some(price) = self.cache.get(&order.client_order_id).copied() else {
703 log::error!(
704 "Own book update failed - order {client_order_id} not in cache",
705 client_order_id = order.client_order_id
706 );
707 anyhow::bail!(
708 "Order {} not found in own book (cache)",
709 order.client_order_id
710 );
711 };
712
713 let Some(level) = self.levels.get_mut(&price) else {
714 log::error!(
715 "Own book update failed - order {client_order_id} cached level {price:?} missing",
716 client_order_id = order.client_order_id
717 );
718 anyhow::bail!(
719 "Order {} not found in own book (level)",
720 order.client_order_id
721 );
722 };
723
724 if order.price == level.price.value {
725 level.update(order);
726 if order.size.is_zero() {
727 self.cache.shift_remove(&order.client_order_id);
728
729 if level.is_empty() {
730 self.levels.remove(&price);
731 }
732 }
733 return Ok(());
734 }
735
736 level.delete(&order.client_order_id)?;
737 self.cache.shift_remove(&order.client_order_id);
738
739 if level.is_empty() {
740 self.levels.remove(&price);
741 }
742
743 self.add(order);
744 Ok(())
745 }
746
747 pub fn delete(&mut self, order: OwnBookOrder) -> anyhow::Result<()> {
753 self.remove(&order.client_order_id)
754 }
755
756 pub fn remove(&mut self, client_order_id: &ClientOrderId) -> anyhow::Result<()> {
762 let Some(price) = self.cache.get(client_order_id).copied() else {
763 log::error!("Own book remove failed - order {client_order_id} not in cache");
764 anyhow::bail!("Order {client_order_id} not found in own book (cache)");
765 };
766
767 let Some(level) = self.levels.get_mut(&price) else {
768 log::error!(
769 "Own book remove failed - order {client_order_id} cached level {price:?} missing"
770 );
771 anyhow::bail!("Order {client_order_id} not found in own book (level)");
772 };
773
774 level.delete(client_order_id)?;
775
776 if level.is_empty() {
777 self.levels.remove(&price);
778 }
779 self.cache.shift_remove(client_order_id);
780
781 Ok(())
782 }
783
784 #[must_use]
786 #[allow(dead_code)]
787 pub fn sizes(&self) -> f64 {
788 self.levels.values().map(OwnBookLevel::size).sum()
789 }
790
791 #[must_use]
793 #[allow(dead_code)]
794 pub fn exposures(&self) -> f64 {
795 self.levels.values().map(OwnBookLevel::exposure).sum()
796 }
797
798 #[must_use]
800 #[allow(dead_code)]
801 pub fn top(&self) -> Option<&OwnBookLevel> {
802 match self.levels.iter().next() {
803 Some((_, l)) => Option::Some(l),
804 None => Option::None,
805 }
806 }
807}
808
809impl Debug for OwnBookLadder {
810 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
811 f.debug_struct(stringify!(OwnBookLadder))
812 .field("side", &self.side)
813 .field("levels", &self.levels)
814 .field("cache", &self.cache)
815 .finish()
816 }
817}
818
819impl Display for OwnBookLadder {
820 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
821 writeln!(f, "{}(side={})", stringify!(OwnBookLadder), self.side)?;
822 for (price, level) in &self.levels {
823 writeln!(f, " {} -> {} orders", price, level.len())?;
824 }
825 Ok(())
826 }
827}
828
829#[derive(Clone, Debug)]
830pub struct OwnBookLevel {
831 pub price: BookPrice,
832 pub orders: IndexMap<ClientOrderId, OwnBookOrder>,
833}
834
835impl OwnBookLevel {
836 #[must_use]
838 pub fn new(price: BookPrice) -> Self {
839 Self {
840 price,
841 orders: IndexMap::new(),
842 }
843 }
844
845 #[must_use]
847 pub fn from_order(order: OwnBookOrder) -> Self {
848 let mut level = Self {
849 price: order.to_book_price(),
850 orders: IndexMap::new(),
851 };
852 level.orders.insert(order.client_order_id, order);
853 level
854 }
855
856 #[must_use]
858 pub fn len(&self) -> usize {
859 self.orders.len()
860 }
861
862 #[must_use]
864 pub fn is_empty(&self) -> bool {
865 self.orders.is_empty()
866 }
867
868 #[must_use]
870 pub fn first(&self) -> Option<&OwnBookOrder> {
871 self.orders.get_index(0).map(|(_key, order)| order)
872 }
873
874 pub fn iter(&self) -> impl Iterator<Item = &OwnBookOrder> {
876 self.orders.values()
877 }
878
879 #[must_use]
881 pub fn get_orders(&self) -> Vec<OwnBookOrder> {
882 self.orders.values().copied().collect()
883 }
884
885 #[must_use]
887 pub fn size(&self) -> f64 {
888 self.orders.iter().map(|(_, o)| o.size.as_f64()).sum()
889 }
890
891 #[must_use]
893 pub fn size_decimal(&self) -> Decimal {
894 self.orders.iter().map(|(_, o)| o.size.as_decimal()).sum()
895 }
896
897 #[must_use]
899 pub fn exposure(&self) -> f64 {
900 self.orders
901 .iter()
902 .map(|(_, o)| o.price.as_f64() * o.size.as_f64())
903 .sum()
904 }
905
906 pub fn add_bulk(&mut self, orders: &[OwnBookOrder]) {
908 for order in orders {
909 self.add(*order);
910 }
911 }
912
913 pub fn add(&mut self, order: OwnBookOrder) {
915 debug_assert_eq!(order.price, self.price.value);
916
917 self.orders.insert(order.client_order_id, order);
918 }
919
920 pub fn update(&mut self, order: OwnBookOrder) {
923 debug_assert_eq!(order.price, self.price.value);
924
925 if order.size.is_zero() {
926 self.orders.shift_remove(&order.client_order_id);
927 } else {
928 self.orders[&order.client_order_id] = order;
929 }
930 }
931
932 pub fn delete(&mut self, client_order_id: &ClientOrderId) -> anyhow::Result<()> {
938 if self.orders.shift_remove(client_order_id).is_none() {
939 anyhow::bail!("Order {client_order_id} not found for delete");
941 }
942 Ok(())
943 }
944}
945
946impl PartialEq for OwnBookLevel {
947 fn eq(&self, other: &Self) -> bool {
948 self.price == other.price
949 }
950}
951
952impl Eq for OwnBookLevel {}
953
954impl PartialOrd for OwnBookLevel {
955 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
956 Some(self.cmp(other))
957 }
958}
959
960impl Ord for OwnBookLevel {
961 fn cmp(&self, other: &Self) -> Ordering {
962 self.price.cmp(&other.price)
963 }
964}
965
966#[must_use]
967pub fn should_handle_own_book_order(order: &OrderAny) -> bool {
968 order.has_price()
969 && order.time_in_force() != TimeInForce::Ioc
970 && order.time_in_force() != TimeInForce::Fok
971}