#[cfg(test)]
mod test_order_modifications {
use crate::{OrderBook, OrderBookError};
use pricelevel::{Id, OrderType, OrderUpdate, Price, Quantity, Side, TimeInForce};
fn create_order_id() -> Id {
Id::new_uuid()
}
#[test]
fn test_update_price_same_value() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let price = 1000;
let quantity = 10;
let side = Side::Buy;
let result = book.add_limit_order(id, price, quantity, side, TimeInForce::Gtc, None);
assert!(result.is_ok());
let update = OrderUpdate::UpdatePrice {
order_id: id,
new_price: Price::new(price),
};
let result = book.update_order(update);
assert!(result.is_err());
match result {
Err(OrderBookError::InvalidOperation { message }) => {
assert!(message.contains("Cannot update price to the same value"));
}
_ => panic!("Expected InvalidOperation error"),
}
}
#[test]
fn test_update_price_and_quantity() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let price = 1000;
let quantity = 10;
let side = Side::Buy;
let result = book.add_limit_order(id, price, quantity, side, TimeInForce::Gtc, None);
assert!(result.is_ok());
let new_price = 1100;
let new_quantity = 15;
let update = OrderUpdate::UpdatePriceAndQuantity {
order_id: id,
new_price: Price::new(new_price),
new_quantity: Quantity::new(new_quantity),
};
let result = book.update_order(update);
assert!(result.is_ok());
let updated_order = book.get_order(id);
assert!(updated_order.is_some());
let updated_order = updated_order.unwrap();
assert_eq!(updated_order.price().as_u128(), new_price);
assert_eq!(updated_order.visible_quantity(), new_quantity);
}
#[test]
fn test_cancel_nonexistent_order() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let result = book.cancel_order(id);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_update_order_cancel() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let price = 1000;
let quantity = 10;
let side = Side::Buy;
let result = book.add_limit_order(id, price, quantity, side, TimeInForce::Gtc, None);
assert!(result.is_ok());
let update = OrderUpdate::Cancel { order_id: id };
let result = book.update_order(update);
assert!(result.is_ok());
let order = book.get_order(id);
assert!(order.is_none());
}
#[test]
fn test_update_order_replace() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let price = 1000;
let quantity = 10;
let side = Side::Buy;
let result = book.add_limit_order(id, price, quantity, side, TimeInForce::Gtc, None);
assert!(result.is_ok());
let new_price = 1100;
let new_quantity = 15;
let update = OrderUpdate::Replace {
order_id: id,
price: Price::new(new_price),
quantity: Quantity::new(new_quantity),
side: Side::Buy,
};
let result = book.update_order(update);
assert!(result.is_ok());
let replaced_order = book.get_order(id);
assert!(replaced_order.is_some());
let replaced_order = replaced_order.unwrap();
assert_eq!(replaced_order.price().as_u128(), new_price);
assert_eq!(replaced_order.visible_quantity(), new_quantity);
}
#[test]
fn test_replace_with_different_side() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let price = 1000;
let quantity = 10;
let side = Side::Buy;
let result = book.add_limit_order(id, price, quantity, side, TimeInForce::Gtc, None);
assert!(result.is_ok());
let update = OrderUpdate::Replace {
order_id: id,
price: Price::new(1100),
quantity: Quantity::new(15),
side: Side::Sell, };
let result = book.update_order(update);
assert!(result.is_ok());
let replaced_order = book.get_order(id);
assert!(replaced_order.is_some());
assert_eq!(replaced_order.unwrap().side(), Side::Sell);
}
#[test]
fn test_iceberg_order_update_quantity() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let price = 1000;
let visible = 10;
let hidden = 90;
let side = Side::Buy;
let result =
book.add_iceberg_order(id, price, visible, hidden, side, TimeInForce::Gtc, None);
assert!(result.is_ok());
let new_quantity = 15;
let update = OrderUpdate::UpdateQuantity {
order_id: id,
new_quantity: Quantity::new(new_quantity),
};
let result = book.update_order(update);
assert!(result.is_ok());
let updated_order = book.get_order(id);
assert!(updated_order.is_some());
let updated_order = updated_order.unwrap();
assert_eq!(updated_order.visible_quantity(), new_quantity);
match &*updated_order {
OrderType::IcebergOrder {
hidden_quantity, ..
} => {
assert_eq!(hidden_quantity.as_u64(), hidden);
}
_ => panic!("Expected IcebergOrder"),
}
}
}
#[cfg(test)]
mod test_modifications_remaining {
use crate::OrderBook;
use pricelevel::{
Hash32, Id, OrderType, OrderUpdate, PegReferenceType, Price, Quantity, Side, TimeInForce,
TimestampMs,
};
fn create_order_id() -> Id {
Id::new_uuid()
}
#[test]
fn test_update_price_error_cases() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let update = OrderUpdate::UpdatePrice {
order_id: id,
new_price: Price::new(1000),
};
let result = book.update_order(update);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_update_price_and_quantity_nonexistent() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let update = OrderUpdate::UpdatePriceAndQuantity {
order_id: id,
new_price: Price::new(1000),
new_quantity: Quantity::new(10),
};
let result = book.update_order(update);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_update_order_with_all_types() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id1 = create_order_id();
let timestamp = crate::utils::current_time_millis();
let trail_order = OrderType::TrailingStop {
id: id1,
price: Price::new(1000),
quantity: Quantity::new(10),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(timestamp),
time_in_force: TimeInForce::Gtc,
trail_amount: Quantity::new(5),
last_reference_price: Price::new(995),
extra_fields: (),
};
let id2 = create_order_id();
let peg_order = OrderType::PeggedOrder {
id: id2,
price: Price::new(1000),
quantity: Quantity::new(10),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(timestamp),
time_in_force: TimeInForce::Gtc,
reference_price_offset: 5,
reference_price_type: PegReferenceType::BestBid,
extra_fields: (),
};
let id3 = create_order_id();
let mtl_order = OrderType::MarketToLimit {
id: id3,
price: Price::new(1000),
quantity: Quantity::new(10),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(timestamp),
time_in_force: TimeInForce::Gtc,
extra_fields: (),
};
let id4 = create_order_id();
let reserve_order = OrderType::ReserveOrder {
id: id4,
price: Price::new(1000),
visible_quantity: Quantity::new(5),
hidden_quantity: Quantity::new(5),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(timestamp),
time_in_force: TimeInForce::Gtc,
replenish_threshold: Quantity::new(2),
replenish_amount: Some(Quantity::new(3)),
auto_replenish: true,
extra_fields: (),
};
let _ = book.add_order(trail_order);
let _ = book.add_order(peg_order);
let _ = book.add_order(mtl_order);
let _ = book.add_order(reserve_order);
let update1 = OrderUpdate::UpdatePriceAndQuantity {
order_id: id1,
new_price: Price::new(1010),
new_quantity: Quantity::new(15),
};
let update2 = OrderUpdate::UpdatePriceAndQuantity {
order_id: id2,
new_price: Price::new(1010),
new_quantity: Quantity::new(15),
};
let update3 = OrderUpdate::UpdatePriceAndQuantity {
order_id: id3,
new_price: Price::new(1010),
new_quantity: Quantity::new(15),
};
let update4 = OrderUpdate::UpdatePriceAndQuantity {
order_id: id4,
new_price: Price::new(1010),
new_quantity: Quantity::new(15),
};
let result1 = book.update_order(update1);
let result2 = book.update_order(update2);
let result3 = book.update_order(update3);
let result4 = book.update_order(update4);
assert!(result1.is_ok());
assert!(result2.is_ok());
assert!(result3.is_ok());
assert!(result4.is_ok());
let order1 = book.get_order(id1);
let order2 = book.get_order(id2);
let order3 = book.get_order(id3);
let order4 = book.get_order(id4);
assert!(order1.is_some());
assert!(order2.is_some());
assert!(order3.is_some());
assert!(order4.is_some());
assert_eq!(order1.unwrap().price().as_u128(), 1010);
assert_eq!(order2.unwrap().price().as_u128(), 1010);
assert_eq!(order3.unwrap().price().as_u128(), 1010);
assert_eq!(order4.unwrap().price().as_u128(), 1010);
}
#[test]
fn test_replace_with_special_order_types() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let timestamp = crate::utils::current_time_millis();
let reserve_order = OrderType::ReserveOrder {
id,
price: Price::new(1000),
visible_quantity: Quantity::new(5),
hidden_quantity: Quantity::new(5),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(timestamp),
time_in_force: TimeInForce::Gtc,
replenish_threshold: Quantity::new(2),
replenish_amount: Some(Quantity::new(3)),
auto_replenish: true,
extra_fields: (),
};
let _ = book.add_order(reserve_order);
let update = OrderUpdate::Replace {
order_id: id,
price: Price::new(1010),
quantity: Quantity::new(15),
side: Side::Buy,
};
let result = book.update_order(update);
assert!(result.is_ok());
let updated_order = book.get_order(id);
assert!(updated_order.is_some());
assert_eq!(updated_order.clone().unwrap().price().as_u128(), 1010);
assert_eq!(updated_order.unwrap().visible_quantity(), 15);
}
#[test]
fn test_cancel_order_removes_price_level() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let _ = book.add_limit_order(id, 1000, 10, Side::Buy, TimeInForce::Gtc, None);
let update = OrderUpdate::Cancel { order_id: id };
let result = book.update_order(update);
assert!(result.is_ok());
assert_eq!(book.best_bid(), None);
assert!(book.get_order(id).is_none());
}
}
#[cfg(test)]
mod test_modifications_specific {
use crate::{OrderBook, OrderBookError};
use pricelevel::{
Hash32, Id, OrderType, OrderUpdate, PegReferenceType, Price, Quantity, Side, TimeInForce,
TimestampMs,
};
fn create_order_id() -> Id {
Id::new_uuid()
}
#[test]
fn test_update_price_edge_cases() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let update = OrderUpdate::UpdatePrice {
order_id: id,
new_price: Price::new(1000),
};
let result = book.update_order(update);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_cancel_non_existent_order() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let update = OrderUpdate::Cancel { order_id: id };
let result = book.update_order(update);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_update_order_when_order_is_not_found() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let timestamp = crate::utils::current_time_millis();
let reserve_order = OrderType::ReserveOrder {
id,
price: Price::new(1000),
visible_quantity: Quantity::new(5),
hidden_quantity: Quantity::new(5),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(timestamp),
time_in_force: TimeInForce::Gtc,
replenish_threshold: Quantity::new(2),
replenish_amount: Some(Quantity::new(3)),
auto_replenish: true,
extra_fields: (),
};
let _ = book.add_order(reserve_order);
let nonexistent_id = create_order_id();
let update = OrderUpdate::UpdatePrice {
order_id: nonexistent_id,
new_price: Price::new(1100),
};
let result = book.update_order(update);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
let update = OrderUpdate::UpdateQuantity {
order_id: nonexistent_id,
new_quantity: Quantity::new(20),
};
let result = book.update_order(update);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
let update = OrderUpdate::UpdatePriceAndQuantity {
order_id: nonexistent_id,
new_price: Price::new(1100),
new_quantity: Quantity::new(20),
};
let result = book.update_order(update);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
let update = OrderUpdate::Replace {
order_id: nonexistent_id,
price: Price::new(1100),
quantity: Quantity::new(20),
side: Side::Buy,
};
let result = book.update_order(update);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_replace_unsupported_order_type() {
let book: OrderBook<()> = OrderBook::new("TEST");
let id = create_order_id();
let timestamp = crate::utils::current_time_millis();
let peg_order = OrderType::PeggedOrder {
id,
price: Price::new(1000),
quantity: Quantity::new(10),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(timestamp),
time_in_force: TimeInForce::Gtc,
reference_price_offset: 5,
reference_price_type: PegReferenceType::BestBid,
extra_fields: (),
};
let _ = book.add_order(peg_order);
let update = OrderUpdate::Replace {
order_id: id,
price: Price::new(1100),
quantity: Quantity::new(20),
side: Side::Buy,
};
let result = book.update_order(update);
match result {
Err(OrderBookError::InvalidOperation { message }) => {
assert!(message.contains("Replace operation not supported"));
}
Ok(_) => {
let updated_order = book.get_order(id);
assert!(updated_order.is_some());
}
_ => panic!("Unexpected result"),
}
}
}
#[cfg(test)]
mod tests {
use crate::orderbook::OrderBookError;
use crate::orderbook::book::OrderBook;
use crate::orderbook::modifications::OrderQuantity;
use pricelevel::{
Hash32, Id, OrderType, OrderUpdate, Price, Quantity, Side, TimeInForce, TimestampMs,
};
fn setup_book_with_orders() -> OrderBook<()> {
let book: OrderBook<()> = OrderBook::new("TEST");
let sell_order = OrderType::Standard {
id: Id::new(),
side: Side::Sell,
price: Price::new(100),
quantity: Quantity::new(10),
user_id: Hash32::zero(),
time_in_force: TimeInForce::Gtc,
timestamp: TimestampMs::new(0),
extra_fields: (),
};
book.add_order(sell_order).unwrap();
let buy_order = OrderType::Standard {
id: Id::new(),
side: Side::Buy,
price: Price::new(90),
quantity: Quantity::new(10),
user_id: Hash32::zero(),
time_in_force: TimeInForce::Gtc,
timestamp: TimestampMs::new(0),
extra_fields: (),
};
book.add_order(buy_order).unwrap();
book
}
#[test]
fn test_add_post_only_order_crossing_market() {
let book = setup_book_with_orders();
let post_only_order = OrderType::PostOnly {
id: Id::new(),
side: Side::Buy,
price: Price::new(100), quantity: Quantity::new(5),
user_id: Hash32::zero(),
time_in_force: TimeInForce::Gtc,
timestamp: TimestampMs::new(0),
extra_fields: (),
};
let result = book.add_order(post_only_order);
assert!(matches!(result, Err(OrderBookError::PriceCrossing { .. })));
}
#[test]
fn test_add_expired_order() {
let book: OrderBook<()> = OrderBook::new("TEST");
book.set_market_close_timestamp(100);
let expired_order = OrderType::Standard {
id: Id::new(),
side: Side::Buy,
price: Price::new(95),
quantity: Quantity::new(10),
user_id: Hash32::zero(),
time_in_force: TimeInForce::Day, timestamp: TimestampMs::new(101), extra_fields: (),
};
let result = book.add_order(expired_order);
assert!(matches!(
result,
Err(OrderBookError::InvalidOperation { .. })
));
}
#[test]
fn test_successful_cancel_order_removes_level() {
let book: OrderBook<()> = OrderBook::new("TEST");
let order_id = Id::new();
let order = OrderType::Standard {
id: order_id,
side: Side::Sell,
price: Price::new(100),
quantity: Quantity::new(10),
user_id: Hash32::zero(),
time_in_force: TimeInForce::Gtc,
timestamp: TimestampMs::new(0),
extra_fields: (),
};
book.add_order(order).unwrap();
assert!(book.asks.contains_key(&100));
book.cancel_order(order_id).unwrap();
assert!(!book.asks.contains_key(&100)); assert!(book.order_locations.get(&order_id).is_none());
}
#[test]
fn test_update_order_not_found() {
let book: OrderBook<()> = OrderBook::new("TEST");
let non_existent_id = Id::new();
let result = book.update_order(OrderUpdate::Cancel {
order_id: non_existent_id,
});
assert!(result.is_ok() && result.unwrap().is_none());
}
#[test]
fn test_update_price_and_quantity() {
let book = setup_book_with_orders();
let orders: Vec<_> = book.bids.get(&90).unwrap().value().iter_orders().collect();
let original_order_id = orders[0].id();
let result = book.update_order(OrderUpdate::UpdatePriceAndQuantity {
order_id: original_order_id,
new_price: Price::new(92),
new_quantity: Quantity::new(12),
});
assert!(result.is_ok());
let updated_order = book.get_order(original_order_id).unwrap();
assert_eq!(updated_order.price().as_u128(), 92);
assert_eq!(updated_order.visible_quantity(), 12);
assert!(book.bids.contains_key(&92));
assert!(!book.bids.contains_key(&90));
}
#[test]
fn test_set_quantity_for_reserve_order() {
let mut order = OrderType::ReserveOrder {
id: Id::new(),
side: Side::Buy,
price: Price::new(100),
visible_quantity: Quantity::new(10),
hidden_quantity: Quantity::new(90),
replenish_amount: Some(Quantity::new(10)),
auto_replenish: true,
replenish_threshold: Quantity::new(0),
user_id: Hash32::zero(),
time_in_force: TimeInForce::Gtc,
timestamp: TimestampMs::new(0),
extra_fields: (),
};
order.set_quantity(85);
assert_eq!(order.quantity(), 10); assert_eq!(order.total_quantity(), 85);
if let OrderType::ReserveOrder {
visible_quantity,
hidden_quantity,
..
} = order
{
assert_eq!(visible_quantity, Quantity::new(10));
assert_eq!(hidden_quantity, Quantity::new(75));
}
}
}