use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::ops::{Add, Div, Sub};
use crate::enums::{JournalOp, OrderOptions};
use crate::journal::Snapshot;
use crate::order::{OrderId, Price, Quantity};
use crate::report::ExecutionReportParams;
use crate::utils::{current_timestamp_millis, safe_add};
use crate::{
error::{make_error, ErrorType, Result},
journal::JournalLog,
order::{LimitOrder, LimitOrderOptions, MarketOrder, MarketOrderOptions},
{OrderStatus, OrderType, Side, TimeInForce},
};
use crate::{ExecutionReport, FillReport};
use std::collections::VecDeque;
#[derive(Debug, Clone, Default)]
pub struct OrderBookOptions {
pub journaling: bool,
pub snapshot: Option<Snapshot>,
pub replay_logs: Option<Vec<JournalLog>>,
}
#[derive(Debug, PartialEq)]
pub struct Depth {
pub asks: Vec<(Price, Quantity)>, pub bids: Vec<(Price, Quantity)>, }
pub struct OrderBook {
pub(crate) last_op: u64,
pub(crate) symbol: String,
pub(crate) next_order_id: OrderId,
pub(crate) orders: HashMap<OrderId, LimitOrder>,
pub(crate) asks: BTreeMap<Price, VecDeque<OrderId>>,
pub(crate) bids: BTreeMap<Price, VecDeque<OrderId>>,
pub(crate) journaling: bool,
}
impl OrderBook {
pub fn new(symbol: &str, opts: OrderBookOptions) -> Self {
Self {
symbol: symbol.to_string(),
last_op: 0,
next_order_id: OrderId(0),
orders: HashMap::with_capacity(100_000),
asks: BTreeMap::new(),
bids: BTreeMap::new(),
journaling: opts.journaling,
}
}
pub fn symbol(&self) -> &str {
&self.symbol
}
pub fn market(&mut self, options: MarketOrderOptions) -> Result<ExecutionReport> {
self.validate_market_order(&options)?;
let mut order = MarketOrder::new(self.new_order_id(), options);
let mut report = ExecutionReport::new(ExecutionReportParams {
id: order.id,
order_type: OrderType::Market,
side: order.side,
quantity: order.remaining_qty(),
status: order.status,
time_in_force: None,
price: None,
post_only: false,
});
let mut fills = Vec::new();
let remaining_qty = match order.side {
Side::Buy => self.match_with_asks(order.remaining_qty(), &mut fills, None),
Side::Sell => self.match_with_bids(order.remaining_qty(), &mut fills, None),
};
order.executed_qty = order.orig_qty.sub(remaining_qty);
order.status = if order.remaining_qty().value() > 0 {
OrderStatus::PartiallyFilled
} else {
OrderStatus::Filled
};
report.remaining_qty = order.remaining_qty();
report.executed_qty = order.executed_qty;
report.status = order.status;
report.taker_qty = order.executed_qty;
if self.journaling {
self.last_op = safe_add(self.last_op, 1);
report.log = Some(JournalLog {
op_id: self.last_op,
ts: current_timestamp_millis(),
op: JournalOp::Market,
o: OrderOptions::Market(options),
})
}
Ok(report)
}
pub fn market_raw(&mut self, side: Side, quantity: u64) -> Result<ExecutionReport> {
self.market(MarketOrderOptions { side, quantity: Quantity(quantity) })
}
pub fn limit(&mut self, options: LimitOrderOptions) -> Result<ExecutionReport> {
self.validate_limit_order(&options)?;
let mut order = LimitOrder::new(self.new_order_id(), options);
let mut report = ExecutionReport::new(ExecutionReportParams {
id: order.id,
order_type: OrderType::Limit,
side: order.side,
quantity: order.orig_qty,
status: order.status,
time_in_force: Some(order.time_in_force),
price: Some(order.price), post_only: order.post_only,
});
let mut fills = Vec::new();
let remaining_qty = match order.side {
Side::Buy => self.match_with_asks(order.remaining_qty(), &mut fills, Some(order.price)),
Side::Sell => {
self.match_with_bids(order.remaining_qty(), &mut fills, Some(order.price))
}
};
order.executed_qty = order.orig_qty.sub(remaining_qty);
order.taker_qty = order.orig_qty.sub(order.remaining_qty());
order.maker_qty = order.remaining_qty();
if order.remaining_qty().value() > 0 {
if order.time_in_force == TimeInForce::IOC {
order.status = OrderStatus::Canceled;
} else {
order.status = OrderStatus::PartiallyFilled;
self.orders.insert(order.id, order);
if order.side == Side::Buy {
self.bids.entry(order.price).or_default().push_back(order.id);
} else {
self.asks.entry(order.price).or_default().push_back(order.id);
}
}
} else {
order.status = OrderStatus::Filled;
}
report.remaining_qty = order.remaining_qty();
report.executed_qty = order.executed_qty;
report.taker_qty = order.taker_qty;
report.maker_qty = order.maker_qty;
report.status = order.status;
if self.journaling {
self.last_op = safe_add(self.last_op, 1);
report.log = Some(JournalLog {
op_id: self.last_op,
ts: current_timestamp_millis(),
op: JournalOp::Limit,
o: OrderOptions::Limit(options),
})
}
Ok(report)
}
pub fn limit_raw(
&mut self,
side: Side,
quantity: u64,
price: u64,
time_in_force: Option<TimeInForce>,
post_only: Option<bool>,
) -> Result<ExecutionReport> {
self.limit(LimitOrderOptions {
side,
quantity: Quantity(quantity),
price: Price(price),
time_in_force,
post_only,
})
}
pub fn cancel(&mut self, id: OrderId) -> Result<ExecutionReport> {
let mut order = match self.orders.remove(&id) {
Some(o) => o,
None => return Err(make_error(ErrorType::OrderNotFound)),
};
let book_side = match order.side {
Side::Buy => &mut self.bids,
Side::Sell => &mut self.asks,
};
if let Some(queue) = book_side.get_mut(&order.price) {
if let Some(pos) = queue.iter().position(|x| *x == id) {
queue.remove(pos);
}
if queue.is_empty() {
book_side.remove(&order.price);
}
}
order.status = OrderStatus::Canceled;
let mut report = ExecutionReport {
order_id: order.id,
orig_qty: order.orig_qty,
executed_qty: order.executed_qty,
remaining_qty: order.remaining_qty(),
taker_qty: order.taker_qty,
maker_qty: order.maker_qty,
order_type: order.order_type,
side: order.side,
price: order.price,
status: order.status,
time_in_force: order.time_in_force,
post_only: order.post_only,
fills: Vec::new(),
log: None,
};
if self.journaling {
self.last_op = safe_add(self.last_op, 1);
report.log = Some(JournalLog {
op_id: self.last_op,
ts: current_timestamp_millis(),
op: JournalOp::Cancel,
o: OrderOptions::Cancel(order.id),
})
}
Ok(report)
}
pub fn cancel_raw(&mut self, id: u64) -> Result<ExecutionReport> {
self.cancel(OrderId(id))
}
pub fn modify(
&mut self,
id: OrderId,
price: Option<Price>,
quantity: Option<Quantity>,
) -> Result<ExecutionReport> {
let old_journaling = self.journaling;
self.journaling = false;
let report = match self.cancel(id) {
Ok(o) => o,
Err(e) => {
self.journaling = old_journaling;
return Err(e);
}
};
let mut report = match (price, quantity) {
(None, Some(quantity)) => self.limit(LimitOrderOptions {
side: report.side,
quantity,
price: report.price,
time_in_force: Some(report.time_in_force),
post_only: Some(report.post_only),
}),
(Some(price), None) => self.limit(LimitOrderOptions {
side: report.side,
quantity: report.remaining_qty,
price,
time_in_force: Some(report.time_in_force),
post_only: Some(report.post_only),
}),
(Some(price), Some(quantity)) => self.limit(LimitOrderOptions {
side: report.side,
quantity,
price,
time_in_force: Some(report.time_in_force),
post_only: Some(report.post_only),
}),
(None, None) => {
self.journaling = old_journaling;
return Err(make_error(ErrorType::InvalidPriceOrQuantity));
}
};
self.journaling = old_journaling;
if let Ok(r) = report.as_mut() {
if self.journaling {
self.last_op = safe_add(self.last_op, 1);
r.log = Some(JournalLog {
op_id: self.last_op,
ts: current_timestamp_millis(),
op: JournalOp::Modify,
o: OrderOptions::Modify { id, price, quantity },
});
}
}
report
}
pub fn modify_raw(
&mut self,
id: u64,
price: Option<u64>,
quantity: Option<u64>,
) -> Result<ExecutionReport> {
self.modify(OrderId(id), price.map(Price), quantity.map(Quantity))
}
pub fn get_orders_at_price(&self, price: Price, side: Side) -> Vec<LimitOrder> {
let mut orders = Vec::new();
let queue = match side {
Side::Buy => self.bids.get(&price),
Side::Sell => self.asks.get(&price),
};
if let Some(q) = queue {
for id in q {
if let Some(order) = self.orders.get(id) {
orders.push(*order);
}
}
}
orders
}
pub fn get_order(&self, id: OrderId) -> Result<LimitOrder> {
match self.orders.get(&id) {
Some(o) => Ok(*o),
None => Err(make_error(ErrorType::OrderNotFound)),
}
}
pub fn best_bid(&self) -> Option<Price> {
self.bids.last_key_value().map(|(price, _)| *price)
}
pub fn best_ask(&self) -> Option<Price> {
self.asks.first_key_value().map(|(price, _)| *price)
}
pub fn mid_price(&self) -> Option<Price> {
match (self.best_bid(), self.best_ask()) {
(Some(bid), Some(ask)) => Some(bid.add(ask).div(Price(2))),
_ => None,
}
}
pub fn spread(&self) -> Option<Price> {
match (self.best_bid(), self.best_ask()) {
(Some(bid), Some(ask)) => Some(ask.sub(bid)),
_ => None,
}
}
pub fn snapshot(&self) -> Snapshot {
Snapshot {
orders: self.orders.clone(),
bids: self.bids.clone(),
asks: self.asks.clone(),
last_op: self.last_op,
next_order_id: self.next_order_id,
ts: current_timestamp_millis(),
}
}
pub fn restore_snapshot(&mut self, snapshot: Snapshot) {
self.orders = snapshot.orders;
self.bids = snapshot.bids;
self.asks = snapshot.asks;
self.last_op = snapshot.last_op;
self.next_order_id = snapshot.next_order_id;
}
pub fn replay_logs(&mut self, mut logs: Vec<JournalLog>) -> Result<()> {
logs.sort_by_key(|log| log.op_id);
for log in &logs {
match &log.o {
OrderOptions::Market(opts) => self.market(*opts)?,
OrderOptions::Limit(opts) => self.limit(*opts)?,
OrderOptions::Cancel(id) => self.cancel(*id)?,
OrderOptions::Modify { id, price, quantity } => {
self.modify(*id, *price, *quantity)?
}
};
}
Ok(())
}
pub fn depth(&self, limit: Option<usize>) -> Depth {
let levels = limit.unwrap_or(100);
Depth {
asks: self.get_asks_prices_and_volume(levels),
bids: self.get_bids_prices_and_volume(levels),
}
}
fn get_asks_prices_and_volume(&self, levels: usize) -> Vec<(Price, Quantity)> {
let mut asks = Vec::with_capacity(levels);
for (ask_price, queue) in self.asks.iter() {
let volume: Quantity = queue
.iter()
.filter_map(|id| self.orders.get(id))
.map(|order| order.remaining_qty())
.sum();
asks.push((*ask_price, volume));
}
asks
}
fn get_bids_prices_and_volume(&self, levels: usize) -> Vec<(Price, Quantity)> {
let mut bids = Vec::with_capacity(levels);
for (bid_price, queue) in self.bids.iter().rev() {
let volume: Quantity = queue
.iter()
.filter_map(|id| self.orders.get(id))
.map(|order| order.remaining_qty())
.sum();
bids.push((*bid_price, volume));
}
bids
}
fn match_with_asks(
&mut self,
quantity_to_fill: Quantity,
fills: &mut Vec<FillReport>,
limit_price: Option<Price>,
) -> Quantity {
if self.asks.is_empty() {
return quantity_to_fill;
}
let mut remaining_qty = quantity_to_fill;
let mut filled_prices = Vec::new();
for (ask_price, queue) in self.asks.iter_mut() {
if remaining_qty.value() == 0 {
break;
}
if let Some(limit_price) = limit_price {
if limit_price < *ask_price {
break;
}
}
remaining_qty = Self::process_queue(&mut self.orders, queue, remaining_qty, fills);
if queue.is_empty() {
filled_prices.push(*ask_price);
}
}
for price in filled_prices {
self.asks.remove(&price);
}
remaining_qty
}
fn match_with_bids(
&mut self,
quantity_to_fill: Quantity,
fills: &mut Vec<FillReport>,
limit_price: Option<Price>,
) -> Quantity {
if self.bids.is_empty() {
return quantity_to_fill;
}
let mut remaining_qty = quantity_to_fill;
let mut filled_prices = Vec::new();
for (bid_price, queue) in self.bids.iter_mut().rev() {
if remaining_qty.value() == 0 {
break;
}
if let Some(limit_price) = limit_price {
if limit_price > *bid_price {
break;
}
}
remaining_qty = Self::process_queue(&mut self.orders, queue, remaining_qty, fills);
if queue.is_empty() {
filled_prices.push(*bid_price);
}
}
for price in filled_prices {
self.bids.remove(&price);
}
remaining_qty
}
fn process_queue(
orders: &mut HashMap<OrderId, LimitOrder>,
order_queue: &mut VecDeque<OrderId>,
remaining_qty: Quantity,
fills: &mut Vec<FillReport>,
) -> Quantity {
let mut quantity_left = remaining_qty;
while !order_queue.is_empty() && quantity_left.value() > 0 {
let Some(head_order_uuid) = order_queue.front() else { break };
let Some(mut head_order) = orders.remove(head_order_uuid) else { break };
if quantity_left < head_order.remaining_qty() {
head_order.executed_qty = head_order.executed_qty.add(quantity_left);
head_order.status = OrderStatus::PartiallyFilled;
fills.push(FillReport {
order_id: head_order.id,
price: head_order.price,
quantity: quantity_left,
status: head_order.status,
});
orders.insert(head_order.id, head_order);
quantity_left = Quantity(0);
} else {
order_queue.pop_front();
quantity_left = quantity_left.sub(head_order.remaining_qty());
head_order.executed_qty = head_order.executed_qty.add(head_order.remaining_qty());
head_order.status = OrderStatus::Filled;
fills.push(FillReport {
order_id: head_order.id,
price: head_order.price,
quantity: head_order.executed_qty,
status: head_order.status,
});
}
}
quantity_left
}
fn validate_market_order(&self, options: &MarketOrderOptions) -> Result<()> {
if options.quantity.value() == 0 {
return Err(make_error(ErrorType::InvalidQuantity));
}
if (options.side == Side::Buy && self.asks.is_empty())
|| (options.side == Side::Sell && self.bids.is_empty())
{
return Err(make_error(ErrorType::OrderBookEmpty));
}
Ok(())
}
fn validate_limit_order(&self, options: &LimitOrderOptions) -> Result<()> {
if options.quantity.value() == 0 {
return Err(make_error(ErrorType::InvalidQuantity));
}
if options.price.value() == 0 {
return Err(make_error(ErrorType::InvalidPrice));
}
let time_in_force = options.time_in_force.unwrap_or(TimeInForce::GTC);
if time_in_force == TimeInForce::FOK
&& !self.limit_order_is_fillable(options.side, options.quantity, options.price)
{
return Err(make_error(ErrorType::OrderFOK));
}
if options.post_only.unwrap_or(false) {
let crosses = match options.side {
Side::Buy => {
if let Some((best_ask, _)) = self.asks.first_key_value() {
options.price >= *best_ask
} else {
false
}
}
Side::Sell => {
if let Some((best_bid, _)) = self.bids.last_key_value() {
options.price <= *best_bid
} else {
false
}
}
};
if crosses {
return Err(make_error(ErrorType::OrderPostOnly));
}
}
Ok(())
}
fn limit_order_is_fillable(&self, side: Side, quantity: Quantity, price: Price) -> bool {
if side == Side::Buy {
self.limit_buy_order_is_fillable(quantity, price)
} else {
self.limit_sell_order_is_fillable(quantity, price)
}
}
fn limit_buy_order_is_fillable(&self, quantity: Quantity, price: Price) -> bool {
let mut cumulative_qty = Quantity(0);
for (ask_price, queue) in self.asks.iter() {
if price >= *ask_price && cumulative_qty < quantity {
for id in queue.iter() {
if let Some(order) = self.orders.get(id) {
cumulative_qty += order.remaining_qty().value();
}
}
} else {
break;
}
}
cumulative_qty >= quantity
}
fn limit_sell_order_is_fillable(&self, quantity: Quantity, price: Price) -> bool {
let mut cumulative_qty = Quantity(0);
for (bid_price, queue) in self.bids.iter().rev() {
if price <= *bid_price && cumulative_qty < quantity {
for id in queue.iter() {
if let Some(order) = self.orders.get(id) {
cumulative_qty += order.remaining_qty().value()
}
}
} else {
break;
}
}
cumulative_qty >= quantity
}
fn new_order_id(&mut self) -> OrderId {
let id = self.next_order_id;
self.next_order_id += 1;
id
}
}
impl fmt::Display for OrderBook {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (price, order_ids) in self.asks.iter().rev() {
let volume: Quantity = order_ids
.iter()
.filter_map(|id| self.orders.get(id))
.map(|order| order.remaining_qty())
.sum();
writeln!(f, "{} -> {}", price.value(), volume.value())?;
}
writeln!(f, "------------------------------------")?;
for (price, order_ids) in self.bids.iter().rev() {
let volume: Quantity = order_ids
.iter()
.filter_map(|id| self.orders.get(id))
.map(|order| order.remaining_qty())
.sum();
writeln!(f, "{} -> {}", price.value(), volume.value())?;
}
Ok(())
}
}
#[cfg(test)]
mod tests;