#[cfg(test)]
mod tests {
use crate::OrderBook;
use crate::orderbook::repricing::RepricingOperations;
use pricelevel::{
Hash32, Id, OrderType, PegReferenceType, Price, Quantity, Side, TimeInForce, TimestampMs,
};
fn create_order_id() -> Id {
Id::new_uuid()
}
fn current_time_millis() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64
}
#[test]
fn test_pegged_order_registration() {
let book: OrderBook<()> = OrderBook::new("TEST");
assert_eq!(book.pegged_order_count(), 0);
let id = create_order_id();
let pegged_order = OrderType::PeggedOrder {
id,
price: Price::new(100),
quantity: Quantity::new(10),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
reference_price_offset: 5,
reference_price_type: PegReferenceType::BestBid,
extra_fields: (),
};
let result = book.add_order(pegged_order);
assert!(result.is_ok());
assert_eq!(book.pegged_order_count(), 1);
assert!(book.pegged_order_ids().contains(&id));
}
#[test]
fn test_trailing_stop_registration() {
let book: OrderBook<()> = OrderBook::new("TEST");
assert_eq!(book.trailing_stop_count(), 0);
let id = create_order_id();
let trailing_order = OrderType::TrailingStop {
id,
price: Price::new(95),
quantity: Quantity::new(10),
side: Side::Sell,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
trail_amount: Quantity::new(5),
last_reference_price: Price::new(100),
extra_fields: (),
};
let result = book.add_order(trailing_order);
assert!(result.is_ok());
assert_eq!(book.trailing_stop_count(), 1);
assert!(book.trailing_stop_ids().contains(&id));
}
#[test]
fn test_special_order_unregistration_on_cancel() {
let book: OrderBook<()> = OrderBook::new("TEST");
let pegged_id = create_order_id();
let pegged_order = OrderType::PeggedOrder {
id: pegged_id,
price: Price::new(100),
quantity: Quantity::new(10),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
reference_price_offset: 5,
reference_price_type: PegReferenceType::BestBid,
extra_fields: (),
};
book.add_order(pegged_order).unwrap();
assert_eq!(book.pegged_order_count(), 1);
let trailing_id = create_order_id();
let trailing_order = OrderType::TrailingStop {
id: trailing_id,
price: Price::new(110), quantity: Quantity::new(10),
side: Side::Sell,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
trail_amount: Quantity::new(5),
last_reference_price: Price::new(115),
extra_fields: (),
};
book.add_order(trailing_order).unwrap();
assert_eq!(book.pegged_order_count(), 1);
assert_eq!(book.trailing_stop_count(), 1);
book.cancel_order(pegged_id).unwrap();
assert_eq!(book.pegged_order_count(), 0);
assert_eq!(book.trailing_stop_count(), 1);
book.cancel_order(trailing_id).unwrap();
assert_eq!(book.pegged_order_count(), 0);
assert_eq!(book.trailing_stop_count(), 0);
}
#[test]
fn test_reprice_pegged_order_best_bid() {
let book: OrderBook<()> = OrderBook::new("TEST");
let liquidity_id = create_order_id();
let liquidity_order = OrderType::Standard {
id: liquidity_id,
price: Price::new(100),
quantity: Quantity::new(100),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
extra_fields: (),
};
book.add_order(liquidity_order).unwrap();
let pegged_id = create_order_id();
let pegged_order = OrderType::PeggedOrder {
id: pegged_id,
price: Price::new(90), quantity: Quantity::new(10),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
reference_price_offset: 5,
reference_price_type: PegReferenceType::BestBid,
extra_fields: (),
};
book.add_order(pegged_order).unwrap();
let repriced = book.reprice_pegged_orders().unwrap();
assert_eq!(repriced, 1);
let order = book.get_order(pegged_id).unwrap();
assert_eq!(order.price().as_u128(), 105);
}
#[test]
fn test_reprice_pegged_order_best_ask() {
let book: OrderBook<()> = OrderBook::new("TEST");
let liquidity_id = create_order_id();
let liquidity_order = OrderType::Standard {
id: liquidity_id,
price: Price::new(110),
quantity: Quantity::new(100),
side: Side::Sell,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
extra_fields: (),
};
book.add_order(liquidity_order).unwrap();
let pegged_id = create_order_id();
let pegged_order = OrderType::PeggedOrder {
id: pegged_id,
price: Price::new(120), quantity: Quantity::new(10),
side: Side::Sell,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
reference_price_offset: -3,
reference_price_type: PegReferenceType::BestAsk,
extra_fields: (),
};
book.add_order(pegged_order).unwrap();
let repriced = book.reprice_pegged_orders().unwrap();
assert_eq!(repriced, 1);
let order = book.get_order(pegged_id).unwrap();
assert_eq!(order.price().as_u128(), 107);
}
#[test]
fn test_reprice_trailing_stop_sell_market_rises() {
use crate::orderbook::repricing::calculate_trailing_stop_price;
let result = calculate_trailing_stop_price(
Side::Sell,
95, 5, 100, 110, );
assert_eq!(result, Some((105, 110))); }
#[test]
fn test_reprice_trailing_stop_buy_market_falls() {
use crate::orderbook::repricing::calculate_trailing_stop_price;
let result = calculate_trailing_stop_price(
Side::Buy,
105, 5, 100, 90, );
assert_eq!(result, Some((95, 90))); }
#[test]
fn test_reprice_special_orders_combined() {
use crate::orderbook::repricing::{calculate_pegged_price, calculate_trailing_stop_price};
let pegged_price = calculate_pegged_price(
PegReferenceType::BestBid,
2,
Side::Buy,
Some(100), Some(120), Some(110), None,
);
assert_eq!(pegged_price, Some(102));
let trailing_result = calculate_trailing_stop_price(
Side::Sell,
95, 5, 99, 100, );
assert_eq!(trailing_result, None);
}
#[test]
fn test_should_trigger_trailing_stop_sell() {
let book: OrderBook<()> = OrderBook::new("TEST");
let order = OrderType::TrailingStop::<()> {
id: create_order_id(),
price: Price::new(95),
quantity: Quantity::new(10),
side: Side::Sell,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
trail_amount: Quantity::new(5),
last_reference_price: Price::new(100),
extra_fields: (),
};
assert!(book.should_trigger_trailing_stop(&order, 95));
assert!(book.should_trigger_trailing_stop(&order, 90));
assert!(!book.should_trigger_trailing_stop(&order, 96));
assert!(!book.should_trigger_trailing_stop(&order, 100));
}
#[test]
fn test_should_trigger_trailing_stop_buy() {
let book: OrderBook<()> = OrderBook::new("TEST");
let order = OrderType::TrailingStop::<()> {
id: create_order_id(),
price: Price::new(105),
quantity: Quantity::new(10),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
trail_amount: Quantity::new(5),
last_reference_price: Price::new(100),
extra_fields: (),
};
assert!(book.should_trigger_trailing_stop(&order, 105));
assert!(book.should_trigger_trailing_stop(&order, 110));
assert!(!book.should_trigger_trailing_stop(&order, 104));
assert!(!book.should_trigger_trailing_stop(&order, 100));
}
#[test]
fn test_no_reprice_when_price_unchanged() {
use crate::orderbook::repricing::calculate_pegged_price;
let calculated_price = calculate_pegged_price(
PegReferenceType::BestBid,
5,
Side::Buy,
Some(100), Some(120), Some(110), None,
);
assert_eq!(calculated_price, Some(105));
let current_price = 105u128;
assert_eq!(calculated_price.unwrap(), current_price);
}
#[test]
fn test_standard_order_not_tracked() {
let book: OrderBook<()> = OrderBook::new("TEST");
let standard = OrderType::Standard {
id: create_order_id(),
price: Price::new(100),
quantity: Quantity::new(100),
side: Side::Buy,
user_id: Hash32::zero(),
timestamp: TimestampMs::new(current_time_millis()),
time_in_force: TimeInForce::Gtc,
extra_fields: (),
};
book.add_order(standard).unwrap();
assert_eq!(book.pegged_order_count(), 0);
assert_eq!(book.trailing_stop_count(), 0);
}
}