1use crate::cash_flow::CashFlow;
4use crate::margin::MarginEffect;
5use crate::order::{
6 Order, TimeInForce, UnindexedOrder,
7 id::ClientOrderId,
8 state::{ActiveOrderState, Open},
9};
10use chrono::{DateTime, Utc};
11use fnv::{FnvHashMap, FnvHashSet};
12use rust_decimal::Decimal;
13use rustrade_instrument::{
14 Side, asset::name::AssetNameExchange, exchange::ExchangeId,
15 instrument::name::InstrumentNameExchange,
16};
17use serde::{Deserialize, Serialize};
18use std::collections::{BTreeMap, BTreeSet, hash_map::Entry};
19use thiserror::Error;
20
21pub type OpenOrder = Order<ExchangeId, InstrumentNameExchange, Open>;
23
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
32pub struct Reservation {
33 pub asset: AssetNameExchange,
35 pub amount: Decimal,
43 pub borrowed: Decimal,
47 pub excess_borrowed: Decimal,
51 pub effect: MarginEffect,
55 pub unreported_interest: Vec<CashFlow<AssetNameExchange, InstrumentNameExchange>>,
68}
69
70impl Reservation {
71 pub fn loan(&self) -> Decimal {
73 self.borrowed + self.excess_borrowed
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
83pub struct RestingOrder {
84 pub order: OpenOrder,
85 pub reservation: Option<Reservation>,
90}
91
92#[derive(Debug, Clone, Error)]
98#[error("an order is already resting under {}", .0.order.key.cid)]
99pub struct AlreadyResting(pub Box<RestingOrder>);
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
106struct QueuePosition {
107 price_rank: Decimal,
110 time_exchange: DateTime<Utc>,
112 seq: u64,
116}
117
118impl QueuePosition {
119 fn new(side: Side, price: Decimal, time_exchange: DateTime<Utc>, seq: u64) -> Self {
120 Self {
121 price_rank: match side {
122 Side::Buy => -price,
123 Side::Sell => price,
124 },
125 time_exchange,
126 seq,
127 }
128 }
129}
130
131#[derive(Debug, Default)]
188pub struct OpenOrders {
189 by_id: FnvHashMap<ClientOrderId, RestingOrder>,
190 queues: FnvHashMap<(InstrumentNameExchange, Side), BTreeMap<QueuePosition, ClientOrderId>>,
192 expiries: BTreeSet<(DateTime<Utc>, ClientOrderId)>,
199 held: FnvHashMap<InstrumentNameExchange, Vec<ClientOrderId>>,
202 unqueued: FnvHashSet<ClientOrderId>,
208 seq: u64,
210}
211
212fn deadline_of(order: &OpenOrder) -> Option<DateTime<Utc>> {
214 match order.time_in_force {
215 TimeInForce::GoodTillDate { expiry } => Some(expiry),
216 _ => None,
217 }
218}
219
220impl OpenOrders {
221 pub fn insert(
235 &mut self,
236 order: OpenOrder,
237 reservation: Option<Reservation>,
238 ) -> Result<(), AlreadyResting> {
239 self.index(order, reservation, true)
240 }
241
242 fn index(
245 &mut self,
246 order: OpenOrder,
247 reservation: Option<Reservation>,
248 queue: bool,
249 ) -> Result<(), AlreadyResting> {
250 let cid = order.key.cid.clone();
251 let vacant = match self.by_id.entry(cid.clone()) {
252 Entry::Occupied(_) => {
253 return Err(AlreadyResting(Box::new(RestingOrder {
254 order,
255 reservation,
256 })));
257 }
258 Entry::Vacant(vacant) => vacant,
259 };
260
261 if let Some(expiry) = deadline_of(&order) {
262 self.expiries.insert((expiry, cid.clone()));
263 }
264
265 if let Some(price) = order.price.filter(|_| queue) {
266 self.seq += 1;
267 self.queues
268 .entry((order.key.instrument.clone(), order.side))
269 .or_default()
270 .insert(
271 QueuePosition::new(order.side, price, order.state.time_exchange, self.seq),
272 cid,
273 );
274 }
275
276 vacant.insert(RestingOrder { order, reservation });
277 Ok(())
278 }
279
280 pub fn hold(
292 &mut self,
293 order: OpenOrder,
294 reservation: Option<Reservation>,
295 ) -> Result<(), AlreadyResting> {
296 let instrument = order.key.instrument.clone();
297 let cid = order.key.cid.clone();
298 let priced = order.price.is_some();
299 self.index(order, reservation, false)?;
300 if priced {
301 self.unqueued.insert(cid.clone());
302 }
303 self.held.entry(instrument).or_default().push(cid);
304 Ok(())
305 }
306
307 pub fn holds(&self, instrument: &InstrumentNameExchange) -> bool {
309 self.held.contains_key(instrument)
310 }
311
312 pub fn held_on(&self, instrument: &InstrumentNameExchange) -> &[ClientOrderId] {
317 self.held.get(instrument).map_or(&[], Vec::as_slice)
318 }
319
320 pub fn take_held(&mut self, instrument: &InstrumentNameExchange) -> Vec<ClientOrderId> {
325 self.held.remove(instrument).unwrap_or_default()
326 }
327
328 pub fn held(&self) -> Vec<ClientOrderId> {
332 let mut instruments = self.held.keys().collect::<Vec<_>>();
333 instruments.sort_unstable();
334 instruments
335 .into_iter()
336 .flat_map(|instrument| self.held[instrument].iter().cloned())
337 .collect()
338 }
339
340 pub fn contains(&self, cid: &ClientOrderId) -> bool {
342 self.by_id.contains_key(cid)
343 }
344
345 pub fn remove(&mut self, cid: &ClientOrderId) -> Option<RestingOrder> {
347 let resting = self.by_id.remove(cid)?;
348 self.unindex(&resting.order);
349 Some(resting)
350 }
351
352 pub fn expired_as_of(&self, now: DateTime<Utc>) -> Vec<ClientOrderId> {
363 self.expiries
364 .iter()
365 .take_while(|(expiry, _)| *expiry <= now)
366 .map(|(_, cid)| cid.clone())
367 .collect()
368 }
369
370 pub fn next_expiry(&self) -> Option<DateTime<Utc>> {
372 self.expiries.first().map(|(expiry, _)| *expiry)
373 }
374
375 fn unindex(&mut self, order: &OpenOrder) {
377 if self.unqueued.remove(&order.key.cid) || order.price.is_none() {
380 self.unhold(order);
381 } else {
382 self.dequeue(order);
383 }
384
385 if let Some(expiry) = deadline_of(order) {
386 self.expiries.remove(&(expiry, order.key.cid.clone()));
387 }
388 }
389
390 pub fn get(&self, cid: &ClientOrderId) -> Option<&OpenOrder> {
391 self.by_id.get(cid).map(|resting| &resting.order)
392 }
393
394 pub fn len(&self) -> usize {
395 self.by_id.len()
396 }
397
398 pub fn is_empty(&self) -> bool {
399 self.by_id.is_empty()
400 }
401
402 pub fn iter(&self) -> impl Iterator<Item = &OpenOrder> + '_ {
407 self.by_id.values().map(|resting| &resting.order)
408 }
409
410 pub fn resting(
416 &self,
417 instrument: &InstrumentNameExchange,
418 side: Side,
419 ) -> impl Iterator<Item = &OpenOrder> + '_ {
420 self.queues
421 .get(&(instrument.clone(), side))
422 .into_iter()
423 .flat_map(|queue| queue.values())
424 .filter_map(|cid| self.by_id.get(cid))
426 .map(|resting| &resting.order)
427 }
428
429 fn unhold(&mut self, order: &OpenOrder) {
434 let Some(held) = self.held.get_mut(&order.key.instrument) else {
435 return;
436 };
437 held.retain(|cid| *cid != order.key.cid);
438 if held.is_empty() {
439 self.held.remove(&order.key.instrument);
440 }
441 }
442
443 fn dequeue(&mut self, order: &OpenOrder) {
444 let Some(price) = order.price else {
445 return;
447 };
448
449 let key = (order.key.instrument.clone(), order.side);
450 let Some(queue) = self.queues.get_mut(&key) else {
451 return;
452 };
453
454 let position = queue
458 .range(QueuePosition::new(order.side, price, order.state.time_exchange, 0)..)
459 .find(|(_, queued)| *queued == &order.key.cid)
460 .map(|(position, _)| *position);
461
462 debug_assert!(
469 position.is_some(),
470 "OpenOrders lost the queue position of {} ({} {price} at {}): its ranking fields were \
471 mutated while it was on the book",
472 order.key.cid,
473 order.side,
474 order.state.time_exchange
475 );
476
477 if let Some(position) = position {
478 queue.remove(&position);
479 }
480
481 if queue.is_empty() {
482 self.queues.remove(&key);
483 }
484 }
485
486 pub fn entries(&self) -> Vec<OpenOrderEntry> {
493 let mut queued = self
494 .queues
495 .values()
496 .flat_map(|queue| queue.iter().map(|(position, cid)| (position.seq, cid)))
497 .collect::<Vec<_>>();
498 queued.sort_unstable_by_key(|(seq, _)| *seq);
499 let queued = queued
500 .into_iter()
501 .map(|(_, cid)| (cid.clone(), OrderPlacement::Queued));
502
503 let held = self.held().into_iter();
504 let mut taken = self
508 .unqueued
509 .iter()
510 .filter(|cid| {
511 self.by_id.get(*cid).is_some_and(|resting| {
512 !self.held_on(&resting.order.key.instrument).contains(cid)
513 })
514 })
515 .cloned()
516 .collect::<Vec<_>>();
517 taken.sort_unstable();
518 let held = held.chain(taken).map(|cid| (cid, OrderPlacement::Held));
519
520 let mut unranked = self
521 .by_id
522 .iter()
523 .filter(|(cid, resting)| {
524 resting.order.price.is_none()
525 && !self.held_on(&resting.order.key.instrument).contains(cid)
526 })
527 .map(|(cid, _)| cid.clone())
528 .collect::<Vec<_>>();
529 unranked.sort_unstable();
530 let unranked = unranked
531 .into_iter()
532 .map(|cid| (cid, OrderPlacement::Unranked));
533
534 queued
535 .chain(held)
536 .chain(unranked)
537 .filter_map(|(cid, placement)| {
538 self.by_id.get(&cid).map(|resting| OpenOrderEntry {
539 resting: resting.clone(),
540 placement,
541 })
542 })
543 .collect()
544 }
545
546 pub fn from_entries(
551 entries: impl IntoIterator<Item = OpenOrderEntry>,
552 ) -> Result<Self, AlreadyResting> {
553 let mut book = Self::default();
554 for OpenOrderEntry {
555 resting: RestingOrder { order, reservation },
556 placement,
557 } in entries
558 {
559 match placement {
560 OrderPlacement::Queued => book.insert(order, reservation)?,
561 OrderPlacement::Held => book.hold(order, reservation)?,
562 OrderPlacement::Unranked => book.index(order, reservation, false)?,
563 }
564 }
565 Ok(book)
566 }
567}
568
569#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
571#[serde(rename_all = "snake_case")]
572pub enum OrderPlacement {
573 Queued,
575 Held,
577 Unranked,
580}
581
582#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
585pub struct OpenOrderEntry {
586 pub resting: RestingOrder,
587 pub placement: OrderPlacement,
588}
589
590pub(super) fn as_open(order: UnindexedOrder) -> Option<OpenOrder> {
592 match order.state {
593 crate::order::state::OrderState::Active(ActiveOrderState::Open(open)) => Some(Order {
594 key: order.key,
595 side: order.side,
596 price: order.price,
597 quantity: order.quantity,
598 kind: order.kind,
599 time_in_force: order.time_in_force,
600 state: open,
601 }),
602 _ => None,
603 }
604}
605
606#[cfg(test)]
607#[allow(clippy::unwrap_used, clippy::expect_used)] mod tests {
609 use super::*;
610 use crate::order::{
611 OrderKey, OrderKind, TimeInForce,
612 id::{OrderId, StrategyId, VenueOrderId},
613 };
614 use rust_decimal_macros::dec;
615
616 fn at(millis: i64) -> DateTime<Utc> {
617 DateTime::from_timestamp_millis(millis).unwrap()
618 }
619
620 fn instrument() -> InstrumentNameExchange {
621 InstrumentNameExchange::new("btc_usdt")
622 }
623
624 fn gtd(cid: &str, expiry_millis: i64) -> OpenOrder {
625 let mut order = order(cid, Side::Buy, Some(dec!(100)), 1_000);
626 order.time_in_force = TimeInForce::GoodTillDate {
627 expiry: at(expiry_millis),
628 };
629 order
630 }
631
632 fn order(cid: &str, side: Side, price: Option<Decimal>, millis: i64) -> OpenOrder {
633 Order {
634 key: OrderKey {
635 exchange: ExchangeId::BinanceSpot,
636 instrument: instrument(),
637 strategy: StrategyId::new("test"),
638 cid: ClientOrderId::new(cid),
639 },
640 side,
641 price,
642 quantity: dec!(1),
643 kind: OrderKind::Limit,
644 time_in_force: TimeInForce::GoodUntilCancelled { post_only: false },
645 state: Open {
646 id: VenueOrderId::Assigned(OrderId::new(cid)),
647 time_exchange: at(millis),
648 filled_quantity: Decimal::ZERO,
649 },
650 }
651 }
652
653 fn book(orders: impl IntoIterator<Item = OpenOrder>) -> OpenOrders {
655 let mut open = OpenOrders::default();
656 for order in orders {
657 open.insert(order, None).unwrap();
658 }
659 open
660 }
661
662 fn cids(orders: impl Iterator<Item = &'static OpenOrder>) -> Vec<String> {
663 orders.map(|order| order.key.cid.0.to_string()).collect()
664 }
665
666 fn resting_cids(open: &OpenOrders, side: Side) -> Vec<String> {
667 open.resting(&instrument(), side)
668 .map(|order| order.key.cid.0.to_string())
669 .collect()
670 }
671
672 #[test]
674 fn buys_rest_highest_price_then_earliest_first() {
675 let open = book([
676 order("low", Side::Buy, Some(dec!(99)), 1_000),
677 order("high_late", Side::Buy, Some(dec!(101)), 3_000),
678 order("high_early", Side::Buy, Some(dec!(101)), 2_000),
679 order("mid", Side::Buy, Some(dec!(100)), 500),
680 ]);
681
682 assert_eq!(
683 resting_cids(&open, Side::Buy),
684 ["high_early", "high_late", "mid", "low"],
685 "price first, then arrival — not insertion order, and not the map's order"
686 );
687 }
688
689 #[test]
691 fn sells_rest_lowest_price_then_earliest_first() {
692 let open = book([
693 order("high", Side::Sell, Some(dec!(101)), 1_000),
694 order("low_late", Side::Sell, Some(dec!(99)), 3_000),
695 order("low_early", Side::Sell, Some(dec!(99)), 2_000),
696 ]);
697
698 assert_eq!(
699 resting_cids(&open, Side::Sell),
700 ["low_early", "low_late", "high"]
701 );
702 }
703
704 #[test]
709 fn orders_at_the_same_price_and_instant_both_rest() {
710 let open = book([
711 order("first", Side::Buy, Some(dec!(100)), 1_000),
712 order("second", Side::Buy, Some(dec!(100)), 1_000),
713 ]);
714
715 assert_eq!(open.len(), 2);
716 assert_eq!(
717 resting_cids(&open, Side::Buy),
718 ["first", "second"],
719 "the tie is broken by insertion, which is deterministic"
720 );
721 }
722
723 #[test]
724 fn the_two_sides_are_separate_queues() {
725 let open = book([
726 order("bid", Side::Buy, Some(dec!(99)), 1_000),
727 order("ask", Side::Sell, Some(dec!(101)), 1_000),
728 ]);
729
730 assert_eq!(resting_cids(&open, Side::Buy), ["bid"]);
731 assert_eq!(resting_cids(&open, Side::Sell), ["ask"]);
732 }
733
734 #[test]
735 fn a_removed_order_leaves_the_queue() {
736 let mut open = book([
737 order("a", Side::Buy, Some(dec!(100)), 1_000),
738 order("b", Side::Buy, Some(dec!(99)), 1_000),
739 ]);
740
741 let removed = open.remove(&ClientOrderId::new("a")).expect("a is open");
742 assert_eq!(removed.order.key.cid, ClientOrderId::new("a"));
743
744 assert_eq!(resting_cids(&open, Side::Buy), ["b"]);
745 assert_eq!(open.len(), 1);
746 assert!(open.get(&ClientOrderId::new("a")).is_none());
747 assert!(open.remove(&ClientOrderId::new("a")).is_none());
748 }
749
750 #[test]
752 fn emptying_the_book_empties_the_queues() {
753 let mut open = book([
754 order("a", Side::Buy, Some(dec!(100)), 1_000),
755 order("b", Side::Buy, Some(dec!(99)), 2_000),
756 ]);
757
758 open.remove(&ClientOrderId::new("a"));
759 open.remove(&ClientOrderId::new("b"));
760
761 assert!(open.is_empty());
762 assert!(resting_cids(&open, Side::Buy).is_empty());
763
764 open.insert(order("c", Side::Buy, Some(dec!(98)), 3_000), None)
765 .unwrap();
766 assert_eq!(resting_cids(&open, Side::Buy), ["c"]);
767 }
768
769 #[test]
772 fn a_second_order_under_a_resting_id_is_refused_and_the_first_keeps_its_place() {
773 let mut open = book([
774 order("taken", Side::Buy, Some(dec!(100)), 1_000),
775 order("other", Side::Buy, Some(dec!(99)), 1_000),
776 ]);
777 let held = Reservation {
778 asset: AssetNameExchange::new("usdt"),
779 amount: dec!(98),
780 borrowed: Decimal::ZERO,
781 excess_borrowed: Decimal::ZERO,
782 effect: MarginEffect::NoSideEffect,
783 unreported_interest: Vec::new(),
784 };
785
786 let Err(AlreadyResting(refused)) = open.insert(
787 order("taken", Side::Buy, Some(dec!(98)), 2_000),
788 Some(held.clone()),
789 ) else {
790 panic!("an id already resting is refused");
791 };
792
793 assert_eq!(
794 refused.order.price,
795 Some(dec!(98)),
796 "the refused order comes back"
797 );
798 assert_eq!(
799 refused.reservation,
800 Some(held),
801 "and so does what the caller may already hold against it"
802 );
803 assert_eq!(open.len(), 2, "nothing was added");
804 assert_eq!(
805 open.get(&ClientOrderId::new("taken"))
806 .map(|order| order.price),
807 Some(Some(dec!(100))),
808 "the resting order is the one still held"
809 );
810 assert_eq!(
811 resting_cids(&open, Side::Buy),
812 ["taken", "other"],
813 "at its own price and place, appearing exactly once"
814 );
815 }
816
817 #[test]
819 fn an_order_without_a_price_is_held_but_never_rests() {
820 let open = book([
821 order("priceless", Side::Buy, None, 1_000),
822 order("priced", Side::Buy, Some(dec!(100)), 1_000),
823 ]);
824
825 assert_eq!(open.len(), 2, "a snapshot must still report it");
826 assert!(open.get(&ClientOrderId::new("priceless")).is_some());
827 assert_eq!(
828 resting_cids(&open, Side::Buy),
829 ["priced"],
830 "an order with no price to wait at has nothing to wait for"
831 );
832 }
833
834 #[test]
836 fn only_orders_past_their_deadline_are_swept() {
837 let open = book([
838 order("no_deadline", Side::Buy, Some(dec!(100)), 1_000),
839 gtd("due", 5_000),
840 gtd("later", 9_000),
841 ]);
842
843 assert!(
844 open.expired_as_of(at(4_999)).is_empty(),
845 "nothing is due before the earliest deadline"
846 );
847 assert_eq!(
848 open.expired_as_of(at(5_000)),
849 [ClientOrderId::new("due")],
850 "a deadline is reached at its instant, not after it"
851 );
852 assert_eq!(
853 open.expired_as_of(at(9_999)),
854 [ClientOrderId::new("due"), ClientOrderId::new("later")],
855 "earliest deadline first, and an order without one is never swept"
856 );
857 }
858
859 #[test]
864 fn orders_sharing_a_deadline_are_both_swept_in_id_order() {
865 let open = book([gtd("b", 5_000), gtd("a", 5_000)]);
866
867 assert_eq!(
868 open.expired_as_of(at(5_000)),
869 [ClientOrderId::new("a"), ClientOrderId::new("b")],
870 "the tie is broken by id, which does not depend on map iteration"
871 );
872 }
873
874 #[test]
876 fn a_removed_order_is_no_longer_swept() {
877 let mut open = book([gtd("gone", 5_000), gtd("stays", 5_000)]);
878
879 open.remove(&ClientOrderId::new("gone"));
880
881 assert_eq!(
882 open.expired_as_of(at(5_000)),
883 [ClientOrderId::new("stays")],
884 "a deadline that outlived its order would retire an order twice"
885 );
886 }
887
888 #[test]
890 fn a_refused_order_leaves_the_resting_ones_deadline() {
891 let mut open = book([gtd("taken", 5_000)]);
892
893 assert!(open.insert(gtd("taken", 9_000), None).is_err());
894
895 assert_eq!(
896 open.expired_as_of(at(9_000)),
897 [ClientOrderId::new("taken")],
898 "the resting order's deadline, once: the refused one's never reached the index"
899 );
900 assert_eq!(open.expired_as_of(at(5_000)), [ClientOrderId::new("taken")]);
901 }
902
903 #[test]
906 fn an_id_is_free_again_once_its_order_leaves_the_book() {
907 let mut open = book([gtd("reused", 5_000)]);
908 open.remove(&ClientOrderId::new("reused")).unwrap();
909
910 open.insert(order("reused", Side::Buy, Some(dec!(100)), 6_000), None)
911 .unwrap();
912
913 assert!(
914 open.expired_as_of(at(9_000)).is_empty(),
915 "the first order's deadline left with it"
916 );
917 assert_eq!(resting_cids(&open, Side::Buy), ["reused"]);
918 }
919
920 #[test]
921 fn iter_reports_every_open_order() {
922 let open = book([
923 order("a", Side::Buy, Some(dec!(100)), 1_000),
924 order("b", Side::Sell, Some(dec!(101)), 1_000),
925 order("c", Side::Buy, None, 1_000),
926 ]);
927
928 let mut all = cids(Vec::leak(open.iter().cloned().collect::<Vec<_>>()).iter());
929 all.sort();
930 assert_eq!(all, ["a", "b", "c"]);
931 }
932
933 fn market(cid: &str, instrument: &str, millis: i64) -> OpenOrder {
934 let mut order = order(cid, Side::Buy, None, millis);
935 order.key.instrument = InstrumentNameExchange::new(instrument);
936 order.kind = OrderKind::Market;
937 order
938 }
939
940 fn ids(cids: Vec<ClientOrderId>) -> Vec<String> {
941 cids.into_iter().map(|cid| cid.0.to_string()).collect()
942 }
943
944 #[test]
948 fn a_book_rebuilt_from_its_entries_is_the_same_book() {
949 let mut open = OpenOrders::default();
950 open.insert(order("second_cid", Side::Buy, Some(dec!(100)), 1_000), None)
952 .unwrap();
953 open.insert(order("first_cid", Side::Buy, Some(dec!(100)), 1_000), None)
954 .unwrap();
955 open.insert(order("best", Side::Buy, Some(dec!(101)), 2_000), None)
956 .unwrap();
957 open.insert(gtd("deadline", 5_000), None).unwrap();
958 open.insert(order("ask", Side::Sell, Some(dec!(105)), 1_000), None)
959 .unwrap();
960 open.insert(order("unpriced", Side::Buy, None, 1_000), None)
961 .unwrap();
962 open.hold(market("held_2", "btc_usdt", 3_000), None)
963 .unwrap();
964 open.hold(market("held_1", "eth_usdt", 4_000), None)
965 .unwrap();
966 let mut held_limit = order("held_limit", Side::Sell, Some(dec!(104)), 4_500);
967 held_limit.key.instrument = instrument();
968 open.hold(held_limit, None).unwrap();
969
970 let entries = open.entries();
971 let rebuilt = OpenOrders::from_entries(entries.clone()).unwrap();
972
973 assert_eq!(rebuilt.entries(), entries);
974 for side in [Side::Buy, Side::Sell] {
975 assert_eq!(resting_cids(&rebuilt, side), resting_cids(&open, side));
976 }
977 assert_eq!(
978 resting_cids(&rebuilt, Side::Buy),
979 ["best", "second_cid", "first_cid", "deadline"]
980 );
981 assert_eq!(ids(rebuilt.held()), ids(open.held()));
982 assert_eq!(ids(rebuilt.held()), ["held_2", "held_limit", "held_1"]);
983 assert_eq!(rebuilt.next_expiry(), Some(at(5_000)));
984 assert_eq!(rebuilt.len(), open.len());
985 assert_eq!(
986 entries
987 .iter()
988 .filter(|entry| entry.placement == OrderPlacement::Unranked)
989 .map(|entry| entry.resting.order.key.cid.0.as_str())
990 .collect::<Vec<_>>(),
991 ["unpriced"]
992 );
993 }
994
995 #[test]
996 fn a_book_whose_entries_repeat_an_id_is_refused() {
997 let entry = OpenOrderEntry {
998 resting: RestingOrder {
999 order: order("twice", Side::Buy, Some(dec!(100)), 1_000),
1000 reservation: None,
1001 },
1002 placement: OrderPlacement::Queued,
1003 };
1004 let refused = OpenOrders::from_entries([entry.clone(), entry]).unwrap_err();
1005 assert_eq!(refused.0.order.key.cid, ClientOrderId::new("twice"));
1006 }
1007
1008 #[test]
1009 fn held_orders_are_handed_out_once_per_instrument_in_arrival_order() {
1010 let mut open = OpenOrders::default();
1011 for order in [
1012 market("b1", "eth_usdt", 1_000),
1013 market("a1", "btc_usdt", 2_000),
1014 market("b2", "eth_usdt", 3_000),
1015 ] {
1016 open.hold(order, None).unwrap();
1017 }
1018
1019 assert!(open.holds(&InstrumentNameExchange::new("eth_usdt")));
1020 assert_eq!(
1021 ids(open.held()),
1022 ["a1", "b1", "b2"],
1023 "by instrument, then earliest arrival first"
1024 );
1025 assert_eq!(open.len(), 3, "a snapshot reports what is held");
1026 assert!(
1027 resting_cids(&open, Side::Buy).is_empty(),
1028 "and matching never reaches it"
1029 );
1030
1031 let eth = InstrumentNameExchange::new("eth_usdt");
1032 assert_eq!(ids(open.take_held(ð)), ["b1", "b2"]);
1033 assert!(!open.holds(ð));
1034 assert!(open.take_held(ð).is_empty(), "handed out once");
1035 assert_eq!(open.len(), 3, "still in the map until removed");
1036 }
1037
1038 #[test]
1039 fn removing_a_held_order_stops_it_being_held() {
1040 let mut open = OpenOrders::default();
1041 open.hold(market("gone", "btc_usdt", 1_000), None).unwrap();
1042 open.hold(market("kept", "btc_usdt", 2_000), None).unwrap();
1043
1044 assert!(open.remove(&ClientOrderId::new("gone")).is_some());
1045 assert_eq!(ids(open.held()), ["kept"]);
1046
1047 assert!(open.remove(&ClientOrderId::new("kept")).is_some());
1048 assert!(
1049 !open.holds(&InstrumentNameExchange::new("btc_usdt")),
1050 "an instrument with nothing left held holds nothing"
1051 );
1052 }
1053
1054 #[test]
1055 fn a_held_order_under_a_resting_id_is_refused_and_nothing_changes() {
1056 let mut open = book([order("taken", Side::Buy, Some(dec!(100)), 1_000)]);
1057
1058 let refused = open.hold(market("taken", "btc_usdt", 2_000), None);
1059
1060 assert!(refused.is_err());
1061 assert!(open.held().is_empty());
1062 assert_eq!(resting_cids(&open, Side::Buy), ["taken"]);
1063 }
1064
1065 #[test]
1066 fn a_held_limit_order_stays_off_the_queues_until_it_is_removed_and_booked_again() {
1067 let mut open = OpenOrders::default();
1068 let limit = order("limit", Side::Buy, Some(dec!(100)), 1_000);
1069 open.hold(limit.clone(), None).unwrap();
1070
1071 assert!(open.holds(&instrument()));
1072 assert!(
1073 resting_cids(&open, Side::Buy).is_empty(),
1074 "held with its price, but never matched"
1075 );
1076
1077 assert_eq!(ids(open.take_held(&instrument())), ["limit"]);
1078 assert!(
1079 resting_cids(&open, Side::Buy).is_empty(),
1080 "handed out, it is still off the queues"
1081 );
1082 assert!(open.remove(&ClientOrderId::new("limit")).is_some());
1083
1084 open.insert(limit, None).unwrap();
1085 assert_eq!(resting_cids(&open, Side::Buy), ["limit"]);
1086 assert!(open.remove(&ClientOrderId::new("limit")).is_some());
1087 assert!(open.is_empty());
1088 }
1089
1090 #[test]
1091 fn a_held_limit_order_cancelled_before_its_release_leaves_nothing_behind() {
1092 let mut open = book([order("resting", Side::Buy, Some(dec!(100)), 1_000)]);
1093 open.hold(order("held", Side::Buy, Some(dec!(100)), 2_000), None)
1094 .unwrap();
1095
1096 assert!(open.remove(&ClientOrderId::new("held")).is_some());
1097
1098 assert!(!open.holds(&instrument()));
1099 assert_eq!(resting_cids(&open, Side::Buy), ["resting"]);
1100 }
1101}