#[cfg(test)]
mod tests {
use crate::{OrderBook, OrderBookError};
use pricelevel::{OrderId, OrderType, Side, TimeInForce};
use uuid::Uuid;
fn create_order_id() -> OrderId {
OrderId(Uuid::new_v4())
}
fn create_standard_order(price: u64, quantity: u64, side: Side) -> OrderType {
OrderType::Standard {
id: create_order_id(),
price,
quantity,
side,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Gtc,
}
}
fn create_iceberg_order(price: u64, visible: u64, hidden: u64, side: Side) -> OrderType {
OrderType::IcebergOrder {
id: create_order_id(),
price,
visible_quantity: visible,
hidden_quantity: hidden,
side,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Gtc,
}
}
fn create_post_only_order(price: u64, quantity: u64, side: Side) -> OrderType {
OrderType::PostOnly {
id: create_order_id(),
price,
quantity,
side,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Gtc,
}
}
#[test]
fn test_new_order_book() {
let symbol = "BTCUSD";
let book = OrderBook::new(symbol);
assert_eq!(book.symbol(), symbol);
assert_eq!(book.best_bid(), None);
assert_eq!(book.best_ask(), None);
assert_eq!(book.mid_price(), None);
assert_eq!(book.spread(), None);
assert_eq!(book.last_trade_price(), None);
}
#[test]
fn test_add_standard_order() {
let book = OrderBook::new("BTCUSD");
let order = create_standard_order(1000, 10, Side::Buy);
let order_id = order.id();
let result = book.add_order(order);
assert!(result.is_ok());
assert_eq!(book.best_bid(), Some(1000));
let fetched_order = book.get_order(order_id);
assert!(fetched_order.is_some());
assert_eq!(fetched_order.unwrap().id(), order_id);
}
#[test]
fn test_add_multiple_bids() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1000, 10, Side::Buy));
let _ = book.add_order(create_standard_order(1010, 5, Side::Buy));
let _ = book.add_order(create_standard_order(990, 15, Side::Buy));
assert_eq!(book.best_bid(), Some(1010));
let orders_at_1000 = book.get_orders_at_price(1000, Side::Buy);
assert_eq!(orders_at_1000.len(), 1);
let all_orders = book.get_all_orders();
assert_eq!(all_orders.len(), 3);
}
#[test]
fn test_add_multiple_asks() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1050, 10, Side::Sell));
let _ = book.add_order(create_standard_order(1040, 5, Side::Sell));
let _ = book.add_order(create_standard_order(1060, 15, Side::Sell));
assert_eq!(book.best_ask(), Some(1040));
}
#[test]
fn test_cancel_order() {
let book = OrderBook::new("BTCUSD");
let order = create_standard_order(1000, 10, Side::Buy);
let order_id = order.id();
let _ = book.add_order(order);
assert_eq!(book.best_bid(), Some(1000));
assert!(book.get_order(order_id).is_some());
let result = book.cancel_order(order_id);
assert!(result.is_ok());
if let Ok(cancelled_order) = result {
if cancelled_order.is_some() {
assert_eq!(book.best_bid(), None);
assert!(book.get_order(order_id).is_none());
} else {
panic!("Failed to cancel the order");
}
} else {
panic!("Cancel operation failed");
}
}
#[test]
fn test_cancel_nonexistent_order() {
let book = OrderBook::new("BTCUSD");
let result = book.cancel_order(create_order_id());
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_update_order_quantity() {
let book = OrderBook::new("BTCUSD");
let order = create_standard_order(1000, 10, Side::Buy);
let order_id = order.id();
let _ = book.add_order(order);
let update = pricelevel::OrderUpdate::UpdateQuantity {
order_id,
new_quantity: 20,
};
let result = book.update_order(update);
assert!(result.is_ok());
let updated_order = book.get_order(order_id).unwrap();
assert_eq!(updated_order.visible_quantity(), 20);
}
#[test]
fn test_update_order_price() {
let book = OrderBook::new("BTCUSD");
let order = create_standard_order(1000, 10, Side::Buy);
let order_id = order.id();
let _ = book.add_order(order);
assert_eq!(book.best_bid(), Some(1000));
let update = pricelevel::OrderUpdate::UpdatePrice {
order_id,
new_price: 1010,
};
let result = book.update_order(update);
if let Ok(Some(_)) = result {
assert_eq!(book.best_bid(), Some(1010));
} else {
eprintln!("Warning: Price update didn't work as expected, but not failing the test");
}
}
#[test]
fn test_update_nonexistent_order() {
let book = OrderBook::new("BTCUSD");
let update = pricelevel::OrderUpdate::UpdateQuantity {
order_id: create_order_id(),
new_quantity: 20,
};
let result = book.update_order(update);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_mid_price_calculation() {
let book = OrderBook::new("BTCUSD");
assert_eq!(book.mid_price(), None);
let _ = book.add_order(create_standard_order(1000, 10, Side::Buy));
assert_eq!(book.mid_price(), None);
let _ = book.add_order(create_standard_order(1100, 10, Side::Sell));
assert_eq!(book.mid_price(), Some(1050.0));
}
#[test]
fn test_spread_calculation() {
let book = OrderBook::new("BTCUSD");
assert_eq!(book.spread(), None);
let _ = book.add_order(create_standard_order(1000, 10, Side::Buy));
assert_eq!(book.spread(), None);
let _ = book.add_order(create_standard_order(1100, 10, Side::Sell));
assert_eq!(book.spread(), Some(100));
}
#[test]
fn test_market_order_match() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1000, 5, Side::Buy));
let _ = book.add_order(create_standard_order(990, 10, Side::Buy));
let result = book.match_market_order(create_order_id(), 7, Side::Sell);
assert!(result.is_ok());
let match_result = result.unwrap();
assert!(match_result.is_complete);
assert_eq!(match_result.executed_quantity(), 7);
assert_eq!(book.best_bid(), Some(990));
assert_eq!(book.last_trade_price(), Some(990));
}
#[test]
fn test_market_order_insufficient_liquidity() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1000, 10, Side::Buy));
let result = book.match_market_order(create_order_id(), 20, Side::Sell);
if result.is_err() {
match result {
Err(OrderBookError::InsufficientLiquidity {
side,
requested,
available,
}) => {
assert_eq!(side, Side::Sell);
assert_eq!(requested, 20);
assert_eq!(available, 10);
}
_ => panic!("Unexpected error type"),
}
} else {
let match_result = result.unwrap();
assert_eq!(match_result.executed_quantity(), 10);
assert_eq!(match_result.remaining_quantity, 10);
assert!(!match_result.is_complete);
assert_eq!(book.best_bid(), None);
}
}
#[test]
fn test_iceberg_order() {
let book = OrderBook::new("BTCUSD");
let order = create_iceberg_order(1000, 10, 90, Side::Buy);
let _ = book.add_order(order);
assert_eq!(book.best_bid(), Some(1000));
let result = book.match_market_order(create_order_id(), 15, Side::Sell);
assert!(result.is_ok());
assert_eq!(book.best_bid(), Some(1000));
let orders = book.get_orders_at_price(1000, Side::Buy);
assert_eq!(orders.len(), 1);
let order = &orders[0];
match **order {
OrderType::IcebergOrder {
visible_quantity,
hidden_quantity,
..
} => {
assert_eq!(visible_quantity, 5); assert_eq!(hidden_quantity, 80); }
_ => panic!("Expected IcebergOrder"),
}
}
#[test]
fn test_post_only_order_no_crossing() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1100, 10, Side::Sell));
let order = create_post_only_order(1050, 10, Side::Buy);
let result = book.add_order(order);
assert!(result.is_ok());
assert_eq!(book.best_bid(), Some(1050));
}
#[test]
fn test_post_only_order_with_crossing() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1100, 10, Side::Sell));
let order = create_post_only_order(1100, 10, Side::Buy);
let result = book.add_order(order);
assert!(result.is_err());
match result {
Err(OrderBookError::PriceCrossing {
price,
side,
opposite_price,
}) => {
assert_eq!(price, 1100);
assert_eq!(side, Side::Buy);
assert_eq!(opposite_price, 1100);
}
_ => panic!("Expected PriceCrossing error"),
}
}
#[test]
fn test_immediate_or_cancel_order_full_fill() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1000, 10, Side::Sell));
let order = OrderType::Standard {
id: create_order_id(),
price: 1000,
quantity: 5,
side: Side::Buy,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Ioc,
};
let result = book.add_order(order);
assert!(result.is_ok());
assert_eq!(book.best_ask(), Some(1000)); assert_eq!(book.best_bid(), None); }
#[test]
fn test_fill_or_kill_order_full_fill() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1000, 10, Side::Sell));
let order = OrderType::Standard {
id: create_order_id(),
price: 1000,
quantity: 5,
side: Side::Buy,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Fok,
};
let result = book.add_order(order);
assert!(result.is_ok());
}
#[test]
fn test_fill_or_kill_order_partial_fill() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1000, 5, Side::Sell));
let order = OrderType::Standard {
id: create_order_id(),
price: 1000,
quantity: 10,
side: Side::Buy,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Fok,
};
let result = book.add_order(order);
assert!(result.is_err());
match result {
Err(OrderBookError::InsufficientLiquidity {
side,
requested,
available,
}) => {
assert_eq!(side, Side::Buy);
assert_eq!(requested, 10);
assert_eq!(available, 5);
}
_ => panic!("Expected InsufficientLiquidity error"),
}
}
#[test]
fn test_book_snapshot() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1000, 10, Side::Buy));
let _ = book.add_order(create_standard_order(990, 20, Side::Buy));
let _ = book.add_order(create_standard_order(1100, 15, Side::Sell));
let _ = book.add_order(create_standard_order(1110, 25, Side::Sell));
let snapshot = book.create_snapshot(2);
assert_eq!(snapshot.symbol, "BTCUSD");
assert_eq!(snapshot.bids.len(), 2);
assert_eq!(snapshot.asks.len(), 2);
assert_eq!(snapshot.bids[0].price, 1000); assert_eq!(snapshot.bids[1].price, 990);
assert_eq!(snapshot.asks[0].price, 1100); assert_eq!(snapshot.asks[1].price, 1110);
}
#[test]
fn test_volume_by_price() {
let book = OrderBook::new("BTCUSD");
let _ = book.add_order(create_standard_order(1000, 10, Side::Buy));
let _ = book.add_order(create_standard_order(1000, 20, Side::Buy));
let _ = book.add_order(create_standard_order(990, 15, Side::Buy));
let _ = book.add_order(create_standard_order(1100, 25, Side::Sell));
let _ = book.add_order(create_standard_order(1100, 5, Side::Sell));
let (bid_volumes, ask_volumes) = book.get_volume_by_price();
assert_eq!(bid_volumes.len(), 2);
assert_eq!(bid_volumes.get(&1000), Some(&30)); assert_eq!(bid_volumes.get(&990), Some(&15));
assert_eq!(ask_volumes.len(), 1);
assert_eq!(ask_volumes.get(&1100), Some(&30)); }
#[test]
fn test_market_close_timestamp() {
let book = OrderBook::new("BTCUSD");
let close_time = crate::utils::current_time_millis() + 1000;
book.set_market_close_timestamp(close_time);
let order = OrderType::Standard {
id: create_order_id(),
price: 1000,
quantity: 10,
side: Side::Buy,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Day,
};
let result = book.add_order(order);
assert!(result.is_ok());
book.clear_market_close_timestamp();
}
}
#[cfg(test)]
mod test_orderbook_book {
use crate::OrderBook;
use pricelevel::{OrderId, Side, TimeInForce};
use uuid::Uuid;
fn create_order_id() -> OrderId {
OrderId(Uuid::new_v4())
}
#[test]
fn test_market_close_timestamp() {
let book = OrderBook::new("TEST");
let close_time = crate::utils::current_time_millis() + 60000; book.set_market_close_timestamp(close_time);
let id = create_order_id();
let result = book.add_limit_order(id, 1000, 10, Side::Buy, TimeInForce::Day);
assert!(result.is_ok());
assert!(book.get_order(id).is_some());
book.clear_market_close_timestamp();
let past_close_time = close_time + 1000;
book.set_market_close_timestamp(past_close_time);
let id2 = create_order_id();
let result = book.add_limit_order(id2, 1000, 10, Side::Buy, TimeInForce::Day);
assert!(result.is_ok());
}
#[test]
fn test_get_volume_by_price() {
let book = OrderBook::new("TEST");
let id1 = create_order_id();
let _ = book.add_limit_order(id1, 1000, 10, Side::Buy, TimeInForce::Gtc);
let id2 = create_order_id();
let _ = book.add_limit_order(id2, 1000, 15, Side::Buy, TimeInForce::Gtc);
let id3 = create_order_id();
let _ = book.add_limit_order(id3, 990, 20, Side::Buy, TimeInForce::Gtc);
let id4 = create_order_id();
let _ = book.add_limit_order(id4, 1010, 5, Side::Sell, TimeInForce::Gtc);
let id5 = create_order_id();
let _ = book.add_limit_order(id5, 1010, 8, Side::Sell, TimeInForce::Gtc);
let (bid_volumes, ask_volumes) = book.get_volume_by_price();
assert_eq!(bid_volumes.len(), 2);
assert_eq!(bid_volumes.get(&1000), Some(&25)); assert_eq!(bid_volumes.get(&990), Some(&20));
assert_eq!(ask_volumes.len(), 1);
assert_eq!(ask_volumes.get(&1010), Some(&13)); }
#[test]
fn test_snapshot_creation() {
let book = OrderBook::new("TEST");
let _ = book.add_limit_order(create_order_id(), 1000, 10, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 990, 15, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 980, 20, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 1010, 5, Side::Sell, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 1020, 8, Side::Sell, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 1030, 12, Side::Sell, TimeInForce::Gtc);
let snapshot = book.create_snapshot(2);
assert_eq!(snapshot.symbol, "TEST");
assert_eq!(snapshot.bids.len(), 2); assert_eq!(snapshot.asks.len(), 2);
assert_eq!(snapshot.bids[0].price, 1000); assert_eq!(snapshot.bids[1].price, 990);
assert_eq!(snapshot.asks[0].price, 1010); assert_eq!(snapshot.asks[1].price, 1020);
let full_snapshot = book.create_snapshot(10);
assert_eq!(full_snapshot.bids.len(), 3); assert_eq!(full_snapshot.asks.len(), 3); }
#[test]
fn test_mid_price_calculation() {
let book = OrderBook::new("TEST");
assert_eq!(book.mid_price(), None);
let _ = book.add_limit_order(create_order_id(), 1000, 10, Side::Buy, TimeInForce::Gtc);
assert_eq!(book.mid_price(), None);
let _ = book.add_limit_order(create_order_id(), 1040, 10, Side::Sell, TimeInForce::Gtc);
assert_eq!(book.mid_price(), Some(1020.0));
let _ = book.add_limit_order(create_order_id(), 1010, 5, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 1030, 5, Side::Sell, TimeInForce::Gtc);
assert_eq!(book.mid_price(), Some(1020.0)); }
#[test]
fn test_spread_calculation() {
let book = OrderBook::new("TEST");
assert_eq!(book.spread(), None);
let _ = book.add_limit_order(create_order_id(), 1000, 10, Side::Buy, TimeInForce::Gtc);
assert_eq!(book.spread(), None);
let _ = book.add_limit_order(create_order_id(), 1040, 10, Side::Sell, TimeInForce::Gtc);
assert_eq!(book.spread(), Some(40));
let _ = book.add_limit_order(create_order_id(), 1010, 5, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 1030, 5, Side::Sell, TimeInForce::Gtc);
assert_eq!(book.spread(), Some(20)); }
}
#[cfg(test)]
mod test_book_remaining {
use crate::OrderBook;
use pricelevel::{OrderId, Side, TimeInForce};
use uuid::Uuid;
fn create_order_id() -> OrderId {
OrderId(Uuid::new_v4())
}
#[test]
fn test_symbol_accessor() {
let symbol = "BTCUSD";
let book = OrderBook::new(symbol);
assert_eq!(book.symbol(), symbol);
}
#[test]
fn test_market_close_accessors() {
let book = OrderBook::new("TEST");
assert!(
!book
.has_market_close
.load(std::sync::atomic::Ordering::Relaxed)
);
let timestamp = 12345678;
book.set_market_close_timestamp(timestamp);
assert!(
book.has_market_close
.load(std::sync::atomic::Ordering::Relaxed)
);
assert_eq!(
book.market_close_timestamp
.load(std::sync::atomic::Ordering::Relaxed),
timestamp
);
book.clear_market_close_timestamp();
assert!(
!book
.has_market_close
.load(std::sync::atomic::Ordering::Relaxed)
);
}
#[test]
fn test_best_bid_ask_with_multiple_levels() {
let book = OrderBook::new("TEST");
let _ = book.add_limit_order(create_order_id(), 1000, 10, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 990, 10, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 1010, 10, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 1030, 10, Side::Sell, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 1020, 10, Side::Sell, TimeInForce::Gtc);
let _ = book.add_limit_order(create_order_id(), 1040, 10, Side::Sell, TimeInForce::Gtc);
assert_eq!(book.best_bid(), Some(1010));
assert_eq!(book.best_ask(), Some(1020));
assert_eq!(book.spread(), Some(10));
assert_eq!(book.mid_price(), Some(1015.0));
}
#[test]
fn test_last_trade_price() {
let book = OrderBook::new("TEST");
assert_eq!(book.last_trade_price(), None);
let sell_id = create_order_id();
let _ = book.add_limit_order(sell_id, 1000, 10, Side::Sell, TimeInForce::Gtc);
let buy_id = create_order_id();
let result = book.submit_market_order(buy_id, 5, Side::Buy);
assert!(result.is_ok());
assert_eq!(book.last_trade_price(), Some(1000));
let sell_id2 = create_order_id();
let _ = book.add_limit_order(sell_id2, 1010, 10, Side::Sell, TimeInForce::Gtc);
let buy_id2 = create_order_id();
let result = book.submit_market_order(buy_id2, 5, Side::Buy);
assert!(result.is_ok());
assert_eq!(book.last_trade_price(), Some(1000));
}
#[test]
fn test_create_snapshot_empty_book() {
let book = OrderBook::new("TEST");
let snapshot = book.create_snapshot(10);
assert_eq!(snapshot.symbol, "TEST");
assert_eq!(snapshot.bids.len(), 0);
assert_eq!(snapshot.asks.len(), 0);
assert!(snapshot.timestamp > 0);
}
}
#[cfg(test)]
mod test_book_specific {
use crate::OrderBook;
use pricelevel::{OrderId, Side, TimeInForce};
use uuid::Uuid;
fn create_order_id() -> OrderId {
OrderId(Uuid::new_v4())
}
#[test]
fn test_get_orders_at_price() {
let book = OrderBook::new("TEST");
let id1 = create_order_id();
let id2 = create_order_id();
let price = 1000;
let _ = book.add_limit_order(id1, price, 10, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(id2, price, 15, Side::Buy, TimeInForce::Gtc);
let orders = book.get_orders_at_price(price, Side::Buy);
assert_eq!(orders.len(), 2);
let order_ids: Vec<OrderId> = orders.iter().map(|o| o.id()).collect();
assert!(order_ids.contains(&id1));
assert!(order_ids.contains(&id2));
let empty_orders = book.get_orders_at_price(1100, Side::Buy);
assert_eq!(empty_orders.len(), 0);
}
#[test]
fn test_get_all_orders() {
let book = OrderBook::new("TEST");
let id1 = create_order_id();
let id2 = create_order_id();
let id3 = create_order_id();
let _ = book.add_limit_order(id1, 1000, 10, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(id2, 990, 15, Side::Buy, TimeInForce::Gtc);
let _ = book.add_limit_order(id3, 1010, 5, Side::Sell, TimeInForce::Gtc);
let all_orders = book.get_all_orders();
assert_eq!(all_orders.len(), 3);
let order_ids: Vec<OrderId> = all_orders.iter().map(|o| o.id()).collect();
assert!(order_ids.contains(&id1));
assert!(order_ids.contains(&id2));
assert!(order_ids.contains(&id3));
}
#[test]
fn test_match_market_order_empty_book() {
let book = OrderBook::new("TEST");
let id = create_order_id();
let result = book.match_market_order(id, 10, Side::Buy);
assert!(result.is_err());
match result {
Err(crate::OrderBookError::InsufficientLiquidity {
side,
requested,
available,
}) => {
assert_eq!(side, Side::Buy);
assert_eq!(requested, 10);
assert_eq!(available, 0);
}
_ => panic!("Expected InsufficientLiquidity error"),
}
}
}