1use std::cmp::Ordering;
4use std::collections::BTreeMap;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Order {
15 pub id: u64,
17 pub symbol_id: u32,
19 pub side: Side,
21 pub order_type: OrderType,
23 pub price: Price,
25 pub quantity: Quantity,
27 pub remaining: Quantity,
29 pub tif: TimeInForce,
31 pub timestamp: u64,
33 pub trader_id: u64,
35 pub status: OrderStatus,
37}
38
39impl Order {
40 pub fn limit(
42 id: u64,
43 symbol_id: u32,
44 side: Side,
45 price: Price,
46 quantity: Quantity,
47 trader_id: u64,
48 timestamp: u64,
49 ) -> Self {
50 Self {
51 id,
52 symbol_id,
53 side,
54 order_type: OrderType::Limit,
55 price,
56 quantity,
57 remaining: quantity,
58 tif: TimeInForce::GTC,
59 timestamp,
60 trader_id,
61 status: OrderStatus::New,
62 }
63 }
64
65 pub fn market(
67 id: u64,
68 symbol_id: u32,
69 side: Side,
70 quantity: Quantity,
71 trader_id: u64,
72 timestamp: u64,
73 ) -> Self {
74 Self {
75 id,
76 symbol_id,
77 side,
78 order_type: OrderType::Market,
79 price: Price(0), quantity,
81 remaining: quantity,
82 tif: TimeInForce::IOC,
83 timestamp,
84 trader_id,
85 status: OrderStatus::New,
86 }
87 }
88
89 pub fn is_filled(&self) -> bool {
91 self.remaining.0 == 0
92 }
93
94 pub fn is_active(&self) -> bool {
96 matches!(self.status, OrderStatus::New | OrderStatus::PartiallyFilled)
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
102pub enum Side {
103 Buy,
105 Sell,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111pub enum OrderType {
112 Limit,
114 Market,
116 Stop,
118 StopLimit,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
124pub enum TimeInForce {
125 GTC,
127 IOC,
129 FOK,
131 Day,
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
137pub enum OrderStatus {
138 New,
140 PartiallyFilled,
142 Filled,
144 Canceled,
146 Rejected,
148 Expired,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
158pub struct Price(pub i64);
159
160impl Price {
161 pub fn from_f64(price: f64) -> Self {
163 Self((price * 100_000_000.0) as i64)
164 }
165
166 pub fn to_f64(self) -> f64 {
168 self.0 as f64 / 100_000_000.0
169 }
170}
171
172impl Ord for Price {
173 fn cmp(&self, other: &Self) -> Ordering {
174 self.0.cmp(&other.0)
175 }
176}
177
178impl PartialOrd for Price {
179 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
180 Some(self.cmp(other))
181 }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
186pub struct Quantity(pub u64);
187
188impl Quantity {
189 pub fn from_f64(qty: f64) -> Self {
191 Self((qty * 100_000_000.0) as u64)
192 }
193
194 pub fn to_f64(self) -> f64 {
196 self.0 as f64 / 100_000_000.0
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct PriceLevel {
207 pub price: Price,
209 pub orders: Vec<u64>, pub total_quantity: Quantity,
213 pub order_count: u32,
215}
216
217impl PriceLevel {
218 pub fn new(price: Price) -> Self {
220 Self {
221 price,
222 orders: Vec::new(),
223 total_quantity: Quantity(0),
224 order_count: 0,
225 }
226 }
227
228 pub fn add_order(&mut self, order_id: u64, quantity: Quantity) {
230 self.orders.push(order_id);
231 self.total_quantity.0 += quantity.0;
232 self.order_count += 1;
233 }
234
235 pub fn remove_order(&mut self, order_id: u64, quantity: Quantity) -> bool {
237 if let Some(pos) = self.orders.iter().position(|&id| id == order_id) {
238 self.orders.remove(pos);
239 self.total_quantity.0 = self.total_quantity.0.saturating_sub(quantity.0);
240 self.order_count = self.order_count.saturating_sub(1);
241 true
242 } else {
243 false
244 }
245 }
246
247 pub fn is_empty(&self) -> bool {
249 self.orders.is_empty()
250 }
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct OrderBook {
256 pub symbol_id: u32,
258 pub bids: BTreeMap<Price, PriceLevel>,
260 pub asks: BTreeMap<Price, PriceLevel>,
262 pub last_price: Option<Price>,
264 pub volume_24h: Quantity,
266}
267
268impl OrderBook {
269 pub fn new(symbol_id: u32) -> Self {
271 Self {
272 symbol_id,
273 bids: BTreeMap::new(),
274 asks: BTreeMap::new(),
275 last_price: None,
276 volume_24h: Quantity(0),
277 }
278 }
279
280 pub fn best_bid(&self) -> Option<Price> {
282 self.bids.keys().next_back().copied()
283 }
284
285 pub fn best_ask(&self) -> Option<Price> {
287 self.asks.keys().next().copied()
288 }
289
290 pub fn spread(&self) -> Option<Price> {
292 match (self.best_ask(), self.best_bid()) {
293 (Some(ask), Some(bid)) => Some(Price(ask.0 - bid.0)),
294 _ => None,
295 }
296 }
297
298 pub fn mid_price(&self) -> Option<Price> {
300 match (self.best_ask(), self.best_bid()) {
301 (Some(ask), Some(bid)) => Some(Price((ask.0 + bid.0) / 2)),
302 _ => None,
303 }
304 }
305
306 pub fn bid_depth(&self) -> Quantity {
308 Quantity(self.bids.values().map(|l| l.total_quantity.0).sum())
309 }
310
311 pub fn ask_depth(&self) -> Quantity {
313 Quantity(self.asks.values().map(|l| l.total_quantity.0).sum())
314 }
315
316 pub fn depth_at_levels(&self, n: usize) -> (Quantity, Quantity) {
318 let bid_depth: u64 = self
319 .bids
320 .values()
321 .rev()
322 .take(n)
323 .map(|l| l.total_quantity.0)
324 .sum();
325 let ask_depth: u64 = self.asks.values().take(n).map(|l| l.total_quantity.0).sum();
326 (Quantity(bid_depth), Quantity(ask_depth))
327 }
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct Trade {
337 pub id: u64,
339 pub symbol_id: u32,
341 pub buy_order_id: u64,
343 pub sell_order_id: u64,
345 pub price: Price,
347 pub quantity: Quantity,
349 pub timestamp: u64,
351 pub aggressor: Side,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct MatchResult {
358 pub order_id: u64,
360 pub status: OrderStatus,
362 pub filled_quantity: Quantity,
364 pub avg_price: Price,
366 pub trades: Vec<Trade>,
368 pub remaining: Quantity,
370}
371
372impl MatchResult {
373 pub fn no_match(order_id: u64, quantity: Quantity) -> Self {
375 Self {
376 order_id,
377 status: OrderStatus::New,
378 filled_quantity: Quantity(0),
379 avg_price: Price(0),
380 trades: Vec::new(),
381 remaining: quantity,
382 }
383 }
384
385 pub fn full_fill(order_id: u64, trades: Vec<Trade>) -> Self {
387 let filled: u64 = trades.iter().map(|t| t.quantity.0).sum();
388 let avg_price = if filled > 0 {
390 let total_value: f64 = trades
391 .iter()
392 .map(|t| t.price.to_f64() * t.quantity.to_f64())
393 .sum();
394 let total_qty: f64 = trades.iter().map(|t| t.quantity.to_f64()).sum();
395 Price::from_f64(total_value / total_qty)
396 } else {
397 Price(0)
398 };
399
400 Self {
401 order_id,
402 status: OrderStatus::Filled,
403 filled_quantity: Quantity(filled),
404 avg_price,
405 trades,
406 remaining: Quantity(0),
407 }
408 }
409
410 pub fn partial_fill(order_id: u64, trades: Vec<Trade>, remaining: Quantity) -> Self {
412 let filled: u64 = trades.iter().map(|t| t.quantity.0).sum();
413 let avg_price = if filled > 0 {
415 let total_value: f64 = trades
416 .iter()
417 .map(|t| t.price.to_f64() * t.quantity.to_f64())
418 .sum();
419 let total_qty: f64 = trades.iter().map(|t| t.quantity.to_f64()).sum();
420 Price::from_f64(total_value / total_qty)
421 } else {
422 Price(0)
423 };
424
425 Self {
426 order_id,
427 status: OrderStatus::PartiallyFilled,
428 filled_quantity: Quantity(filled),
429 avg_price,
430 trades,
431 remaining,
432 }
433 }
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct L2Snapshot {
443 pub symbol_id: u32,
445 pub bids: Vec<(Price, Quantity)>,
447 pub asks: Vec<(Price, Quantity)>,
449 pub timestamp: u64,
451}
452
453impl L2Snapshot {
454 pub fn from_book(book: &OrderBook, depth: usize, timestamp: u64) -> Self {
456 let bids: Vec<_> = book
457 .bids
458 .iter()
459 .rev()
460 .take(depth)
461 .map(|(&price, level)| (price, level.total_quantity))
462 .collect();
463
464 let asks: Vec<_> = book
465 .asks
466 .iter()
467 .take(depth)
468 .map(|(&price, level)| (price, level.total_quantity))
469 .collect();
470
471 Self {
472 symbol_id: book.symbol_id,
473 bids,
474 asks,
475 timestamp,
476 }
477 }
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize)]
486pub struct EngineConfig {
487 pub max_order_size: Quantity,
489 pub min_order_size: Quantity,
491 pub tick_size: Price,
493 pub max_price_levels: usize,
495 pub self_trade_prevention: bool,
497}
498
499impl Default for EngineConfig {
500 fn default() -> Self {
501 Self {
502 max_order_size: Quantity::from_f64(1_000_000.0),
503 min_order_size: Quantity::from_f64(0.001),
504 tick_size: Price::from_f64(0.01),
505 max_price_levels: 1000,
506 self_trade_prevention: true,
507 }
508 }
509}