Skip to main content

off_grid_primitives/spot/
pair.rs

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    /// Pair ID
17    pub pair_id: Vec<u8>,
18    /// base asset id
19    pub base_asset_id: Vec<u8>,
20    /// quote asset id
21    pub quote_asset_id: Vec<u8>,
22    /// L1 state
23    pub l1: L1,
24    /// market price
25    pub market_price: Option<u64>,
26    /// Orderbook
27    pub orderbook: OrderBook,
28    /// list of exchange clients which shares the orderbook 
29    pub clients: Vec<Vec<u8>>,
30    /// Hash map of client id -> client admin account id
31    pub client_admin_account_ids: HashMap<Vec<u8>, Vec<u8>>,
32    /// Hash map of client id -> client fee account id
33    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        // Store client and associated accounts
67        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        // Set up fee account for the orderbook
74        self.orderbook
75            .fee_recipients
76            .insert(cid.clone(), fee_account_id.clone());
77
78        // Emit event using the values we already have, avoiding extra lookups
79        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        // Remove from in-memory structures
92        self.clients.retain(|c| *c != cid);
93        self.client_admin_account_ids.remove(&cid);
94        self.client_fee_account_ids.remove(&cid);
95        // remove fee account from the orderbook
96        self.orderbook.fee_recipients.remove(&cid);
97
98        // Emit an event indicating the client was removed from this pair.
99        // We keep `cid` so downstream consumers know which client changed,
100        // and set admin/fee accounts to None to indicate removal.
101        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    /// Match all orders at a specific price level until the taker order is fully filled or no more orders at the price level
116    /// Returns remaining_amount after matching
117    /// `is_matching_asks` indicates if we're matching against ask orders (true) or bid orders (false)
118    /// Continues matching until remaining amount is 0 or no more orders at the price level
119    #[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        // Get the first order at this price level
130        let mut maker_order_id = match self.orderbook.l3.head(price) {
131            Some(id) => id,
132            None => return Ok(taker_order.clone()), // No more orders
133        };
134
135        // Keep matching until remaining is 0 or price level is empty
136        while current_remaining > 0 {
137            // Check if price level is empty
138            if self.orderbook.l3.is_empty(price) {
139                // Remove price level: if matching asks, price level is ask (is_bid = false)
140                // if matching bids, price level is bid (is_bid = true)
141                let is_bid = !is_matching_asks;
142                self.orderbook.l2.remove_price(is_bid, price)?;
143                break;
144            }
145
146            // Get maker order and refresh taker order before executing
147            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            // traverse to the next order at the price level
175            maker_order_id = match self.orderbook.l3.next(price, maker_order_id) {
176                Some(id) => id,
177                None => break, // No more orders
178            };
179        }
180
181        let updated = match self.orderbook.l3.get_order(taker_id) {
182            Ok(order) => order.clone(),
183            Err(_) => {
184                // if the taker order is not found, it means the taker order is fully filled
185                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    /// Place a limit order (internal helper)
195    /// Returns (remaining_amount, bid_head, ask_head)
196    /// Continues matching until remaining amount is 0 or no more matching orders available
197    #[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        // Get last matched price
205        let mut lmp = self.l1.lmp().unwrap_or(0);
206
207        // Clear empty heads
208        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            // Limit Buy: match against ask orders
213            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            // Match against ask orders while ask_head <= limit_price
222            let mut current_remaining = taker_order.cqty;
223            while current_remaining > 0 && ask_head != 0 && ask_head <= limit_price {
224                lmp = ask_head; // Update lmp to current match price
225                let match_price = ask_head;
226
227                // Match at this price level until remaining is 0 or price level is empty
228                let updated = self._match_at(
229                    match_price,
230                    true, // matching against asks
231                    taker_order,
232                )?;
233                *taker_order = updated;
234                current_remaining = taker_order.cqty;
235
236                // Update ask_head after matching (price level might be empty now)
237                ask_head = self.orderbook.clear_empty_head_or_zero(false);
238            }
239
240
241            // Update bid_head
242            bid_head = self.orderbook.clear_empty_head_or_zero(true);
243        } else {
244            // Limit Sell: match against bid orders
245            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            // Match against bid orders while bid_head >= limit_price
254            let mut current_remaining = taker_order.cqty;
255            while current_remaining > 0 && bid_head != 0 && bid_head >= limit_price {
256                lmp = bid_head; // Update lmp to current match price
257                let match_price = bid_head;
258
259                // Match at this price level until remaining is 0 or price level is empty
260                let updated = self._match_at(
261                    match_price,
262                    false, // matching against bids
263                    taker_order,
264                )?;
265                *taker_order = updated;
266                current_remaining = taker_order.cqty;
267
268                // Update bid_head after matching (price level might be empty now)
269                bid_head = self.orderbook.clear_empty_head_or_zero(true);
270            }
271
272            // Update ask_head
273            ask_head = self.orderbook.clear_empty_head_or_zero(false);
274        }
275
276        // Set new market price if matches occurred
277        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    /// Handle time_in_force logic for an order after matching
293    /// - `order_id`: The ID of the order to handle
294    /// - `price`: The price of the order
295    /// - `amount`: The original amount of the order
296    /// - `remaining`: The remaining amount after matching
297    /// - `is_bid`: Whether this is a bid order (true) or ask order (false)
298    /// - `time_in_force`: The time in force policy
299    /// Returns an error if FOK order is not fully filled
300    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        // determine the make price 
308        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        // set the market price based on the make price
322        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                // IOC: Fill what can be filled immediately, cancel the rest
334                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                // GTC: Place remaining in orderbook
341                if maker_order.cqty > 0 {
342                    // set the order's fee as maker fee basis points after finishing matching
343                    maker_order.fee_bps = maker_fee_bps;
344
345                    // update maker order price to make price 
346                    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    /// Matches against existing orders first, then places remaining in orderbook based on time_in_force
402    /// - returns the order id and if a dormant order was found.
403    /// - `cid` is the gateway client id.
404    /// - `existing_order_id` is the order id to update with the transaction if it exists.
405    /// - `owner` is the owner of the order.
406    /// - `price` is the price of the order.
407    /// - `amount` is the total amount of the order.
408    /// - `public_amount` is the public amount of the order in case of iceberg order.
409    /// - `timestamp` is the timestamp of the order.
410    /// - `expires_at` is the expiring timestamp of the order.
411    /// - `maker_fee_bps` is the maker fee basis points of the order.
412    /// - `taker_fee_bps` is the taker fee basis points of the order.
413    /// - `time_in_force` is the time in force of the order.
414    pub fn limit_sell(
415        &mut self,
416        // gateway client id
417        cid: impl Into<Vec<u8>>,
418        // order id to update with the transaction if it exists
419        existing_order_id: Option<OrderId>, // None if new order
420        // owner of the order
421        owner: impl Into<Vec<u8>>,
422        // price of the order
423        price: u64,
424        // total amount of the order
425        amnt: u64,
426        // iceberg quantity of the order
427        iqty: u64,
428        // timestamp of the order
429        timestamp: i64,
430        // expiring timestamp of the order
431        expires_at: i64,
432        // maker fee basis points of the order
433        maker_fee_bps: i16,
434        // taker fee basis points of the order
435        taker_fee_bps: i16,
436        // time in force of the order
437        time_in_force: TimeInForce,
438    ) -> Result<OrderId, OrderBookError> {
439        // If existing order id is provided, update the order
440        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        // place taker order to feed into _limit_order function
450        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        // Match against existing orders FIRST (before placing in orderbook)
478        let (taker_order, _bid_head, _ask_head) = self._limit_order(
479            price,
480            &mut taker_order.clone(),
481        )?;
482
483        // Handle time_in_force logic as maker order
484        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    /// Place a limit buy order (bid order)
490    /// Matches against existing orders first, then places remaining in orderbook based on time_in_force
491    /// - returns the order id and if a dormant order was found.
492    /// - `cid` is the gateway client id.
493    /// - `existing_order_id` is the order id to update with the transaction if it exists.
494    /// - `owner` is the owner of the order.
495    /// - `price` is the price of the order.
496    /// - `amount` is the total amount of the order.
497    /// - `public_amount` is the public amount of the order in case of iceberg order.
498    /// - `timestamp` is the timestamp of the order.
499    /// - `expires_at` is the expiring timestamp of the order.
500    /// - `maker_fee_bps` is the maker fee basis points of the order.
501    /// - `taker_fee_bps` is the taker fee basis points of the order.
502    /// - `time_in_force` is the time in force of the order.
503    pub fn limit_buy(
504        &mut self,
505        // gateway client id
506        cid: impl Into<Vec<u8>>,
507        // order id to update with the transaction if it exists
508        existing_order_id: Option<OrderId>, // None if new order
509        // owner of the order
510        owner: impl Into<Vec<u8>>,
511        // price of the order
512        price: u64,
513        // total amount of the order
514        amnt: u64,
515        // iceberg quantity of the order
516        iqty: u64,
517        // timestamp of the order
518        timestamp: i64,
519        // expiring timestamp of the order
520        expires_at: i64,
521        // maker fee basis points of the order
522        maker_fee_bps: i16,
523        // taker fee basis points of the order
524        taker_fee_bps: i16,
525        // time in force of the order
526        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    /// Execute a market sell order
579    /// Matches against existing orders first (market orders match at any price)
580    /// - returns the match result.
581    /// - `cid` is the gateway client id.
582    /// - `existing_order_id` is the order id to update with the transaction if it exists.
583    /// - `owner` is the owner of the order.
584    /// - `amount` is the total amount of the order.
585    /// - `clear` is whether to clear the order.
586    /// - `taker_fee_bps` is the taker fee basis points of the order.
587    /// - `time_in_force` is the time in force of the order.
588    pub fn market_sell(
589        &mut self,
590        // gateway client id
591        cid: impl Into<Vec<u8>>,
592        // existing order id to update with the transaction if it exists
593        existing_order_id: Option<OrderId>, // None if new order
594        // owner of the order
595        owner: impl Into<Vec<u8>>,
596        // total amount of the order
597        amnt: u64,
598        // iceberg quantity of the order
599        iqty: u64,
600        // timestamp of the order
601        timestamp: i64,
602        // expiring timestamp of the order
603        expires_at: i64,
604        // maker fee basis points of the order
605        maker_fee_bps: i16,
606        // taker fee basis points of the order
607        taker_fee_bps: i16,
608        // time in force of the order
609        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 existing order id is provided, update the order
615        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        // get the best bid price
626        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    /// Execute a market buy order
670    /// Matches against existing orders first (market orders match at any price)
671    /// - returns the match result.
672    /// - `cid` is the gateway client id.
673    /// - `existing_order_id` is the order id to update with the transaction if it exists.
674    /// - `owner` is the owner of the order.
675    /// - `amount` is the total amount of the order.
676    /// - `taker_fee_bps` is the taker fee basis points of the order.
677    /// - `time_in_force` is the time in force of the order.
678    pub fn market_buy(
679        &mut self,
680        // gateway client id
681        cid: impl Into<Vec<u8>>,
682        // existing order id to update with the transaction if it exists
683        existing_order_id: Option<OrderId>, // None if new order
684        // owner of the order
685        owner: impl Into<Vec<u8>>,
686        // total amount of the order
687        amnt: u64,
688        // iceberg quantity of the order
689        iqty: u64,
690        // timestamp of the order
691        timestamp: i64,
692        // expiring timestamp of the order
693        expires_at: i64,
694        // maker fee basis points of the order
695        maker_fee_bps: i16,
696        // taker fee basis points of the order
697        taker_fee_bps: i16,
698        // time in force of the order
699        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 existing order id is provided, update the order
705        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        // get the best ask price
716        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}