use rustc_hash::FxHashMap;
use crate::{Order, OrderId, Price, PriceLevels, Quantity, Side, TimeInForce, Timestamp, TradeId};
#[cfg(test)]
use crate::OrderStatus;
#[derive(Clone, Debug)]
pub struct OrderBook {
bids: PriceLevels,
asks: PriceLevels,
pub(crate) orders: FxHashMap<OrderId, Order>,
next_order_id: u64,
next_trade_id: u64,
next_timestamp: u64,
}
impl OrderBook {
pub fn new() -> Self {
Self {
bids: PriceLevels::new(Side::Buy),
asks: PriceLevels::new(Side::Sell),
orders: FxHashMap::default(),
next_order_id: 1,
next_trade_id: 1,
next_timestamp: 1,
}
}
pub fn next_order_id(&mut self) -> OrderId {
let id = OrderId(self.next_order_id);
self.next_order_id += 1;
id
}
pub fn next_trade_id(&mut self) -> TradeId {
let id = TradeId(self.next_trade_id);
self.next_trade_id += 1;
id
}
pub fn next_timestamp(&mut self) -> Timestamp {
let ts = self.next_timestamp;
self.next_timestamp += 1;
ts
}
pub fn peek_next_order_id(&self) -> OrderId {
OrderId(self.next_order_id)
}
pub fn get_order(&self, order_id: OrderId) -> Option<&Order> {
self.orders.get(&order_id)
}
pub fn get_order_mut(&mut self, order_id: OrderId) -> Option<&mut Order> {
self.orders.get_mut(&order_id)
}
pub fn contains_order(&self, order_id: OrderId) -> bool {
self.orders.contains_key(&order_id)
}
pub fn order_count(&self) -> usize {
self.orders.len()
}
pub fn active_order_count(&self) -> usize {
self.orders.values().filter(|o| o.is_active()).count()
}
pub fn bids(&self) -> &PriceLevels {
&self.bids
}
pub fn asks(&self) -> &PriceLevels {
&self.asks
}
pub fn bids_mut(&mut self) -> &mut PriceLevels {
&mut self.bids
}
pub fn asks_mut(&mut self) -> &mut PriceLevels {
&mut self.asks
}
pub fn side(&self, side: Side) -> &PriceLevels {
match side {
Side::Buy => &self.bids,
Side::Sell => &self.asks,
}
}
pub fn side_mut(&mut self, side: Side) -> &mut PriceLevels {
match side {
Side::Buy => &mut self.bids,
Side::Sell => &mut self.asks,
}
}
pub fn opposite_side(&self, side: Side) -> &PriceLevels {
self.side(side.opposite())
}
pub fn opposite_side_mut(&mut self, side: Side) -> &mut PriceLevels {
self.side_mut(side.opposite())
}
pub fn best_bid(&self) -> Option<Price> {
self.bids.best_price()
}
pub fn best_ask(&self) -> Option<Price> {
self.asks.best_price()
}
pub fn best_bid_ask(&self) -> (Option<Price>, Option<Price>) {
(self.best_bid(), self.best_ask())
}
pub fn spread(&self) -> Option<i64> {
match (self.best_bid(), self.best_ask()) {
(Some(bid), Some(ask)) => Some(ask.0 - bid.0),
_ => None,
}
}
pub fn is_crossed(&self) -> bool {
match (self.best_bid(), self.best_ask()) {
(Some(bid), Some(ask)) => bid >= ask,
_ => false,
}
}
pub fn add_order(&mut self, mut order: Order) {
assert!(
!self.orders.contains_key(&order.id),
"order {} already exists",
order.id
);
let side = order.side;
let price = order.price;
let quantity = order.remaining_quantity;
let order_id = order.id;
let index = self.side_mut(side).insert_order(price, order_id, quantity);
order.position_in_level = index;
self.orders.insert(order_id, order);
}
pub fn cancel_order(&mut self, order_id: OrderId) -> Option<Quantity> {
let order = self.orders.get_mut(&order_id)?;
if !order.is_active() {
return None;
}
let side = order.side;
let price = order.price;
let remaining = order.remaining_quantity;
let index = order.position_in_level;
order.cancel();
self.side_mut(side).mark_tombstone(price, index, remaining);
Some(remaining)
}
pub fn create_order(
&mut self,
side: Side,
price: Price,
quantity: Quantity,
time_in_force: TimeInForce,
) -> Order {
let id = self.next_order_id();
let timestamp = self.next_timestamp();
Order::new(id, side, price, quantity, timestamp, time_in_force)
}
pub fn clear_history(&mut self) -> usize {
let before = self.orders.len();
self.orders.retain(|_, order| order.is_active());
before - self.orders.len()
}
pub fn compact(&mut self) {
self.bids.compact();
self.asks.compact();
}
}
impl Default for OrderBook {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_book_is_empty() {
let book = OrderBook::new();
assert_eq!(book.order_count(), 0);
assert_eq!(book.active_order_count(), 0);
assert_eq!(book.best_bid(), None);
assert_eq!(book.best_ask(), None);
assert_eq!(book.spread(), None);
assert!(!book.is_crossed());
}
#[test]
fn id_generation_is_monotonic() {
let mut book = OrderBook::new();
assert_eq!(book.next_order_id(), OrderId(1));
assert_eq!(book.next_order_id(), OrderId(2));
assert_eq!(book.next_order_id(), OrderId(3));
assert_eq!(book.next_trade_id(), TradeId(1));
assert_eq!(book.next_trade_id(), TradeId(2));
assert_eq!(book.next_timestamp(), 1);
assert_eq!(book.next_timestamp(), 2);
}
#[test]
fn peek_order_id_does_not_consume() {
let mut book = OrderBook::new();
assert_eq!(book.peek_next_order_id(), OrderId(1));
assert_eq!(book.peek_next_order_id(), OrderId(1));
assert_eq!(book.next_order_id(), OrderId(1));
assert_eq!(book.peek_next_order_id(), OrderId(2));
}
#[test]
fn add_and_get_order() {
let mut book = OrderBook::new();
let order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let order_id = order.id;
book.add_order(order);
assert!(book.contains_order(order_id));
assert_eq!(book.order_count(), 1);
assert_eq!(book.active_order_count(), 1);
let retrieved = book.get_order(order_id).unwrap();
assert_eq!(retrieved.id, order_id);
assert_eq!(retrieved.price, Price(100_00));
assert_eq!(retrieved.remaining_quantity, 100);
}
#[test]
fn add_order_updates_best_prices() {
let mut book = OrderBook::new();
let bid = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
book.add_order(bid);
assert_eq!(book.best_bid(), Some(Price(100_00)));
assert_eq!(book.best_ask(), None);
let ask = book.create_order(Side::Sell, Price(101_00), 100, TimeInForce::GTC);
book.add_order(ask);
assert_eq!(book.best_bid(), Some(Price(100_00)));
assert_eq!(book.best_ask(), Some(Price(101_00)));
}
#[test]
fn spread_calculation() {
let mut book = OrderBook::new();
assert_eq!(book.spread(), None);
let bid = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
book.add_order(bid);
assert_eq!(book.spread(), None);
let ask = book.create_order(Side::Sell, Price(101_50), 100, TimeInForce::GTC);
book.add_order(ask);
assert_eq!(book.spread(), Some(150)); }
#[test]
fn cancel_order() {
let mut book = OrderBook::new();
let order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let order_id = order.id;
book.add_order(order);
assert_eq!(book.active_order_count(), 1);
assert_eq!(book.best_bid(), Some(Price(100_00)));
let cancelled = book.cancel_order(order_id);
assert_eq!(cancelled, Some(100));
assert_eq!(book.order_count(), 1);
assert_eq!(book.active_order_count(), 0);
assert_eq!(
book.get_order(order_id).unwrap().status,
OrderStatus::Cancelled
);
assert_eq!(book.best_bid(), None);
}
#[test]
fn cancel_nonexistent_order() {
let mut book = OrderBook::new();
assert_eq!(book.cancel_order(OrderId(999)), None);
}
#[test]
fn cancel_already_cancelled() {
let mut book = OrderBook::new();
let order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let order_id = order.id;
book.add_order(order);
book.cancel_order(order_id);
assert_eq!(book.cancel_order(order_id), None); }
#[test]
fn multiple_orders_same_price() {
let mut book = OrderBook::new();
let o1 = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let o2 = book.create_order(Side::Buy, Price(100_00), 200, TimeInForce::GTC);
let o3 = book.create_order(Side::Buy, Price(100_00), 150, TimeInForce::GTC);
book.add_order(o1);
book.add_order(o2);
book.add_order(o3);
assert_eq!(book.active_order_count(), 3);
assert_eq!(book.bids().level_count(), 1);
assert_eq!(book.bids().total_quantity(), 450);
}
#[test]
fn multiple_price_levels() {
let mut book = OrderBook::new();
let o1 = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let o2 = book.create_order(Side::Buy, Price(99_00), 200, TimeInForce::GTC);
let o3 = book.create_order(Side::Sell, Price(101_00), 150, TimeInForce::GTC);
let o4 = book.create_order(Side::Sell, Price(102_00), 175, TimeInForce::GTC);
book.add_order(o1);
book.add_order(o2);
book.add_order(o3);
book.add_order(o4);
assert_eq!(book.bids().level_count(), 2);
assert_eq!(book.asks().level_count(), 2);
assert_eq!(book.best_bid(), Some(Price(100_00)));
assert_eq!(book.best_ask(), Some(Price(101_00)));
}
#[test]
fn is_crossed() {
let mut book = OrderBook::new();
assert!(!book.is_crossed());
let bid = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let ask = book.create_order(Side::Sell, Price(101_00), 100, TimeInForce::GTC);
book.add_order(bid);
book.add_order(ask);
assert!(!book.is_crossed());
let high_bid = book.create_order(Side::Buy, Price(102_00), 100, TimeInForce::GTC);
book.add_order(high_bid);
assert!(book.is_crossed());
}
#[test]
fn opposite_side() {
let mut book = OrderBook::new();
let bid = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let ask = book.create_order(Side::Sell, Price(101_00), 100, TimeInForce::GTC);
book.add_order(bid);
book.add_order(ask);
assert_eq!(
book.opposite_side(Side::Buy).best_price(),
Some(Price(101_00))
);
assert_eq!(
book.opposite_side(Side::Sell).best_price(),
Some(Price(100_00))
);
}
#[test]
#[should_panic(expected = "already exists")]
fn add_duplicate_order_panics() {
let mut book = OrderBook::new();
let order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let order_clone = order.clone();
book.add_order(order);
book.add_order(order_clone); }
#[test]
fn get_order_mut() {
let mut book = OrderBook::new();
let order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let order_id = order.id;
book.add_order(order);
{
let order = book.get_order_mut(order_id).unwrap();
order.fill(30);
}
let order = book.get_order(order_id).unwrap();
assert_eq!(order.remaining_quantity, 70);
assert_eq!(order.filled_quantity, 30);
}
#[test]
fn clear_history_removes_inactive_orders() {
let mut book = OrderBook::new();
let o1 = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let o2 = book.create_order(Side::Buy, Price(99_00), 100, TimeInForce::GTC);
let o3 = book.create_order(Side::Sell, Price(101_00), 100, TimeInForce::GTC);
let o1_id = o1.id;
let o2_id = o2.id;
let o3_id = o3.id;
book.add_order(o1);
book.add_order(o2);
book.add_order(o3);
assert_eq!(book.order_count(), 3);
book.cancel_order(o1_id);
assert_eq!(book.order_count(), 3);
assert_eq!(book.active_order_count(), 2);
let removed = book.clear_history();
assert_eq!(removed, 1);
assert_eq!(book.order_count(), 2);
assert_eq!(book.active_order_count(), 2);
assert!(book.get_order(o1_id).is_none());
assert!(book.get_order(o2_id).is_some());
assert!(book.get_order(o3_id).is_some());
}
}