use crate::{Order, OrderBook, Price, Quantity, Side, Trade};
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MatchResult {
pub trades: Vec<Trade>,
pub remaining_quantity: Quantity,
}
impl MatchResult {
pub fn filled_quantity(&self) -> Quantity {
self.trades.iter().map(|t| t.quantity).sum()
}
pub fn is_fully_filled(&self) -> bool {
self.remaining_quantity == 0
}
pub fn is_empty(&self) -> bool {
self.trades.is_empty()
}
}
impl OrderBook {
#[inline]
fn prices_cross(incoming_side: Side, incoming_price: Price, resting_price: Price) -> bool {
match incoming_side {
Side::Buy => incoming_price >= resting_price,
Side::Sell => incoming_price <= resting_price,
}
}
pub fn match_order(&mut self, incoming: &mut Order) -> MatchResult {
let mut result = MatchResult {
trades: Vec::new(),
remaining_quantity: incoming.remaining_quantity,
};
while incoming.remaining_quantity > 0 {
let opposite = self.opposite_side(incoming.side);
let best_price = match opposite.best_price() {
Some(p) => p,
None => break, };
if !Self::prices_cross(incoming.side, incoming.price, best_price) {
break; }
self.match_at_price(incoming, best_price, &mut result);
}
result.remaining_quantity = incoming.remaining_quantity;
result
}
fn match_at_price(&mut self, incoming: &mut Order, price: Price, result: &mut MatchResult) {
while incoming.remaining_quantity > 0 {
let opposite = self.opposite_side_mut(incoming.side);
let resting_id = match opposite.get_level_mut(price).and_then(|l| l.front()) {
Some(id) => id,
_ => break, };
let resting_remaining = match self.get_order(resting_id) {
Some(o) => o.remaining_quantity,
None => {
self.opposite_side_mut(incoming.side)
.get_level_mut(price)
.map(|l| l.pop_front(0));
continue;
}
};
let fill_qty = incoming.remaining_quantity.min(resting_remaining);
let trade = Trade::new(
self.next_trade_id(),
price, fill_qty,
incoming.id,
resting_id,
incoming.side,
self.next_timestamp(),
);
result.trades.push(trade);
incoming.fill(fill_qty);
let resting_fully_filled = {
let resting = self
.get_order_mut(resting_id)
.expect("invariant: resting order exists in book");
resting.fill(fill_qty);
resting.remaining_quantity == 0
};
let opposite = self.opposite_side_mut(incoming.side);
if resting_fully_filled {
if let Some(level) = opposite.get_level_mut(price) {
level.pop_front(fill_qty);
if level.is_empty() {
opposite.remove_level(price);
}
}
} else {
if let Some(level) = opposite.get_level_mut(price) {
level.decrease_quantity(fill_qty);
}
}
}
}
pub fn available_to_fill(&self, side: Side, price: Price) -> Quantity {
self.opposite_side(side).quantity_at_or_better(price)
}
pub fn can_fully_fill(&self, side: Side, price: Price, quantity: Quantity) -> bool {
self.available_to_fill(side, price) >= quantity
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{OrderId, OrderStatus, TimeInForce};
fn book_with_asks(asks: &[(i64, u64)]) -> OrderBook {
let mut book = OrderBook::new();
for &(price, qty) in asks {
let order = book.create_order(Side::Sell, Price(price), qty, TimeInForce::GTC);
book.add_order(order);
}
book
}
fn book_with_bids(bids: &[(i64, u64)]) -> OrderBook {
let mut book = OrderBook::new();
for &(price, qty) in bids {
let order = book.create_order(Side::Buy, Price(price), qty, TimeInForce::GTC);
book.add_order(order);
}
book
}
#[test]
fn no_match_empty_book() {
let mut book = OrderBook::new();
let mut order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert!(result.is_empty());
assert_eq!(result.remaining_quantity, 100);
assert!(!result.is_fully_filled());
}
#[test]
fn no_match_prices_dont_cross() {
let mut book = book_with_asks(&[(101_00, 100)]);
let mut order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert!(result.is_empty());
assert_eq!(result.remaining_quantity, 100);
assert_eq!(book.best_ask(), Some(Price(101_00)));
}
#[test]
fn full_fill_exact_quantity() {
let mut book = book_with_asks(&[(100_00, 100)]);
let mut order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert_eq!(result.trades.len(), 1);
assert_eq!(result.filled_quantity(), 100);
assert!(result.is_fully_filled());
let trade = &result.trades[0];
assert_eq!(trade.price, Price(100_00));
assert_eq!(trade.quantity, 100);
assert_eq!(trade.aggressor_side, Side::Buy);
assert_eq!(book.best_ask(), None);
let resting = book.get_order(OrderId(1)).unwrap();
assert_eq!(resting.status, OrderStatus::Filled);
}
#[test]
fn full_fill_incoming_smaller() {
let mut book = book_with_asks(&[(100_00, 200)]);
let mut order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert_eq!(result.trades.len(), 1);
assert!(result.is_fully_filled());
assert_eq!(book.best_ask(), Some(Price(100_00)));
let resting = book.get_order(OrderId(1)).unwrap();
assert_eq!(resting.remaining_quantity, 100);
assert_eq!(resting.status, OrderStatus::PartiallyFilled);
}
#[test]
fn partial_fill_incoming_larger() {
let mut book = book_with_asks(&[(100_00, 50)]);
let mut order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert_eq!(result.trades.len(), 1);
assert_eq!(result.filled_quantity(), 50);
assert_eq!(result.remaining_quantity, 50);
assert!(!result.is_fully_filled());
assert_eq!(book.best_ask(), None);
}
#[test]
fn fifo_same_price() {
let mut book = book_with_asks(&[(100_00, 30), (100_00, 40), (100_00, 50)]);
let mut order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert_eq!(result.trades.len(), 3);
assert_eq!(result.trades[0].quantity, 30); assert_eq!(result.trades[1].quantity, 40); assert_eq!(result.trades[2].quantity, 30); assert!(result.is_fully_filled());
assert_eq!(
book.get_order(OrderId(1)).unwrap().status,
OrderStatus::Filled
);
assert_eq!(
book.get_order(OrderId(2)).unwrap().status,
OrderStatus::Filled
);
assert_eq!(
book.get_order(OrderId(3)).unwrap().status,
OrderStatus::PartiallyFilled
);
assert_eq!(book.get_order(OrderId(3)).unwrap().remaining_quantity, 20);
}
#[test]
fn price_priority_buy_sweeps_asks() {
let mut book = book_with_asks(&[(100_00, 50), (101_00, 50), (102_00, 50)]);
let mut order = book.create_order(Side::Buy, Price(102_00), 120, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert_eq!(result.trades.len(), 3);
assert_eq!(result.trades[0].price, Price(100_00));
assert_eq!(result.trades[0].quantity, 50);
assert_eq!(result.trades[1].price, Price(101_00));
assert_eq!(result.trades[1].quantity, 50);
assert_eq!(result.trades[2].price, Price(102_00));
assert_eq!(result.trades[2].quantity, 20);
assert!(result.is_fully_filled());
assert_eq!(book.best_ask(), Some(Price(102_00)));
assert_eq!(book.asks().total_quantity(), 30);
}
#[test]
fn price_priority_sell_sweeps_bids() {
let mut book = book_with_bids(&[(100_00, 50), (99_00, 50), (98_00, 50)]);
let mut order = book.create_order(Side::Sell, Price(98_00), 120, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert_eq!(result.trades.len(), 3);
assert_eq!(result.trades[0].price, Price(100_00));
assert_eq!(result.trades[1].price, Price(99_00));
assert_eq!(result.trades[2].price, Price(98_00));
assert!(result.is_fully_filled());
}
#[test]
fn price_improvement_for_buyer() {
let mut book = book_with_asks(&[(100_00, 100)]);
let mut order = book.create_order(Side::Buy, Price(105_00), 100, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert_eq!(result.trades[0].price, Price(100_00));
}
#[test]
fn price_improvement_for_seller() {
let mut book = book_with_bids(&[(105_00, 100)]);
let mut order = book.create_order(Side::Sell, Price(100_00), 100, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert_eq!(result.trades[0].price, Price(105_00));
}
#[test]
fn incoming_order_state_after_full_fill() {
let mut book = book_with_asks(&[(100_00, 100)]);
let mut order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
book.match_order(&mut order);
assert_eq!(order.remaining_quantity, 0);
assert_eq!(order.filled_quantity, 100);
assert_eq!(order.status, OrderStatus::Filled);
}
#[test]
fn incoming_order_state_after_partial_fill() {
let mut book = book_with_asks(&[(100_00, 30)]);
let mut order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
book.match_order(&mut order);
assert_eq!(order.remaining_quantity, 70);
assert_eq!(order.filled_quantity, 30);
assert_eq!(order.status, OrderStatus::PartiallyFilled);
}
#[test]
fn available_to_fill() {
let book = book_with_asks(&[(100_00, 50), (101_00, 75), (102_00, 100)]);
assert_eq!(book.available_to_fill(Side::Buy, Price(100_00)), 50);
assert_eq!(book.available_to_fill(Side::Buy, Price(101_00)), 125);
assert_eq!(book.available_to_fill(Side::Buy, Price(102_00)), 225);
}
#[test]
fn can_fully_fill() {
let book = book_with_asks(&[(100_00, 100)]);
assert!(book.can_fully_fill(Side::Buy, Price(100_00), 50));
assert!(book.can_fully_fill(Side::Buy, Price(100_00), 100));
assert!(!book.can_fully_fill(Side::Buy, Price(100_00), 101));
assert!(!book.can_fully_fill(Side::Buy, Price(99_00), 50)); }
#[test]
fn match_clears_multiple_levels() {
let mut book = book_with_asks(&[(100_00, 10), (101_00, 10)]);
let mut order = book.create_order(Side::Buy, Price(101_00), 20, TimeInForce::GTC);
book.match_order(&mut order);
assert_eq!(book.asks().level_count(), 0);
assert_eq!(book.best_ask(), None);
}
#[test]
fn trade_ids_are_sequential() {
let mut book = book_with_asks(&[(100_00, 30), (100_00, 30), (100_00, 30)]);
let mut order = book.create_order(Side::Buy, Price(100_00), 90, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert_eq!(result.trades[0].id.0, 1);
assert_eq!(result.trades[1].id.0, 2);
assert_eq!(result.trades[2].id.0, 3);
}
#[test]
fn timestamps_are_sequential() {
let mut book = book_with_asks(&[(100_00, 30), (100_00, 30)]);
let mut order = book.create_order(Side::Buy, Price(100_00), 60, TimeInForce::GTC);
let result = book.match_order(&mut order);
assert!(result.trades[0].timestamp < result.trades[1].timestamp);
}
}