use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct L3Order {
pub order_id: String,
pub price: Decimal,
pub qty: Decimal,
pub timestamp: u64,
pub sequence: u64,
}
impl L3Order {
pub fn new(order_id: impl Into<String>, price: Decimal, qty: Decimal) -> Self {
Self {
order_id: order_id.into(),
price,
qty,
timestamp: 0,
sequence: 0,
}
}
pub fn with_metadata(
order_id: impl Into<String>,
price: Decimal,
qty: Decimal,
timestamp: u64,
sequence: u64,
) -> Self {
Self {
order_id: order_id.into(),
price,
qty,
timestamp,
sequence,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct L3PriceLevel {
pub price: Decimal,
orders: Vec<L3Order>,
total_qty: Decimal,
}
impl L3PriceLevel {
pub fn new(price: Decimal) -> Self {
Self {
price,
orders: Vec::new(),
total_qty: Decimal::ZERO,
}
}
pub fn add_order(&mut self, order: L3Order) {
self.total_qty += order.qty;
self.orders.push(order);
}
pub fn remove_order(&mut self, order_id: &str) -> Option<L3Order> {
if let Some(idx) = self.orders.iter().position(|o| o.order_id == order_id) {
let order = self.orders.remove(idx);
self.total_qty -= order.qty;
Some(order)
} else {
None
}
}
pub fn modify_order(&mut self, order_id: &str, new_qty: Decimal) -> bool {
if let Some(order) = self.orders.iter_mut().find(|o| o.order_id == order_id) {
self.total_qty = self.total_qty - order.qty + new_qty;
order.qty = new_qty;
true
} else {
false
}
}
pub fn get_order(&self, order_id: &str) -> Option<&L3Order> {
self.orders.iter().find(|o| o.order_id == order_id)
}
pub fn queue_position(&self, order_id: &str) -> Option<QueuePosition> {
let mut qty_ahead = Decimal::ZERO;
for (idx, order) in self.orders.iter().enumerate() {
if order.order_id == order_id {
return Some(QueuePosition {
position: idx,
orders_ahead: idx,
qty_ahead,
total_orders: self.orders.len(),
total_qty: self.total_qty,
});
}
qty_ahead += order.qty;
}
None
}
pub fn total_qty(&self) -> Decimal {
self.total_qty
}
pub fn order_count(&self) -> usize {
self.orders.len()
}
pub fn is_empty(&self) -> bool {
self.orders.is_empty()
}
pub fn orders(&self) -> impl Iterator<Item = &L3Order> {
self.orders.iter()
}
pub fn oldest(&self) -> Option<&L3Order> {
self.orders.first()
}
pub fn newest(&self) -> Option<&L3Order> {
self.orders.last()
}
pub fn avg_order_size(&self) -> Option<Decimal> {
if self.orders.is_empty() {
None
} else {
Some(self.total_qty / Decimal::from(self.orders.len()))
}
}
pub fn recalculate_total(&mut self) {
self.total_qty = self.orders.iter().map(|o| o.qty).sum();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct QueuePosition {
pub position: usize,
pub orders_ahead: usize,
pub qty_ahead: Decimal,
pub total_orders: usize,
pub total_qty: Decimal,
}
impl QueuePosition {
pub fn fill_probability(&self) -> f64 {
if self.total_orders == 0 {
return 0.0;
}
1.0 - (self.position as f64 / self.total_orders as f64)
}
pub fn is_first(&self) -> bool {
self.position == 0
}
pub fn is_last(&self) -> bool {
self.position == self.total_orders.saturating_sub(1)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OrderLocation {
pub price: Decimal,
pub side: L3Side,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum L3Side {
Bid,
Ask,
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_l3_order_creation() {
let order = L3Order::new("order1", dec!(100.50), dec!(1.5));
assert_eq!(order.order_id, "order1");
assert_eq!(order.price, dec!(100.50));
assert_eq!(order.qty, dec!(1.5));
}
#[test]
fn test_price_level_add_order() {
let mut level = L3PriceLevel::new(dec!(100));
level.add_order(L3Order::new("o1", dec!(100), dec!(1)));
level.add_order(L3Order::new("o2", dec!(100), dec!(2)));
assert_eq!(level.order_count(), 2);
assert_eq!(level.total_qty(), dec!(3));
}
#[test]
fn test_price_level_remove_order() {
let mut level = L3PriceLevel::new(dec!(100));
level.add_order(L3Order::new("o1", dec!(100), dec!(1)));
level.add_order(L3Order::new("o2", dec!(100), dec!(2)));
let removed = level.remove_order("o1");
assert!(removed.is_some());
assert_eq!(removed.unwrap().order_id, "o1");
assert_eq!(level.order_count(), 1);
assert_eq!(level.total_qty(), dec!(2));
}
#[test]
fn test_price_level_modify_order() {
let mut level = L3PriceLevel::new(dec!(100));
level.add_order(L3Order::new("o1", dec!(100), dec!(5)));
assert!(level.modify_order("o1", dec!(3)));
assert_eq!(level.total_qty(), dec!(3));
assert_eq!(level.get_order("o1").unwrap().qty, dec!(3));
}
#[test]
fn test_queue_position() {
let mut level = L3PriceLevel::new(dec!(100));
level.add_order(L3Order::new("o1", dec!(100), dec!(1)));
level.add_order(L3Order::new("o2", dec!(100), dec!(2)));
level.add_order(L3Order::new("o3", dec!(100), dec!(3)));
let pos1 = level.queue_position("o1").unwrap();
assert_eq!(pos1.position, 0);
assert_eq!(pos1.qty_ahead, dec!(0));
assert!(pos1.is_first());
let pos2 = level.queue_position("o2").unwrap();
assert_eq!(pos2.position, 1);
assert_eq!(pos2.qty_ahead, dec!(1));
let pos3 = level.queue_position("o3").unwrap();
assert_eq!(pos3.position, 2);
assert_eq!(pos3.qty_ahead, dec!(3));
assert!(pos3.is_last());
}
#[test]
fn test_fifo_order() {
let mut level = L3PriceLevel::new(dec!(100));
level.add_order(L3Order::new("first", dec!(100), dec!(1)));
level.add_order(L3Order::new("second", dec!(100), dec!(1)));
level.add_order(L3Order::new("third", dec!(100), dec!(1)));
assert_eq!(level.oldest().unwrap().order_id, "first");
assert_eq!(level.newest().unwrap().order_id, "third");
}
#[test]
fn test_queue_position_fill_probability() {
let mut level = L3PriceLevel::new(dec!(100));
level.add_order(L3Order::new("o1", dec!(100), dec!(1)));
level.add_order(L3Order::new("o2", dec!(100), dec!(1)));
level.add_order(L3Order::new("o3", dec!(100), dec!(1)));
level.add_order(L3Order::new("o4", dec!(100), dec!(1)));
let pos1 = level.queue_position("o1").unwrap();
assert_eq!(pos1.fill_probability(), 1.0);
let pos4 = level.queue_position("o4").unwrap();
assert_eq!(pos4.fill_probability(), 0.25);
}
}