1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::spot::Order;
6
7use super::event::{self, SpotEvent};
8use super::orderbook::{OrderBook, OrderBookError};
9use super::orders::OrderId;
10use super::time_in_force::TimeInForce;
11
12use super::market::L1;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
15pub struct Pair {
16 pub pair_id: Vec<u8>,
18 pub base_asset_id: Vec<u8>,
20 pub quote_asset_id: Vec<u8>,
22 pub l1: L1,
24 pub market_price: Option<u64>,
26 pub orderbook: OrderBook,
28 pub clients: Vec<Vec<u8>>,
30 pub client_admin_account_ids: HashMap<Vec<u8>, Vec<u8>>,
32 pub client_fee_account_ids: HashMap<Vec<u8>, Vec<u8>>,
34}
35
36impl Pair {
37
38 pub fn new() -> Self {
39 Self {
40 pair_id: Vec::new(),
41 base_asset_id: Vec::new(),
42 quote_asset_id: Vec::new(),
43 l1: L1::default(),
44 market_price: None,
45 orderbook: OrderBook::default(),
46 clients: Vec::new(),
47 client_admin_account_ids: HashMap::new(),
48 client_fee_account_ids: HashMap::new(),
49 }
50 }
51
52 pub fn add_client(
53 &mut self,
54 cid: impl Into<Vec<u8>>,
55 admin_account_id: impl Into<Vec<u8>>,
56 fee_account_id: impl Into<Vec<u8>>,
57 ) {
58 let cid = cid.into();
59 let admin_account_id = admin_account_id.into();
60 let fee_account_id = fee_account_id.into();
61 let timestamp = std::time::SystemTime::now()
62 .duration_since(std::time::UNIX_EPOCH)
63 .unwrap()
64 .as_millis() as i64;
65
66 self.clients.push(cid.clone());
68 self.client_admin_account_ids
69 .insert(cid.clone(), admin_account_id.clone());
70 self.client_fee_account_ids
71 .insert(cid.clone(), fee_account_id.clone());
72
73 self.orderbook
75 .fee_recipients
76 .insert(cid.clone(), fee_account_id.clone());
77
78 event::emit_event(SpotEvent::SpotPairClientAccountChanged {
80 pair_id: self.pair_id.clone(),
81 cid: Some(cid),
82 admin_account_id: Some(admin_account_id),
83 fee_account_id: Some(fee_account_id),
84 timestamp,
85 });
86 }
87
88 pub fn remove_client(&mut self, cid: impl Into<Vec<u8>>) {
89 let cid = cid.into();
90
91 self.clients.retain(|c| *c != cid);
93 self.client_admin_account_ids.remove(&cid);
94 self.client_fee_account_ids.remove(&cid);
95 self.orderbook.fee_recipients.remove(&cid);
97
98 let timestamp = std::time::SystemTime::now()
102 .duration_since(std::time::UNIX_EPOCH)
103 .unwrap()
104 .as_millis() as i64;
105
106 event::emit_event(SpotEvent::SpotPairClientAccountChanged {
107 pair_id: self.pair_id.clone(),
108 cid: Some(cid),
109 admin_account_id: None,
110 fee_account_id: None,
111 timestamp,
112 });
113 }
114
115 #[cfg_attr(test, allow(dead_code))]
120 pub fn _match_at(
121 &mut self,
122 price: u64,
123 is_matching_asks: bool,
124 taker_order: &mut Order,
125 ) -> Result<Order, OrderBookError> {
126 let taker_id = taker_order.id;
127 let mut current_remaining = taker_order.cqty;
128
129 let mut maker_order_id = match self.orderbook.l3.head(price) {
131 Some(id) => id,
132 None => return Ok(taker_order.clone()), };
134
135 while current_remaining > 0 {
137 if self.orderbook.l3.is_empty(price) {
139 let is_bid = !is_matching_asks;
142 self.orderbook.l2.remove_price(is_bid, price)?;
143 break;
144 }
145
146 let maker_order = self.orderbook.l3.get_order(maker_order_id)?.clone();
148 let taker_current = match self.orderbook.l3.get_order(taker_id) {
149 Ok(order) => order.clone(),
150 Err(_) => break,
151 };
152
153 let now = std::time::SystemTime::now()
154 .duration_since(std::time::UNIX_EPOCH)
155 .unwrap()
156 .as_millis() as i64;
157
158 self.orderbook.execute(
159 taker_current,
160 maker_order,
161 self.pair_id.clone(),
162 self.base_asset_id.clone(),
163 self.quote_asset_id.clone(),
164 now,
165 )?;
166
167 match self.orderbook.l3.get_order(taker_id) {
168 Ok(updated) => current_remaining = updated.cqty,
169 Err(_) => {
170 break;
171 }
172 }
173
174 maker_order_id = match self.orderbook.l3.next(price, maker_order_id) {
176 Some(id) => id,
177 None => break, };
179 }
180
181 let updated = match self.orderbook.l3.get_order(taker_id) {
182 Ok(order) => order.clone(),
183 Err(_) => {
184 let mut fallback = taker_order.clone();
186 fallback.cqty = 0;
187 fallback.pqty = 0;
188 return Ok(fallback);
189 }
190 };
191 Ok(updated)
192 }
193
194 #[cfg_attr(test, allow(dead_code))]
198 pub fn _limit_order(
199 &mut self,
200 limit_price: u64,
201 taker_order: &mut Order,
202 ) -> Result<(Order, u64, u64), OrderBookError> {
203
204 let mut lmp = self.l1.lmp().unwrap_or(0);
206
207 let mut bid_head = self.orderbook.clear_empty_head_or_zero(true);
209 let mut ask_head = self.orderbook.clear_empty_head_or_zero(false);
210
211 if taker_order.is_bid {
212 if lmp != 0 {
214 if ask_head != 0 && limit_price < ask_head {
215 return Ok((taker_order.clone(), bid_head, ask_head));
216 } else if ask_head == 0 {
217 return Ok((taker_order.clone(), bid_head, ask_head));
218 }
219 }
220
221 let mut current_remaining = taker_order.cqty;
223 while current_remaining > 0 && ask_head != 0 && ask_head <= limit_price {
224 lmp = ask_head; let match_price = ask_head;
226
227 let updated = self._match_at(
229 match_price,
230 true, taker_order,
232 )?;
233 *taker_order = updated;
234 current_remaining = taker_order.cqty;
235
236 ask_head = self.orderbook.clear_empty_head_or_zero(false);
238 }
239
240
241 bid_head = self.orderbook.clear_empty_head_or_zero(true);
243 } else {
244 if lmp != 0 {
246 if bid_head != 0 && limit_price > bid_head {
247 return Ok((taker_order.clone(), bid_head, ask_head));
248 } else if bid_head == 0 {
249 return Ok((taker_order.clone(), bid_head, ask_head));
250 }
251 }
252
253 let mut current_remaining = taker_order.cqty;
255 while current_remaining > 0 && bid_head != 0 && bid_head >= limit_price {
256 lmp = bid_head; let match_price = bid_head;
258
259 let updated = self._match_at(
261 match_price,
262 false, taker_order,
264 )?;
265 *taker_order = updated;
266 current_remaining = taker_order.cqty;
267
268 bid_head = self.orderbook.clear_empty_head_or_zero(true);
270 }
271
272 ask_head = self.orderbook.clear_empty_head_or_zero(false);
274 }
275
276 if lmp != 0 {
278 self.l1.set_lmp(lmp);
279 event::emit_event(SpotEvent::SpotNewMarketPrice {
280 client_ids: self.clients.clone(),
281 pair_id: self.pair_id.clone(),
282 base_asset_id: self.base_asset_id.clone(),
283 quote_asset_id: self.quote_asset_id.clone(),
284 price: lmp,
285 timestamp: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64,
286 });
287 }
288
289 Ok((taker_order.clone(), bid_head, ask_head))
290 }
291
292 fn _handle_time_in_force_post_matching(
301 &mut self,
302 time_in_force: TimeInForce,
303 maker_order: &mut Order,
304 is_limit_order: bool,
305 maker_fee_bps: i16,
306 ) -> Result<(), OrderBookError> {
307 let make_price = if is_limit_order {
309 if maker_order.is_bid {
310 self.l1.det_limit_buy_make_price(maker_order.price, self.orderbook.l2.bid_head().unwrap_or(0), self.orderbook.l2.ask_head().unwrap_or(0), maker_fee_bps as u32)
311 } else {
312 self.l1.det_limit_sell_make_price(maker_order.price, self.orderbook.l2.bid_head().unwrap_or(0), self.orderbook.l2.ask_head().unwrap_or(0), maker_fee_bps as u32)
313 }
314 } else {
315 if maker_order.is_bid {
316 self.l1.det_market_buy_make_price(self.orderbook.l2.bid_head().unwrap_or(0), self.orderbook.l2.ask_head().unwrap_or(0), maker_fee_bps as u32)
317 } else {
318 self.l1.det_market_sell_make_price(self.orderbook.l2.bid_head().unwrap_or(0), self.orderbook.l2.ask_head().unwrap_or(0), maker_fee_bps as u32)
319 }
320 };
321 self.market_price = Some(make_price);
323 event::emit_event(SpotEvent::SpotNewMarketPrice {
324 client_ids: self.clients.clone(),
325 pair_id: self.pair_id.clone(),
326 base_asset_id: self.base_asset_id.clone(),
327 quote_asset_id: self.quote_asset_id.clone(),
328 price: make_price,
329 timestamp: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64,
330 });
331 match time_in_force {
332 TimeInForce::ImmediateOrCancel => {
333 if maker_order.cqty > 0 {
335 self.orderbook.cancel_order(maker_order.cid.clone(), self.pair_id.clone(), maker_order.is_bid, maker_order.id, maker_order.owner.clone())?;
336 }
337 Ok(())
338 }
339 TimeInForce::GoodTillCanceled => {
340 if maker_order.cqty > 0 {
342 maker_order.fee_bps = maker_fee_bps;
344
345 maker_order.price = make_price;
347 }
348 Ok(())
349 }
350 _ => Err(OrderBookError::UnsupportedTimeInForce),
351 }
352 }
353
354 fn can_fill_fok(&self, limit_price: u64, taker_order: &Order) -> Result<bool, OrderBookError> {
355 let mut remaining = taker_order.cqty;
356
357 let prices = if taker_order.is_bid {
358 self.orderbook.l2.collect_ask_prices()
359 } else {
360 self.orderbook.l2.collect_bid_prices()
361 };
362
363 for price in prices {
364 if taker_order.is_bid {
365 if price > limit_price {
366 break;
367 }
368 } else if price < limit_price {
369 break;
370 }
371
372 let level_cqty = if taker_order.is_bid {
373 match self.orderbook.l2.current_ask_level(price) {
374 Some(level) => level,
375 None => continue,
376 }
377 } else {
378 match self.orderbook.l2.current_bid_level(price) {
379 Some(level) => level,
380 None => continue,
381 }
382 };
383
384 let required = self
385 .orderbook
386 .get_required(taker_order.clone(), price, level_cqty)?;
387
388 if remaining <= required {
389 return Ok(true);
390 }
391
392 remaining = remaining.saturating_sub(required);
393 if remaining == 0 {
394 return Ok(true);
395 }
396 }
397
398 Ok(false)
399 }
400
401 pub fn limit_sell(
415 &mut self,
416 cid: impl Into<Vec<u8>>,
418 existing_order_id: Option<OrderId>, owner: impl Into<Vec<u8>>,
422 price: u64,
424 amnt: u64,
426 iqty: u64,
428 timestamp: i64,
430 expires_at: i64,
432 maker_fee_bps: i16,
434 taker_fee_bps: i16,
436 time_in_force: TimeInForce,
438 ) -> Result<OrderId, OrderBookError> {
439 let cid_vec: Vec<u8> = cid.into();
441 let owner_vec: Vec<u8> = owner.into();
442 if let Some(existing_order_id) = existing_order_id {
443 let order = self.orderbook.l3.get_order(existing_order_id)?;
444 if order.cid != cid_vec {
445 return Err(OrderBookError::OrderNotOwnedBySender);
446 }
447 }
448
449 let taker_order = self.orderbook.place_ask(
451 cid_vec.clone(),
452 self.pair_id.clone(),
453 self.base_asset_id.clone(),
454 self.quote_asset_id.clone(),
455 owner_vec.clone(),
456 price,
457 amnt,
458 iqty,
459 timestamp,
460 expires_at,
461 taker_fee_bps,
462 )?;
463
464 if matches!(time_in_force, TimeInForce::FillOrKill)
465 && !self.can_fill_fok(price, &taker_order)?
466 {
467 self.orderbook.cancel_order(
468 cid_vec.clone(),
469 self.pair_id.clone(),
470 false,
471 taker_order.id,
472 owner_vec.clone(),
473 )?;
474 return Err(OrderBookError::OrderNotFullyFilled);
475 }
476
477 let (taker_order, _bid_head, _ask_head) = self._limit_order(
479 price,
480 &mut taker_order.clone(),
481 )?;
482
483 self._handle_time_in_force_post_matching(time_in_force, &mut taker_order.clone(), true, maker_fee_bps)?;
485
486 Ok(taker_order.id)
487 }
488
489 pub fn limit_buy(
504 &mut self,
505 cid: impl Into<Vec<u8>>,
507 existing_order_id: Option<OrderId>, owner: impl Into<Vec<u8>>,
511 price: u64,
513 amnt: u64,
515 iqty: u64,
517 timestamp: i64,
519 expires_at: i64,
521 maker_fee_bps: i16,
523 taker_fee_bps: i16,
525 time_in_force: TimeInForce,
527 ) -> Result<(), OrderBookError> {
528
529 let cid_vec: Vec<u8> = cid.into();
530 let owner_vec: Vec<u8> = owner.into();
531 if let Some(existing_order_id) = existing_order_id {
532 let order = self.orderbook.l3.get_order(existing_order_id)?;
533 if order.cid != cid_vec {
534 return Err(OrderBookError::OrderNotSupportedByClientId);
535 }
536 if order.owner != owner_vec {
537 return Err(OrderBookError::OrderNotOwnedBySender);
538 }
539 }
540
541 let taker_order = self.orderbook.place_bid(
542 cid_vec.clone(),
543 self.pair_id.clone(),
544 self.base_asset_id.clone(),
545 self.quote_asset_id.clone(),
546 owner_vec.clone(),
547 price,
548 amnt,
549 iqty,
550 timestamp,
551 expires_at,
552 taker_fee_bps,
553 )?;
554
555 if matches!(time_in_force, TimeInForce::FillOrKill)
556 && !self.can_fill_fok(price, &taker_order)?
557 {
558 self.orderbook.cancel_order(
559 cid_vec.clone(),
560 self.pair_id.clone(),
561 true,
562 taker_order.id,
563 owner_vec.clone(),
564 )?;
565 return Err(OrderBookError::OrderNotFullyFilled);
566 }
567
568 let (taker_order, _bid_head, _ask_head) = self._limit_order(
569 price,
570 &mut taker_order.clone(),
571 )?;
572
573 self._handle_time_in_force_post_matching(time_in_force, &mut taker_order.clone(), true, maker_fee_bps)?;
574
575 Ok(())
576 }
577
578 pub fn market_sell(
589 &mut self,
590 cid: impl Into<Vec<u8>>,
592 existing_order_id: Option<OrderId>, owner: impl Into<Vec<u8>>,
596 amnt: u64,
598 iqty: u64,
600 timestamp: i64,
602 expires_at: i64,
604 maker_fee_bps: i16,
606 taker_fee_bps: i16,
608 time_in_force: TimeInForce,
610 ) -> Result<(), OrderBookError> {
611
612 let cid_vec: Vec<u8> = cid.into();
613 let owner_vec: Vec<u8> = owner.into();
614 if let Some(existing_order_id) = existing_order_id {
616 let order = self.orderbook.l3.get_order(existing_order_id)?;
617 if order.cid != cid_vec {
618 return Err(OrderBookError::OrderNotSupportedByClientId);
619 }
620 if order.owner != owner_vec {
621 return Err(OrderBookError::OrderNotOwnedBySender);
622 }
623 }
624
625 let best_bid_price = self.orderbook.l2.bid_head();
627 if best_bid_price.is_none() {
628 return Err(OrderBookError::NoBidOrdersInOrderbook);
629 }
630 let price = best_bid_price.unwrap();
631
632 let taker_order = self.orderbook.place_ask(
633 cid_vec.clone(),
634 self.pair_id.clone(),
635 self.base_asset_id.clone(),
636 self.quote_asset_id.clone(),
637 owner_vec.clone(),
638 price,
639 amnt,
640 iqty,
641 timestamp,
642 expires_at,
643 taker_fee_bps,
644 )?;
645
646 if matches!(time_in_force, TimeInForce::FillOrKill)
647 && !self.can_fill_fok(price, &taker_order)?
648 {
649 self.orderbook.cancel_order(
650 cid_vec.clone(),
651 self.pair_id.clone(),
652 false,
653 taker_order.id,
654 owner_vec.clone(),
655 )?;
656 return Err(OrderBookError::OrderNotFullyFilled);
657 }
658
659 let (taker_order, _bid_head, _ask_head) = self._limit_order(
660 0,
661 &mut taker_order.clone(),
662 )?;
663
664 self._handle_time_in_force_post_matching(time_in_force, &mut taker_order.clone(), false, maker_fee_bps)?;
665
666 Ok(())
667 }
668
669 pub fn market_buy(
679 &mut self,
680 cid: impl Into<Vec<u8>>,
682 existing_order_id: Option<OrderId>, owner: impl Into<Vec<u8>>,
686 amnt: u64,
688 iqty: u64,
690 timestamp: i64,
692 expires_at: i64,
694 maker_fee_bps: i16,
696 taker_fee_bps: i16,
698 time_in_force: TimeInForce,
700 ) -> Result<(), OrderBookError> {
701
702 let cid_vec: Vec<u8> = cid.into();
703 let owner_vec: Vec<u8> = owner.into();
704 if let Some(existing_order_id) = existing_order_id {
706 let order = self.orderbook.l3.get_order(existing_order_id)?;
707 if order.cid != cid_vec {
708 return Err(OrderBookError::OrderNotSupportedByClientId);
709 }
710 if order.owner != owner_vec {
711 return Err(OrderBookError::OrderNotOwnedBySender);
712 }
713 }
714
715 let best_ask_price = self.orderbook.l2.ask_head();
717 if best_ask_price.is_none() {
718 return Err(OrderBookError::NoAskOrdersInOrderbook);
719 }
720 let price = best_ask_price.unwrap();
721
722 let taker_order = self.orderbook.place_bid(
723 cid_vec.clone(),
724 self.pair_id.clone(),
725 self.base_asset_id.clone(),
726 self.quote_asset_id.clone(),
727 owner_vec.clone(),
728 price,
729 amnt,
730 iqty,
731 timestamp,
732 expires_at,
733 taker_fee_bps,
734 )?;
735
736 if matches!(time_in_force, TimeInForce::FillOrKill)
737 && !self.can_fill_fok(price, &taker_order)?
738 {
739 self.orderbook.cancel_order(
740 cid_vec.clone(),
741 self.pair_id.clone(),
742 true,
743 taker_order.id,
744 owner_vec.clone(),
745 )?;
746 return Err(OrderBookError::OrderNotFullyFilled);
747 }
748
749 let (taker_order, _bid_head, _ask_head) = self._limit_order(
750 u64::MAX,
751 &mut taker_order.clone(),
752 )?;
753
754 self._handle_time_in_force_post_matching(time_in_force, &mut taker_order.clone(), false,maker_fee_bps)?;
755
756 Ok(())
757 }
758
759 pub fn cancel_order(
760 &mut self,
761 cid: impl Into<Vec<u8>>,
762 pair_id: impl Into<Vec<u8>>,
763 is_bid: bool,
764 order_id: OrderId,
765 owner: impl Into<Vec<u8>>,
766 ) -> Result<(), OrderBookError> {
767 self.orderbook
768 .cancel_order(cid, pair_id, is_bid, order_id, owner)?;
769 Ok(())
770 }
771}