2023-04-13 10:50:39 -07:00
use crate ::logs ::FilledPerpOrderLog ;
2023-02-25 11:34:16 -08:00
use crate ::state ::MangoAccountRefMut ;
2022-03-21 12:29:28 -07:00
use crate ::{
2022-09-06 05:23:39 -07:00
error ::* ,
2023-01-20 04:47:01 -08:00
state ::{ orderbook ::bookside ::* , EventQueue , PerpMarket } ,
2022-03-21 12:29:28 -07:00
} ;
use anchor_lang ::prelude ::* ;
2022-03-24 11:20:56 -07:00
use bytemuck ::cast ;
2022-03-21 12:29:28 -07:00
use fixed ::types ::I80F48 ;
2022-12-07 12:03:28 -08:00
use std ::cell ::RefMut ;
2022-03-21 12:29:28 -07:00
2022-11-08 06:27:56 -08:00
use super ::* ;
2022-03-21 12:29:28 -07:00
/// Drop at most this many expired orders from a BookSide when trying to match orders.
/// This exists as a guard against excessive compute use.
const DROP_EXPIRED_ORDER_LIMIT : usize = 5 ;
2022-12-07 12:03:28 -08:00
pub struct Orderbook < ' a > {
pub bids : RefMut < ' a , BookSide > ,
pub asks : RefMut < ' a , BookSide > ,
2022-04-01 01:36:04 -07:00
}
2022-11-08 06:27:56 -08:00
2022-12-07 12:03:28 -08:00
impl < ' a > Orderbook < ' a > {
2022-11-08 06:27:56 -08:00
pub fn init ( & mut self ) {
2022-12-07 11:54:18 -08:00
self . bids . nodes . order_tree_type = OrderTreeType ::Bids . into ( ) ;
self . asks . nodes . order_tree_type = OrderTreeType ::Asks . into ( ) ;
2022-03-21 12:29:28 -07:00
}
2022-11-08 06:27:56 -08:00
pub fn bookside_mut ( & mut self , side : Side ) -> & mut BookSide {
2022-04-01 01:36:04 -07:00
match side {
Side ::Bid = > & mut self . bids ,
Side ::Ask = > & mut self . asks ,
}
}
2022-11-08 06:27:56 -08:00
pub fn bookside ( & self , side : Side ) -> & BookSide {
2022-04-01 01:36:04 -07:00
match side {
2022-11-08 06:27:56 -08:00
Side ::Bid = > & self . bids ,
Side ::Ask = > & self . asks ,
2022-04-01 01:36:04 -07:00
}
}
2022-03-26 09:06:55 -07:00
#[ allow(clippy::too_many_arguments) ]
pub fn new_order (
& mut self ,
2022-11-08 06:27:56 -08:00
order : Order ,
2022-03-26 09:06:55 -07:00
perp_market : & mut PerpMarket ,
event_queue : & mut EventQueue ,
oracle_price : I80F48 ,
2022-07-25 07:07:53 -07:00
mango_account : & mut MangoAccountRefMut ,
2022-03-26 09:06:55 -07:00
mango_account_pk : & Pubkey ,
now_ts : u64 ,
2022-04-01 01:36:04 -07:00
mut limit : u8 ,
2023-01-25 00:03:35 -08:00
) -> std ::result ::Result < Option < u128 > , Error > {
2022-11-08 06:27:56 -08:00
let side = order . side ;
2022-04-01 01:36:04 -07:00
let other_side = side . invert_side ( ) ;
let market = perp_market ;
2022-11-08 06:27:56 -08:00
let oracle_price_lots = market . native_price_to_lot ( oracle_price ) ;
let post_only = order . is_post_only ( ) ;
let mut post_target = order . post_target ( ) ;
let ( price_lots , price_data ) = order . price ( now_ts , oracle_price_lots , self ) ? ;
2022-03-21 12:29:28 -07:00
// generate new order id
2022-11-08 06:27:56 -08:00
let order_id = market . gen_order_id ( side , price_data ) ;
2022-03-21 12:29:28 -07:00
2023-01-20 05:05:07 -08:00
// IOC orders have a fee penalty applied regardless of match
if order . needs_penalty_fee ( ) {
2023-02-25 11:34:16 -08:00
apply_penalty ( market , mango_account ) ? ;
2023-01-20 05:05:07 -08:00
}
2023-02-25 11:34:16 -08:00
let perp_position = mango_account . perp_position_mut ( market . perp_market_index ) ? ;
2022-04-01 01:36:04 -07:00
// Iterate through book and match against this new order.
2022-03-21 12:29:28 -07:00
//
2022-04-01 01:36:04 -07:00
// Any changes to matching orders on the other side of the book are collected in
// matched_changes/matched_deletes and then applied after this loop.
2022-11-08 06:27:56 -08:00
let mut remaining_base_lots = order . max_base_lots ;
let mut remaining_quote_lots = order . max_quote_lots ;
let mut matched_order_changes : Vec < ( BookSideOrderHandle , i64 ) > = vec! [ ] ;
let mut matched_order_deletes : Vec < ( BookSideOrderTree , u128 ) > = vec! [ ] ;
2022-03-21 12:29:28 -07:00
let mut number_of_dropped_expired_orders = 0 ;
2022-11-08 06:27:56 -08:00
let opposing_bookside = self . bookside_mut ( other_side ) ;
for best_opposing in opposing_bookside . iter_all_including_invalid ( now_ts , oracle_price_lots )
{
2023-01-19 02:29:00 -08:00
if remaining_base_lots = = 0 | | remaining_quote_lots = = 0 {
break ;
}
2022-12-07 11:54:18 -08:00
if ! best_opposing . is_valid ( ) {
2022-03-21 12:29:28 -07:00
// 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 ;
2022-03-24 11:20:56 -07:00
let event = OutEvent ::new (
2022-04-01 01:36:04 -07:00
other_side ,
2022-11-08 06:27:56 -08:00
best_opposing . node . owner_slot ,
2022-03-24 11:20:56 -07:00
now_ts ,
event_queue . header . seq_num ,
2022-11-08 06:27:56 -08:00
best_opposing . node . owner ,
best_opposing . node . quantity ,
2022-03-24 11:20:56 -07:00
) ;
event_queue . push_back ( cast ( event ) ) . unwrap ( ) ;
2022-11-08 06:27:56 -08:00
matched_order_deletes
. push ( ( best_opposing . handle . order_tree , best_opposing . node . key ) ) ;
2022-03-21 12:29:28 -07:00
}
continue ;
}
2022-11-08 06:27:56 -08:00
let best_opposing_price = best_opposing . price_lots ;
2022-03-21 12:29:28 -07:00
2022-04-01 06:47:12 -07:00
if ! side . is_price_within_limit ( best_opposing_price , price_lots ) {
2022-03-21 12:29:28 -07:00
break ;
} else if post_only {
msg! ( " Order could not be placed due to PostOnly " ) ;
2022-11-08 06:27:56 -08:00
post_target = None ;
2022-03-21 12:29:28 -07:00
break ; // return silently to not fail other instructions in tx
} else if limit = = 0 {
msg! ( " Order matching limit reached " ) ;
2022-11-08 06:27:56 -08:00
post_target = None ;
2022-03-21 12:29:28 -07:00
break ;
}
2022-04-01 06:47:12 -07:00
let max_match_by_quote = remaining_quote_lots / best_opposing_price ;
let match_base_lots = remaining_base_lots
2022-11-08 06:27:56 -08:00
. min ( best_opposing . node . quantity )
2022-03-21 12:29:28 -07:00
. min ( max_match_by_quote ) ;
2023-02-24 02:56:33 -08:00
let match_quote_lots = match_base_lots * best_opposing_price ;
remaining_base_lots - = match_base_lots ;
remaining_quote_lots - = match_quote_lots ;
2023-01-20 05:05:07 -08:00
assert! ( remaining_quote_lots > = 0 ) ;
2022-03-21 12:29:28 -07:00
2023-02-24 02:56:33 -08:00
let new_best_opposing_quantity = best_opposing . node . quantity - match_base_lots ;
2022-04-01 01:36:04 -07:00
let maker_out = new_best_opposing_quantity = = 0 ;
2022-03-21 12:29:28 -07:00
if maker_out {
2022-11-08 06:27:56 -08:00
matched_order_deletes
. push ( ( best_opposing . handle . order_tree , best_opposing . node . key ) ) ;
2022-03-21 12:29:28 -07:00
} else {
2022-11-08 06:27:56 -08:00
matched_order_changes . push ( ( best_opposing . handle , new_best_opposing_quantity ) ) ;
2022-03-21 12:29:28 -07:00
}
2022-03-24 11:20:56 -07:00
let fill = FillEvent ::new (
2022-04-01 01:36:04 -07:00
side ,
2022-03-24 11:20:56 -07:00
maker_out ,
2022-11-08 06:27:56 -08:00
best_opposing . node . owner_slot ,
2022-03-24 11:20:56 -07:00
now_ts ,
event_queue . header . seq_num ,
2022-11-08 06:27:56 -08:00
best_opposing . node . owner ,
2023-02-02 00:15:06 -08:00
best_opposing . node . client_order_id ,
2022-03-24 11:20:56 -07:00
market . maker_fee ,
2022-11-08 06:27:56 -08:00
best_opposing . node . timestamp ,
2022-03-24 11:20:56 -07:00
* mango_account_pk ,
2022-11-08 06:27:56 -08:00
order . client_order_id ,
2022-03-25 01:46:38 -07:00
market . taker_fee ,
2022-04-01 01:36:04 -07:00
best_opposing_price ,
2022-04-01 06:47:12 -07:00
match_base_lots ,
2022-03-24 11:20:56 -07:00
) ;
event_queue . push_back ( cast ( fill ) ) . unwrap ( ) ;
2022-03-21 12:29:28 -07:00
limit - = 1 ;
2023-04-13 10:50:39 -07:00
emit! ( FilledPerpOrderLog {
mango_group : market . group . key ( ) ,
perp_market_index : market . perp_market_index ,
seq_num : event_queue . header . seq_num ,
} ) ;
2022-03-21 12:29:28 -07:00
}
2023-02-24 02:56:33 -08:00
let total_quote_lots_taken = order . max_quote_lots - remaining_quote_lots ;
let total_base_lots_taken = order . max_base_lots - remaining_base_lots ;
2023-01-20 05:05:07 -08:00
assert! ( total_quote_lots_taken > = 0 ) ;
assert! ( total_base_lots_taken > = 0 ) ;
// Record the taker trade in the account already, even though it will only be
// realized when the fill event gets executed
if total_quote_lots_taken > 0 | | total_base_lots_taken > 0 {
perp_position . add_taker_trade ( side , total_base_lots_taken , total_quote_lots_taken ) ;
2023-02-25 11:34:16 -08:00
apply_fees ( market , mango_account , total_quote_lots_taken ) ? ;
2023-01-20 05:05:07 -08:00
}
2022-03-21 12:29:28 -07:00
// Apply changes to matched asks (handles invalidate on delete!)
2022-04-01 01:36:04 -07:00
for ( handle , new_quantity ) in matched_order_changes {
opposing_bookside
2022-12-07 11:54:18 -08:00
. node_mut ( handle . node )
2022-03-21 12:29:28 -07:00
. unwrap ( )
. as_leaf_mut ( )
. unwrap ( )
. quantity = new_quantity ;
}
2022-11-08 06:27:56 -08:00
for ( component , key ) in matched_order_deletes {
let _removed_leaf = opposing_bookside . remove_by_key ( component , key ) . unwrap ( ) ;
2022-03-21 12:29:28 -07:00
}
2023-01-20 05:05:07 -08:00
//
// Place remainder on the book if requested
//
2022-03-21 12:29:28 -07:00
// If there are still quantity unmatched, place on the book
2022-04-01 06:47:12 -07:00
let book_base_quantity = remaining_base_lots . min ( remaining_quote_lots / price_lots ) ;
2022-11-08 06:27:56 -08:00
if book_base_quantity < = 0 {
post_target = None ;
}
2023-01-20 05:05:07 -08:00
if post_target . is_some ( ) {
// price limit check computed lazily to save CU on average
let native_price = market . lot_to_native_price ( price_lots ) ;
if ! market . inside_price_limit ( side , native_price , oracle_price ) {
msg! ( " Posting on book disallowed due to price limits, order price {:?}, oracle price {:?} " , native_price , oracle_price ) ;
post_target = None ;
}
}
2022-11-08 06:27:56 -08:00
if let Some ( order_tree_target ) = post_target {
let bookside = self . bookside_mut ( side ) ;
2022-03-21 12:29:28 -07:00
// Drop an expired order if possible
2022-12-07 11:54:18 -08:00
if let Some ( expired_order ) = bookside . remove_one_expired ( order_tree_target , now_ts ) {
2022-03-24 11:20:56 -07:00
let event = OutEvent ::new (
2022-04-01 01:36:04 -07:00
side ,
expired_order . owner_slot ,
2022-03-24 11:20:56 -07:00
now_ts ,
event_queue . header . seq_num ,
2022-04-01 01:36:04 -07:00
expired_order . owner ,
expired_order . quantity ,
2022-03-24 11:20:56 -07:00
) ;
event_queue . push_back ( cast ( event ) ) . unwrap ( ) ;
}
2022-03-21 12:29:28 -07:00
2022-12-07 11:54:18 -08:00
if bookside . is_full ( ) {
2022-03-21 12:29:28 -07:00
// If this bid is higher than lowest bid, boot that bid and insert this one
2022-12-07 11:54:18 -08:00
let ( worst_order , worst_price ) =
bookside . remove_worst ( now_ts , oracle_price_lots ) . unwrap ( ) ;
2022-03-24 11:20:56 -07:00
// MangoErrorCode::OutOfSpace
2022-04-01 01:36:04 -07:00
require! (
2022-12-07 11:54:18 -08:00
side . is_price_better ( price_lots , worst_price ) ,
2022-04-01 01:36:04 -07:00
MangoError ::SomeError
2022-03-26 09:06:55 -07:00
) ;
let event = OutEvent ::new (
2022-04-01 01:36:04 -07:00
side ,
worst_order . owner_slot ,
2022-03-26 09:06:55 -07:00
now_ts ,
event_queue . header . seq_num ,
2022-04-01 01:36:04 -07:00
worst_order . owner ,
worst_order . quantity ,
2022-03-26 09:06:55 -07:00
) ;
event_queue . push_back ( cast ( event ) ) . unwrap ( ) ;
}
2022-08-30 03:47:15 -07:00
let owner_slot = mango_account . perp_next_order_slot ( ) ? ;
2022-04-01 01:36:04 -07:00
let new_order = LeafNode ::new (
2022-03-28 12:13:16 -07:00
owner_slot as u8 ,
2022-03-26 09:06:55 -07:00
order_id ,
* mango_account_pk ,
book_base_quantity ,
now_ts ,
2022-11-08 06:27:56 -08:00
PostOrderType ::Limit , // TODO: Support order types? needed?
order . time_in_force ,
order . peg_limit ( ) ,
2023-02-02 00:15:06 -08:00
order . client_order_id ,
2022-03-26 09:06:55 -07:00
) ;
2022-12-07 11:54:18 -08:00
let _result = bookside . insert_leaf ( order_tree_target , & new_order ) ? ;
2022-03-26 09:06:55 -07:00
// TODO OPT remove if PlacePerpOrder needs more compute
msg! (
2022-04-01 01:36:04 -07:00
" {} on book order_id={} quantity={} price={} " ,
match side {
Side ::Bid = > " bid " ,
Side ::Ask = > " ask " ,
} ,
2022-03-26 09:06:55 -07:00
order_id ,
book_base_quantity ,
2022-04-01 06:47:12 -07:00
price_lots
2022-03-26 09:06:55 -07:00
) ;
2022-04-04 00:23:01 -07:00
2022-11-08 06:27:56 -08:00
mango_account . add_perp_order (
market . perp_market_index ,
side ,
order_tree_target ,
& new_order ,
2022-12-08 10:55:32 -08:00
order . client_order_id ,
2022-11-08 06:27:56 -08:00
) ? ;
2022-03-26 09:06:55 -07:00
}
2023-01-25 00:03:35 -08:00
if post_target . is_some ( ) {
Ok ( Some ( order_id ) )
} else {
Ok ( None )
}
2022-03-26 09:06:55 -07:00
}
2022-05-16 01:34:22 -07:00
2022-09-06 05:23:39 -07:00
/// Cancels up to `limit` orders that are listed on the mango account for the given perp market.
/// Optionally filters by `side_to_cancel_option`.
/// The orders are removed from the book and from the mango account open order list.
pub fn cancel_all_orders (
2022-05-16 01:34:22 -07:00
& mut self ,
2022-07-25 07:07:53 -07:00
mango_account : & mut MangoAccountRefMut ,
2022-05-16 01:34:22 -07:00
perp_market : & mut PerpMarket ,
mut limit : u8 ,
side_to_cancel_option : Option < Side > ,
) -> Result < ( ) > {
2022-07-25 07:07:53 -07:00
for i in 0 .. mango_account . header . perp_oo_count ( ) {
2022-08-29 06:36:21 -07:00
let oo = mango_account . perp_order_by_raw_index ( i ) ;
2023-01-20 04:47:01 -08:00
if ! oo . is_active_for_market ( perp_market . perp_market_index ) {
2022-05-16 01:34:22 -07:00
continue ;
}
2022-12-06 00:25:43 -08:00
let order_side_and_tree = oo . side_and_tree ( ) ;
2022-05-16 01:34:22 -07:00
if let Some ( side_to_cancel ) = side_to_cancel_option {
2022-11-08 06:27:56 -08:00
if side_to_cancel ! = order_side_and_tree . side ( ) {
2022-05-16 01:34:22 -07:00
continue ;
}
}
2022-11-08 06:27:56 -08:00
let order_id = oo . id ;
2023-02-14 01:44:51 -08:00
let cancel_result =
self . cancel_order ( mango_account , order_id , order_side_and_tree , None ) ;
if cancel_result . is_anchor_error_with_code ( MangoError ::PerpOrderIdNotFound . into ( ) ) {
// It's possible for the order to be filled or expired already.
// There will be an event on the queue, the perp order slot is freed once
// it is processed.
msg! (
" order {} was not found on orderbook, expired or filled already " ,
order_id
) ;
} else {
cancel_result ? ;
}
2022-05-16 01:34:22 -07:00
2022-05-18 08:16:14 -07:00
limit - = 1 ;
2022-05-16 01:34:22 -07:00
if limit = = 0 {
break ;
}
}
Ok ( ( ) )
}
2022-09-06 05:23:39 -07:00
/// Cancels an order on a side, removing it from the book and the mango account orders list
pub fn cancel_order (
& mut self ,
mango_account : & mut MangoAccountRefMut ,
2022-11-08 06:27:56 -08:00
order_id : u128 ,
side_and_tree : SideAndOrderTree ,
2022-09-06 05:23:39 -07:00
expected_owner : Option < Pubkey > ,
) -> Result < LeafNode > {
2022-11-08 06:27:56 -08:00
let side = side_and_tree . side ( ) ;
let book_component = side_and_tree . order_tree ( ) ;
2022-12-07 11:54:18 -08:00
let leaf_node = self . bookside_mut ( side ) .
remove_by_key ( book_component , order_id ) . ok_or_else ( | | {
2023-02-14 01:44:51 -08:00
// possibly already filled or expired?
error_msg_typed! ( MangoError ::PerpOrderIdNotFound , " no perp order with id {order_id}, side {side:?}, component {book_component:?} found on the orderbook " )
} ) ? ;
2022-09-06 05:23:39 -07:00
if let Some ( owner ) = expected_owner {
require_keys_eq! ( leaf_node . owner , owner ) ;
2022-05-16 01:34:22 -07:00
}
2022-09-06 05:23:39 -07:00
mango_account . remove_perp_order ( leaf_node . owner_slot as usize , leaf_node . quantity ) ? ;
Ok ( leaf_node )
2022-05-16 01:34:22 -07:00
}
2022-03-21 12:29:28 -07:00
}
2022-03-25 01:46:38 -07:00
/// Apply taker fees to the taker account and update the markets' fees_accrued for
/// both the maker and taker fees.
fn apply_fees (
market : & mut PerpMarket ,
2023-02-25 11:34:16 -08:00
account : & mut MangoAccountRefMut ,
2023-01-20 05:05:07 -08:00
quote_lots : i64 ,
2022-03-25 01:46:38 -07:00
) -> Result < ( ) > {
2023-02-24 02:56:33 -08:00
let quote_native = I80F48 ::from_num ( market . quote_lot_size * quote_lots ) ;
2022-09-22 09:55:12 -07:00
2023-02-27 10:51:09 -08:00
// The maker fees apply to the maker's account only when the fill event is consumed.
2023-02-24 02:56:33 -08:00
let maker_fees = quote_native * market . maker_fee ;
2023-02-27 10:51:09 -08:00
2023-02-24 02:56:33 -08:00
let taker_fees = quote_native * market . taker_fee ;
2022-03-25 01:46:38 -07:00
2022-10-07 12:12:55 -07:00
// taker fees should never be negative
require_gte! ( taker_fees , 0 ) ;
2023-02-27 10:51:09 -08:00
// Part of the taker fees that go to the dao, instead of paying for maker rebates
let taker_dao_fees = ( taker_fees + maker_fees . min ( I80F48 ::ZERO ) ) . max ( I80F48 ::ZERO ) ;
2023-02-27 07:36:27 -08:00
account
. fixed
2023-02-27 10:51:09 -08:00
. accrue_buyback_fees ( taker_dao_fees . floor ( ) . to_num ::< u64 > ( ) ) ;
2023-02-25 11:34:16 -08:00
let perp_position = account . perp_position_mut ( market . perp_market_index ) ? ;
2023-01-20 05:05:07 -08:00
perp_position . record_trading_fee ( taker_fees ) ;
2023-02-24 02:56:33 -08:00
perp_position . taker_volume + = taker_fees . to_num ::< u64 > ( ) ;
2023-01-20 05:05:07 -08:00
// Accrue maker fees immediately: they can be negative and applying them later
// risks that fees_accrued is settled to 0 before they apply. It going negative
// breaks assumptions.
2023-02-24 02:56:33 -08:00
market . fees_accrued + = taker_fees + maker_fees ;
2022-09-22 09:55:12 -07:00
Ok ( ( ) )
}
/// Applies a fixed penalty fee to the account, and update the market's fees_accrued
2023-02-25 11:34:16 -08:00
fn apply_penalty ( market : & mut PerpMarket , account : & mut MangoAccountRefMut ) -> Result < ( ) > {
2022-09-22 09:55:12 -07:00
let fee_penalty = I80F48 ::from_num ( market . fee_penalty ) ;
2023-02-27 07:36:27 -08:00
account
. fixed
. accrue_buyback_fees ( fee_penalty . floor ( ) . to_num ::< u64 > ( ) ) ;
2023-02-25 11:34:16 -08:00
let perp_position = account . perp_position_mut ( market . perp_market_index ) ? ;
2023-01-20 05:05:07 -08:00
perp_position . record_trading_fee ( fee_penalty ) ;
2023-02-24 02:56:33 -08:00
market . fees_accrued + = fee_penalty ;
2022-03-25 01:46:38 -07:00
Ok ( ( ) )
}