1use crate::margin::MarginEffect;
4use crate::order::{
5 Order, TimeInForce, UnindexedOrder,
6 id::ClientOrderId,
7 state::{ActiveOrderState, Open},
8};
9use chrono::{DateTime, Utc};
10use fnv::{FnvHashMap, FnvHashSet};
11use rust_decimal::Decimal;
12use rustrade_instrument::{
13 Side, asset::name::AssetNameExchange, exchange::ExchangeId,
14 instrument::name::InstrumentNameExchange,
15};
16use std::collections::{BTreeMap, BTreeSet, hash_map::Entry};
17use thiserror::Error;
18
19pub type OpenOrder = Order<ExchangeId, InstrumentNameExchange, Open>;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct Reservation {
31 pub asset: AssetNameExchange,
33 pub amount: Decimal,
41 pub borrowed: Decimal,
45 pub effect: MarginEffect,
49}
50
51#[derive(Debug, Clone)]
57pub struct RestingOrder {
58 pub order: OpenOrder,
59 pub reservation: Option<Reservation>,
64}
65
66#[derive(Debug, Clone, Error)]
72#[error("an order is already resting under {}", .0.order.key.cid)]
73pub struct AlreadyResting(pub Box<RestingOrder>);
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
80struct QueuePosition {
81 price_rank: Decimal,
84 time_exchange: DateTime<Utc>,
86 seq: u64,
90}
91
92impl QueuePosition {
93 fn new(side: Side, price: Decimal, time_exchange: DateTime<Utc>, seq: u64) -> Self {
94 Self {
95 price_rank: match side {
96 Side::Buy => -price,
97 Side::Sell => price,
98 },
99 time_exchange,
100 seq,
101 }
102 }
103}
104
105#[derive(Debug, Default)]
162pub struct OpenOrders {
163 by_id: FnvHashMap<ClientOrderId, RestingOrder>,
164 queues: FnvHashMap<(InstrumentNameExchange, Side), BTreeMap<QueuePosition, ClientOrderId>>,
166 expiries: BTreeSet<(DateTime<Utc>, ClientOrderId)>,
173 held: FnvHashMap<InstrumentNameExchange, Vec<ClientOrderId>>,
176 unqueued: FnvHashSet<ClientOrderId>,
182 seq: u64,
184}
185
186fn deadline_of(order: &OpenOrder) -> Option<DateTime<Utc>> {
188 match order.time_in_force {
189 TimeInForce::GoodTillDate { expiry } => Some(expiry),
190 _ => None,
191 }
192}
193
194impl OpenOrders {
195 pub fn insert(
209 &mut self,
210 order: OpenOrder,
211 reservation: Option<Reservation>,
212 ) -> Result<(), AlreadyResting> {
213 self.index(order, reservation, true)
214 }
215
216 fn index(
219 &mut self,
220 order: OpenOrder,
221 reservation: Option<Reservation>,
222 queue: bool,
223 ) -> Result<(), AlreadyResting> {
224 let cid = order.key.cid.clone();
225 let vacant = match self.by_id.entry(cid.clone()) {
226 Entry::Occupied(_) => {
227 return Err(AlreadyResting(Box::new(RestingOrder {
228 order,
229 reservation,
230 })));
231 }
232 Entry::Vacant(vacant) => vacant,
233 };
234
235 if let Some(expiry) = deadline_of(&order) {
236 self.expiries.insert((expiry, cid.clone()));
237 }
238
239 if let Some(price) = order.price.filter(|_| queue) {
240 self.seq += 1;
241 self.queues
242 .entry((order.key.instrument.clone(), order.side))
243 .or_default()
244 .insert(
245 QueuePosition::new(order.side, price, order.state.time_exchange, self.seq),
246 cid,
247 );
248 }
249
250 vacant.insert(RestingOrder { order, reservation });
251 Ok(())
252 }
253
254 pub fn hold(
266 &mut self,
267 order: OpenOrder,
268 reservation: Option<Reservation>,
269 ) -> Result<(), AlreadyResting> {
270 let instrument = order.key.instrument.clone();
271 let cid = order.key.cid.clone();
272 let priced = order.price.is_some();
273 self.index(order, reservation, false)?;
274 if priced {
275 self.unqueued.insert(cid.clone());
276 }
277 self.held.entry(instrument).or_default().push(cid);
278 Ok(())
279 }
280
281 pub fn holds(&self, instrument: &InstrumentNameExchange) -> bool {
283 self.held.contains_key(instrument)
284 }
285
286 pub fn held_on(&self, instrument: &InstrumentNameExchange) -> &[ClientOrderId] {
291 self.held.get(instrument).map_or(&[], Vec::as_slice)
292 }
293
294 pub fn take_held(&mut self, instrument: &InstrumentNameExchange) -> Vec<ClientOrderId> {
299 self.held.remove(instrument).unwrap_or_default()
300 }
301
302 pub fn held(&self) -> Vec<ClientOrderId> {
306 let mut instruments = self.held.keys().collect::<Vec<_>>();
307 instruments.sort_unstable();
308 instruments
309 .into_iter()
310 .flat_map(|instrument| self.held[instrument].iter().cloned())
311 .collect()
312 }
313
314 pub fn contains(&self, cid: &ClientOrderId) -> bool {
316 self.by_id.contains_key(cid)
317 }
318
319 pub fn remove(&mut self, cid: &ClientOrderId) -> Option<RestingOrder> {
321 let resting = self.by_id.remove(cid)?;
322 self.unindex(&resting.order);
323 Some(resting)
324 }
325
326 pub fn expired_as_of(&self, now: DateTime<Utc>) -> Vec<ClientOrderId> {
337 self.expiries
338 .iter()
339 .take_while(|(expiry, _)| *expiry <= now)
340 .map(|(_, cid)| cid.clone())
341 .collect()
342 }
343
344 pub fn next_expiry(&self) -> Option<DateTime<Utc>> {
346 self.expiries.first().map(|(expiry, _)| *expiry)
347 }
348
349 fn unindex(&mut self, order: &OpenOrder) {
351 if self.unqueued.remove(&order.key.cid) || order.price.is_none() {
354 self.unhold(order);
355 } else {
356 self.dequeue(order);
357 }
358
359 if let Some(expiry) = deadline_of(order) {
360 self.expiries.remove(&(expiry, order.key.cid.clone()));
361 }
362 }
363
364 pub fn get(&self, cid: &ClientOrderId) -> Option<&OpenOrder> {
365 self.by_id.get(cid).map(|resting| &resting.order)
366 }
367
368 pub fn len(&self) -> usize {
369 self.by_id.len()
370 }
371
372 pub fn is_empty(&self) -> bool {
373 self.by_id.is_empty()
374 }
375
376 pub fn iter(&self) -> impl Iterator<Item = &OpenOrder> + '_ {
381 self.by_id.values().map(|resting| &resting.order)
382 }
383
384 pub fn resting(
390 &self,
391 instrument: &InstrumentNameExchange,
392 side: Side,
393 ) -> impl Iterator<Item = &OpenOrder> + '_ {
394 self.queues
395 .get(&(instrument.clone(), side))
396 .into_iter()
397 .flat_map(|queue| queue.values())
398 .filter_map(|cid| self.by_id.get(cid))
400 .map(|resting| &resting.order)
401 }
402
403 fn unhold(&mut self, order: &OpenOrder) {
408 let Some(held) = self.held.get_mut(&order.key.instrument) else {
409 return;
410 };
411 held.retain(|cid| *cid != order.key.cid);
412 if held.is_empty() {
413 self.held.remove(&order.key.instrument);
414 }
415 }
416
417 fn dequeue(&mut self, order: &OpenOrder) {
418 let Some(price) = order.price else {
419 return;
421 };
422
423 let key = (order.key.instrument.clone(), order.side);
424 let Some(queue) = self.queues.get_mut(&key) else {
425 return;
426 };
427
428 let position = queue
432 .range(QueuePosition::new(order.side, price, order.state.time_exchange, 0)..)
433 .find(|(_, queued)| *queued == &order.key.cid)
434 .map(|(position, _)| *position);
435
436 debug_assert!(
443 position.is_some(),
444 "OpenOrders lost the queue position of {} ({} {price} at {}): its ranking fields were \
445 mutated while it was on the book",
446 order.key.cid,
447 order.side,
448 order.state.time_exchange
449 );
450
451 if let Some(position) = position {
452 queue.remove(&position);
453 }
454
455 if queue.is_empty() {
456 self.queues.remove(&key);
457 }
458 }
459}
460
461pub(super) fn as_open(order: UnindexedOrder) -> Option<OpenOrder> {
463 match order.state {
464 crate::order::state::OrderState::Active(ActiveOrderState::Open(open)) => Some(Order {
465 key: order.key,
466 side: order.side,
467 price: order.price,
468 quantity: order.quantity,
469 kind: order.kind,
470 time_in_force: order.time_in_force,
471 state: open,
472 }),
473 _ => None,
474 }
475}
476
477#[cfg(test)]
478#[allow(clippy::unwrap_used, clippy::expect_used)] mod tests {
480 use super::*;
481 use crate::order::{
482 OrderKey, OrderKind, TimeInForce,
483 id::{OrderId, StrategyId, VenueOrderId},
484 };
485 use rust_decimal_macros::dec;
486
487 fn at(millis: i64) -> DateTime<Utc> {
488 DateTime::from_timestamp_millis(millis).unwrap()
489 }
490
491 fn instrument() -> InstrumentNameExchange {
492 InstrumentNameExchange::new("btc_usdt")
493 }
494
495 fn gtd(cid: &str, expiry_millis: i64) -> OpenOrder {
496 let mut order = order(cid, Side::Buy, Some(dec!(100)), 1_000);
497 order.time_in_force = TimeInForce::GoodTillDate {
498 expiry: at(expiry_millis),
499 };
500 order
501 }
502
503 fn order(cid: &str, side: Side, price: Option<Decimal>, millis: i64) -> OpenOrder {
504 Order {
505 key: OrderKey {
506 exchange: ExchangeId::BinanceSpot,
507 instrument: instrument(),
508 strategy: StrategyId::new("test"),
509 cid: ClientOrderId::new(cid),
510 },
511 side,
512 price,
513 quantity: dec!(1),
514 kind: OrderKind::Limit,
515 time_in_force: TimeInForce::GoodUntilCancelled { post_only: false },
516 state: Open {
517 id: VenueOrderId::Assigned(OrderId::new(cid)),
518 time_exchange: at(millis),
519 filled_quantity: Decimal::ZERO,
520 },
521 }
522 }
523
524 fn book(orders: impl IntoIterator<Item = OpenOrder>) -> OpenOrders {
526 let mut open = OpenOrders::default();
527 for order in orders {
528 open.insert(order, None).unwrap();
529 }
530 open
531 }
532
533 fn cids(orders: impl Iterator<Item = &'static OpenOrder>) -> Vec<String> {
534 orders.map(|order| order.key.cid.0.to_string()).collect()
535 }
536
537 fn resting_cids(open: &OpenOrders, side: Side) -> Vec<String> {
538 open.resting(&instrument(), side)
539 .map(|order| order.key.cid.0.to_string())
540 .collect()
541 }
542
543 #[test]
545 fn buys_rest_highest_price_then_earliest_first() {
546 let open = book([
547 order("low", Side::Buy, Some(dec!(99)), 1_000),
548 order("high_late", Side::Buy, Some(dec!(101)), 3_000),
549 order("high_early", Side::Buy, Some(dec!(101)), 2_000),
550 order("mid", Side::Buy, Some(dec!(100)), 500),
551 ]);
552
553 assert_eq!(
554 resting_cids(&open, Side::Buy),
555 ["high_early", "high_late", "mid", "low"],
556 "price first, then arrival — not insertion order, and not the map's order"
557 );
558 }
559
560 #[test]
562 fn sells_rest_lowest_price_then_earliest_first() {
563 let open = book([
564 order("high", Side::Sell, Some(dec!(101)), 1_000),
565 order("low_late", Side::Sell, Some(dec!(99)), 3_000),
566 order("low_early", Side::Sell, Some(dec!(99)), 2_000),
567 ]);
568
569 assert_eq!(
570 resting_cids(&open, Side::Sell),
571 ["low_early", "low_late", "high"]
572 );
573 }
574
575 #[test]
580 fn orders_at_the_same_price_and_instant_both_rest() {
581 let open = book([
582 order("first", Side::Buy, Some(dec!(100)), 1_000),
583 order("second", Side::Buy, Some(dec!(100)), 1_000),
584 ]);
585
586 assert_eq!(open.len(), 2);
587 assert_eq!(
588 resting_cids(&open, Side::Buy),
589 ["first", "second"],
590 "the tie is broken by insertion, which is deterministic"
591 );
592 }
593
594 #[test]
595 fn the_two_sides_are_separate_queues() {
596 let open = book([
597 order("bid", Side::Buy, Some(dec!(99)), 1_000),
598 order("ask", Side::Sell, Some(dec!(101)), 1_000),
599 ]);
600
601 assert_eq!(resting_cids(&open, Side::Buy), ["bid"]);
602 assert_eq!(resting_cids(&open, Side::Sell), ["ask"]);
603 }
604
605 #[test]
606 fn a_removed_order_leaves_the_queue() {
607 let mut open = book([
608 order("a", Side::Buy, Some(dec!(100)), 1_000),
609 order("b", Side::Buy, Some(dec!(99)), 1_000),
610 ]);
611
612 let removed = open.remove(&ClientOrderId::new("a")).expect("a is open");
613 assert_eq!(removed.order.key.cid, ClientOrderId::new("a"));
614
615 assert_eq!(resting_cids(&open, Side::Buy), ["b"]);
616 assert_eq!(open.len(), 1);
617 assert!(open.get(&ClientOrderId::new("a")).is_none());
618 assert!(open.remove(&ClientOrderId::new("a")).is_none());
619 }
620
621 #[test]
623 fn emptying_the_book_empties_the_queues() {
624 let mut open = book([
625 order("a", Side::Buy, Some(dec!(100)), 1_000),
626 order("b", Side::Buy, Some(dec!(99)), 2_000),
627 ]);
628
629 open.remove(&ClientOrderId::new("a"));
630 open.remove(&ClientOrderId::new("b"));
631
632 assert!(open.is_empty());
633 assert!(resting_cids(&open, Side::Buy).is_empty());
634
635 open.insert(order("c", Side::Buy, Some(dec!(98)), 3_000), None)
636 .unwrap();
637 assert_eq!(resting_cids(&open, Side::Buy), ["c"]);
638 }
639
640 #[test]
643 fn a_second_order_under_a_resting_id_is_refused_and_the_first_keeps_its_place() {
644 let mut open = book([
645 order("taken", Side::Buy, Some(dec!(100)), 1_000),
646 order("other", Side::Buy, Some(dec!(99)), 1_000),
647 ]);
648 let held = Reservation {
649 asset: AssetNameExchange::new("usdt"),
650 amount: dec!(98),
651 borrowed: Decimal::ZERO,
652 effect: MarginEffect::NoSideEffect,
653 };
654
655 let Err(AlreadyResting(refused)) = open.insert(
656 order("taken", Side::Buy, Some(dec!(98)), 2_000),
657 Some(held.clone()),
658 ) else {
659 panic!("an id already resting is refused");
660 };
661
662 assert_eq!(
663 refused.order.price,
664 Some(dec!(98)),
665 "the refused order comes back"
666 );
667 assert_eq!(
668 refused.reservation,
669 Some(held),
670 "and so does what the caller may already hold against it"
671 );
672 assert_eq!(open.len(), 2, "nothing was added");
673 assert_eq!(
674 open.get(&ClientOrderId::new("taken"))
675 .map(|order| order.price),
676 Some(Some(dec!(100))),
677 "the resting order is the one still held"
678 );
679 assert_eq!(
680 resting_cids(&open, Side::Buy),
681 ["taken", "other"],
682 "at its own price and place, appearing exactly once"
683 );
684 }
685
686 #[test]
688 fn an_order_without_a_price_is_held_but_never_rests() {
689 let open = book([
690 order("priceless", Side::Buy, None, 1_000),
691 order("priced", Side::Buy, Some(dec!(100)), 1_000),
692 ]);
693
694 assert_eq!(open.len(), 2, "a snapshot must still report it");
695 assert!(open.get(&ClientOrderId::new("priceless")).is_some());
696 assert_eq!(
697 resting_cids(&open, Side::Buy),
698 ["priced"],
699 "an order with no price to wait at has nothing to wait for"
700 );
701 }
702
703 #[test]
705 fn only_orders_past_their_deadline_are_swept() {
706 let open = book([
707 order("no_deadline", Side::Buy, Some(dec!(100)), 1_000),
708 gtd("due", 5_000),
709 gtd("later", 9_000),
710 ]);
711
712 assert!(
713 open.expired_as_of(at(4_999)).is_empty(),
714 "nothing is due before the earliest deadline"
715 );
716 assert_eq!(
717 open.expired_as_of(at(5_000)),
718 [ClientOrderId::new("due")],
719 "a deadline is reached at its instant, not after it"
720 );
721 assert_eq!(
722 open.expired_as_of(at(9_999)),
723 [ClientOrderId::new("due"), ClientOrderId::new("later")],
724 "earliest deadline first, and an order without one is never swept"
725 );
726 }
727
728 #[test]
733 fn orders_sharing_a_deadline_are_both_swept_in_id_order() {
734 let open = book([gtd("b", 5_000), gtd("a", 5_000)]);
735
736 assert_eq!(
737 open.expired_as_of(at(5_000)),
738 [ClientOrderId::new("a"), ClientOrderId::new("b")],
739 "the tie is broken by id, which does not depend on map iteration"
740 );
741 }
742
743 #[test]
745 fn a_removed_order_is_no_longer_swept() {
746 let mut open = book([gtd("gone", 5_000), gtd("stays", 5_000)]);
747
748 open.remove(&ClientOrderId::new("gone"));
749
750 assert_eq!(
751 open.expired_as_of(at(5_000)),
752 [ClientOrderId::new("stays")],
753 "a deadline that outlived its order would retire an order twice"
754 );
755 }
756
757 #[test]
759 fn a_refused_order_leaves_the_resting_ones_deadline() {
760 let mut open = book([gtd("taken", 5_000)]);
761
762 assert!(open.insert(gtd("taken", 9_000), None).is_err());
763
764 assert_eq!(
765 open.expired_as_of(at(9_000)),
766 [ClientOrderId::new("taken")],
767 "the resting order's deadline, once: the refused one's never reached the index"
768 );
769 assert_eq!(open.expired_as_of(at(5_000)), [ClientOrderId::new("taken")]);
770 }
771
772 #[test]
775 fn an_id_is_free_again_once_its_order_leaves_the_book() {
776 let mut open = book([gtd("reused", 5_000)]);
777 open.remove(&ClientOrderId::new("reused")).unwrap();
778
779 open.insert(order("reused", Side::Buy, Some(dec!(100)), 6_000), None)
780 .unwrap();
781
782 assert!(
783 open.expired_as_of(at(9_000)).is_empty(),
784 "the first order's deadline left with it"
785 );
786 assert_eq!(resting_cids(&open, Side::Buy), ["reused"]);
787 }
788
789 #[test]
790 fn iter_reports_every_open_order() {
791 let open = book([
792 order("a", Side::Buy, Some(dec!(100)), 1_000),
793 order("b", Side::Sell, Some(dec!(101)), 1_000),
794 order("c", Side::Buy, None, 1_000),
795 ]);
796
797 let mut all = cids(Vec::leak(open.iter().cloned().collect::<Vec<_>>()).iter());
798 all.sort();
799 assert_eq!(all, ["a", "b", "c"]);
800 }
801
802 fn market(cid: &str, instrument: &str, millis: i64) -> OpenOrder {
803 let mut order = order(cid, Side::Buy, None, millis);
804 order.key.instrument = InstrumentNameExchange::new(instrument);
805 order.kind = OrderKind::Market;
806 order
807 }
808
809 fn ids(cids: Vec<ClientOrderId>) -> Vec<String> {
810 cids.into_iter().map(|cid| cid.0.to_string()).collect()
811 }
812
813 #[test]
814 fn held_orders_are_handed_out_once_per_instrument_in_arrival_order() {
815 let mut open = OpenOrders::default();
816 for order in [
817 market("b1", "eth_usdt", 1_000),
818 market("a1", "btc_usdt", 2_000),
819 market("b2", "eth_usdt", 3_000),
820 ] {
821 open.hold(order, None).unwrap();
822 }
823
824 assert!(open.holds(&InstrumentNameExchange::new("eth_usdt")));
825 assert_eq!(
826 ids(open.held()),
827 ["a1", "b1", "b2"],
828 "by instrument, then earliest arrival first"
829 );
830 assert_eq!(open.len(), 3, "a snapshot reports what is held");
831 assert!(
832 resting_cids(&open, Side::Buy).is_empty(),
833 "and matching never reaches it"
834 );
835
836 let eth = InstrumentNameExchange::new("eth_usdt");
837 assert_eq!(ids(open.take_held(ð)), ["b1", "b2"]);
838 assert!(!open.holds(ð));
839 assert!(open.take_held(ð).is_empty(), "handed out once");
840 assert_eq!(open.len(), 3, "still in the map until removed");
841 }
842
843 #[test]
844 fn removing_a_held_order_stops_it_being_held() {
845 let mut open = OpenOrders::default();
846 open.hold(market("gone", "btc_usdt", 1_000), None).unwrap();
847 open.hold(market("kept", "btc_usdt", 2_000), None).unwrap();
848
849 assert!(open.remove(&ClientOrderId::new("gone")).is_some());
850 assert_eq!(ids(open.held()), ["kept"]);
851
852 assert!(open.remove(&ClientOrderId::new("kept")).is_some());
853 assert!(
854 !open.holds(&InstrumentNameExchange::new("btc_usdt")),
855 "an instrument with nothing left held holds nothing"
856 );
857 }
858
859 #[test]
860 fn a_held_order_under_a_resting_id_is_refused_and_nothing_changes() {
861 let mut open = book([order("taken", Side::Buy, Some(dec!(100)), 1_000)]);
862
863 let refused = open.hold(market("taken", "btc_usdt", 2_000), None);
864
865 assert!(refused.is_err());
866 assert!(open.held().is_empty());
867 assert_eq!(resting_cids(&open, Side::Buy), ["taken"]);
868 }
869
870 #[test]
871 fn a_held_limit_order_stays_off_the_queues_until_it_is_removed_and_booked_again() {
872 let mut open = OpenOrders::default();
873 let limit = order("limit", Side::Buy, Some(dec!(100)), 1_000);
874 open.hold(limit.clone(), None).unwrap();
875
876 assert!(open.holds(&instrument()));
877 assert!(
878 resting_cids(&open, Side::Buy).is_empty(),
879 "held with its price, but never matched"
880 );
881
882 assert_eq!(ids(open.take_held(&instrument())), ["limit"]);
883 assert!(
884 resting_cids(&open, Side::Buy).is_empty(),
885 "handed out, it is still off the queues"
886 );
887 assert!(open.remove(&ClientOrderId::new("limit")).is_some());
888
889 open.insert(limit, None).unwrap();
890 assert_eq!(resting_cids(&open, Side::Buy), ["limit"]);
891 assert!(open.remove(&ClientOrderId::new("limit")).is_some());
892 assert!(open.is_empty());
893 }
894
895 #[test]
896 fn a_held_limit_order_cancelled_before_its_release_leaves_nothing_behind() {
897 let mut open = book([order("resting", Side::Buy, Some(dec!(100)), 1_000)]);
898 open.hold(order("held", Side::Buy, Some(dec!(100)), 2_000), None)
899 .unwrap();
900
901 assert!(open.remove(&ClientOrderId::new("held")).is_some());
902
903 assert!(!open.holds(&instrument()));
904 assert_eq!(resting_cids(&open, Side::Buy), ["resting"]);
905 }
906}