Skip to main content

off_grid_primitives/spot/
orders.rs

1use serde::{Deserialize, Serialize};
2use std::collections::{BTreeMap, HashMap};
3use ulid::Ulid;
4
5pub type OrderId = Ulid;
6
7/// Represents an order stored in the order book.
8#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
9pub struct Order {
10    /// client order id
11    pub cid: Vec<u8>,
12    /// order id
13    pub id: OrderId,
14    /// owner of the order
15    pub owner: Vec<u8>,
16    /// is bid order
17    pub is_bid: bool,
18    /// price of the order in 8 decimals
19    pub price: u64,
20    /// whole amount of the order in 8 decimals without iceberg protection
21    pub amnt: u64,
22    /// iceberg quantity of the order in 8 decimals to hide the order from the public
23    pub iqty: u64,
24    /// public quantity of the order in 8 decimals with iceberg protection
25    pub pqty: u64,
26    /// current quantity of the order in 8 decimals without iceberg protection
27    pub cqty: u64,
28    /// timestamp of the order in milliseconds
29    pub timestamp: i64,
30    /// expires at timestamp in milliseconds
31    pub expires_at: i64,
32    /// fee basis points of the order (maker or taker)
33    pub fee_bps: i16,
34}
35
36impl Order {
37    #[allow(clippy::too_many_arguments)]
38    pub fn new(
39        cid: Vec<u8>,
40        id: OrderId,
41        owner: Vec<u8>,
42        is_bid: bool,
43        price: u64,
44        amnt: u64,
45        iqty: u64,
46        pqty: u64,
47        cqty: u64,
48        timestamp: i64,
49        expires_at: i64,
50        fee_bps: i16,
51    ) -> Self {
52        Self {
53            cid,
54            id,
55            owner,
56            is_bid,
57            price,
58            amnt,
59            iqty,
60            pqty,
61            cqty,
62            timestamp,
63            expires_at,
64            fee_bps,
65        }
66    }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
70pub struct Node {
71    pub prev: Option<OrderId>,
72    pub next: Option<OrderId>,
73}
74
75#[derive(Debug, thiserror::Error, PartialEq, Eq)]
76pub enum L3Error {
77    #[error("price is zero")]
78    PriceIsZero,
79    #[error("order does not exist: {0}")]
80    OrderDoesNotExist(OrderId),
81    #[error("iceberg quantity is bigger than whole amount")]
82    IcebergQuantityIsBiggerThanWholeAmount,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
86pub struct L3 {
87    /// Mapping price -> head of the linked list in a price level.
88    pub price_head: BTreeMap<u64, OrderId>,
89    /// Mapping price -> last of the linked list in a price level.
90    pub price_tail: BTreeMap<u64, OrderId>,
91    /// Order details keyed by id. Mapping order_id -> Order.
92    pub order_nodes: HashMap<OrderId, Node>,
93    /// Mapping order_id -> Order.
94    pub orders: HashMap<OrderId, Order>,
95    /// dust limit to determine if the order should be deleted
96    pub dust: u64,
97    /// Last displaced order when IDs collide.
98    pub dormant_order: Option<OrderId>,
99}
100
101impl L3 {
102    pub fn new() -> Self {
103        Self {
104            price_head: BTreeMap::new(),
105            price_tail: BTreeMap::new(),
106            order_nodes: HashMap::new(),
107            orders: HashMap::new(),
108            dust: 1,
109            dormant_order: None,
110        }
111    }
112
113    fn ensure_price(price: u64) -> Result<(), L3Error> {
114        if price == 0 {
115            Err(L3Error::PriceIsZero)
116        } else {
117            Ok(())
118        }
119    }
120
121    /// Sets the dust limit to determine if the order should be deleted
122    pub fn set_dust(&mut self, dust: u64) {
123        self.dust = dust;
124    }
125
126    /// Inserts an order id into the linked structure for a given price level,
127    /// keeping FIFO (append at tail).
128    pub fn insert_id(&mut self, price: u64, id: OrderId, _amount: u128) -> Result<(), L3Error> {
129        Self::ensure_price(price)?;
130        // ensure the order exists from the orders map
131        let order_node = self.order_nodes.get_mut(&id).ok_or(L3Error::OrderDoesNotExist(id))?;
132
133        let price_tail = self.price_tail.get(&price).copied();
134
135        // if the price level is not empty
136        if let Some(price_tail) = price_tail {
137            // insert the order node at the tail of the price level
138            // First, update the prev of the new order node to point to old tail
139            order_node.prev = Some(price_tail);
140            order_node.next = None;
141            // To avoid double mutable borrow of self.order_nodes, we do this after:
142            let price_tail_node_ptr = self.order_nodes.get_mut(&price_tail).ok_or(L3Error::OrderDoesNotExist(price_tail))?;
143            price_tail_node_ptr.next = Some(id);
144            // Update the tail pointer to the new node
145            self.price_tail.insert(price, id);
146        } 
147        // if the price level is empty
148        else {
149            // insert the order node at the head of the price level
150            order_node.prev = None;
151            order_node.next = None;
152            self.price_head.insert(price, id);
153            self.price_tail.insert(price, id);
154        }
155
156        Ok(())
157    }
158
159    /// Removes and returns the first order id at the given price level.
160    /// returns (order_id, is_empty)
161    /// - `order_id` is the id of the first order in the price level.
162    /// - `is_empty` is true when the price level becomes empty.
163    pub fn pop_front(&mut self, price: u64) -> Result<(Option<Order>, bool), L3Error> {
164        Self::ensure_price(price)?;
165        let head_id = self.price_head.get(&price).copied();
166        if let Some(head_id) = head_id {
167            let order_node = self
168                .order_nodes
169                .get_mut(&head_id)
170                .ok_or(L3Error::OrderDoesNotExist(head_id))?;
171            let next = order_node.next;
172            if let Some(next) = next {
173                order_node.next = None;
174                let next_node = self
175                    .order_nodes
176                    .get_mut(&next)
177                    .ok_or(L3Error::OrderDoesNotExist(next))?;
178                next_node.prev = None;
179                // set next node as the new head of the price level
180                self.price_head.insert(price, next);
181                // return order from the head_id value
182                let order = self.orders.get(&head_id).ok_or(L3Error::OrderDoesNotExist(head_id))?;
183                Ok((Some(order.clone()), false))
184            } else {
185                self.price_head.remove(&price);
186                self.price_tail.remove(&price);
187                // return order from the head_id value
188                let order = self.orders.get(&head_id).ok_or(L3Error::OrderDoesNotExist(head_id))?;
189                Ok((Some(order.clone()), true))
190            }
191        } else {
192            Ok((None, true))
193        }
194    }
195
196    pub fn set_iceberg_quantity(&mut self, id: OrderId, iqty: u64) -> Result<Order, L3Error> {
197        let order = self.orders.get_mut(&id).ok_or(L3Error::OrderDoesNotExist(id))?;
198        // update iqty of the order and public quantity of the order
199        order.iqty = iqty;
200        // update pqty from the difference between amnt and iqty
201        if order.iqty > order.amnt {
202            return Err(L3Error::IcebergQuantityIsBiggerThanWholeAmount);
203        }
204
205        let new_pqty = order.amnt - order.iqty;
206
207        order.pqty = if order.cqty >= new_pqty { new_pqty } else { order.cqty };
208        Ok(order.clone())
209    }
210
211    /// Creates a new order, assigning the next id. Returns the new order id.
212    pub fn create_order(
213        &mut self,
214        cid: impl Into<Vec<u8>>,
215        owner: impl Into<Vec<u8>>,
216        is_bid: bool,
217        price: u64,
218        amnt: u64,
219        iqty: u64,
220        timestamp: i64,
221        expires_at: i64,
222        maker_fee_bps: i16,
223    ) -> Result<Order, L3Error> {
224        Self::ensure_price(price)?;
225        let cid = cid.into();
226        // generate a new order id
227        let id = Ulid::new();
228        let owner = owner.into();
229        if iqty > amnt {
230            return Err(L3Error::IcebergQuantityIsBiggerThanWholeAmount);
231        }
232        let pqty = amnt - iqty;
233        let order = Order::new(
234            cid,
235            id,
236            owner,
237            is_bid,
238            price,
239            amnt,
240            iqty,
241            pqty,
242            amnt,
243            timestamp,
244            expires_at,
245            maker_fee_bps,
246        );
247
248        // Create a new node for the order
249        self.order_nodes.insert(
250            id,
251            Node {
252                prev: None,
253                next: None,
254            },
255        );
256        self.orders.insert(id, order.clone());
257        self.insert_id(price, id, amnt as u128)?;
258
259        Ok(order)
260    }
261
262    /// Decreases the deposit amount for a given order id.
263    /// If the order is the order that needs to be cleared by floating point precision, the amount_to_send will be the full amount of the order.
264    /// Returns `(amount_to_send, deleted_price_if_level_empty)` where:
265    /// - `amount_to_send` is the liquidity that should be returned to the caller. based on the dust limit and the clear flag.
266    /// - `deleted_price_if_level_empty` is `Some(price)` when the price level becomes empty.
267    pub fn decrease_order(
268        &mut self,
269        id: OrderId,
270        amount: u64,
271        dust: u64,
272        clear: bool,
273    ) -> Result<(u64, Option<u64>), L3Error> {
274
275        let mut amount_to_send;
276        let mut should_delete = false;
277
278        {
279            let order = match self.orders.get_mut(&id) {
280                Some(order) => order,
281                None => return Ok((0, None)),
282            };
283
284            let original = order.cqty;
285            amount_to_send = amount.min(original);
286            let decreased = original.saturating_sub(amount_to_send);
287
288            if clear || decreased <= dust {
289                amount_to_send = original;
290                should_delete = true;
291            } else {
292                // update the current base quantity of the order
293                order.cqty = decreased;
294                // update the current public quantity of the order
295                // if pqty is bigger than cqty, keep pqty unchanged, otherwise set pqty to cqty as it does not need to hide anymore
296                order.pqty = if order.cqty >= order.pqty { order.pqty } else { order.cqty };
297            }
298        }
299
300        if should_delete {
301            let delete_price = self.delete_order(id)?;
302            Ok((amount_to_send, delete_price))
303        } else {
304            Ok((amount_to_send, None))
305        }
306    }
307
308    /// Deletes an order from the storage, returning the price level if it becomes empty.
309    /// - returns the price level if it becomes empty.
310    pub fn delete_order(&mut self, id: OrderId) -> Result<Option<u64>, L3Error> {
311
312        // remove order id from the list of orders
313        let price = self
314            .orders
315            .get(&id)
316            .ok_or(L3Error::OrderDoesNotExist(id))?
317            .price;
318
319        // remove order node from the order nodes map
320        let order_node = self.order_nodes.get_mut(&id).ok_or(L3Error::OrderDoesNotExist(id))?;
321        let prev = order_node.prev;
322        let next = order_node.next;
323        
324        let mut emptied_price = None;
325        // connect prev and next nodes
326        // if both prev and next are some, connect prev to next
327        if let (Some(prev), Some(next)) = (prev, next) {
328            let prev_node = self
329                .order_nodes
330                .get_mut(&prev)
331                .ok_or(L3Error::OrderDoesNotExist(prev))?;
332            prev_node.next = Some(next);
333        }
334        // if prev is some and next is none, make prev the tail of the price level
335        else if let Some(prev) = prev {
336            let prev_node = self
337                .order_nodes
338                .get_mut(&prev)
339                .ok_or(L3Error::OrderDoesNotExist(prev))?;
340            prev_node.next = None;
341            self.price_tail.insert(price, prev);
342        }
343        // if prev is none and next is some, make next the head of the price level
344        else if let Some(next) = next {
345            let next_node = self
346                .order_nodes
347                .get_mut(&next)
348                .ok_or(L3Error::OrderDoesNotExist(next))?;
349            next_node.prev = None;
350            self.price_head.insert(price, next);
351        }
352        // if both prev and next are none, the price level is empty, and return the price level
353        else {
354            self.price_head.remove(&price);
355            self.price_tail.remove(&price);
356            self.order_nodes.remove(&id);
357            emptied_price = Some(price);
358        }
359        
360        // remove order from the orders map
361        self.orders.remove(&id);
362        Ok(emptied_price)
363    }
364
365    /// Returns the next id that would be assigned on order creation.
366    pub fn next_make_id(&self) -> OrderId {
367        Ulid::new()
368    }
369
370    /// Collects up to `n` order ids from the front of the specified price level.
371    pub fn get_order_ids(&self, price: u64, n: u32) -> Vec<OrderId> {
372        let mut result = Vec::with_capacity(n as usize);
373        let mut current = self.price_head.get(&price).copied();
374
375        while let Some(id) = current {
376            result.push(id);
377            if result.len() as u32 >= n {
378                break;
379            }
380
381            current = self.order_nodes.get(&id).and_then(|node| node.next);
382        }
383
384        result
385    }
386
387    /// Collects up to `n` orders from the front of the specified price level.
388    pub fn get_orders(&self, price: u64, n: u32) -> Vec<Order> {
389        self.get_order_ids(price, n)
390            .into_iter()
391            .filter_map(|id| self.orders.get(&id).cloned())
392            .collect()
393    }
394
395    /// Collects orders within the `[start, end)` window from the specified price level.
396    pub fn get_orders_in_range(&self, price: u64, start: u32, end: u32) -> Vec<Order> {
397        if start >= end {
398            return Vec::new();
399        }
400
401        let mut current_index = 0;
402        let mut current = self.price_head.get(&price).copied();
403        let mut result = Vec::with_capacity((end - start) as usize);
404
405        while let Some(id) = current {
406            if current_index >= start && current_index < end {
407                if let Some(order) = self.orders.get(&id) {
408                    result.push(order.clone());
409                }
410            }
411
412            if current_index >= end {
413                break;
414            }
415
416            current = self.order_nodes.get(&id).and_then(|node| node.next);
417            current_index += 1;
418        }
419
420        result
421    }
422
423    pub fn head(&self, price: u64) -> Option<OrderId> {
424        self.price_head.get(&price).copied()
425    }
426
427    pub fn tail(&self, price: u64) -> Option<OrderId> {
428        self.price_tail.get(&price).copied()
429    }
430
431    pub fn is_empty(&self, price: u64) -> bool {
432        self.price_head.get(&price).is_none()
433    }
434
435    pub fn next(&self, _price: u64, current: OrderId) -> Option<OrderId> {
436        // get the next node in the price level from the current node
437        let next_node = self.order_nodes.get(&current).and_then(|node| node.next);
438        match next_node {
439            Some(next) => Some(next),
440            None => None,   
441        }
442    }
443
444    pub fn get_order(&self, id: OrderId) -> Result<&Order, L3Error> {
445        self.orders.get(&id).ok_or(L3Error::OrderDoesNotExist(id))
446    }
447
448    /// Remove orders that have expired. Returns removed order ids.
449    pub fn remove_dormant_orders(&mut self, now: i64) -> Vec<(OrderId, Order)> {
450        let expired_orders: Vec<(OrderId, Order)> = self
451            .orders
452            .iter()
453            .filter_map(|(id, order)| {
454                if order.expires_at <= now {
455                    Some((*id, order.clone()))
456                } else {
457                    None
458                }
459            })
460            .collect();
461
462        for (id, _) in &expired_orders {
463            let _ = self.delete_order(*id);
464        }
465
466        expired_orders
467    }
468}