use std::collections::BTreeMap;
use super::{BookLevel, BookPrice, OrderBook};
use crate::{
enums::{BookType, OrderSide, OrderSideSpecified},
orderbook::BookIntegrityError,
types::{Price, Quantity, fixed::FIXED_SCALAR, quantity::QuantityRaw},
};
#[must_use]
pub fn get_quantity_for_price(
price: Price,
order_side: OrderSideSpecified,
levels: &BTreeMap<BookPrice, BookLevel>,
) -> f64 {
let mut matched_size: f64 = 0.0;
for (book_price, level) in levels {
match order_side {
OrderSideSpecified::Buy => {
if book_price.value > price {
break;
}
}
OrderSideSpecified::Sell => {
if book_price.value < price {
break;
}
}
}
matched_size += level.size();
}
matched_size
}
#[must_use]
pub fn get_levels_for_price(
price: Price,
order_side: OrderSideSpecified,
levels: &BTreeMap<BookPrice, BookLevel>,
size_precision: u8,
) -> Vec<(Price, Quantity)> {
let mut result = Vec::new();
for (book_price, level) in levels {
match order_side {
OrderSideSpecified::Buy => {
if book_price.value > price {
break;
}
}
OrderSideSpecified::Sell => {
if book_price.value < price {
break;
}
}
}
let level_size = Quantity::new(level.size(), size_precision);
result.push((level.price.value, level_size));
}
result
}
#[must_use]
pub fn get_avg_px_for_quantity(qty: Quantity, levels: &BTreeMap<BookPrice, BookLevel>) -> f64 {
let mut cumulative_size_raw: QuantityRaw = 0;
let mut cumulative_value = 0.0;
for (book_price, level) in levels {
let size_this_level = level.size_raw().min(qty.raw - cumulative_size_raw);
cumulative_size_raw += size_this_level;
cumulative_value += book_price.value.as_f64() * size_this_level as f64;
if cumulative_size_raw >= qty.raw {
break;
}
}
if cumulative_size_raw == 0 {
0.0
} else {
cumulative_value / cumulative_size_raw as f64
}
}
#[must_use]
pub fn get_worst_px_for_quantity(
qty: Quantity,
levels: &BTreeMap<BookPrice, BookLevel>,
) -> Option<Price> {
let mut cumulative_size_raw: QuantityRaw = 0;
let mut worst_price: Option<Price> = None;
for (book_price, level) in levels {
let size_this_level = level.size_raw().min(qty.raw - cumulative_size_raw);
if size_this_level == 0 {
continue;
}
cumulative_size_raw += size_this_level;
worst_price = Some(book_price.value);
if cumulative_size_raw >= qty.raw {
break;
}
}
if cumulative_size_raw == 0 {
None
} else {
worst_price
}
}
#[must_use]
pub fn get_avg_px_qty_for_exposure(
target_exposure: Quantity,
levels: &BTreeMap<BookPrice, BookLevel>,
) -> (f64, f64, f64) {
let mut cumulative_exposure = 0.0;
let mut cumulative_size_raw: QuantityRaw = 0;
let mut final_price = levels
.first_key_value()
.map_or(0.0, |(price, _)| price.value.as_f64());
let target_exposure_raw = target_exposure.raw as f64;
for (book_price, level) in levels {
let price = book_price.value.as_f64();
if price == 0.0 {
continue;
}
let level_exposure = price * level.size_raw() as f64;
let exposure_this_level = level_exposure.min(target_exposure_raw - cumulative_exposure);
let size_this_level = (exposure_this_level / price).floor() as QuantityRaw;
if size_this_level == 0 {
continue;
}
final_price = price;
cumulative_exposure += price * size_this_level as f64;
cumulative_size_raw += size_this_level;
if cumulative_exposure >= target_exposure_raw {
break;
}
}
if cumulative_size_raw == 0 {
(0.0, 0.0, final_price)
} else {
let avg_price = cumulative_exposure / cumulative_size_raw as f64;
(
avg_price,
cumulative_size_raw as f64 / FIXED_SCALAR,
final_price,
)
}
}
pub fn book_check_integrity(book: &OrderBook) -> Result<(), BookIntegrityError> {
match book.book_type {
BookType::L1_MBP => {
if book.bids.len() > 1 {
return Err(BookIntegrityError::TooManyLevels(
OrderSide::Buy,
book.bids.len(),
));
}
if book.asks.len() > 1 {
return Err(BookIntegrityError::TooManyLevels(
OrderSide::Sell,
book.asks.len(),
));
}
}
BookType::L2_MBP => {
for bid_level in book.bids.levels.values() {
let num_orders = bid_level.orders.len();
if num_orders > 1 {
return Err(BookIntegrityError::TooManyOrders(
OrderSide::Buy,
num_orders,
));
}
}
for ask_level in book.asks.levels.values() {
let num_orders = ask_level.orders.len();
if num_orders > 1 {
return Err(BookIntegrityError::TooManyOrders(
OrderSide::Sell,
num_orders,
));
}
}
}
BookType::L3_MBO => {}
}
if let (Some(top_bid_level), Some(top_ask_level)) = (book.bids.top(), book.asks.top()) {
let best_bid = top_bid_level.price;
let best_ask = top_ask_level.price;
if best_bid.value > best_ask.value {
return Err(BookIntegrityError::OrdersCrossed(best_bid, best_ask));
}
}
Ok(())
}