Skip to main content

off_grid_primitives/spot/
matching_engine.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4use crate::spot::event::SpotEvent;
5
6use super::event::{self, EventQueue};
7use super::orderbook::{OrderBook, OrderBookError};
8use super::orders::OrderId;
9use super::pair::Pair;
10use super::time_in_force::TimeInForce;
11
12/// Matching engine managing spot trading pairs and their orderbooks.
13///
14/// # Examples
15///
16/// Basic usage:
17///
18/// ```
19/// use off_grid_primitives::spot::MatchingEngine;
20///
21/// // Create a new matching engine
22/// let engine = MatchingEngine::new();
23///
24/// // By default there are no pairs
25/// assert_eq!(engine.pair_count(), 0);
26/// assert!(!engine.has_pair(&b"BTC-USD".to_vec()));
27/// ```
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct MatchingEngine {
30    pairs: HashMap<Vec<u8>, Pair>,
31    total_pairs: u32,
32}
33
34impl MatchingEngine {
35    /// Create a new exchange instance
36    pub fn new() -> Self {
37        Self {
38            pairs: HashMap::new(),
39            total_pairs: 0,
40        }
41    }
42
43    pub fn add_pair(&mut self, cid: impl Into<Vec<u8>>, client_admin_account_id: impl Into<Vec<u8>>, client_fee_account_id: impl Into<Vec<u8>>, pair_id: impl Into<Vec<u8>>, timestamp: i64) {
44        // check if the pair already exists
45        let pair_id_vec = pair_id.into();
46        if self.pairs.contains_key(&pair_id_vec) {
47            // add the client to the pair
48            let cid_vec = cid.into();
49            self.pairs.get_mut(&pair_id_vec).unwrap().add_client(cid_vec.clone(), client_admin_account_id, client_fee_account_id);
50            // emit the event
51            event::emit_event(SpotEvent::SpotPairAdded {
52                cid: cid_vec,
53                pair_id: pair_id_vec,
54                timestamp: timestamp,
55            });
56            return;
57        }
58
59        // create the pair
60        let mut pair = Pair::new();
61        pair.pair_id = pair_id_vec.clone();
62        let cid_vec = cid.into();
63        pair.add_client(cid_vec.clone(), client_admin_account_id, client_fee_account_id);
64        // emit the event
65        event::emit_event(SpotEvent::SpotPairAdded {
66            cid: cid_vec,
67            pair_id: pair_id_vec.clone(),
68            timestamp: timestamp,
69        });
70        self.pairs.insert(pair_id_vec, pair);
71        self.total_pairs += 1;
72    }
73
74    pub fn add_pair_client(
75        &mut self,
76        cid: impl Into<Vec<u8>>,
77        pair_id: impl Into<Vec<u8>>,
78        admin_account_id: impl Into<Vec<u8>>,
79        fee_account_id: impl Into<Vec<u8>>,
80    ) -> Result<EventQueue, OrderBookError> {
81        let pair_id_vec = pair_id.into();
82        let pair = self.pairs.get_mut(&pair_id_vec).unwrap();
83        pair.add_client(cid.into(), admin_account_id, fee_account_id);
84        Ok(event::drain_events())
85    }
86
87    /// Place a limit sell order (ask order)
88    /// Matches against existing orders first, then places remaining in orderbook based on time_in_force
89    ///
90    /// Returns `((order_id, found_dormant), events)` where:
91    /// - `order_id`: The order ID of the placed order
92    /// - `found_dormant`: Whether a dormant order was found and reused
93    /// - `events`: Vector of events emitted during this operation
94    pub fn limit_sell(
95        &mut self,
96        cid: impl Into<Vec<u8>>,
97        pair_id: impl Into<Vec<u8>>,
98        existing_order_id: Option<OrderId>,
99        owner: impl Into<Vec<u8>>,
100        price: u64,
101        // whole amount
102        amnt: u64,
103        // iceberg quantity
104        iqty: u64,
105        timestamp: i64,
106        expires_at: i64,
107        maker_fee_bps: i16,
108        taker_fee_bps: i16,
109        time_in_force: TimeInForce,
110    ) -> Result<EventQueue, OrderBookError> {
111        // find a pair
112        let pair_id_vec = pair_id.into();
113        let pair = self.pairs.get_mut(&pair_id_vec).unwrap();
114        pair.limit_sell(
115            cid,
116            existing_order_id,
117            owner,
118            price,
119            amnt,
120            iqty,
121            timestamp,
122            expires_at,
123            maker_fee_bps,
124            taker_fee_bps,
125            time_in_force,
126        )?;
127        
128        // Drain all events that were emitted during this operation
129        let events = event::drain_events();
130        
131        Ok(events)
132    }
133
134    /// Place a limit buy order (bid order)
135    /// Matches against existing orders first, then places remaining in orderbook based on time_in_force
136    ///
137    /// Returns `((order_id, found_dormant), events)` where:
138    /// - `order_id`: The order ID of the placed order
139    /// - `found_dormant`: Whether a dormant order was found and reused
140    /// - `events`: Vector of events emitted during this operation
141    pub fn limit_buy(
142        &mut self,
143        cid: impl Into<Vec<u8>>,
144        pair_id: impl Into<Vec<u8>>,
145        existing_order_id: Option<OrderId>,
146        owner: impl Into<Vec<u8>>,
147        price: u64,
148        amount: u64,
149        public_amount: u64,
150        timestamp: i64,
151        expires_at: i64,
152        maker_fee_bps: i16,
153        taker_fee_bps: i16,
154        time_in_force: TimeInForce,
155    ) -> Result<EventQueue, OrderBookError> {
156        // find a pair
157        let pair_id_vec = pair_id.into();
158        let pair = self.pairs.get_mut(&pair_id_vec).unwrap();
159        pair.limit_buy(
160            cid,
161            existing_order_id,
162            owner,
163            price,
164            amount,
165            public_amount,
166            timestamp,
167            expires_at,
168            maker_fee_bps,
169            taker_fee_bps,
170            time_in_force,
171        )?;
172        
173        // Drain all events that were emitted during this operation
174        let events = event::drain_events();
175        
176        Ok(events)
177    }
178
179    /// Execute a market sell order
180    /// Matches against existing orders first (market orders match at any price)
181    ///
182    /// Returns `(OrderMatch, events)` where:
183    /// - `OrderMatch`: Contains trade execution details
184    /// - `events`: Vector of events emitted during this operation
185    pub fn market_sell(
186        &mut self,
187        cid: impl Into<Vec<u8>>,
188        pair_id: impl Into<Vec<u8>>,
189        existing_order_id: Option<OrderId>,
190        owner: impl Into<Vec<u8>>,
191        amount: u64,
192        public_amount: u64,
193        timestamp: i64,
194        expires_at: i64,
195        maker_fee_bps: i16,
196        taker_fee_bps: i16,
197        time_in_force: TimeInForce,
198    ) -> Result<EventQueue, OrderBookError> {
199        let pair_id_vec = pair_id.into();
200        let pair = self.pairs.get_mut(&pair_id_vec).unwrap();
201        pair.market_sell(
202            cid,
203            existing_order_id,
204            owner,
205            amount,
206            public_amount,
207            timestamp,
208            expires_at,
209            maker_fee_bps,
210            taker_fee_bps,
211            time_in_force,
212        )?;
213        
214        // Drain all events that were emitted during this operation
215        let events = event::drain_events();
216        
217        Ok(events)
218    }
219
220    /// Execute a market buy order
221    /// Matches against existing orders first (market orders match at any price)
222    ///
223    /// Returns `(OrderMatch, events)` where:
224    /// - `OrderMatch`: Contains trade execution details
225    /// - `events`: Vector of events emitted during this operation
226    pub fn market_buy(
227        &mut self,
228        cid: impl Into<Vec<u8>>,
229        pair_id: impl Into<Vec<u8>>,
230        existing_order_id: Option<OrderId>,
231        owner: impl Into<Vec<u8>>,
232        amount: u64,
233        public_amount: u64,
234        timestamp: i64,
235        expires_at: i64,
236        maker_fee_bps: i16,
237        taker_fee_bps: i16,
238        time_in_force: TimeInForce,
239    ) -> Result<EventQueue, OrderBookError> {
240        let pair_id_vec = pair_id.into();
241        let pair = self.pairs.get_mut(&pair_id_vec).unwrap();
242        pair.market_buy(
243            cid,
244            existing_order_id,
245            owner,
246            amount,
247            public_amount,
248            timestamp,
249            expires_at,
250            maker_fee_bps,
251            taker_fee_bps,
252            time_in_force,
253        )?;
254        
255        // Drain all events that were emitted during this operation
256        let events = event::drain_events();
257        
258        Ok(events)
259    }
260
261    /// Cancel an order
262    ///
263    /// Returns `events` - Vector of events emitted during this operation
264    ///
265    /// - `order_id`: The ID of the order to cancel
266    /// - `owner`: The owner of the order (for authorization)
267    /// - `is_bid`: Whether the order is a bid (true) or ask (false)
268    pub fn cancel_order(
269        &mut self,
270        cid: impl Into<Vec<u8>>,
271        pair_id: impl Into<Vec<u8>>,
272        order_id: OrderId,
273        owner: impl Into<Vec<u8>>,
274        is_bid: bool,
275        ) -> Result<EventQueue, OrderBookError> {
276        let pair_id_vec = pair_id.into();
277        let pair = self.pairs.get_mut(&pair_id_vec).unwrap();
278        pair.cancel_order(cid, pair_id_vec, is_bid, order_id, owner)?;
279        
280        // Drain all events that were emitted during this operation
281        let events = event::drain_events();
282        
283        Ok(events)
284    }
285
286    /// Get the number of pairs in the matching engine
287    pub fn pair_count(&self) -> usize {
288        self.pairs.len()
289    }
290
291    /// Check if a pair exists
292    pub fn has_pair(&self, pair_id: &Vec<u8>) -> bool {
293        self.pairs.contains_key(&pair_id.clone())
294    }
295
296    /// Get the orderbook for a pair (for tests / inspection).
297    pub fn orderbook(&self, pair_id: &[u8]) -> Option<&OrderBook> {
298        self.pairs.get(pair_id).map(|p| &p.orderbook)
299    }
300}
301
302impl Default for MatchingEngine {
303    fn default() -> Self {
304        Self::new()
305    }
306}