diff --git a/src/dlob/dlob.rs b/src/dlob/dlob.rs new file mode 100644 index 0000000..3176db2 --- /dev/null +++ b/src/dlob/dlob.rs @@ -0,0 +1,664 @@ +use dashmap::{DashMap, DashSet}; +use drift::state::oracle::OraclePriceData; +use drift::state::user::{MarketType, Order}; +use solana_sdk::pubkey::Pubkey; +use std::cell::Cell; +use std::collections::BinaryHeap; + +use crate::dlob::dlob_node::{get_order_signature, DLOBNode, Node, NodeType}; +use crate::dlob::market::{get_order_lists, Exchange, Market, OpenOrders, SubType}; +use crate::math::order::is_resting_limit_order; +use crate::utils::market_type_to_string; + +use super::dlob_node::DirectionalNode; + +pub struct DLOB { + exchange: Exchange, + _open_orders: OpenOrders, + _initialized: bool, + _max_slot_for_resting_limit_orders: Cell, +} + +impl DLOB { + pub fn new() -> DLOB { + let exchange = Exchange::new(); + exchange.insert("perp".to_string(), DashMap::new()); + exchange.insert("spot".to_string(), DashMap::new()); + + let open_orders = OpenOrders::new(); + open_orders.insert("perp".to_string(), DashSet::new()); + open_orders.insert("spot".to_string(), DashSet::new()); + + DLOB { + exchange, + _open_orders: open_orders, + _initialized: true, + _max_slot_for_resting_limit_orders: Cell::new(0), + } + } + + pub fn add_market(&self, market_type: &str, market_index: u16) { + if !self + .exchange + .get(market_type) + .unwrap() + .contains_key(&market_index) + { + self.exchange + .get(market_type) + .unwrap() + .insert(market_index, Market::new()); + } + } + + pub fn insert_node(&self, node: &Node) { + let market_type = market_type_to_string(&node.get_order().market_type); + let market_index = node.get_order().market_index; + + if !self + .exchange + .get(&market_type) + .unwrap() + .contains_key(&market_index) + { + self.add_market(&market_type, market_index); + } + + let markets_for_market_type = self + .exchange + .get(&market_type) + .expect(format!("Market type {} not found", market_type).as_str()); + let mut market = markets_for_market_type + .get_mut(&market_index) + .expect(format!("Market index {} not found", market_index).as_str()); + + let (order_list, subtype) = + market.get_list_for_order(&node.get_order(), node.get_order().slot); + + if let Some(order_list) = order_list { + match subtype { + SubType::Bid => order_list.insert_bid(node.clone()), + SubType::Ask => order_list.insert_ask(node.clone()), + _ => {} + } + } else { + panic!("Order list not found for order {:?}", node.get_order()); + } + } + + pub fn get_order(&self, order_id: u32, user_account: Pubkey) -> Option { + let order_signature = get_order_signature(order_id, user_account); + for order_list in get_order_lists(&self.exchange) { + if let Some(node) = order_list.get_node(&order_signature) { + return Some(node.get_order().clone()); + } + } + + None + } + + fn update_resting_limit_orders_for_market_type(&mut self, slot: u64, market_type: String) { + let mut new_taking_asks: BinaryHeap = BinaryHeap::new(); + let mut new_taking_bids: BinaryHeap = BinaryHeap::new(); + if let Some(market) = self.exchange.get_mut(&market_type) { + for mut market_ref in market.iter_mut() { + let market = market_ref.value_mut(); + + for directional_node in market.taking_limit_orders.bids.iter() { + if is_resting_limit_order(directional_node.node.get_order(), slot) { + market + .resting_limit_orders + .insert_bid(directional_node.node) + } else { + new_taking_bids.push(*directional_node) + } + } + + for directional_node in market.taking_limit_orders.asks.iter() { + if is_resting_limit_order(directional_node.node.get_order(), slot) { + market + .resting_limit_orders + .insert_ask(directional_node.node); + } else { + new_taking_asks.push(*directional_node); + } + } + + market.taking_limit_orders.bids = new_taking_bids.clone(); + market.taking_limit_orders.asks = new_taking_asks.clone(); + } + } + } + + pub fn update_resting_limit_orders(&mut self, slot: u64) { + if slot <= self._max_slot_for_resting_limit_orders.get() { + return; + } + + self._max_slot_for_resting_limit_orders.set(slot); + + self.update_resting_limit_orders_for_market_type( + slot, + market_type_to_string(&MarketType::Perp), + ); + self.update_resting_limit_orders_for_market_type( + slot, + market_type_to_string(&MarketType::Spot), + ); + } + + pub fn get_best_orders( + &self, + market_type: MarketType, + sub_type: SubType, + node_type: NodeType, + market_index: u16, + ) -> Vec { + let market_type_str = market_type_to_string(&market_type); + let markets_for_market_type = self + .exchange + .get(&market_type_str) + .expect(format!("Market type {} not found", market_type_str).as_str()); + let market = markets_for_market_type + .get(&market_index) + .expect(format!("Market index {} not found", market_index).as_str()); + + let mut order_list = market.get_order_list_for_node_type(node_type); + + let mut best_orders: Vec = vec![]; + + match sub_type { + SubType::Bid => { + while !order_list.bids_empty() { + if let Some(node) = order_list.get_best_bid() { + best_orders.push(node); + } + } + } + SubType::Ask => { + while !order_list.asks_empty() { + if let Some(node) = order_list.get_best_ask() { + best_orders.push(node); + } + } + } + _ => unimplemented!(), + } + + best_orders + } + + pub fn get_resting_limit_asks( + &mut self, + slot: u64, + market_type: MarketType, + market_index: u16, + oracle_price_data: OraclePriceData, + ) -> Vec { + self.update_resting_limit_orders(slot); + + let mut resting_limit_orders = self.get_best_orders( + market_type, + SubType::Ask, + NodeType::RestingLimit, + market_index, + ); + let mut floating_limit_orders = self.get_best_orders( + market_type, + SubType::Ask, + NodeType::FloatingLimit, + market_index, + ); + + let comparative = Box::new( + |node_a: &Node, node_b: &Node, slot: u64, oracle_price_data: OraclePriceData| { + node_a.get_price(oracle_price_data, slot) + > node_b.get_price(oracle_price_data, slot) + }, + ); + + let mut all_orders = vec![]; + all_orders.append(&mut resting_limit_orders); + all_orders.append(&mut floating_limit_orders); + + all_orders.sort_by(|a, b| { + if comparative(a, b, slot, oracle_price_data) { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Less + } + }); + + all_orders + } + + pub fn get_resting_limit_bids( + &mut self, + slot: u64, + market_type: MarketType, + market_index: u16, + oracle_price_data: OraclePriceData, + ) -> Vec { + self.update_resting_limit_orders(slot); + + let mut resting_limit_orders = self.get_best_orders( + market_type, + SubType::Bid, + NodeType::RestingLimit, + market_index, + ); + let mut floating_limit_orders = self.get_best_orders( + market_type, + SubType::Bid, + NodeType::FloatingLimit, + market_index, + ); + + let comparative = Box::new( + |node_a: &Node, node_b: &Node, slot: u64, oracle_price_data: OraclePriceData| { + node_a.get_price(oracle_price_data, slot) + < node_b.get_price(oracle_price_data, slot) + }, + ); + + let mut all_orders = vec![]; + all_orders.append(&mut resting_limit_orders); + all_orders.append(&mut floating_limit_orders); + + all_orders.sort_by(|a, b| { + if comparative(a, b, slot, oracle_price_data) { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Less + } + }); + + all_orders + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dlob::dlob_node::create_node; + use drift::{ + math::constants::PRICE_PRECISION_U64, + state::user::{Order, OrderType}, + }; + use solana_sdk::pubkey::Pubkey; + + #[test] + fn test_dlob_insert() { + let dlob = DLOB::new(); + let user_account = Pubkey::new_unique(); + let taking_limit_order = Order { + order_id: 1, + slot: 1, + market_index: 0, + market_type: MarketType::Perp, + ..Order::default() + }; + let floating_limit_order = Order { + order_id: 2, + oracle_price_offset: 1, + market_index: 0, + market_type: MarketType::Perp, + ..Order::default() + }; + let resting_limit_order = Order { + order_id: 3, + slot: 3, + market_index: 0, + market_type: MarketType::Perp, + ..Order::default() + }; + let market_order = Order { + order_id: 4, + slot: 4, + market_index: 0, + market_type: MarketType::Perp, + ..Order::default() + }; + let trigger_order = Order { + order_id: 5, + slot: 5, + market_index: 0, + market_type: MarketType::Perp, + ..Order::default() + }; + + let taking_limit_node = + create_node(NodeType::TakingLimit, taking_limit_order, user_account); + let floating_limit_node = + create_node(NodeType::FloatingLimit, floating_limit_order, user_account); + let resting_limit_node = + create_node(NodeType::RestingLimit, resting_limit_order, user_account); + let market_node = create_node(NodeType::Market, market_order, user_account); + let trigger_node = create_node(NodeType::Trigger, trigger_order, user_account); + + dlob.insert_node(&taking_limit_node); + dlob.insert_node(&floating_limit_node); + dlob.insert_node(&resting_limit_node); + dlob.insert_node(&market_node); + dlob.insert_node(&trigger_node); + + assert!(dlob.get_order(1, user_account).is_some()); + assert!(dlob.get_order(2, user_account).is_some()); + assert!(dlob.get_order(3, user_account).is_some()); + assert!(dlob.get_order(4, user_account).is_some()); + assert!(dlob.get_order(5, user_account).is_some()); + } + + #[test] + fn test_dlob_ordering() { + let dlob = DLOB::new(); + + let user_account = Pubkey::new_unique(); + let order_1 = Order { + order_id: 1, + slot: 1, + market_index: 0, + direction: drift::controller::position::PositionDirection::Long, + market_type: MarketType::Perp, + auction_duration: 1, + ..Order::default() + }; + let order_2 = Order { + order_id: 2, + slot: 2, + market_index: 0, + direction: drift::controller::position::PositionDirection::Long, + market_type: MarketType::Perp, + auction_duration: 1, + ..Order::default() + }; + let order_3 = Order { + order_id: 3, + slot: 3, + market_index: 0, + direction: drift::controller::position::PositionDirection::Long, + market_type: MarketType::Perp, + auction_duration: 1, + ..Order::default() + }; + let order_4 = Order { + order_id: 4, + slot: 4, + market_index: 0, + direction: drift::controller::position::PositionDirection::Long, + market_type: MarketType::Perp, + auction_duration: 1, + ..Order::default() + }; + let order_5 = Order { + order_id: 5, + slot: 5, + market_index: 0, + direction: drift::controller::position::PositionDirection::Long, + market_type: MarketType::Perp, + auction_duration: 1, + ..Order::default() + }; + + let node_1 = create_node(NodeType::TakingLimit, order_1, user_account); + let node_2 = create_node(NodeType::TakingLimit, order_2, user_account); + let node_3 = create_node(NodeType::TakingLimit, order_3, user_account); + let node_4 = create_node(NodeType::TakingLimit, order_4, user_account); + let node_5 = create_node(NodeType::TakingLimit, order_5, user_account); + + dlob.insert_node(&node_1); + dlob.insert_node(&node_2); + dlob.insert_node(&node_3); + dlob.insert_node(&node_4); + dlob.insert_node(&node_5); + + assert!(dlob.get_order(1, user_account).is_some()); + assert!(dlob.get_order(2, user_account).is_some()); + assert!(dlob.get_order(3, user_account).is_some()); + assert!(dlob.get_order(4, user_account).is_some()); + assert!(dlob.get_order(5, user_account).is_some()); + + let best_orders = + dlob.get_best_orders(MarketType::Perp, SubType::Bid, NodeType::TakingLimit, 0); + + assert_eq!(best_orders[0].get_order().slot, 1); + assert_eq!(best_orders[1].get_order().slot, 2); + assert_eq!(best_orders[2].get_order().slot, 3); + assert_eq!(best_orders[3].get_order().slot, 4); + assert_eq!(best_orders[4].get_order().slot, 5); + } + + #[test] + fn test_update_resting_limit_orders() { + let mut dlob = DLOB::new(); + + let user_account = Pubkey::new_unique(); + let order_1 = Order { + order_id: 1, + slot: 1, + market_index: 0, + direction: drift::controller::position::PositionDirection::Long, + market_type: MarketType::Perp, + auction_duration: 1, + ..Order::default() + }; + + let node_1 = create_node(NodeType::TakingLimit, order_1, user_account); + + dlob.insert_node(&node_1); + + let markets_for_market_type = dlob.exchange.get("perp").unwrap(); + let market = markets_for_market_type.get(&0).unwrap(); + + assert_eq!(market.taking_limit_orders.bids.len(), 1); + + let slot = 5; + + drop(market); + drop(markets_for_market_type); + + dlob.update_resting_limit_orders(slot); + + let markets_for_market_type = dlob.exchange.get("perp").unwrap(); + let market = markets_for_market_type.get(&0).unwrap(); + + assert_eq!(market.taking_limit_orders.bids.len(), 0); + assert_eq!(market.resting_limit_orders.bids.len(), 1); + } + + #[test] + fn test_get_resting_limit_asks() { + let mut dlob = DLOB::new(); + + let v_ask = 15; + let v_bid = 10; + + let oracle_price_data = OraclePriceData { + price: (v_bid + v_ask) / 2, + confidence: 1, + delay: 0, + has_sufficient_number_of_data_points: true, + }; + + let user_account = Pubkey::new_unique(); + let order_1 = Order { + order_id: 1, + slot: 1, + market_index: 0, + direction: drift::controller::position::PositionDirection::Short, + market_type: MarketType::Perp, + order_type: OrderType::Limit, + auction_duration: 10, + price: 11 * PRICE_PRECISION_U64, + ..Order::default() + }; + + let order_2 = Order { + order_id: 2, + slot: 11, + market_index: 0, + direction: drift::controller::position::PositionDirection::Short, + market_type: MarketType::Perp, + order_type: OrderType::Limit, + auction_duration: 10, + price: 12 * PRICE_PRECISION_U64, + ..Order::default() + }; + + let order_3 = Order { + order_id: 3, + slot: 21, + market_index: 0, + direction: drift::controller::position::PositionDirection::Short, + market_type: MarketType::Perp, + order_type: OrderType::Limit, + auction_duration: 10, + price: 13 * PRICE_PRECISION_U64, + ..Order::default() + }; + + let node_1 = create_node(NodeType::TakingLimit, order_1, user_account); + let node_2 = create_node(NodeType::TakingLimit, order_2, user_account); + let node_3 = create_node(NodeType::TakingLimit, order_3, user_account); + + dlob.insert_node(&node_1); + dlob.insert_node(&node_2); + dlob.insert_node(&node_3); + + let mut slot = 1; + + dbg!("expecting 0"); + let resting_limit_asks = + dlob.get_resting_limit_asks(slot, MarketType::Perp, 0, oracle_price_data); + + assert_eq!(resting_limit_asks.len(), 0); + + slot += 11; + + dbg!("expecting 1"); + let resting_limit_asks = + dlob.get_resting_limit_asks(slot, MarketType::Perp, 0, oracle_price_data); + + assert_eq!(resting_limit_asks.len(), 1); + assert_eq!(resting_limit_asks[0].get_order().order_id, 1); + + slot += 11; + + dbg!("expecting 2"); + let resting_limit_asks = + dlob.get_resting_limit_asks(slot, MarketType::Perp, 0, oracle_price_data); + + assert_eq!(resting_limit_asks.len(), 2); + assert_eq!(resting_limit_asks[0].get_order().order_id, 1); + assert_eq!(resting_limit_asks[1].get_order().order_id, 2); + + slot += 11; + + dbg!("expecting 3"); + let resting_limit_asks = + dlob.get_resting_limit_asks(slot, MarketType::Perp, 0, oracle_price_data); + + assert_eq!(resting_limit_asks.len(), 3); + assert_eq!(resting_limit_asks[0].get_order().order_id, 1); + assert_eq!(resting_limit_asks[1].get_order().order_id, 2); + assert_eq!(resting_limit_asks[2].get_order().order_id, 3); + } + + #[test] + fn test_get_resting_limit_bids() { + let mut dlob = DLOB::new(); + + let v_ask = 15; + let v_bid = 10; + + let oracle_price_data = OraclePriceData { + price: (v_bid + v_ask) / 2, + confidence: 1, + delay: 0, + has_sufficient_number_of_data_points: true, + }; + + let user_account = Pubkey::new_unique(); + let order_1 = Order { + order_id: 1, + slot: 1, + market_index: 0, + direction: drift::controller::position::PositionDirection::Long, + market_type: MarketType::Perp, + order_type: OrderType::Limit, + auction_duration: 10, + price: 11, + ..Order::default() + }; + + let order_2 = Order { + order_id: 2, + slot: 11, + market_index: 0, + direction: drift::controller::position::PositionDirection::Long, + market_type: MarketType::Perp, + order_type: OrderType::Limit, + auction_duration: 10, + price: 12, + ..Order::default() + }; + + let order_3 = Order { + order_id: 3, + slot: 21, + market_index: 0, + direction: drift::controller::position::PositionDirection::Long, + market_type: MarketType::Perp, + order_type: OrderType::Limit, + auction_duration: 10, + price: 13, + ..Order::default() + }; + + let node_1 = create_node(NodeType::TakingLimit, order_1, user_account); + let node_2 = create_node(NodeType::TakingLimit, order_2, user_account); + let node_3 = create_node(NodeType::TakingLimit, order_3, user_account); + + dlob.insert_node(&node_1); + dlob.insert_node(&node_2); + dlob.insert_node(&node_3); + + let mut slot = 1; + + dbg!("expecting 0"); + let resting_limit_bids = + dlob.get_resting_limit_bids(slot, MarketType::Perp, 0, oracle_price_data); + + assert_eq!(resting_limit_bids.len(), 0); + + slot += 11; + + dbg!("expecting 1"); + let resting_limit_bids = + dlob.get_resting_limit_bids(slot, MarketType::Perp, 0, oracle_price_data); + + assert_eq!(resting_limit_bids.len(), 1); + assert_eq!(resting_limit_bids[0].get_order().order_id, 1); + + slot += 11; + + dbg!("expecting 2"); + let resting_limit_bids = + dlob.get_resting_limit_bids(slot, MarketType::Perp, 0, oracle_price_data); + + assert_eq!(resting_limit_bids.len(), 2); + assert_eq!(resting_limit_bids[0].get_order().order_id, 2); + assert_eq!(resting_limit_bids[1].get_order().order_id, 1); + + slot += 11; + + dbg!("expecting 3"); + let resting_limit_bids = + dlob.get_resting_limit_bids(slot, MarketType::Perp, 0, oracle_price_data); + + assert_eq!(resting_limit_bids.len(), 3); + assert_eq!(resting_limit_bids[0].get_order().order_id, 3); + assert_eq!(resting_limit_bids[1].get_order().order_id, 2); + assert_eq!(resting_limit_bids[2].get_order().order_id, 1); + } +} diff --git a/src/dlob/dlob_node.rs b/src/dlob/dlob_node.rs new file mode 100644 index 0000000..a3219d8 --- /dev/null +++ b/src/dlob/dlob_node.rs @@ -0,0 +1,493 @@ +use crate::math::order::get_limit_price; +use drift::state::{oracle::OraclePriceData, user::Order}; +use solana_sdk::pubkey::Pubkey; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum NodeType { + TakingLimit, + RestingLimit, + FloatingLimit, + Market, + Trigger, + VAMM, +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub(crate) enum SortDirection { + Ascending, + Descending, +} + +pub(crate) trait DLOBNode { + fn get_price(&self, oracle_price_data: OraclePriceData, slot: u64) -> u64; + fn is_vamm_node(&self) -> bool; + fn is_base_filled(&self) -> bool; + fn get_sort_value(&self, order: &Order) -> Option; + fn get_order(&self) -> &Order; + fn get_user_account(&self) -> Pubkey; + fn set_order(&mut self, order: Order); + fn get_node_type(&self) -> NodeType; + fn set_node_type(&mut self, node_type: NodeType); +} + +#[derive(Copy, Clone, Debug)] +pub enum Node { + OrderNode(OrderNode), + VAMMNode(VAMMNode), +} + +#[derive(Clone, Copy, Debug)] +pub struct DirectionalNode { + pub node: Node, + sort_direction: SortDirection, +} + +impl DirectionalNode { + pub fn new(node: Node, sort_direction: SortDirection) -> Self { + Self { + node, + sort_direction, + } + } +} + +impl PartialEq for DirectionalNode { + fn eq(&self, other: &Self) -> bool { + self.node.eq(&other.node) + } +} + +impl Eq for DirectionalNode {} + +impl PartialOrd for DirectionalNode { + fn partial_cmp(&self, other: &Self) -> Option { + let mut cmp = self + .node + .get_sort_value(self.node.get_order()) + .partial_cmp(&other.node.get_sort_value(other.node.get_order())) + .unwrap_or(std::cmp::Ordering::Equal); + + if cmp == std::cmp::Ordering::Equal { + cmp = self.node.get_order().slot.cmp(&other.node.get_order().slot); + } + + if self.sort_direction == SortDirection::Ascending { + cmp = cmp.reverse(); + } + + Some(cmp) + } +} + +impl Ord for DirectionalNode { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl PartialEq for Node { + fn eq(&self, other: &Self) -> bool { + self.get_sort_value(self.get_order()) == other.get_sort_value(other.get_order()) + } +} + +impl Eq for Node {} + +impl Node { + pub fn new(node_type: NodeType, order: Order, user_account: Pubkey) -> Self { + match node_type { + NodeType::TakingLimit => { + Node::OrderNode(OrderNode::new(NodeType::TakingLimit, order, user_account)) + } + NodeType::RestingLimit => { + Node::OrderNode(OrderNode::new(NodeType::RestingLimit, order, user_account)) + } + NodeType::FloatingLimit => { + Node::OrderNode(OrderNode::new(NodeType::FloatingLimit, order, user_account)) + } + NodeType::Market => { + Node::OrderNode(OrderNode::new(NodeType::Market, order, user_account)) + } + NodeType::Trigger => { + Node::OrderNode(OrderNode::new(NodeType::Trigger, order, user_account)) + } + NodeType::VAMM => Node::VAMMNode(VAMMNode::new(order, 0)), + } + } +} + +impl DLOBNode for Node { + fn get_price(&self, oracle_price_data: OraclePriceData, slot: u64) -> u64 { + match self { + Node::OrderNode(order_node) => order_node.get_price(oracle_price_data, slot), + Node::VAMMNode(vamm_node) => vamm_node.get_price(oracle_price_data, slot), + } + } + + fn is_vamm_node(&self) -> bool { + match self { + Node::OrderNode(_) => false, + Node::VAMMNode(_) => true, + } + } + + fn is_base_filled(&self) -> bool { + match self { + Node::OrderNode(order_node) => order_node.is_base_filled(), + Node::VAMMNode(vamm_node) => vamm_node.is_base_filled(), + } + } + + fn get_sort_value(&self, order: &Order) -> Option { + match self { + Node::OrderNode(order_node) => order_node.get_sort_value(order), + Node::VAMMNode(vamm_node) => vamm_node.get_sort_value(order), + } + } + + fn get_order(&self) -> &Order { + match self { + Node::OrderNode(order_node) => order_node.get_order(), + Node::VAMMNode(vamm_node) => vamm_node.get_order(), + } + } + + fn get_user_account(&self) -> Pubkey { + match self { + Node::OrderNode(order_node) => order_node.get_user_account(), + Node::VAMMNode(vamm_node) => vamm_node.get_user_account(), + } + } + + fn set_order(&mut self, order: Order) { + match self { + Node::OrderNode(order_node) => order_node.set_order(order), + Node::VAMMNode(vamm_node) => vamm_node.set_order(order), + } + } + + fn get_node_type(&self) -> NodeType { + match self { + Node::OrderNode(order_node) => order_node.get_node_type(), + Node::VAMMNode(_) => NodeType::VAMM, + } + } + + fn set_node_type(&mut self, node_type: NodeType) { + match self { + Node::OrderNode(order_node) => order_node.set_node_type(node_type), + Node::VAMMNode(_) => unimplemented!(), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct OrderNode { + pub order: Order, + pub user_account: Pubkey, + pub node_type: NodeType, +} + +impl OrderNode { + pub fn new(node_type: NodeType, order: Order, user_account: Pubkey) -> Self { + Self { + order, + user_account, + node_type, + } + } +} + +impl DLOBNode for OrderNode { + fn get_price(&self, oracle_price_data: OraclePriceData, slot: u64) -> u64 { + get_limit_price(&self.order, &oracle_price_data, slot, None) + } + + fn is_vamm_node(&self) -> bool { + false + } + + fn is_base_filled(&self) -> bool { + self.order.base_asset_amount_filled == self.order.base_asset_amount + } + + fn get_sort_value(&self, order: &Order) -> Option { + match self.node_type { + NodeType::TakingLimit => Some(order.slot.into()), + NodeType::RestingLimit => Some(order.price.into()), + NodeType::FloatingLimit => Some(order.oracle_price_offset.into()), + NodeType::Market => Some(order.slot.into()), + NodeType::Trigger => Some(order.trigger_price.into()), + NodeType::VAMM => None, + } + } + + fn get_order(&self) -> &Order { + &self.order + } + + fn get_user_account(&self) -> Pubkey { + self.user_account + } + + fn set_order(&mut self, order: Order) { + self.order = order; + } + + fn get_node_type(&self) -> NodeType { + self.node_type + } + + fn set_node_type(&mut self, node_type: NodeType) { + self.node_type = node_type; + } +} + +#[derive(Copy, Clone, Debug)] +pub struct VAMMNode { + pub order: Order, + pub price: u64, +} + +impl VAMMNode { + pub fn new(order: Order, price: u64) -> Self { + Self { order, price } + } +} + +impl DLOBNode for VAMMNode { + fn get_price(&self, _oracle_price_data: OraclePriceData, _slot: u64) -> u64 { + self.price + } + + fn is_vamm_node(&self) -> bool { + true + } + + fn is_base_filled(&self) -> bool { + false + } + + fn get_sort_value(&self, _order: &Order) -> Option { + None + } + + fn get_order(&self) -> &Order { + &self.order + } + + fn get_user_account(&self) -> Pubkey { + unimplemented!() + } + + fn set_order(&mut self, _order: Order) { + unimplemented!() + } + + fn get_node_type(&self) -> NodeType { + NodeType::VAMM + } + + fn set_node_type(&mut self, _node_type: NodeType) { + unimplemented!() + } +} + +pub(crate) fn create_node(node_type: NodeType, order: Order, user_account: Pubkey) -> Node { + Node::new(node_type, order, user_account) +} + +pub(crate) fn get_order_signature(order_id: u32, user_account: Pubkey) -> String { + format!("{}-{}", order_id, user_account.to_string()) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_set_next_prev() { + let mut order = Order::default(); + + order.slot = 100; + order.price = 1_000; + order.trigger_price = 500; + order.oracle_price_offset = 5_000; + + let user_account = Pubkey::new_unique(); + + let taking_limit_order_node = create_node(NodeType::TakingLimit, order, user_account); + let resting_limit_order_node = create_node(NodeType::RestingLimit, order, user_account); + let floating_limit_order_node = create_node(NodeType::FloatingLimit, order, user_account); + let market_order_node = create_node(NodeType::Market, order, user_account); + let trigger_order_node = create_node(NodeType::Trigger, order, user_account); + + assert_eq!(taking_limit_order_node.get_sort_value(&order), Some(100)); + assert_eq!(resting_limit_order_node.get_sort_value(&order), Some(1_000)); + assert_eq!(market_order_node.get_sort_value(&order), Some(100)); + assert_eq!(trigger_order_node.get_sort_value(&order), Some(500)); + assert_eq!( + floating_limit_order_node.get_sort_value(&order), + Some(5_000) + ); + + let mut order_2 = Order::default(); + + order_2.slot = 200; + order_2.price = 2_000; + order_2.trigger_price = 600; + order_2.oracle_price_offset = 6_000; + + let taking_limit_order_node_2 = create_node(NodeType::TakingLimit, order_2, user_account); + let resting_limit_order_node_2 = create_node(NodeType::RestingLimit, order_2, user_account); + let floating_limit_order_node_2 = + create_node(NodeType::FloatingLimit, order_2, user_account); + let market_order_node_2 = create_node(NodeType::Market, order_2, user_account); + let trigger_order_node_2 = create_node(NodeType::Trigger, order_2, user_account); + + assert_eq!( + taking_limit_order_node_2.get_sort_value(&order_2), + Some(200) + ); + assert_eq!( + resting_limit_order_node_2.get_sort_value(&order_2), + Some(2_000) + ); + assert_eq!(market_order_node_2.get_sort_value(&order_2), Some(200)); + assert_eq!(trigger_order_node_2.get_sort_value(&order_2), Some(600)); + assert_eq!( + floating_limit_order_node_2.get_sort_value(&order_2), + Some(6_000) + ); + + let mut order_3 = Order::default(); + + order_3.slot = 300; + order_3.price = 3_000; + order_3.trigger_price = 700; + order_3.oracle_price_offset = 7_000; + + let taking_limit_order_node_3 = create_node(NodeType::TakingLimit, order_3, user_account); + let resting_limit_order_node_3 = create_node(NodeType::RestingLimit, order_3, user_account); + let floating_limit_order_node_3 = + create_node(NodeType::FloatingLimit, order_3, user_account); + let market_order_node_3 = create_node(NodeType::Market, order_3, user_account); + let trigger_order_node_3 = create_node(NodeType::Trigger, order_3, user_account); + + assert_eq!( + taking_limit_order_node_3.get_sort_value(&order_3), + Some(300) + ); + assert_eq!( + resting_limit_order_node_3.get_sort_value(&order_3), + Some(3_000) + ); + assert_eq!(market_order_node_3.get_sort_value(&order_3), Some(300)); + assert_eq!(trigger_order_node_3.get_sort_value(&order_3), Some(700)); + assert_eq!( + floating_limit_order_node_3.get_sort_value(&order_3), + Some(7_000) + ); + } + + #[test] + fn test_set_order() { + let user_account = Pubkey::new_unique(); + + let mut order = Order::default(); + + order.slot = 100; + order.price = 1_000; + order.trigger_price = 500; + order.oracle_price_offset = 5_000; + + let mut taking_limit_order_node = create_node(NodeType::TakingLimit, order, user_account); + let mut resting_limit_order_node = create_node(NodeType::RestingLimit, order, user_account); + let mut floating_limit_order_node = + create_node(NodeType::FloatingLimit, order, user_account); + let mut market_order_node = create_node(NodeType::Market, order, user_account); + let mut trigger_order_node = create_node(NodeType::Trigger, order, user_account); + + let mut order_2 = Order::default(); + + order_2.slot = 200; + order_2.price = 2_000; + order_2.trigger_price = 600; + order_2.oracle_price_offset = 6_000; + + taking_limit_order_node.set_order(order_2); + resting_limit_order_node.set_order(order_2); + floating_limit_order_node.set_order(order_2); + market_order_node.set_order(order_2); + trigger_order_node.set_order(order_2); + + assert_eq!(taking_limit_order_node.get_order().slot, 200); + assert_eq!(resting_limit_order_node.get_order().price, 2_000); + assert_eq!( + floating_limit_order_node.get_order().oracle_price_offset, + 6_000 + ); + assert_eq!(market_order_node.get_order().slot, 200); + assert_eq!(trigger_order_node.get_order().trigger_price, 600); + } + + #[test] + fn test_eq() { + let user_account = Pubkey::new_unique(); + + let mut order = Order::default(); + + order.slot = 100; + order.price = 1_000; + order.trigger_price = 500; + order.oracle_price_offset = 5_000; + + let taking_limit_order_node = create_node(NodeType::TakingLimit, order, user_account); + let resting_limit_order_node = create_node(NodeType::RestingLimit, order, user_account); + let floating_limit_order_node = create_node(NodeType::FloatingLimit, order, user_account); + let market_order_node = create_node(NodeType::Market, order, user_account); + let trigger_order_node = create_node(NodeType::Trigger, order, user_account); + + let mut order_2 = Order::default(); + + order_2.slot = 200; + order_2.price = 2_000; + order_2.trigger_price = 600; + order_2.oracle_price_offset = 6_000; + + let taking_limit_order_node_2 = create_node(NodeType::TakingLimit, order_2, user_account); + let resting_limit_order_node_2 = create_node(NodeType::RestingLimit, order_2, user_account); + let floating_limit_order_node_2 = + create_node(NodeType::FloatingLimit, order_2, user_account); + let market_order_node_2 = create_node(NodeType::Market, order_2, user_account); + let trigger_order_node_2 = create_node(NodeType::Trigger, order_2, user_account); + + assert_eq!(taking_limit_order_node, taking_limit_order_node); + assert_eq!(resting_limit_order_node, resting_limit_order_node); + assert_eq!(floating_limit_order_node, floating_limit_order_node); + assert_eq!(market_order_node, market_order_node); + assert_eq!(trigger_order_node, trigger_order_node); + + assert_ne!(taking_limit_order_node, taking_limit_order_node_2); + assert_ne!(resting_limit_order_node, resting_limit_order_node_2); + assert_ne!(floating_limit_order_node, floating_limit_order_node_2); + assert_ne!(market_order_node, market_order_node_2); + assert_ne!(trigger_order_node, trigger_order_node_2); + } + + #[test] + #[should_panic(expected = "not implemented")] + fn test_vamm_node_get_user_account_panics() { + let order = Order::default(); + let vamm_node = VAMMNode::new(order, 100); + vamm_node.get_user_account(); + } + + #[test] + #[should_panic(expected = "not implemented")] + fn test_vamm_node_set_order_panics() { + let order = Order::default(); + let mut vamm_node = VAMMNode::new(order, 100); + vamm_node.set_order(order); + } +} diff --git a/src/dlob/market.rs b/src/dlob/market.rs new file mode 100644 index 0000000..7dcc73e --- /dev/null +++ b/src/dlob/market.rs @@ -0,0 +1,142 @@ +use dashmap::{DashMap, DashSet}; +use drift::controller::position::PositionDirection; +use drift::state::user::{Order, OrderTriggerCondition, OrderType}; + +use crate::dlob::dlob_node::{Node, NodeType, SortDirection}; +use crate::dlob::order_list::Orderlist; +use crate::is_one_of_variant; +use crate::math::order::is_resting_limit_order; + +#[derive(Clone)] +pub(crate) struct Market { + pub resting_limit_orders: Orderlist, + pub floating_limit_orders: Orderlist, + pub taking_limit_orders: Orderlist, + pub market_orders: Orderlist, + pub trigger_orders: Orderlist, +} + +#[derive(Copy, Clone)] +pub enum SubType { + Bid, + Ask, + Above, + Below, +} + +impl Market { + pub(crate) fn new() -> Market { + Market { + resting_limit_orders: Orderlist::new( + SortDirection::Descending, + SortDirection::Ascending, + ), + floating_limit_orders: Orderlist::new( + SortDirection::Descending, + SortDirection::Ascending, + ), + taking_limit_orders: Orderlist::new(SortDirection::Ascending, SortDirection::Ascending), + market_orders: Orderlist::new(SortDirection::Ascending, SortDirection::Ascending), + trigger_orders: Orderlist::new(SortDirection::Ascending, SortDirection::Descending), + } + } + + pub(crate) fn get_list_for_order( + &mut self, + order: &Order, + slot: u64, + ) -> (Option<&mut Orderlist>, SubType) { + let is_inactive_trigger_order = order.must_be_triggered() && !order.triggered(); + + let node_type = if is_inactive_trigger_order { + NodeType::Trigger + } else if is_one_of_variant( + &order.order_type, + &[ + OrderType::Market, + OrderType::TriggerMarket, + OrderType::Oracle, + ], + ) { + NodeType::Market + } else if order.oracle_price_offset != 0 { + NodeType::FloatingLimit + } else { + if is_resting_limit_order(order, slot) { + NodeType::RestingLimit + } else { + NodeType::TakingLimit + } + }; + + let order_list = match node_type { + NodeType::RestingLimit => &mut self.resting_limit_orders, + NodeType::FloatingLimit => &mut self.floating_limit_orders, + NodeType::TakingLimit => &mut self.taking_limit_orders, + NodeType::Market => &mut self.market_orders, + NodeType::Trigger => &mut self.trigger_orders, + NodeType::VAMM => return (None, SubType::Bid), + }; + + let sub_type = if is_inactive_trigger_order { + if order.trigger_condition == OrderTriggerCondition::Above { + SubType::Bid + } else { + SubType::Ask + } + } else { + match order.direction { + PositionDirection::Long => SubType::Bid, + PositionDirection::Short => SubType::Ask, + } + }; + + (Some(order_list), sub_type) + } + + pub(crate) fn get_best_order( + &self, + order_list: &mut Orderlist, + sub_type: SubType, + ) -> Option { + match sub_type { + SubType::Bid => order_list.get_best_bid(), + SubType::Ask => order_list.get_best_ask(), + _ => unimplemented!(), + }; + + None + } + + pub(crate) fn get_order_list_for_node_type(&self, node_type: NodeType) -> Orderlist { + match node_type { + NodeType::RestingLimit => &self.resting_limit_orders, + NodeType::FloatingLimit => &self.floating_limit_orders, + NodeType::TakingLimit => &self.taking_limit_orders, + NodeType::Market => &self.market_orders, + NodeType::Trigger => &self.trigger_orders, + NodeType::VAMM => panic!("VAMM order list not found"), + } + .clone() + } +} + +pub(crate) type Exchange = DashMap>; + +pub fn get_order_lists(exchange: &Exchange) -> Vec { + let mut order_lists = vec![]; + + for market_type_ref in exchange.iter() { + for market_ref in market_type_ref.iter() { + order_lists.push(market_ref.value().resting_limit_orders.clone()); + order_lists.push(market_ref.value().floating_limit_orders.clone()); + order_lists.push(market_ref.value().taking_limit_orders.clone()); + order_lists.push(market_ref.value().market_orders.clone()); + order_lists.push(market_ref.value().trigger_orders.clone()); + } + } + + order_lists +} + +pub(crate) type OpenOrders = DashMap>; diff --git a/src/dlob/mod.rs b/src/dlob/mod.rs new file mode 100644 index 0000000..3f27555 --- /dev/null +++ b/src/dlob/mod.rs @@ -0,0 +1,4 @@ +pub mod dlob; +mod dlob_node; +mod market; +mod order_list; diff --git a/src/dlob/order_list.rs b/src/dlob/order_list.rs new file mode 100644 index 0000000..921af38 --- /dev/null +++ b/src/dlob/order_list.rs @@ -0,0 +1,175 @@ +use std::collections::BinaryHeap; + +use dashmap::DashMap; + +use crate::dlob::dlob_node::{get_order_signature, DLOBNode, DirectionalNode, Node, SortDirection}; + +#[derive(Clone, Debug)] +pub struct Orderlist { + pub bids: BinaryHeap, + pub asks: BinaryHeap, + pub order_sigs: DashMap, + bid_sort_direction: SortDirection, + ask_sort_direction: SortDirection, +} + +impl Orderlist { + pub fn new(bid_sort_direction: SortDirection, ask_sort_direction: SortDirection) -> Self { + Orderlist { + bids: BinaryHeap::new(), + asks: BinaryHeap::new(), + order_sigs: DashMap::new(), + bid_sort_direction, + ask_sort_direction, + } + } + + pub fn insert_bid(&mut self, node: Node) { + let order_sig = get_order_signature(node.get_order().order_id, node.get_user_account()); + self.order_sigs.insert(order_sig.clone(), node.clone()); + let directional = DirectionalNode::new(node, self.bid_sort_direction); + self.bids.push(directional); + } + + pub fn insert_ask(&mut self, node: Node) { + let order_sig = get_order_signature(node.get_order().order_id, node.get_user_account()); + self.order_sigs.insert(order_sig.clone(), node.clone()); + let directional = DirectionalNode::new(node, self.ask_sort_direction); + self.asks.push(directional); + } + + pub fn get_best_bid(&mut self) -> Option { + if let Some(node) = self.bids.pop().map(|node| node.node.clone()) { + let order_sig = get_order_signature(node.get_order().order_id, node.get_user_account()); + if self.order_sigs.contains_key(&order_sig) { + self.order_sigs.remove(&order_sig); + return Some(node); + } + } + None + } + + pub fn get_best_ask(&mut self) -> Option { + if let Some(node) = self.asks.pop().map(|node| node.node.clone()) { + let order_sig = get_order_signature(node.get_order().order_id, node.get_user_account()); + if self.order_sigs.contains_key(&order_sig) { + self.order_sigs.remove(&order_sig); + return Some(node); + } + } + None + } + + pub fn get_node(&self, order_sig: &String) -> Option { + self.order_sigs.get(order_sig).map(|node| node.clone()) + } + + pub fn bids_empty(&self) -> bool { + self.bids.is_empty() + } + + pub fn asks_empty(&self) -> bool { + self.asks.is_empty() + } +} + +#[cfg(test)] +mod tests { + use crate::dlob::dlob_node::{create_node, NodeType}; + + use super::*; + use drift::state::user::Order; + use solana_sdk::pubkey::Pubkey; + + #[test] + fn test_insertion_and_ordering() { + let mut orderlist = Orderlist::new(SortDirection::Ascending, SortDirection::Ascending); + let user_account = Pubkey::new_unique(); + let order_1 = Order { + order_id: 1, + slot: 1, + ..Order::default() + }; + let order_2 = Order { + order_id: 2, + slot: 2, + ..Order::default() + }; + let order_3 = Order { + order_id: 3, + slot: 3, + ..Order::default() + }; + let order_4 = Order { + order_id: 4, + slot: 4, + ..Order::default() + }; + let order_5 = Order { + order_id: 5, + slot: 5, + ..Order::default() + }; + let order_6 = Order { + order_id: 6, + slot: 1, + ..Order::default() + }; + let order_7 = Order { + order_id: 7, + slot: 2, + ..Order::default() + }; + let order_8 = Order { + order_id: 8, + slot: 3, + ..Order::default() + }; + let order_9 = Order { + order_id: 9, + slot: 4, + ..Order::default() + }; + let order_10 = Order { + order_id: 10, + slot: 5, + ..Order::default() + }; + + let node_1 = create_node(NodeType::TakingLimit, order_1, user_account); + let node_2 = create_node(NodeType::TakingLimit, order_2, user_account); + let node_3 = create_node(NodeType::TakingLimit, order_3, user_account); + let node_4 = create_node(NodeType::TakingLimit, order_4, user_account); + let node_5 = create_node(NodeType::TakingLimit, order_5, user_account); + + let node_6 = create_node(NodeType::TakingLimit, order_6, user_account); + let node_7 = create_node(NodeType::TakingLimit, order_7, user_account); + let node_8 = create_node(NodeType::TakingLimit, order_8, user_account); + let node_9 = create_node(NodeType::TakingLimit, order_9, user_account); + let node_10 = create_node(NodeType::TakingLimit, order_10, user_account); + + orderlist.insert_bid(node_1); + orderlist.insert_bid(node_2); + orderlist.insert_bid(node_3); + orderlist.insert_bid(node_4); + orderlist.insert_bid(node_5); + + orderlist.insert_ask(node_6); + orderlist.insert_ask(node_7); + orderlist.insert_ask(node_8); + orderlist.insert_ask(node_9); + orderlist.insert_ask(node_10); + + assert_eq!(orderlist.get_best_bid().unwrap().get_order().slot, 1); + assert_eq!(orderlist.get_best_bid().unwrap().get_order().slot, 2); + assert_eq!(orderlist.get_best_bid().unwrap().get_order().slot, 3); + assert_eq!(orderlist.get_best_bid().unwrap().get_order().slot, 4); + assert_eq!(orderlist.get_best_bid().unwrap().get_order().slot, 5); + + assert_eq!(orderlist.get_best_ask().unwrap().get_order().slot, 1); + assert_eq!(orderlist.get_best_ask().unwrap().get_order().slot, 2); + assert_eq!(orderlist.get_best_ask().unwrap().get_order().slot, 3); + assert_eq!(orderlist.get_best_ask().unwrap().get_order().slot, 4); + assert_eq!(orderlist.get_best_ask().unwrap().get_order().slot, 5); + } +} diff --git a/src/dlob.rs b/src/dlob_client.rs similarity index 100% rename from src/dlob.rs rename to src/dlob_client.rs diff --git a/src/lib.rs b/src/lib.rs index 88afa48..7d37a59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,10 +67,12 @@ pub mod websocket_program_account_subscriber; // subscribers pub mod auction_subscriber; -pub mod dlob; +pub mod dlob_client; pub mod event_subscriber; pub mod slot_subscriber; +pub mod dlob; +pub mod math; pub mod usermap; use types::*; diff --git a/src/math/auction.rs b/src/math/auction.rs new file mode 100644 index 0000000..854f768 --- /dev/null +++ b/src/math/auction.rs @@ -0,0 +1,93 @@ +use std::cmp::min; + +use drift::{ + controller::position::PositionDirection, + state::user::{Order, OrderType}, +}; + +use crate::is_one_of_variant; + +pub fn is_auction_complete(order: &Order, slot: u64) -> bool { + if order.auction_duration == 0 { + return true; + } + + (order.slot + order.auction_duration as u64) < slot +} + +#[track_caller] +pub fn get_auction_price(order: &Order, slot: u64, price: i64) -> i128 { + if is_one_of_variant( + &order.order_type, + &[ + OrderType::Market, + OrderType::TriggerMarket, + OrderType::Limit, + OrderType::TriggerLimit, + ], + ) { + get_auction_price_for_fixed_auction(order, slot) + } else if order.order_type == OrderType::Oracle { + get_auction_price_for_oracle_offset_auction(order, slot, price) + } else { + panic!("Invalid order type") + } +} + +fn get_auction_price_for_fixed_auction(order: &Order, slot: u64) -> i128 { + let slots_elapsed = slot - order.slot; + + let auction_start_price = order.auction_start_price as i128; + let auction_end_price = order.auction_end_price as i128; + let delta_denominator: i128 = order.auction_duration.into(); + let delta_numerator: i128 = min(slots_elapsed, order.auction_duration as u64).into(); + + if delta_denominator == 0 { + return auction_start_price; + } + + match order.direction { + PositionDirection::Long => { + let price_delta = + auction_end_price - auction_start_price * delta_numerator / delta_denominator; + auction_start_price + price_delta + } + PositionDirection::Short => { + let price_delta = + auction_start_price - auction_end_price * delta_numerator / delta_denominator; + auction_start_price - price_delta + } + } +} + +fn get_auction_price_for_oracle_offset_auction( + order: &Order, + slot: u64, + oracle_price: i64, +) -> i128 { + let slots_elapsed = slot - order.slot; + + let auction_start_price = order.auction_start_price as i128; + let auction_end_price = order.auction_end_price as i128; + let delta_denominator: i128 = order.auction_duration.into(); + let delta_numerator: i128 = min(slots_elapsed, order.auction_duration as u64).into(); + + if delta_denominator == 0 { + return auction_start_price; + } + + let price_offset = match order.direction { + PositionDirection::Long => { + let price_delta = + auction_end_price - auction_start_price * delta_numerator / delta_denominator; + auction_start_price + price_delta + } + PositionDirection::Short => { + let price_delta = + auction_start_price - auction_end_price * delta_numerator / delta_denominator; + auction_start_price - price_delta + } + }; + + oracle_price as i128 + price_offset +} diff --git a/src/math/mod.rs b/src/math/mod.rs new file mode 100644 index 0000000..827fbfd --- /dev/null +++ b/src/math/mod.rs @@ -0,0 +1,2 @@ +pub mod auction; +pub mod order; diff --git a/src/math/order.rs b/src/math/order.rs new file mode 100644 index 0000000..ee882d6 --- /dev/null +++ b/src/math/order.rs @@ -0,0 +1,58 @@ +use drift::{ + controller::position::PositionDirection, + state::{ + oracle::OraclePriceData, + user::{Order, OrderType}, + }, +}; + +use crate::math::auction::{get_auction_price, is_auction_complete}; + +pub fn get_limit_price( + order: &Order, + oracle_price_data: &OraclePriceData, + slot: u64, + fallback_price: Option, +) -> u64 { + if has_auction_price(order, slot) { + get_auction_price(order, slot, oracle_price_data.price) + .try_into() + .unwrap() + } else if order.oracle_price_offset != 0 { + (oracle_price_data.price as i128 + order.oracle_price_offset as i128) + .try_into() + .unwrap() + } else if order.price == 0 { + match fallback_price { + Some(price) => price, + None => panic!("Order price is 0 and no fallback price was provided"), + } + } else { + order.price + } +} + +fn has_auction_price(order: &Order, slot: u64) -> bool { + !is_auction_complete(order, slot) + && (order.auction_start_price != 0 || order.auction_end_price != 0) +} + +pub fn is_resting_limit_order(order: &Order, slot: u64) -> bool { + if !order.is_limit_order() { + return false; + } + + if order.order_type == OrderType::TriggerLimit { + return match order.direction { + PositionDirection::Long if order.trigger_price < order.price => { + return false; + } + PositionDirection::Short if order.trigger_price > order.price => { + return false; + } + _ => is_auction_complete(order, slot), + }; + }; + + order.post_only || is_auction_complete(order, slot) +} diff --git a/src/types.rs b/src/types.rs index 6e6ca42..45f5783 100644 --- a/src/types.rs +++ b/src/types.rs @@ -28,6 +28,10 @@ use crate::Wallet; pub type SdkResult = Result; +pub fn is_one_of_variant(value: &T, variants: &[T]) -> bool { + variants.iter().any(|variant| value == variant) +} + /// Drift program context #[derive(Debug, Copy, Clone)] #[repr(u8)] @@ -234,6 +238,8 @@ pub enum SdkError { CouldntJoin(#[from] tokio::task::JoinError), #[error("Couldn't send unsubscribe message: {0}")] CouldntUnsubscribe(#[from] tokio::sync::mpsc::error::SendError<()>), + #[error("MathError")] + MathError(String), } impl SdkError { diff --git a/src/utils.rs b/src/utils.rs index 2b8c82e..e5de521 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,7 @@ //! SDK utility functions use anchor_lang::AccountDeserialize; +use drift::state::user::MarketType; use serde_json::json; use solana_account_decoder::UiAccountData; use solana_sdk::{ @@ -127,6 +128,13 @@ where T::try_deserialize(&mut decoded_data_slice).map_err(|err| SdkError::Anchor(Box::new(err))) } +pub(crate) fn market_type_to_string(market_type: &MarketType) -> String { + match market_type { + MarketType::Perp => "perp".to_string(), + MarketType::Spot => "spot".to_string(), + } +} + #[cfg(any(test, test_utils))] pub mod envs { //! test env vars