Perp: Merge ordertrees for fixed and oracle_pegged (#321)
By sharing the nodes list we're much less likely to grossly over-allocate space.
This commit is contained in:
parent
6aea3f97db
commit
c44dc045cf
|
@ -25,15 +25,13 @@ const_assert_eq!(
|
|||
std::mem::size_of::<Orderbook>(),
|
||||
2 * std::mem::size_of::<BookSide>() + 2400
|
||||
);
|
||||
const_assert_eq!(std::mem::size_of::<Orderbook>(), 495040);
|
||||
const_assert_eq!(std::mem::size_of::<Orderbook>(), 249824);
|
||||
const_assert_eq!(std::mem::size_of::<Orderbook>() % 8, 0);
|
||||
|
||||
impl Orderbook {
|
||||
pub fn init(&mut self) {
|
||||
self.bids.fixed.order_tree_type = OrderTreeType::Bids.into();
|
||||
self.bids.oracle_pegged.order_tree_type = OrderTreeType::Bids.into();
|
||||
self.asks.fixed.order_tree_type = OrderTreeType::Asks.into();
|
||||
self.asks.oracle_pegged.order_tree_type = OrderTreeType::Asks.into();
|
||||
self.bids.nodes.order_tree_type = OrderTreeType::Bids.into();
|
||||
self.asks.nodes.order_tree_type = OrderTreeType::Asks.into();
|
||||
}
|
||||
|
||||
pub fn bookside_mut(&mut self, side: Side) -> &mut BookSide {
|
||||
|
@ -124,7 +122,7 @@ impl Orderbook {
|
|||
let opposing_bookside = self.bookside_mut(other_side);
|
||||
for best_opposing in opposing_bookside.iter_all_including_invalid(now_ts, oracle_price_lots)
|
||||
{
|
||||
if !best_opposing.is_valid {
|
||||
if !best_opposing.is_valid() {
|
||||
// Remove the order from the book unless we've done that enough
|
||||
if number_of_dropped_expired_orders < DROP_EXPIRED_ORDER_LIMIT {
|
||||
number_of_dropped_expired_orders += 1;
|
||||
|
@ -212,7 +210,7 @@ impl Orderbook {
|
|||
// Apply changes to matched asks (handles invalidate on delete!)
|
||||
for (handle, new_quantity) in matched_order_changes {
|
||||
opposing_bookside
|
||||
.node_mut(handle)
|
||||
.node_mut(handle.node)
|
||||
.unwrap()
|
||||
.as_leaf_mut()
|
||||
.unwrap()
|
||||
|
@ -229,10 +227,9 @@ impl Orderbook {
|
|||
}
|
||||
if let Some(order_tree_target) = post_target {
|
||||
let bookside = self.bookside_mut(side);
|
||||
let order_tree = bookside.orders_mut(order_tree_target);
|
||||
|
||||
// Drop an expired order if possible
|
||||
if let Some(expired_order) = order_tree.remove_one_expired(now_ts) {
|
||||
if let Some(expired_order) = bookside.remove_one_expired(order_tree_target, now_ts) {
|
||||
let event = OutEvent::new(
|
||||
side,
|
||||
expired_order.owner_slot,
|
||||
|
@ -244,12 +241,13 @@ impl Orderbook {
|
|||
event_queue.push_back(cast(event)).unwrap();
|
||||
}
|
||||
|
||||
if order_tree.is_full() {
|
||||
if bookside.is_full() {
|
||||
// If this bid is higher than lowest bid, boot that bid and insert this one
|
||||
let worst_order = order_tree.remove_worst().unwrap();
|
||||
let (worst_order, worst_price) =
|
||||
bookside.remove_worst(now_ts, oracle_price_lots).unwrap();
|
||||
// MangoErrorCode::OutOfSpace
|
||||
require!(
|
||||
side.is_price_data_better(price_data, worst_order.price_data()),
|
||||
side.is_price_better(price_lots, worst_price),
|
||||
MangoError::SomeError
|
||||
);
|
||||
let event = OutEvent::new(
|
||||
|
@ -275,7 +273,7 @@ impl Orderbook {
|
|||
order.time_in_force,
|
||||
order.peg_limit(),
|
||||
);
|
||||
let _result = order_tree.insert_leaf(&new_order)?;
|
||||
let _result = bookside.insert_leaf(order_tree_target, &new_order)?;
|
||||
|
||||
// TODO OPT remove if PlacePerpOrder needs more compute
|
||||
msg!(
|
||||
|
@ -357,8 +355,8 @@ impl Orderbook {
|
|||
) -> Result<LeafNode> {
|
||||
let side = side_and_tree.side();
|
||||
let book_component = side_and_tree.order_tree();
|
||||
let leaf_node = self.bookside_mut(side).orders_mut(book_component).
|
||||
remove_by_key(order_id).ok_or_else(|| {
|
||||
let leaf_node = self.bookside_mut(side).
|
||||
remove_by_key(book_component, order_id).ok_or_else(|| {
|
||||
error_msg!("invalid perp order id {order_id} for side {side:?} and component {book_component:?}")
|
||||
})?;
|
||||
if let Some(owner) = expected_owner {
|
||||
|
|
|
@ -17,8 +17,8 @@ use super::*;
|
|||
)]
|
||||
#[repr(u8)]
|
||||
pub enum BookSideOrderTree {
|
||||
Fixed,
|
||||
OraclePegged,
|
||||
Fixed = 0,
|
||||
OraclePegged = 1,
|
||||
}
|
||||
|
||||
/// Reference to a node in a book side component
|
||||
|
@ -30,14 +30,16 @@ pub struct BookSideOrderHandle {
|
|||
#[zero_copy]
|
||||
#[derive(bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct BookSide {
|
||||
pub fixed: OrderTree,
|
||||
pub oracle_pegged: OrderTree,
|
||||
pub roots: [OrderTreeRoot; 2],
|
||||
pub reserved_roots: [OrderTreeRoot; 4],
|
||||
pub reserved: [u8; 256],
|
||||
pub nodes: OrderTreeNodes,
|
||||
}
|
||||
const_assert_eq!(
|
||||
std::mem::size_of::<BookSide>(),
|
||||
std::mem::size_of::<OrderTree>() * 2
|
||||
std::mem::size_of::<OrderTreeNodes>() + 6 * std::mem::size_of::<OrderTreeRoot>() + 256
|
||||
);
|
||||
const_assert_eq!(std::mem::size_of::<BookSide>(), 246320);
|
||||
const_assert_eq!(std::mem::size_of::<BookSide>(), 123712);
|
||||
const_assert_eq!(std::mem::size_of::<BookSide>() % 8, 0);
|
||||
|
||||
impl BookSide {
|
||||
|
@ -50,7 +52,7 @@ impl BookSide {
|
|||
now_ts: u64,
|
||||
oracle_price_lots: i64,
|
||||
) -> impl Iterator<Item = BookSideIterItem> {
|
||||
BookSideIter::new(self, now_ts, oracle_price_lots).filter(|it| it.is_valid)
|
||||
BookSideIter::new(self, now_ts, oracle_price_lots).filter(|it| it.is_valid())
|
||||
}
|
||||
|
||||
/// Iterate over all entries, including invalid orders
|
||||
|
@ -58,43 +60,76 @@ impl BookSide {
|
|||
BookSideIter::new(self, now_ts, oracle_price_lots)
|
||||
}
|
||||
|
||||
pub fn orders(&self, component: BookSideOrderTree) -> &OrderTree {
|
||||
match component {
|
||||
BookSideOrderTree::Fixed => &self.fixed,
|
||||
BookSideOrderTree::OraclePegged => &self.oracle_pegged,
|
||||
}
|
||||
pub fn node(&self, handle: NodeHandle) -> Option<&AnyNode> {
|
||||
self.nodes.node(handle)
|
||||
}
|
||||
|
||||
pub fn orders_mut(&mut self, component: BookSideOrderTree) -> &mut OrderTree {
|
||||
match component {
|
||||
BookSideOrderTree::Fixed => &mut self.fixed,
|
||||
BookSideOrderTree::OraclePegged => &mut self.oracle_pegged,
|
||||
}
|
||||
pub fn node_mut(&mut self, handle: NodeHandle) -> Option<&mut AnyNode> {
|
||||
self.nodes.node_mut(handle)
|
||||
}
|
||||
|
||||
pub fn node(&self, key: BookSideOrderHandle) -> Option<&AnyNode> {
|
||||
self.orders(key.order_tree).node(key.node)
|
||||
pub fn root(&self, component: BookSideOrderTree) -> &OrderTreeRoot {
|
||||
&self.roots[component as usize]
|
||||
}
|
||||
|
||||
pub fn node_mut(&mut self, key: BookSideOrderHandle) -> Option<&mut AnyNode> {
|
||||
self.orders_mut(key.order_tree).node_mut(key.node)
|
||||
pub fn root_mut(&mut self, component: BookSideOrderTree) -> &mut OrderTreeRoot {
|
||||
&mut self.roots[component as usize]
|
||||
}
|
||||
|
||||
pub fn is_full(&self, component: BookSideOrderTree) -> bool {
|
||||
self.orders(component).is_full()
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.nodes.is_full()
|
||||
}
|
||||
|
||||
pub fn remove_worst(&mut self, component: BookSideOrderTree) -> Option<LeafNode> {
|
||||
self.orders_mut(component).remove_worst()
|
||||
pub fn insert_leaf(
|
||||
&mut self,
|
||||
component: BookSideOrderTree,
|
||||
new_leaf: &LeafNode,
|
||||
) -> Result<(NodeHandle, Option<LeafNode>)> {
|
||||
let root = &mut self.roots[component as usize];
|
||||
self.nodes.insert_leaf(root, new_leaf)
|
||||
}
|
||||
|
||||
/// Remove the order with the lowest expiry timestamp, if that's < now_ts.
|
||||
/// Remove the overall worst-price order.
|
||||
pub fn remove_worst(&mut self, now_ts: u64, oracle_price_lots: i64) -> Option<(LeafNode, i64)> {
|
||||
let worst_fixed = self.nodes.find_worst(&self.roots[0]);
|
||||
let worst_pegged = self.nodes.find_worst(&self.roots[1]);
|
||||
let side = match self.nodes.order_tree_type() {
|
||||
OrderTreeType::Bids => Side::Bid,
|
||||
OrderTreeType::Asks => Side::Ask,
|
||||
};
|
||||
let worse = rank_orders(
|
||||
side,
|
||||
worst_fixed,
|
||||
worst_pegged,
|
||||
true,
|
||||
now_ts,
|
||||
oracle_price_lots,
|
||||
)?;
|
||||
let price = worse.price_lots;
|
||||
let key = worse.node.key;
|
||||
let order_tree = worse.handle.order_tree;
|
||||
let n = self.remove_by_key(order_tree, key)?;
|
||||
Some((n, price))
|
||||
}
|
||||
|
||||
/// Remove the order with the lowest expiry timestamp in the component, if that's < now_ts.
|
||||
/// If there is none, try to remove the lowest expiry one from the other component.
|
||||
pub fn remove_one_expired(
|
||||
&mut self,
|
||||
component: BookSideOrderTree,
|
||||
now_ts: u64,
|
||||
) -> Option<LeafNode> {
|
||||
self.orders_mut(component).remove_one_expired(now_ts)
|
||||
let root = &mut self.roots[component as usize];
|
||||
if let Some(n) = self.nodes.remove_one_expired(root, now_ts) {
|
||||
return Some(n);
|
||||
}
|
||||
|
||||
let other_component = match component {
|
||||
BookSideOrderTree::Fixed => BookSideOrderTree::OraclePegged,
|
||||
BookSideOrderTree::OraclePegged => BookSideOrderTree::Fixed,
|
||||
};
|
||||
let other_root = &mut self.roots[other_component as usize];
|
||||
self.nodes.remove_one_expired(other_root, now_ts)
|
||||
}
|
||||
|
||||
pub fn remove_by_key(
|
||||
|
@ -102,11 +137,8 @@ impl BookSide {
|
|||
component: BookSideOrderTree,
|
||||
search_key: u128,
|
||||
) -> Option<LeafNode> {
|
||||
self.orders_mut(component).remove_by_key(search_key)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: BookSideOrderHandle) -> Option<AnyNode> {
|
||||
self.orders_mut(key.order_tree).remove(key.node)
|
||||
let root = &mut self.roots[component as usize];
|
||||
self.nodes.remove_by_key(root, search_key)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,18 +147,10 @@ mod tests {
|
|||
use super::*;
|
||||
use bytemuck::Zeroable;
|
||||
|
||||
fn new_order_tree(order_tree_type: OrderTreeType) -> OrderTree {
|
||||
OrderTree {
|
||||
order_tree_type: order_tree_type.into(),
|
||||
padding: [0u8; 3],
|
||||
bump_index: 0,
|
||||
free_list_len: 0,
|
||||
free_list_head: 0,
|
||||
root_node: 0,
|
||||
leaf_count: 0,
|
||||
nodes: [AnyNode::zeroed(); MAX_ORDERTREE_NODES],
|
||||
reserved: [0; 256],
|
||||
}
|
||||
fn new_order_tree(order_tree_type: OrderTreeType) -> OrderTreeNodes {
|
||||
let mut ot = OrderTreeNodes::zeroed();
|
||||
ot.order_tree_type = order_tree_type.into();
|
||||
ot
|
||||
}
|
||||
|
||||
fn bookside_iteration_random_helper(side: Side) {
|
||||
|
@ -138,8 +162,9 @@ mod tests {
|
|||
Side::Ask => OrderTreeType::Asks,
|
||||
};
|
||||
|
||||
let mut fixed = new_order_tree(order_tree_type);
|
||||
let mut oracle_pegged = new_order_tree(order_tree_type);
|
||||
let mut order_tree = new_order_tree(order_tree_type);
|
||||
let mut root_fixed = OrderTreeRoot::zeroed();
|
||||
let mut root_pegged = OrderTreeRoot::zeroed();
|
||||
let new_leaf = |key: u128| {
|
||||
LeafNode::new(
|
||||
0,
|
||||
|
@ -160,9 +185,11 @@ mod tests {
|
|||
// ensure at least one oracle pegged order visible even at oracle price 1
|
||||
let key = new_node_key(side, oracle_pegged_price_data(20), 0);
|
||||
keys.push(key);
|
||||
oracle_pegged.insert_leaf(&new_leaf(key)).unwrap();
|
||||
order_tree
|
||||
.insert_leaf(&mut root_pegged, &new_leaf(key))
|
||||
.unwrap();
|
||||
|
||||
while oracle_pegged.leaf_count < 100 {
|
||||
while root_pegged.leaf_count < 100 {
|
||||
let price_data: u64 = oracle_pegged_price_data(rng.gen_range(-20..20));
|
||||
let seq_num: u64 = rng.gen_range(0..1000);
|
||||
let key = new_node_key(side, price_data, seq_num);
|
||||
|
@ -170,10 +197,12 @@ mod tests {
|
|||
continue;
|
||||
}
|
||||
keys.push(key);
|
||||
oracle_pegged.insert_leaf(&new_leaf(key)).unwrap();
|
||||
order_tree
|
||||
.insert_leaf(&mut root_pegged, &new_leaf(key))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
while fixed.leaf_count < 100 {
|
||||
while root_fixed.leaf_count < 100 {
|
||||
let price_data: u64 = rng.gen_range(1..50);
|
||||
let seq_num: u64 = rng.gen_range(0..1000);
|
||||
let key = new_node_key(side, price_data, seq_num);
|
||||
|
@ -181,12 +210,16 @@ mod tests {
|
|||
continue;
|
||||
}
|
||||
keys.push(key);
|
||||
fixed.insert_leaf(&new_leaf(key)).unwrap();
|
||||
order_tree
|
||||
.insert_leaf(&mut root_fixed, &new_leaf(key))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let bookside = BookSide {
|
||||
fixed,
|
||||
oracle_pegged,
|
||||
roots: [root_fixed, root_pegged],
|
||||
reserved_roots: [OrderTreeRoot::zeroed(); 4],
|
||||
reserved: [0; 256],
|
||||
nodes: order_tree,
|
||||
};
|
||||
|
||||
// verify iteration order for different oracle prices
|
||||
|
@ -219,13 +252,15 @@ mod tests {
|
|||
bookside_iteration_random_helper(Side::Ask);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bookside_order_filtering() {
|
||||
fn bookside_setup() -> BookSide {
|
||||
use std::cell::RefCell;
|
||||
|
||||
let side = Side::Bid;
|
||||
let order_tree_type = OrderTreeType::Bids;
|
||||
|
||||
let mut fixed = new_order_tree(order_tree_type);
|
||||
let mut oracle_pegged = new_order_tree(order_tree_type);
|
||||
let order_tree = RefCell::new(new_order_tree(order_tree_type));
|
||||
let mut root_fixed = OrderTreeRoot::zeroed();
|
||||
let mut root_pegged = OrderTreeRoot::zeroed();
|
||||
let new_node = |key: u128, tif: u16, peg_limit: i64| {
|
||||
LeafNode::new(
|
||||
0,
|
||||
|
@ -241,12 +276,16 @@ mod tests {
|
|||
};
|
||||
let mut add_fixed = |price: i64, tif: u16| {
|
||||
let key = new_node_key(side, fixed_price_data(price).unwrap(), 0);
|
||||
fixed.insert_leaf(&new_node(key, tif, -1)).unwrap();
|
||||
order_tree
|
||||
.borrow_mut()
|
||||
.insert_leaf(&mut root_fixed, &new_node(key, tif, -1))
|
||||
.unwrap();
|
||||
};
|
||||
let mut add_pegged = |price_offset: i64, tif: u16, peg_limit: i64| {
|
||||
let key = new_node_key(side, oracle_pegged_price_data(price_offset), 0);
|
||||
oracle_pegged
|
||||
.insert_leaf(&new_node(key, tif, peg_limit))
|
||||
order_tree
|
||||
.borrow_mut()
|
||||
.insert_leaf(&mut root_pegged, &new_node(key, tif, peg_limit))
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
|
@ -256,10 +295,17 @@ mod tests {
|
|||
add_pegged(-15, 0, -1);
|
||||
add_pegged(-20, 7, 95);
|
||||
|
||||
let bookside = BookSide {
|
||||
fixed,
|
||||
oracle_pegged,
|
||||
};
|
||||
BookSide {
|
||||
roots: [root_fixed, root_pegged],
|
||||
reserved_roots: [OrderTreeRoot::zeroed(); 4],
|
||||
reserved: [0; 256],
|
||||
nodes: order_tree.into_inner(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bookside_order_filtering() {
|
||||
let bookside = bookside_setup();
|
||||
|
||||
let order_prices = |now_ts: u64, oracle: i64| -> Vec<i64> {
|
||||
bookside
|
||||
|
@ -280,4 +326,44 @@ mod tests {
|
|||
assert_eq!(order_prices(0, 2015), vec![2000, 120, 100]);
|
||||
assert_eq!(order_prices(1010, 2015), vec![2000, 100]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bookside_remove_worst() {
|
||||
use std::cell::RefCell;
|
||||
|
||||
let bookside = RefCell::new(bookside_setup());
|
||||
|
||||
let order_prices = |now_ts: u64, oracle: i64| -> Vec<i64> {
|
||||
bookside
|
||||
.borrow()
|
||||
.iter_valid(now_ts, oracle)
|
||||
.map(|it| it.price_lots)
|
||||
.collect()
|
||||
};
|
||||
|
||||
// remove pegged order
|
||||
assert_eq!(order_prices(0, 100), vec![120, 100, 90, 85, 80]);
|
||||
let (_, p) = bookside.borrow_mut().remove_worst(0, 100).unwrap();
|
||||
assert_eq!(p, 80);
|
||||
assert_eq!(order_prices(0, 100), vec![120, 100, 90, 85]);
|
||||
|
||||
// remove fixed order (order at 190=200-10 hits the peg limit)
|
||||
assert_eq!(order_prices(0, 200), vec![185, 120, 100]);
|
||||
let (_, p) = bookside.borrow_mut().remove_worst(0, 200).unwrap();
|
||||
assert_eq!(p, 100);
|
||||
assert_eq!(order_prices(0, 200), vec![185, 120]);
|
||||
|
||||
// remove until end
|
||||
|
||||
assert_eq!(order_prices(0, 100), vec![120, 90, 85]);
|
||||
let (_, p) = bookside.borrow_mut().remove_worst(0, 100).unwrap();
|
||||
assert_eq!(p, 85);
|
||||
assert_eq!(order_prices(0, 100), vec![120, 90]);
|
||||
let (_, p) = bookside.borrow_mut().remove_worst(0, 100).unwrap();
|
||||
assert_eq!(p, 90);
|
||||
assert_eq!(order_prices(0, 100), vec![120]);
|
||||
let (_, p) = bookside.borrow_mut().remove_worst(0, 100).unwrap();
|
||||
assert_eq!(p, 120);
|
||||
assert_eq!(order_prices(0, 100), Vec::<i64>::new());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,13 @@ pub struct BookSideIterItem<'a> {
|
|||
pub handle: BookSideOrderHandle,
|
||||
pub node: &'a LeafNode,
|
||||
pub price_lots: i64,
|
||||
pub is_valid: bool,
|
||||
pub state: OrderState,
|
||||
}
|
||||
|
||||
impl<'a> BookSideIterItem<'a> {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.state == OrderState::Valid
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates the fixed and oracle_pegged OrderTrees simultaneously, allowing users to
|
||||
|
@ -25,8 +31,12 @@ pub struct BookSideIter<'a> {
|
|||
impl<'a> BookSideIter<'a> {
|
||||
pub fn new(book_side: &'a BookSide, now_ts: u64, oracle_price_lots: i64) -> Self {
|
||||
Self {
|
||||
fixed_iter: book_side.fixed.iter(),
|
||||
oracle_pegged_iter: book_side.oracle_pegged.iter(),
|
||||
fixed_iter: book_side
|
||||
.nodes
|
||||
.iter(book_side.root(BookSideOrderTree::Fixed)),
|
||||
oracle_pegged_iter: book_side
|
||||
.nodes
|
||||
.iter(book_side.root(BookSideOrderTree::OraclePegged)),
|
||||
now_ts,
|
||||
oracle_price_lots,
|
||||
}
|
||||
|
@ -34,29 +44,26 @@ impl<'a> BookSideIter<'a> {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum OrderState {
|
||||
pub enum OrderState {
|
||||
Valid,
|
||||
Invalid,
|
||||
Skipped,
|
||||
}
|
||||
|
||||
fn oracle_pegged_price(
|
||||
oracle_price_lots: i64,
|
||||
node: &LeafNode,
|
||||
side: Side,
|
||||
) -> (OrderState, Option<i64>) {
|
||||
/// For pegged orders with offsets that let the price escape the 1..i64::MAX range,
|
||||
/// this function returns Skipped and clamps `price` to that range.
|
||||
fn oracle_pegged_price(oracle_price_lots: i64, node: &LeafNode, side: Side) -> (OrderState, i64) {
|
||||
let price_data = node.price_data();
|
||||
let price_offset = oracle_pegged_price_offset(price_data);
|
||||
if let Some(price) = oracle_price_lots.checked_add(price_offset) {
|
||||
if price >= 1 {
|
||||
if node.peg_limit != -1 && side.is_price_better(price, node.peg_limit) {
|
||||
return (OrderState::Invalid, Some(price));
|
||||
} else {
|
||||
return (OrderState::Valid, Some(price));
|
||||
}
|
||||
let price = oracle_price_lots.saturating_add(price_offset);
|
||||
if price >= 1 && price < i64::MAX {
|
||||
if node.peg_limit != -1 && side.is_price_better(price, node.peg_limit) {
|
||||
return (OrderState::Invalid, price);
|
||||
} else {
|
||||
return (OrderState::Valid, price);
|
||||
}
|
||||
}
|
||||
(OrderState::Skipped, None)
|
||||
(OrderState::Skipped, price.max(1))
|
||||
}
|
||||
|
||||
fn key_for_price(key: u128, price_lots: i64) -> u128 {
|
||||
|
@ -68,6 +75,76 @@ fn key_for_price(key: u128, price_lots: i64) -> u128 {
|
|||
upper | lower
|
||||
}
|
||||
|
||||
fn fixed_to_result(fixed: (NodeHandle, &LeafNode), now_ts: u64) -> BookSideIterItem {
|
||||
let (handle, node) = fixed;
|
||||
let expired = node.is_expired(now_ts);
|
||||
BookSideIterItem {
|
||||
handle: BookSideOrderHandle {
|
||||
order_tree: BookSideOrderTree::Fixed,
|
||||
node: handle,
|
||||
},
|
||||
node,
|
||||
price_lots: fixed_price_lots(node.price_data()),
|
||||
state: if expired {
|
||||
OrderState::Invalid
|
||||
} else {
|
||||
OrderState::Valid
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn oracle_pegged_to_result(
|
||||
pegged: (NodeHandle, &LeafNode, i64, OrderState),
|
||||
now_ts: u64,
|
||||
) -> BookSideIterItem {
|
||||
let (handle, node, price_lots, state) = pegged;
|
||||
let expired = node.is_expired(now_ts);
|
||||
BookSideIterItem {
|
||||
handle: BookSideOrderHandle {
|
||||
order_tree: BookSideOrderTree::OraclePegged,
|
||||
node: handle,
|
||||
},
|
||||
node,
|
||||
price_lots,
|
||||
state: if expired { OrderState::Invalid } else { state },
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns (better, worse); will return the same value twice if no second order passed in
|
||||
pub fn rank_orders<'a>(
|
||||
side: Side,
|
||||
fixed: Option<(NodeHandle, &'a LeafNode)>,
|
||||
oracle_pegged: Option<(NodeHandle, &'a LeafNode)>,
|
||||
return_worse: bool,
|
||||
now_ts: u64,
|
||||
oracle_price_lots: i64,
|
||||
) -> Option<BookSideIterItem<'a>> {
|
||||
// Enrich with data that'll always be needed
|
||||
let oracle_pegged = oracle_pegged.map(|(handle, node)| {
|
||||
let (state, price_lots) = oracle_pegged_price(oracle_price_lots, node, side);
|
||||
(handle, node, price_lots, state)
|
||||
});
|
||||
|
||||
match (fixed, oracle_pegged) {
|
||||
(Some(f), Some(o)) => {
|
||||
let is_better = if side == Side::Bid {
|
||||
|a, b| a > b
|
||||
} else {
|
||||
|a, b| a < b
|
||||
};
|
||||
|
||||
if is_better(f.1.key, key_for_price(o.1.key, o.2)) ^ return_worse {
|
||||
Some(fixed_to_result(f, now_ts))
|
||||
} else {
|
||||
Some(oracle_pegged_to_result(o, now_ts))
|
||||
}
|
||||
}
|
||||
(None, Some(o)) => Some(oracle_pegged_to_result(o, now_ts)),
|
||||
(Some(f), None) => Some(fixed_to_result(f, now_ts)),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BookSideIter<'a> {
|
||||
type Item = BookSideIterItem<'a>;
|
||||
|
||||
|
@ -85,69 +162,21 @@ impl<'a> Iterator for BookSideIter<'a> {
|
|||
o_peek = self.oracle_pegged_iter.next()
|
||||
}
|
||||
|
||||
match (self.fixed_iter.peek(), o_peek) {
|
||||
(Some((d_handle, d_node)), Some((o_handle, o_node))) => {
|
||||
let is_better = if side == Side::Bid {
|
||||
|a, b| a > b
|
||||
} else {
|
||||
|a, b| a < b
|
||||
};
|
||||
let f_peek = self.fixed_iter.peek();
|
||||
|
||||
let (o_valid, o_price_maybe) =
|
||||
oracle_pegged_price(self.oracle_price_lots, o_node, side);
|
||||
let o_price = o_price_maybe.unwrap(); // Skipped orders are skipped above
|
||||
if is_better(d_node.key, key_for_price(o_node.key, o_price)) {
|
||||
self.fixed_iter.next();
|
||||
Some(Self::Item {
|
||||
handle: BookSideOrderHandle {
|
||||
order_tree: BookSideOrderTree::Fixed,
|
||||
node: d_handle,
|
||||
},
|
||||
node: d_node,
|
||||
price_lots: fixed_price_lots(d_node.price_data()),
|
||||
is_valid: d_node.is_not_expired(self.now_ts),
|
||||
})
|
||||
} else {
|
||||
self.oracle_pegged_iter.next();
|
||||
Some(Self::Item {
|
||||
handle: BookSideOrderHandle {
|
||||
order_tree: BookSideOrderTree::OraclePegged,
|
||||
node: o_handle,
|
||||
},
|
||||
node: o_node,
|
||||
price_lots: o_price,
|
||||
is_valid: o_valid == OrderState::Valid
|
||||
&& o_node.is_not_expired(self.now_ts),
|
||||
})
|
||||
}
|
||||
}
|
||||
(None, Some((handle, node))) => {
|
||||
self.oracle_pegged_iter.next();
|
||||
let (valid, price_maybe) = oracle_pegged_price(self.oracle_price_lots, node, side);
|
||||
let price_lots = price_maybe.unwrap(); // Skipped orders are skipped above
|
||||
Some(Self::Item {
|
||||
handle: BookSideOrderHandle {
|
||||
order_tree: BookSideOrderTree::OraclePegged,
|
||||
node: handle,
|
||||
},
|
||||
node,
|
||||
price_lots,
|
||||
is_valid: valid == OrderState::Valid && node.is_not_expired(self.now_ts),
|
||||
})
|
||||
}
|
||||
(Some((handle, node)), None) => {
|
||||
self.fixed_iter.next();
|
||||
Some(Self::Item {
|
||||
handle: BookSideOrderHandle {
|
||||
order_tree: BookSideOrderTree::Fixed,
|
||||
node: handle,
|
||||
},
|
||||
node,
|
||||
price_lots: fixed_price_lots(node.price_data()),
|
||||
is_valid: node.is_not_expired(self.now_ts),
|
||||
})
|
||||
}
|
||||
(None, None) => None,
|
||||
}
|
||||
let better = rank_orders(
|
||||
side,
|
||||
f_peek,
|
||||
o_peek,
|
||||
false,
|
||||
self.now_ts,
|
||||
self.oracle_price_lots,
|
||||
)?;
|
||||
match better.handle.order_tree {
|
||||
BookSideOrderTree::Fixed => self.fixed_iter.next(),
|
||||
BookSideOrderTree::OraclePegged => self.oracle_pegged_iter.next(),
|
||||
};
|
||||
|
||||
Some(better)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,28 +27,27 @@ mod tests {
|
|||
use fixed::types::I80F48;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
|
||||
fn order_tree_leaf_by_key(order_tree: &OrderTree, key: u128) -> Option<&LeafNode> {
|
||||
for (_, leaf) in order_tree.iter() {
|
||||
if leaf.key == key {
|
||||
return Some(leaf);
|
||||
fn order_tree_leaf_by_key(bookside: &BookSide, key: u128) -> Option<&LeafNode> {
|
||||
for component in [BookSideOrderTree::Fixed, BookSideOrderTree::OraclePegged] {
|
||||
for (_, leaf) in bookside.nodes.iter(bookside.root(component)) {
|
||||
if leaf.key == key {
|
||||
return Some(leaf);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn order_tree_contains_key(order_tree: &OrderTree, key: u128) -> bool {
|
||||
for (_, leaf) in order_tree.iter() {
|
||||
if leaf.key == key {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
fn order_tree_contains_key(bookside: &BookSide, key: u128) -> bool {
|
||||
order_tree_leaf_by_key(bookside, key).is_some()
|
||||
}
|
||||
|
||||
fn order_tree_contains_price(order_tree: &OrderTree, price_data: u64) -> bool {
|
||||
for (_, leaf) in order_tree.iter() {
|
||||
if leaf.price_data() == price_data {
|
||||
return true;
|
||||
fn order_tree_contains_price(bookside: &BookSide, price_data: u64) -> bool {
|
||||
for component in [BookSideOrderTree::Fixed, BookSideOrderTree::OraclePegged] {
|
||||
for (_, leaf) in bookside.nodes.iter(bookside.root(component)) {
|
||||
if leaf.price_data() == price_data {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
|
@ -137,41 +136,78 @@ mod tests {
|
|||
1000 + i as i64,
|
||||
1000011 as u64,
|
||||
);
|
||||
if book.bids.fixed.is_full() {
|
||||
if book.bids.is_full() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(book.bids.fixed.is_full());
|
||||
assert_eq!(book.bids.fixed.min_leaf().unwrap().price_data(), 1001);
|
||||
assert!(book.bids.is_full());
|
||||
assert_eq!(
|
||||
fixed_price_lots(book.bids.fixed.max_leaf().unwrap().price_data()),
|
||||
(1000 + book.bids.fixed.leaf_count) as i64
|
||||
book.bids
|
||||
.nodes
|
||||
.min_leaf(&book.bids.roots[0])
|
||||
.unwrap()
|
||||
.1
|
||||
.price_data(),
|
||||
1001
|
||||
);
|
||||
assert_eq!(
|
||||
fixed_price_lots(
|
||||
book.bids
|
||||
.nodes
|
||||
.max_leaf(&book.bids.roots[0])
|
||||
.unwrap()
|
||||
.1
|
||||
.price_data()
|
||||
),
|
||||
(1000 + book.bids.roots[0].leaf_count) as i64
|
||||
);
|
||||
|
||||
// add another bid at a higher price before expiry, replacing the lowest-price one (1001)
|
||||
new_order(&mut book, &mut event_queue, Side::Bid, 1005, 1000000 - 1);
|
||||
assert_eq!(book.bids.fixed.min_leaf().unwrap().price_data(), 1002);
|
||||
assert_eq!(
|
||||
book.bids
|
||||
.nodes
|
||||
.min_leaf(&book.bids.roots[0])
|
||||
.unwrap()
|
||||
.1
|
||||
.price_data(),
|
||||
1002
|
||||
);
|
||||
assert_eq!(event_queue.len(), 1);
|
||||
|
||||
// adding another bid after expiry removes the soonest-expiring order (1005)
|
||||
new_order(&mut book, &mut event_queue, Side::Bid, 999, 2000000);
|
||||
assert_eq!(book.bids.fixed.min_leaf().unwrap().price_data(), 999);
|
||||
assert!(!order_tree_contains_key(&book.bids.fixed, 1005));
|
||||
assert_eq!(
|
||||
book.bids
|
||||
.nodes
|
||||
.min_leaf(&book.bids.roots[0])
|
||||
.unwrap()
|
||||
.1
|
||||
.price_data(),
|
||||
999
|
||||
);
|
||||
assert!(!order_tree_contains_key(&book.bids, 1005));
|
||||
assert_eq!(event_queue.len(), 2);
|
||||
|
||||
// adding an ask will wipe up to three expired bids at the top of the book
|
||||
let bids_max = book.bids.fixed.max_leaf().unwrap().price_data();
|
||||
let bids_count = book.bids.fixed.leaf_count;
|
||||
let bids_max = book
|
||||
.bids
|
||||
.nodes
|
||||
.max_leaf(&book.bids.roots[0])
|
||||
.unwrap()
|
||||
.1
|
||||
.price_data();
|
||||
let bids_count = book.bids.roots[0].leaf_count;
|
||||
new_order(&mut book, &mut event_queue, Side::Ask, 6000, 1500000);
|
||||
assert_eq!(book.bids.fixed.leaf_count, bids_count - 5);
|
||||
assert_eq!(book.asks.fixed.leaf_count, 1);
|
||||
assert_eq!(book.bids.roots[0].leaf_count, bids_count - 5);
|
||||
assert_eq!(book.asks.roots[0].leaf_count, 1);
|
||||
assert_eq!(event_queue.len(), 2 + 5);
|
||||
assert!(!order_tree_contains_price(&book.bids.fixed, bids_max));
|
||||
assert!(!order_tree_contains_price(&book.bids.fixed, bids_max - 1));
|
||||
assert!(!order_tree_contains_price(&book.bids.fixed, bids_max - 2));
|
||||
assert!(!order_tree_contains_price(&book.bids.fixed, bids_max - 3));
|
||||
assert!(!order_tree_contains_price(&book.bids.fixed, bids_max - 4));
|
||||
assert!(order_tree_contains_price(&book.bids.fixed, bids_max - 5));
|
||||
assert!(!order_tree_contains_price(&book.bids, bids_max));
|
||||
assert!(!order_tree_contains_price(&book.bids, bids_max - 1));
|
||||
assert!(!order_tree_contains_price(&book.bids, bids_max - 2));
|
||||
assert!(!order_tree_contains_price(&book.bids, bids_max - 3));
|
||||
assert!(!order_tree_contains_price(&book.bids, bids_max - 4));
|
||||
assert!(order_tree_contains_price(&book.bids, bids_max - 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -236,13 +272,10 @@ mod tests {
|
|||
SideAndOrderTree::BidFixed
|
||||
);
|
||||
assert!(order_tree_contains_key(
|
||||
&book.bids.fixed,
|
||||
&book.bids,
|
||||
maker.perp_order_mut_by_raw_index(0).id
|
||||
));
|
||||
assert!(order_tree_contains_price(
|
||||
&book.bids.fixed,
|
||||
price_lots as u64
|
||||
));
|
||||
assert!(order_tree_contains_price(&book.bids, price_lots as u64));
|
||||
assert_eq!(
|
||||
maker.perp_position_by_raw_index(0).bids_base_lots,
|
||||
bid_quantity
|
||||
|
@ -287,7 +320,7 @@ mod tests {
|
|||
// the remainder of the maker order is still on the book
|
||||
// (the maker account is unchanged: it was not even passed in)
|
||||
let order =
|
||||
order_tree_leaf_by_key(&book.bids.fixed, maker.perp_order_by_raw_index(0).id).unwrap();
|
||||
order_tree_leaf_by_key(&book.bids, maker.perp_order_by_raw_index(0).id).unwrap();
|
||||
assert_eq!(fixed_price_lots(order.price_data()), price_lots);
|
||||
assert_eq!(order.quantity, bid_quantity - match_quantity);
|
||||
|
||||
|
|
|
@ -203,8 +203,8 @@ impl LeafNode {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_not_expired(&self, now_ts: u64) -> bool {
|
||||
self.time_in_force == 0 || now_ts < self.timestamp + self.time_in_force as u64
|
||||
pub fn is_expired(&self, now_ts: u64) -> bool {
|
||||
self.time_in_force > 0 && now_ts >= self.timestamp + self.time_in_force as u64
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,30 +26,47 @@ pub enum OrderTreeType {
|
|||
Asks,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct OrderTreeRoot {
|
||||
pub maybe_node: NodeHandle,
|
||||
pub leaf_count: u32,
|
||||
}
|
||||
const_assert_eq!(std::mem::size_of::<OrderTreeRoot>(), 8);
|
||||
const_assert_eq!(std::mem::size_of::<OrderTreeRoot>() % 8, 0);
|
||||
|
||||
impl OrderTreeRoot {
|
||||
pub fn node(&self) -> Option<NodeHandle> {
|
||||
if self.leaf_count == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.maybe_node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A binary tree on AnyNode::key()
|
||||
///
|
||||
/// The key encodes the price in the top 64 bits.
|
||||
#[zero_copy]
|
||||
#[derive(bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct OrderTree {
|
||||
pub struct OrderTreeNodes {
|
||||
pub order_tree_type: u8, // OrderTreeType, but that's not POD
|
||||
pub padding: [u8; 3],
|
||||
pub bump_index: u32,
|
||||
pub free_list_len: u32,
|
||||
pub free_list_head: NodeHandle,
|
||||
pub root_node: NodeHandle,
|
||||
pub leaf_count: u32,
|
||||
pub reserved: [u8; 512],
|
||||
pub nodes: [AnyNode; MAX_ORDERTREE_NODES],
|
||||
pub reserved: [u8; 256],
|
||||
}
|
||||
const_assert_eq!(
|
||||
std::mem::size_of::<OrderTree>(),
|
||||
1 + 3 + 4 * 2 + 4 + 4 + 4 + 120 * 1024 + 256
|
||||
std::mem::size_of::<OrderTreeNodes>(),
|
||||
1 + 3 + 4 * 2 + 4 + 512 + 120 * 1024
|
||||
);
|
||||
const_assert_eq!(std::mem::size_of::<OrderTree>(), 123160);
|
||||
const_assert_eq!(std::mem::size_of::<OrderTree>() % 8, 0);
|
||||
const_assert_eq!(std::mem::size_of::<OrderTreeNodes>(), 123408);
|
||||
const_assert_eq!(std::mem::size_of::<OrderTreeNodes>() % 8, 0);
|
||||
|
||||
impl OrderTree {
|
||||
impl OrderTreeNodes {
|
||||
pub fn order_tree_type(&self) -> OrderTreeType {
|
||||
OrderTreeType::try_from(self.order_tree_type).unwrap()
|
||||
}
|
||||
|
@ -58,20 +75,20 @@ impl OrderTree {
|
|||
///
|
||||
/// smallest to highest for asks
|
||||
/// highest to smallest for bids
|
||||
pub fn iter(&self) -> OrderTreeIter {
|
||||
OrderTreeIter::new(self)
|
||||
pub fn iter(&self, root: &OrderTreeRoot) -> OrderTreeIter {
|
||||
OrderTreeIter::new(self, root)
|
||||
}
|
||||
|
||||
pub fn node_mut(&mut self, key: NodeHandle) -> Option<&mut AnyNode> {
|
||||
let node = &mut self.nodes[key as usize];
|
||||
pub fn node_mut(&mut self, handle: NodeHandle) -> Option<&mut AnyNode> {
|
||||
let node = &mut self.nodes[handle as usize];
|
||||
let tag = NodeTag::try_from(node.tag);
|
||||
match tag {
|
||||
Ok(NodeTag::InnerNode) | Ok(NodeTag::LeafNode) => Some(node),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn node(&self, key: NodeHandle) -> Option<&AnyNode> {
|
||||
let node = &self.nodes[key as usize];
|
||||
pub fn node(&self, handle: NodeHandle) -> Option<&AnyNode> {
|
||||
let node = &self.nodes[handle as usize];
|
||||
let tag = NodeTag::try_from(node.tag);
|
||||
match tag {
|
||||
Ok(NodeTag::InnerNode) | Ok(NodeTag::LeafNode) => Some(node),
|
||||
|
@ -79,45 +96,37 @@ impl OrderTree {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn remove_min(&mut self) -> Option<LeafNode> {
|
||||
self.remove_by_key(self.min_leaf()?.key)
|
||||
pub fn remove_worst(&mut self, root: &mut OrderTreeRoot) -> Option<LeafNode> {
|
||||
self.remove_by_key(root, self.find_worst(root)?.1.key)
|
||||
}
|
||||
|
||||
pub fn remove_max(&mut self) -> Option<LeafNode> {
|
||||
self.remove_by_key(self.max_leaf()?.key)
|
||||
}
|
||||
|
||||
pub fn remove_worst(&mut self) -> Option<LeafNode> {
|
||||
pub fn find_worst(&self, root: &OrderTreeRoot) -> Option<(NodeHandle, &LeafNode)> {
|
||||
match self.order_tree_type() {
|
||||
OrderTreeType::Bids => self.remove_min(),
|
||||
OrderTreeType::Asks => self.remove_max(),
|
||||
OrderTreeType::Bids => self.min_leaf(root),
|
||||
OrderTreeType::Asks => self.max_leaf(root),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the order with the lowest expiry timestamp, if that's < now_ts.
|
||||
pub fn remove_one_expired(&mut self, now_ts: u64) -> Option<LeafNode> {
|
||||
let (expired_h, expires_at) = self.find_earliest_expiry()?;
|
||||
pub fn remove_one_expired(
|
||||
&mut self,
|
||||
root: &mut OrderTreeRoot,
|
||||
now_ts: u64,
|
||||
) -> Option<LeafNode> {
|
||||
let (handle, expires_at) = self.find_earliest_expiry(root)?;
|
||||
if expires_at < now_ts {
|
||||
self.remove_by_key(self.node(expired_h)?.key()?)
|
||||
self.remove_by_key(root, self.node(handle)?.key()?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root(&self) -> Option<NodeHandle> {
|
||||
if self.leaf_count == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.root_node)
|
||||
}
|
||||
}
|
||||
|
||||
// only for fixed-price ordertrees
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code)]
|
||||
fn to_price_quantity_vec(&self, reverse: bool) -> Vec<(i64, i64)> {
|
||||
fn to_price_quantity_vec(&self, root: &OrderTreeRoot, reverse: bool) -> Vec<(i64, i64)> {
|
||||
let mut pqs = vec![];
|
||||
let mut current: NodeHandle = match self.root() {
|
||||
let mut current: NodeHandle = match root.node() {
|
||||
None => return pqs,
|
||||
Some(node_handle) => node_handle,
|
||||
};
|
||||
|
@ -147,41 +156,49 @@ impl OrderTree {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn min_leaf(&self) -> Option<&LeafNode> {
|
||||
self.leaf_min_max(false)
|
||||
pub fn min_leaf(&self, root: &OrderTreeRoot) -> Option<(NodeHandle, &LeafNode)> {
|
||||
self.leaf_min_max(false, root)
|
||||
}
|
||||
|
||||
pub fn max_leaf(&self) -> Option<&LeafNode> {
|
||||
self.leaf_min_max(true)
|
||||
pub fn max_leaf(&self, root: &OrderTreeRoot) -> Option<(NodeHandle, &LeafNode)> {
|
||||
self.leaf_min_max(true, root)
|
||||
}
|
||||
fn leaf_min_max(&self, find_max: bool) -> Option<&LeafNode> {
|
||||
let mut root: NodeHandle = self.root()?;
|
||||
fn leaf_min_max(
|
||||
&self,
|
||||
find_max: bool,
|
||||
root: &OrderTreeRoot,
|
||||
) -> Option<(NodeHandle, &LeafNode)> {
|
||||
let mut node_handle: NodeHandle = root.node()?;
|
||||
|
||||
let i = if find_max { 1 } else { 0 };
|
||||
loop {
|
||||
let root_contents = self.node(root)?;
|
||||
match root_contents.case()? {
|
||||
let node_contents = self.node(node_handle)?;
|
||||
match node_contents.case()? {
|
||||
NodeRef::Inner(inner) => {
|
||||
root = inner.children[i];
|
||||
node_handle = inner.children[i];
|
||||
}
|
||||
NodeRef::Leaf(leaf) => {
|
||||
return Some(leaf);
|
||||
return Some((node_handle, leaf));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_by_key(&mut self, search_key: u128) -> Option<LeafNode> {
|
||||
pub fn remove_by_key(
|
||||
&mut self,
|
||||
root: &mut OrderTreeRoot,
|
||||
search_key: u128,
|
||||
) -> Option<LeafNode> {
|
||||
// path of InnerNode handles that lead to the removed leaf
|
||||
let mut stack: Vec<(NodeHandle, bool)> = vec![];
|
||||
|
||||
// special case potentially removing the root
|
||||
let mut parent_h = self.root()?;
|
||||
let mut parent_h = root.node()?;
|
||||
let (mut child_h, mut crit_bit) = match self.node(parent_h).unwrap().case().unwrap() {
|
||||
NodeRef::Leaf(&leaf) if leaf.key == search_key => {
|
||||
assert_eq!(self.leaf_count, 1);
|
||||
self.root_node = 0;
|
||||
self.leaf_count = 0;
|
||||
assert_eq!(root.leaf_count, 1);
|
||||
root.maybe_node = 0;
|
||||
root.leaf_count = 0;
|
||||
let _old_root = self.remove(parent_h).unwrap();
|
||||
return Some(leaf);
|
||||
}
|
||||
|
@ -215,7 +232,7 @@ impl OrderTree {
|
|||
let other_child_node_contents = self.remove(other_child_h).unwrap();
|
||||
let new_expiry = other_child_node_contents.earliest_expiry();
|
||||
*self.node_mut(parent_h).unwrap() = other_child_node_contents;
|
||||
self.leaf_count -= 1;
|
||||
root.leaf_count -= 1;
|
||||
let removed_leaf: LeafNode = cast(self.remove(child_h).unwrap());
|
||||
|
||||
// update child min expiry back up to the root
|
||||
|
@ -226,7 +243,8 @@ impl OrderTree {
|
|||
Some(removed_leaf)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: NodeHandle) -> Option<AnyNode> {
|
||||
/// Internal: Removes only the node, does not remove any links etc, use remove_key()
|
||||
fn remove(&mut self, key: NodeHandle) -> Option<AnyNode> {
|
||||
let val = *self.node(key)?;
|
||||
|
||||
self.nodes[key as usize] = cast(FreeNode {
|
||||
|
@ -245,7 +263,8 @@ impl OrderTree {
|
|||
Some(val)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, val: &AnyNode) -> Result<NodeHandle> {
|
||||
/// Internal: Adds only the node, does not add parent links etc, use insert_leaf()
|
||||
fn insert(&mut self, val: &AnyNode) -> Result<NodeHandle> {
|
||||
match NodeTag::try_from(val.tag) {
|
||||
Ok(NodeTag::InnerNode) | Ok(NodeTag::LeafNode) => (),
|
||||
_ => unreachable!(),
|
||||
|
@ -279,18 +298,23 @@ impl OrderTree {
|
|||
*node = *val;
|
||||
Ok(key)
|
||||
}
|
||||
pub fn insert_leaf(&mut self, new_leaf: &LeafNode) -> Result<(NodeHandle, Option<LeafNode>)> {
|
||||
|
||||
pub fn insert_leaf(
|
||||
&mut self,
|
||||
root: &mut OrderTreeRoot,
|
||||
new_leaf: &LeafNode,
|
||||
) -> Result<(NodeHandle, Option<LeafNode>)> {
|
||||
// path of InnerNode handles that lead to the new leaf
|
||||
let mut stack: Vec<(NodeHandle, bool)> = vec![];
|
||||
|
||||
// deal with inserts into an empty tree
|
||||
let mut root: NodeHandle = match self.root() {
|
||||
let mut parent_handle: NodeHandle = match root.node() {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
// create a new root if none exists
|
||||
let handle = self.insert(new_leaf.as_ref())?;
|
||||
self.root_node = handle;
|
||||
self.leaf_count = 1;
|
||||
root.maybe_node = handle;
|
||||
root.leaf_count = 1;
|
||||
return Ok((handle, None));
|
||||
}
|
||||
};
|
||||
|
@ -298,46 +322,46 @@ impl OrderTree {
|
|||
// walk down the tree until we find the insert location
|
||||
loop {
|
||||
// require if the new node will be a child of the root
|
||||
let root_contents = *self.node(root).unwrap();
|
||||
let root_key = root_contents.key().unwrap();
|
||||
if root_key == new_leaf.key {
|
||||
let parent_contents = *self.node(parent_handle).unwrap();
|
||||
let parent_key = parent_contents.key().unwrap();
|
||||
if parent_key == new_leaf.key {
|
||||
// This should never happen because key should never match
|
||||
if let Some(NodeRef::Leaf(&old_root_as_leaf)) = root_contents.case() {
|
||||
if let Some(NodeRef::Leaf(&old_parent_as_leaf)) = parent_contents.case() {
|
||||
// clobber the existing leaf
|
||||
*self.node_mut(root).unwrap() = *new_leaf.as_ref();
|
||||
*self.node_mut(parent_handle).unwrap() = *new_leaf.as_ref();
|
||||
self.update_parent_earliest_expiry(
|
||||
&stack,
|
||||
old_root_as_leaf.expiry(),
|
||||
old_parent_as_leaf.expiry(),
|
||||
new_leaf.expiry(),
|
||||
);
|
||||
return Ok((root, Some(old_root_as_leaf)));
|
||||
return Ok((parent_handle, Some(old_parent_as_leaf)));
|
||||
}
|
||||
// InnerNodes have a random child's key, so matching can happen and is fine
|
||||
}
|
||||
let shared_prefix_len: u32 = (root_key ^ new_leaf.key).leading_zeros();
|
||||
match root_contents.case() {
|
||||
let shared_prefix_len: u32 = (parent_key ^ new_leaf.key).leading_zeros();
|
||||
match parent_contents.case() {
|
||||
None => unreachable!(),
|
||||
Some(NodeRef::Inner(inner)) => {
|
||||
let keep_old_root = shared_prefix_len >= inner.prefix_len;
|
||||
if keep_old_root {
|
||||
let keep_old_parent = shared_prefix_len >= inner.prefix_len;
|
||||
if keep_old_parent {
|
||||
let (child, crit_bit) = inner.walk_down(new_leaf.key);
|
||||
stack.push((root, crit_bit));
|
||||
root = child;
|
||||
stack.push((parent_handle, crit_bit));
|
||||
parent_handle = child;
|
||||
continue;
|
||||
};
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
// implies root is a Leaf or Inner where shared_prefix_len < prefix_len
|
||||
// we'll replace root with a new InnerNode that has new_leaf and root as children
|
||||
// implies parent is a Leaf or Inner where shared_prefix_len < prefix_len
|
||||
// we'll replace parent with a new InnerNode that has new_leaf and parent as children
|
||||
|
||||
// change the root in place to represent the LCA of [new_leaf] and [root]
|
||||
// change the parent in place to represent the LCA of [new_leaf] and [parent]
|
||||
let crit_bit_mask: u128 = 1u128 << (127 - shared_prefix_len);
|
||||
let new_leaf_crit_bit = (crit_bit_mask & new_leaf.key) != 0;
|
||||
let old_root_crit_bit = !new_leaf_crit_bit;
|
||||
let old_parent_crit_bit = !new_leaf_crit_bit;
|
||||
|
||||
let new_leaf_handle = self.insert(new_leaf.as_ref())?;
|
||||
let moved_root_handle = match self.insert(&root_contents) {
|
||||
let moved_parent_handle = match self.insert(&parent_contents) {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
self.remove(new_leaf_handle).unwrap();
|
||||
|
@ -345,23 +369,23 @@ impl OrderTree {
|
|||
}
|
||||
};
|
||||
|
||||
let new_root: &mut InnerNode = cast_mut(self.node_mut(root).unwrap());
|
||||
*new_root = InnerNode::new(shared_prefix_len, new_leaf.key);
|
||||
let new_parent: &mut InnerNode = cast_mut(self.node_mut(parent_handle).unwrap());
|
||||
*new_parent = InnerNode::new(shared_prefix_len, new_leaf.key);
|
||||
|
||||
new_root.children[new_leaf_crit_bit as usize] = new_leaf_handle;
|
||||
new_root.children[old_root_crit_bit as usize] = moved_root_handle;
|
||||
new_parent.children[new_leaf_crit_bit as usize] = new_leaf_handle;
|
||||
new_parent.children[old_parent_crit_bit as usize] = moved_parent_handle;
|
||||
|
||||
let new_leaf_expiry = new_leaf.expiry();
|
||||
let old_root_expiry = root_contents.earliest_expiry();
|
||||
new_root.child_earliest_expiry[new_leaf_crit_bit as usize] = new_leaf_expiry;
|
||||
new_root.child_earliest_expiry[old_root_crit_bit as usize] = old_root_expiry;
|
||||
let old_parent_expiry = parent_contents.earliest_expiry();
|
||||
new_parent.child_earliest_expiry[new_leaf_crit_bit as usize] = new_leaf_expiry;
|
||||
new_parent.child_earliest_expiry[old_parent_crit_bit as usize] = old_parent_expiry;
|
||||
|
||||
// walk up the stack and fix up the new min if needed
|
||||
if new_leaf_expiry < old_root_expiry {
|
||||
self.update_parent_earliest_expiry(&stack, old_root_expiry, new_leaf_expiry);
|
||||
if new_leaf_expiry < old_parent_expiry {
|
||||
self.update_parent_earliest_expiry(&stack, old_parent_expiry, new_leaf_expiry);
|
||||
}
|
||||
|
||||
self.leaf_count += 1;
|
||||
root.leaf_count += 1;
|
||||
return Ok((new_leaf_handle, None));
|
||||
}
|
||||
}
|
||||
|
@ -394,8 +418,8 @@ impl OrderTree {
|
|||
}
|
||||
|
||||
/// Returns the handle of the node with the lowest expiry timestamp, and this timestamp
|
||||
pub fn find_earliest_expiry(&self) -> Option<(NodeHandle, u64)> {
|
||||
let mut current: NodeHandle = match self.root() {
|
||||
pub fn find_earliest_expiry(&self, root: &OrderTreeRoot) -> Option<(NodeHandle, u64)> {
|
||||
let mut current: NodeHandle = match root.node() {
|
||||
Some(h) => h,
|
||||
None => return None,
|
||||
};
|
||||
|
@ -423,34 +447,21 @@ mod tests {
|
|||
use super::*;
|
||||
use bytemuck::Zeroable;
|
||||
|
||||
fn new_order_tree(order_tree_type: OrderTreeType) -> OrderTree {
|
||||
OrderTree {
|
||||
order_tree_type: order_tree_type.into(),
|
||||
padding: [0u8; 3],
|
||||
bump_index: 0,
|
||||
free_list_len: 0,
|
||||
free_list_head: 0,
|
||||
root_node: 0,
|
||||
leaf_count: 0,
|
||||
nodes: [AnyNode::zeroed(); MAX_ORDERTREE_NODES],
|
||||
reserved: [0; 256],
|
||||
}
|
||||
fn new_order_tree(order_tree_type: OrderTreeType) -> OrderTreeNodes {
|
||||
let mut ot = OrderTreeNodes::zeroed();
|
||||
ot.order_tree_type = order_tree_type.into();
|
||||
ot
|
||||
}
|
||||
|
||||
fn verify_order_tree(order_tree: &OrderTree) {
|
||||
verify_order_tree_invariant(order_tree);
|
||||
verify_order_tree_iteration(order_tree);
|
||||
verify_order_tree_expiry(order_tree);
|
||||
fn verify_order_tree(order_tree: &OrderTreeNodes, root: &OrderTreeRoot) {
|
||||
verify_order_tree_invariant(order_tree, root);
|
||||
verify_order_tree_iteration(order_tree, root);
|
||||
verify_order_tree_expiry(order_tree, root);
|
||||
}
|
||||
|
||||
// check that BookSide binary tree key invariant holds
|
||||
fn verify_order_tree_invariant(order_tree: &OrderTree) {
|
||||
let r = match order_tree.root() {
|
||||
Some(h) => h,
|
||||
None => return,
|
||||
};
|
||||
|
||||
fn recursive_check(order_tree: &OrderTree, h: NodeHandle) {
|
||||
fn verify_order_tree_invariant(order_tree: &OrderTreeNodes, root: &OrderTreeRoot) {
|
||||
fn recursive_check(order_tree: &OrderTreeNodes, h: NodeHandle) {
|
||||
match order_tree.node(h).unwrap().case().unwrap() {
|
||||
NodeRef::Inner(&inner) => {
|
||||
let left = order_tree.node(inner.children[0]).unwrap().key().unwrap();
|
||||
|
@ -471,15 +482,18 @@ mod tests {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
recursive_check(order_tree, r);
|
||||
|
||||
if let Some(r) = root.node() {
|
||||
recursive_check(order_tree, r);
|
||||
}
|
||||
}
|
||||
|
||||
// check that iteration of order tree has the right order and misses no leaves
|
||||
fn verify_order_tree_iteration(order_tree: &OrderTree) {
|
||||
fn verify_order_tree_iteration(order_tree: &OrderTreeNodes, root: &OrderTreeRoot) {
|
||||
let mut total = 0;
|
||||
let ascending = order_tree.order_tree_type() == OrderTreeType::Asks;
|
||||
let mut last_key = if ascending { 0 } else { u128::MAX };
|
||||
for (_, node) in order_tree.iter() {
|
||||
for (_, node) in order_tree.iter(root) {
|
||||
let key = node.key;
|
||||
if ascending {
|
||||
assert!(key >= last_key);
|
||||
|
@ -489,17 +503,12 @@ mod tests {
|
|||
last_key = key;
|
||||
total += 1;
|
||||
}
|
||||
assert_eq!(order_tree.leaf_count, total);
|
||||
assert_eq!(root.leaf_count, total);
|
||||
}
|
||||
|
||||
// check that BookSide::child_expiry invariant holds
|
||||
fn verify_order_tree_expiry(order_tree: &OrderTree) {
|
||||
let r = match order_tree.root() {
|
||||
Some(h) => h,
|
||||
None => return,
|
||||
};
|
||||
|
||||
fn recursive_check(order_tree: &OrderTree, h: NodeHandle) {
|
||||
fn verify_order_tree_expiry(order_tree: &OrderTreeNodes, root: &OrderTreeRoot) {
|
||||
fn recursive_check(order_tree: &OrderTreeNodes, h: NodeHandle) {
|
||||
match order_tree.node(h).unwrap().case().unwrap() {
|
||||
NodeRef::Inner(&inner) => {
|
||||
let left = order_tree
|
||||
|
@ -521,7 +530,9 @@ mod tests {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
recursive_check(order_tree, r);
|
||||
if let Some(r) = root.node() {
|
||||
recursive_check(order_tree, r);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -541,26 +552,38 @@ mod tests {
|
|||
)
|
||||
};
|
||||
|
||||
assert!(bids.find_earliest_expiry().is_none());
|
||||
let mut root = OrderTreeRoot::zeroed();
|
||||
|
||||
bids.insert_leaf(&new_expiring_leaf(0, 5000)).unwrap();
|
||||
assert_eq!(bids.find_earliest_expiry().unwrap(), (bids.root_node, 5000));
|
||||
verify_order_tree(&bids);
|
||||
assert!(bids.find_earliest_expiry(&root).is_none());
|
||||
|
||||
let (new4000_h, _) = bids.insert_leaf(&new_expiring_leaf(1, 4000)).unwrap();
|
||||
assert_eq!(bids.find_earliest_expiry().unwrap(), (new4000_h, 4000));
|
||||
verify_order_tree(&bids);
|
||||
bids.insert_leaf(&mut root, &new_expiring_leaf(0, 5000))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
bids.find_earliest_expiry(&root).unwrap(),
|
||||
(root.maybe_node, 5000)
|
||||
);
|
||||
verify_order_tree(&bids, &root);
|
||||
|
||||
let (_new4500_h, _) = bids.insert_leaf(&new_expiring_leaf(2, 4500)).unwrap();
|
||||
assert_eq!(bids.find_earliest_expiry().unwrap(), (new4000_h, 4000));
|
||||
verify_order_tree(&bids);
|
||||
let (new4000_h, _) = bids
|
||||
.insert_leaf(&mut root, &new_expiring_leaf(1, 4000))
|
||||
.unwrap();
|
||||
assert_eq!(bids.find_earliest_expiry(&root).unwrap(), (new4000_h, 4000));
|
||||
verify_order_tree(&bids, &root);
|
||||
|
||||
let (new3500_h, _) = bids.insert_leaf(&new_expiring_leaf(3, 3500)).unwrap();
|
||||
assert_eq!(bids.find_earliest_expiry().unwrap(), (new3500_h, 3500));
|
||||
verify_order_tree(&bids);
|
||||
let (_new4500_h, _) = bids
|
||||
.insert_leaf(&mut root, &new_expiring_leaf(2, 4500))
|
||||
.unwrap();
|
||||
assert_eq!(bids.find_earliest_expiry(&root).unwrap(), (new4000_h, 4000));
|
||||
verify_order_tree(&bids, &root);
|
||||
|
||||
let (new3500_h, _) = bids
|
||||
.insert_leaf(&mut root, &new_expiring_leaf(3, 3500))
|
||||
.unwrap();
|
||||
assert_eq!(bids.find_earliest_expiry(&root).unwrap(), (new3500_h, 3500));
|
||||
verify_order_tree(&bids, &root);
|
||||
// the first two levels of the tree are innernodes, with 0;1 on one side and 2;3 on the other
|
||||
assert_eq!(
|
||||
bids.node_mut(bids.root_node)
|
||||
bids.node_mut(root.maybe_node)
|
||||
.unwrap()
|
||||
.as_inner_mut()
|
||||
.unwrap()
|
||||
|
@ -568,37 +591,37 @@ mod tests {
|
|||
[4000, 3500]
|
||||
);
|
||||
|
||||
bids.remove_by_key(3).unwrap();
|
||||
verify_order_tree(&bids);
|
||||
bids.remove_by_key(&mut root, 3).unwrap();
|
||||
verify_order_tree(&bids, &root);
|
||||
assert_eq!(
|
||||
bids.node_mut(bids.root_node)
|
||||
bids.node_mut(root.maybe_node)
|
||||
.unwrap()
|
||||
.as_inner_mut()
|
||||
.unwrap()
|
||||
.child_earliest_expiry,
|
||||
[4000, 4500]
|
||||
);
|
||||
assert_eq!(bids.find_earliest_expiry().unwrap().1, 4000);
|
||||
assert_eq!(bids.find_earliest_expiry(&root).unwrap().1, 4000);
|
||||
|
||||
bids.remove_by_key(0).unwrap();
|
||||
verify_order_tree(&bids);
|
||||
bids.remove_by_key(&mut root, 0).unwrap();
|
||||
verify_order_tree(&bids, &root);
|
||||
assert_eq!(
|
||||
bids.node_mut(bids.root_node)
|
||||
bids.node_mut(root.maybe_node)
|
||||
.unwrap()
|
||||
.as_inner_mut()
|
||||
.unwrap()
|
||||
.child_earliest_expiry,
|
||||
[4000, 4500]
|
||||
);
|
||||
assert_eq!(bids.find_earliest_expiry().unwrap().1, 4000);
|
||||
assert_eq!(bids.find_earliest_expiry(&root).unwrap().1, 4000);
|
||||
|
||||
bids.remove_by_key(1).unwrap();
|
||||
verify_order_tree(&bids);
|
||||
assert_eq!(bids.find_earliest_expiry().unwrap().1, 4500);
|
||||
bids.remove_by_key(&mut root, 1).unwrap();
|
||||
verify_order_tree(&bids, &root);
|
||||
assert_eq!(bids.find_earliest_expiry(&root).unwrap().1, 4500);
|
||||
|
||||
bids.remove_by_key(2).unwrap();
|
||||
verify_order_tree(&bids);
|
||||
assert!(bids.find_earliest_expiry().is_none());
|
||||
bids.remove_by_key(&mut root, 2).unwrap();
|
||||
verify_order_tree(&bids, &root);
|
||||
assert!(bids.find_earliest_expiry(&root).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -606,6 +629,7 @@ mod tests {
|
|||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let mut root = OrderTreeRoot::zeroed();
|
||||
let mut bids = new_order_tree(OrderTreeType::Bids);
|
||||
let new_expiring_leaf = |key: u128, expiry: u64| {
|
||||
LeafNode::new(
|
||||
|
@ -630,8 +654,9 @@ mod tests {
|
|||
}
|
||||
let expiry = rng.gen_range(1..200); // give good chance of duplicate expiry times
|
||||
keys.push(key);
|
||||
bids.insert_leaf(&new_expiring_leaf(key, expiry)).unwrap();
|
||||
verify_order_tree(&bids);
|
||||
bids.insert_leaf(&mut root, &new_expiring_leaf(key, expiry))
|
||||
.unwrap();
|
||||
verify_order_tree(&bids, &root);
|
||||
}
|
||||
|
||||
// remove 50 at random
|
||||
|
@ -640,9 +665,9 @@ mod tests {
|
|||
break;
|
||||
}
|
||||
let k = keys[rng.gen_range(0..keys.len())];
|
||||
bids.remove_by_key(k).unwrap();
|
||||
bids.remove_by_key(&mut root, k).unwrap();
|
||||
keys.retain(|v| *v != k);
|
||||
verify_order_tree(&bids);
|
||||
verify_order_tree(&bids, &root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::*;
|
|||
|
||||
/// Iterate over orders in order (bids=descending, asks=ascending)
|
||||
pub struct OrderTreeIter<'a> {
|
||||
order_tree: &'a OrderTree,
|
||||
order_tree: &'a OrderTreeNodes,
|
||||
/// InnerNodes where the right side still needs to be iterated on
|
||||
stack: Vec<&'a InnerNode>,
|
||||
/// To be returned on `next()`
|
||||
|
@ -14,7 +14,7 @@ pub struct OrderTreeIter<'a> {
|
|||
}
|
||||
|
||||
impl<'a> OrderTreeIter<'a> {
|
||||
pub fn new(order_tree: &'a OrderTree) -> Self {
|
||||
pub fn new(order_tree: &'a OrderTreeNodes, root: &OrderTreeRoot) -> Self {
|
||||
let (left, right) = if order_tree.order_tree_type() == OrderTreeType::Bids {
|
||||
(1, 0)
|
||||
} else {
|
||||
|
@ -29,8 +29,8 @@ impl<'a> OrderTreeIter<'a> {
|
|||
left,
|
||||
right,
|
||||
};
|
||||
if order_tree.leaf_count != 0 {
|
||||
iter.next_leaf = iter.find_leftmost_leaf(order_tree.root_node);
|
||||
if let Some(r) = root.node() {
|
||||
iter.next_leaf = iter.find_leftmost_leaf(r);
|
||||
}
|
||||
iter
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
check_prev_instruction_post_health(&solana, account_0).await;
|
||||
|
||||
let orderbook_data = solana.get_account_boxed::<Orderbook>(orderbook).await;
|
||||
assert_eq!(orderbook_data.bids.fixed.leaf_count, 1);
|
||||
assert_eq!(orderbook_data.bids.roots[0].leaf_count, 1);
|
||||
let order_id_to_cancel = solana
|
||||
.get_account::<MangoAccount>(account_0)
|
||||
.await
|
||||
|
@ -559,7 +559,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
|||
check_prev_instruction_post_health(&solana, account_0).await;
|
||||
|
||||
let orderbook_data = solana.get_account_boxed::<Orderbook>(orderbook).await;
|
||||
assert_eq!(orderbook_data.bids.oracle_pegged.leaf_count, 1);
|
||||
assert_eq!(orderbook_data.bids.roots[1].leaf_count, 1);
|
||||
let perp_order = solana
|
||||
.get_account::<MangoAccount>(account_0)
|
||||
.await
|
||||
|
|
Loading…
Reference in New Issue