use crate::{OrderBook, OrderBookError, current_time_millis};
use pricelevel::{OrderType, Side};
use std::sync::Arc;
use std::sync::atomic::Ordering;
use tracing::trace;
impl OrderBook {
pub(super) fn has_expired(&self, order: &OrderType) -> bool {
let time_in_force = order.time_in_force();
let current_time = current_time_millis();
let market_close = if self.has_market_close.load(Ordering::Relaxed) {
Some(self.market_close_timestamp.load(Ordering::Relaxed))
} else {
None
};
time_in_force.is_expired(current_time, market_close)
}
pub(super) fn will_cross_market(&self, price: u64, side: Side) -> bool {
match side {
Side::Buy => {
if let Some(best_ask) = self.best_ask() {
price >= best_ask
} else {
false
}
}
Side::Sell => {
if let Some(best_bid) = self.best_bid() {
price <= best_bid
} else {
false
}
}
}
}
pub(super) fn handle_immediate_order(
&self,
order: OrderType,
) -> Result<Arc<OrderType>, OrderBookError> {
trace!(
"Order book {}: Handling immediate order {} at price {}",
self.symbol,
order.id(),
order.price()
);
let id = order.id();
let quantity = order.visible_quantity();
let side = order.side();
let is_fok = order.is_fill_or_kill();
let price = order.price();
if is_fok {
let available_liquidity = self.calculate_available_liquidity(side, Some(price));
if available_liquidity < quantity {
return Err(OrderBookError::InsufficientLiquidity {
side,
requested: quantity,
available: available_liquidity,
});
}
}
let match_result = self.match_market_order(id, quantity, side)?;
if is_fok && !match_result.is_complete {
return Err(OrderBookError::InsufficientLiquidity {
side,
requested: quantity,
available: match_result.executed_quantity(),
});
}
let order_arc = Arc::new(order);
if !match_result.transactions.is_empty() {
let transactions = match_result.transactions.as_vec();
if let Some(last_transaction) = transactions.last() {
self.last_trade_price
.store(last_transaction.price, Ordering::SeqCst);
self.has_traded.store(true, Ordering::SeqCst);
}
}
Ok(order_arc)
}
fn calculate_available_liquidity(&self, side: Side, price_limit: Option<u64>) -> u64 {
let mut total_available = 0;
match side {
Side::Buy => {
let mut ask_prices: Vec<u64> = self.asks.iter().map(|item| *item.key()).collect();
ask_prices.sort();
for price in ask_prices {
if let Some(limit) = price_limit {
if price > limit {
break;
}
}
if let Some(price_level) = self.asks.get(&price) {
total_available += price_level.visible_quantity();
}
}
}
Side::Sell => {
let mut bid_prices: Vec<u64> = self.bids.iter().map(|item| *item.key()).collect();
bid_prices.sort_by(|a, b| b.cmp(a));
for price in bid_prices {
if let Some(limit) = price_limit {
if price < limit {
break;
}
}
if let Some(price_level) = self.bids.get(&price) {
total_available += price_level.visible_quantity();
}
}
}
}
total_available
}
}
#[cfg(test)]
mod test_orderbook_private {
use crate::{OrderBook, OrderBookError};
use pricelevel::{OrderId, OrderType, Side, TimeInForce};
use uuid::Uuid;
fn create_order_id() -> OrderId {
OrderId(Uuid::new_v4())
}
#[test]
fn test_has_expired_with_no_market_close() {
let book = OrderBook::new("TEST");
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,
};
assert!(!book.has_expired(&order));
}
#[test]
fn test_has_expired_with_market_close() {
let book = OrderBook::new("TEST");
let current_time = crate::utils::current_time_millis();
book.set_market_close_timestamp(current_time - 1000);
let order = OrderType::Standard {
id: create_order_id(),
price: 1000,
quantity: 10,
side: Side::Buy,
timestamp: current_time,
time_in_force: TimeInForce::Day,
};
assert!(book.has_expired(&order));
}
#[test]
fn test_will_cross_market_buy_no_ask() {
let book = OrderBook::new("TEST");
assert!(!book.will_cross_market(1000, Side::Buy));
}
#[test]
fn test_will_cross_market_sell_no_bid() {
let book = OrderBook::new("TEST");
assert!(!book.will_cross_market(1000, Side::Sell));
}
#[test]
fn test_will_cross_market_buy_with_cross() {
let book = OrderBook::new("TEST");
let id = create_order_id();
let result = book.add_limit_order(id, 1000, 10, Side::Sell, TimeInForce::Gtc);
assert!(result.is_ok());
assert!(book.will_cross_market(1000, Side::Buy));
assert!(book.will_cross_market(1001, Side::Buy));
assert!(!book.will_cross_market(999, Side::Buy));
}
#[test]
fn test_will_cross_market_sell_with_cross() {
let book = OrderBook::new("TEST");
let id = create_order_id();
let result = book.add_limit_order(id, 1000, 10, Side::Buy, TimeInForce::Gtc);
assert!(result.is_ok());
assert!(book.will_cross_market(1000, Side::Sell));
assert!(book.will_cross_market(999, Side::Sell));
assert!(!book.will_cross_market(1001, Side::Sell));
}
#[test]
fn test_handle_immediate_order_fok_insufficient_liquidity() {
let book = OrderBook::new("TEST");
let sell_id = create_order_id();
let _ = book.add_limit_order(sell_id, 1000, 5, Side::Sell, TimeInForce::Gtc);
let buy_id = create_order_id();
let buy_order = OrderType::Standard {
id: buy_id,
price: 1000,
quantity: 10,
side: Side::Buy,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Fok,
};
let result = book.handle_immediate_order(buy_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_handle_immediate_order_fok_sufficient_liquidity() {
let book = OrderBook::new("TEST");
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 buy_order = OrderType::Standard {
id: buy_id,
price: 1000,
quantity: 10,
side: Side::Buy,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Fok,
};
let result = book.handle_immediate_order(buy_order);
assert!(result.is_ok());
assert!(book.get_order(sell_id).is_none());
}
#[test]
fn test_calculate_available_liquidity_for_buy() {
let book = OrderBook::new("TEST");
let id1 = create_order_id();
let _ = book.add_limit_order(id1, 1000, 10, Side::Sell, TimeInForce::Gtc);
let id2 = create_order_id();
let _ = book.add_limit_order(id2, 1010, 15, Side::Sell, TimeInForce::Gtc);
let id3 = create_order_id();
let _ = book.add_limit_order(id3, 990, 5, Side::Sell, TimeInForce::Gtc);
let liquidity = book.calculate_available_liquidity(Side::Buy, Some(1000));
assert_eq!(liquidity, 15);
let liquidity = book.calculate_available_liquidity(Side::Buy, Some(1010));
assert_eq!(liquidity, 30);
let liquidity = book.calculate_available_liquidity(Side::Buy, None);
assert_eq!(liquidity, 30); }
#[test]
fn test_calculate_available_liquidity_for_sell() {
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, 1010, 15, Side::Buy, TimeInForce::Gtc);
let id3 = create_order_id();
let _ = book.add_limit_order(id3, 990, 5, Side::Buy, TimeInForce::Gtc);
let liquidity = book.calculate_available_liquidity(Side::Sell, Some(1000));
assert_eq!(liquidity, 25);
let liquidity = book.calculate_available_liquidity(Side::Sell, Some(990));
assert_eq!(liquidity, 30);
let liquidity = book.calculate_available_liquidity(Side::Sell, None);
assert_eq!(liquidity, 30); }
#[test]
fn test_handle_immediate_order_ioc_partial_fill() {
let book = OrderBook::new("TEST");
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 buy_order = OrderType::Standard {
id: buy_id,
price: 1000,
quantity: 15,
side: Side::Buy,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Ioc,
};
let result = book.handle_immediate_order(buy_order);
assert!(result.is_ok());
assert!(book.get_order(sell_id).is_none());
assert!(book.get_order(buy_id).is_none());
assert_eq!(book.last_trade_price(), Some(1000));
}
}
#[cfg(test)]
mod test_private_remaining {
use crate::OrderBook;
use pricelevel::{OrderId, OrderType, Side, TimeInForce};
use uuid::Uuid;
fn create_order_id() -> OrderId {
OrderId(Uuid::new_v4())
}
#[test]
fn test_handle_immediate_order_match_ioc_partial() {
let book = OrderBook::new("TEST");
let sell_id = create_order_id();
let _ = book.add_limit_order(sell_id, 1000, 5, Side::Sell, TimeInForce::Gtc);
let buy_id = create_order_id();
let buy_order = OrderType::Standard {
id: buy_id,
price: 1000,
quantity: 10,
side: Side::Buy,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Ioc,
};
let result = book.handle_immediate_order(buy_order);
assert!(result.is_ok());
assert_eq!(book.last_trade_price(), Some(1000));
assert!(book.has_traded.load(std::sync::atomic::Ordering::SeqCst));
}
#[test]
fn test_match_market_order_partial_availability() {
let book = OrderBook::new("TEST");
let sell_id = create_order_id();
let _ = book.add_limit_order(sell_id, 1000, 5, Side::Sell, TimeInForce::Gtc);
let buy_id = create_order_id();
let result = book.match_market_order(buy_id, 10, Side::Buy);
assert!(result.is_ok());
let match_result = result.unwrap();
assert_eq!(match_result.executed_quantity(), 5);
assert_eq!(match_result.remaining_quantity, 5);
assert!(!match_result.is_complete);
assert_eq!(book.best_ask(), None);
}
}
#[cfg(test)]
mod test_private_specific {
use crate::{OrderBook, OrderBookError};
use pricelevel::{OrderId, OrderType, Side, TimeInForce};
use uuid::Uuid;
fn create_order_id() -> OrderId {
OrderId(Uuid::new_v4())
}
#[test]
fn test_handle_immediate_order_fok_validation() {
let book = OrderBook::new("TEST");
let sell_id = create_order_id();
let _ = book.add_limit_order(sell_id, 1000, 5, Side::Sell, TimeInForce::Gtc);
let buy_id = create_order_id();
let buy_order = OrderType::Standard {
id: buy_id,
price: 1000,
quantity: 10,
side: Side::Buy,
timestamp: crate::utils::current_time_millis(),
time_in_force: TimeInForce::Fok,
};
let result = book.handle_immediate_order(buy_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_match_market_order_no_matches() {
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(OrderBookError::InsufficientLiquidity {
side,
requested,
available,
}) => {
assert_eq!(side, Side::Buy);
assert_eq!(requested, 10);
assert_eq!(available, 0);
}
_ => panic!("Expected InsufficientLiquidity error"),
}
}
}