2022-03-21 12:29:28 -07:00
|
|
|
use std::cell::RefMut;
|
|
|
|
|
2022-06-08 04:43:12 -07:00
|
|
|
use crate::accounts_zerocopy::*;
|
2022-07-25 07:07:53 -07:00
|
|
|
use crate::state::MangoAccountRefMut;
|
2022-03-21 12:29:28 -07:00
|
|
|
use crate::{
|
2022-09-06 05:23:39 -07:00
|
|
|
error::*,
|
2022-03-21 12:29:28 -07:00
|
|
|
state::{
|
|
|
|
orderbook::{bookside::BookSide, nodes::LeafNode},
|
2022-07-25 07:07:53 -07:00
|
|
|
EventQueue, PerpMarket, FREE_ORDER_SLOT,
|
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;
|
|
|
|
|
|
|
|
use super::{
|
|
|
|
nodes::NodeHandle,
|
|
|
|
order_type::{OrderType, Side},
|
2022-03-24 11:20:56 -07:00
|
|
|
FillEvent, OutEvent,
|
2022-03-21 12:29:28 -07:00
|
|
|
};
|
2022-03-22 02:39:51 -07:00
|
|
|
use crate::util::checked_math as cm;
|
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-04-01 01:36:04 -07:00
|
|
|
/// The implicit limit price to use for market orders
|
|
|
|
fn market_order_limit_for_side(side: Side) -> i64 {
|
|
|
|
match side {
|
|
|
|
Side::Bid => i64::MAX,
|
|
|
|
Side::Ask => 1,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The limit to use for PostOnlySlide orders: the tinyest bit better than
|
|
|
|
/// the best opposing order
|
|
|
|
fn post_only_slide_limit(side: Side, best_other_side: i64, limit: i64) -> i64 {
|
|
|
|
match side {
|
|
|
|
Side::Bid => limit.min(cm!(best_other_side - 1)),
|
|
|
|
Side::Ask => limit.max(cm!(best_other_side + 1)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-21 12:29:28 -07:00
|
|
|
pub struct Book<'a> {
|
|
|
|
pub bids: RefMut<'a, BookSide>, // todo: why refmut?
|
|
|
|
pub asks: RefMut<'a, BookSide>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Book<'a> {
|
2022-06-22 03:45:42 -07:00
|
|
|
pub fn new(bids: RefMut<'a, BookSide>, asks: RefMut<'a, BookSide>) -> Self {
|
|
|
|
Self { bids, asks }
|
|
|
|
}
|
|
|
|
|
2022-03-24 08:12:55 -07:00
|
|
|
pub fn load_mut(
|
2022-03-21 12:29:28 -07:00
|
|
|
bids_ai: &'a AccountInfo,
|
|
|
|
asks_ai: &'a AccountInfo,
|
|
|
|
perp_market: &PerpMarket,
|
|
|
|
) -> std::result::Result<Self, Error> {
|
2022-03-24 07:29:42 -07:00
|
|
|
require!(bids_ai.key == &perp_market.bids, MangoError::SomeError);
|
|
|
|
require!(asks_ai.key == &perp_market.asks, MangoError::SomeError);
|
2022-06-22 03:45:42 -07:00
|
|
|
Ok(Self::new(
|
|
|
|
bids_ai.load_mut::<BookSide>()?,
|
|
|
|
asks_ai.load_mut::<BookSide>()?,
|
|
|
|
))
|
2022-03-21 12:29:28 -07:00
|
|
|
}
|
|
|
|
|
2022-09-06 05:23:39 -07:00
|
|
|
pub fn bookside(&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-03-25 12:03:44 -07:00
|
|
|
/// Returns best valid bid
|
2022-09-06 05:23:39 -07:00
|
|
|
pub fn best_bid_price(&self, now_ts: u64) -> Option<i64> {
|
2022-03-21 12:29:28 -07:00
|
|
|
Some(self.bids.iter_valid(now_ts).next()?.1.price())
|
|
|
|
}
|
|
|
|
|
2022-03-25 12:03:44 -07:00
|
|
|
/// Returns best valid ask
|
2022-09-06 05:23:39 -07:00
|
|
|
pub fn best_ask_price(&self, now_ts: u64) -> Option<i64> {
|
2022-03-21 12:29:28 -07:00
|
|
|
Some(self.asks.iter_valid(now_ts).next()?.1.price())
|
|
|
|
}
|
|
|
|
|
2022-09-06 05:23:39 -07:00
|
|
|
pub fn best_price(&self, now_ts: u64, side: Side) -> Option<i64> {
|
2022-04-01 01:36:04 -07:00
|
|
|
match side {
|
2022-09-06 05:23:39 -07:00
|
|
|
Side::Bid => self.best_bid_price(now_ts),
|
|
|
|
Side::Ask => self.best_ask_price(now_ts),
|
2022-04-01 01:36:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-21 12:29:28 -07:00
|
|
|
/// Get the quantity of valid bids above and including the price
|
2022-09-06 05:23:39 -07:00
|
|
|
pub fn bids_size_above(&self, price: i64, max_depth: i64, now_ts: u64) -> i64 {
|
2022-05-17 08:08:00 -07:00
|
|
|
let mut sum: i64 = 0;
|
2022-03-21 12:29:28 -07:00
|
|
|
for (_, bid) in self.bids.iter_valid(now_ts) {
|
2022-05-17 08:08:00 -07:00
|
|
|
if price > bid.price() || sum >= max_depth {
|
2022-03-21 12:29:28 -07:00
|
|
|
break;
|
|
|
|
}
|
2022-05-17 08:08:00 -07:00
|
|
|
sum = sum.checked_add(bid.quantity).unwrap();
|
2022-03-21 12:29:28 -07:00
|
|
|
}
|
2022-05-17 08:08:00 -07:00
|
|
|
sum.min(max_depth)
|
2022-03-21 12:29:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Walk up the book `quantity` units and return the price at that level. If `quantity` units
|
|
|
|
/// not on book, return None
|
2022-09-06 05:23:39 -07:00
|
|
|
pub fn impact_price(&self, side: Side, quantity: i64, now_ts: u64) -> Option<i64> {
|
2022-05-17 08:08:00 -07:00
|
|
|
let mut sum: i64 = 0;
|
2022-03-21 12:29:28 -07:00
|
|
|
let book_side = match side {
|
|
|
|
Side::Bid => self.bids.iter_valid(now_ts),
|
|
|
|
Side::Ask => self.asks.iter_valid(now_ts),
|
|
|
|
};
|
|
|
|
for (_, order) in book_side {
|
2022-05-17 08:08:00 -07:00
|
|
|
sum = sum.checked_add(order.quantity).unwrap();
|
|
|
|
if sum >= quantity {
|
2022-03-21 12:29:28 -07:00
|
|
|
return Some(order.price());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the quantity of valid asks below and including the price
|
2022-09-06 05:23:39 -07:00
|
|
|
pub fn asks_size_below(&self, price: i64, max_depth: i64, now_ts: u64) -> i64 {
|
2022-03-21 12:29:28 -07:00
|
|
|
let mut s = 0;
|
|
|
|
for (_, ask) in self.asks.iter_valid(now_ts) {
|
|
|
|
if price < ask.price() || s >= max_depth {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
s += ask.quantity;
|
|
|
|
}
|
|
|
|
s.min(max_depth)
|
|
|
|
}
|
|
|
|
/// Get the quantity of valid bids above this order id. Will return full size of book if order id not found
|
2022-09-06 05:23:39 -07:00
|
|
|
pub fn bids_size_above_order(&self, order_id: i128, max_depth: i64, now_ts: u64) -> i64 {
|
2022-03-21 12:29:28 -07:00
|
|
|
let mut s = 0;
|
|
|
|
for (_, bid) in self.bids.iter_valid(now_ts) {
|
|
|
|
if bid.key == order_id || s >= max_depth {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
s += bid.quantity;
|
|
|
|
}
|
|
|
|
s.min(max_depth)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the quantity of valid asks above this order id. Will return full size of book if order id not found
|
2022-09-06 05:23:39 -07:00
|
|
|
pub fn asks_size_below_order(&self, order_id: i128, max_depth: i64, now_ts: u64) -> i64 {
|
2022-03-21 12:29:28 -07:00
|
|
|
let mut s = 0;
|
|
|
|
for (_, ask) in self.asks.iter_valid(now_ts) {
|
|
|
|
if ask.key == order_id || s >= max_depth {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
s += ask.quantity;
|
|
|
|
}
|
|
|
|
s.min(max_depth)
|
|
|
|
}
|
|
|
|
|
2022-03-26 09:06:55 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
pub fn new_order(
|
|
|
|
&mut self,
|
|
|
|
side: Side,
|
|
|
|
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,
|
2022-04-01 06:47:12 -07:00
|
|
|
price_lots: i64,
|
|
|
|
max_base_lots: i64,
|
|
|
|
max_quote_lots: i64,
|
2022-03-26 09:06:55 -07:00
|
|
|
order_type: OrderType,
|
|
|
|
time_in_force: u8,
|
|
|
|
client_order_id: u64,
|
|
|
|
now_ts: u64,
|
2022-04-01 01:36:04 -07:00
|
|
|
mut limit: u8,
|
2022-03-21 12:29:28 -07:00
|
|
|
) -> std::result::Result<(), Error> {
|
2022-04-01 01:36:04 -07:00
|
|
|
let other_side = side.invert_side();
|
|
|
|
let market = perp_market;
|
2022-04-01 06:47:12 -07:00
|
|
|
let (post_only, mut post_allowed, price_lots) = match order_type {
|
|
|
|
OrderType::Limit => (false, true, price_lots),
|
|
|
|
OrderType::ImmediateOrCancel => (false, false, price_lots),
|
|
|
|
OrderType::PostOnly => (true, true, price_lots),
|
2022-04-01 01:36:04 -07:00
|
|
|
OrderType::Market => (false, false, market_order_limit_for_side(side)),
|
2022-03-21 12:29:28 -07:00
|
|
|
OrderType::PostOnlySlide => {
|
2022-09-06 05:23:39 -07:00
|
|
|
let price = if let Some(best_other_price) = self.best_price(now_ts, other_side) {
|
2022-04-01 06:47:12 -07:00
|
|
|
post_only_slide_limit(side, best_other_price, price_lots)
|
2022-03-21 12:29:28 -07:00
|
|
|
} else {
|
2022-04-01 06:47:12 -07:00
|
|
|
price_lots
|
2022-03-21 12:29:28 -07:00
|
|
|
};
|
|
|
|
(true, true, price)
|
|
|
|
}
|
|
|
|
};
|
2022-03-24 09:29:30 -07:00
|
|
|
|
|
|
|
if post_allowed {
|
|
|
|
// price limit check computed lazily to save CU on average
|
2022-04-01 06:47:12 -07:00
|
|
|
let native_price = market.lot_to_native_price(price_lots);
|
2022-04-01 01:36:04 -07:00
|
|
|
if !market.inside_price_limit(side, native_price, oracle_price) {
|
2022-03-24 09:29:30 -07:00
|
|
|
msg!("Posting on book disallowed due to price limits");
|
|
|
|
post_allowed = false;
|
|
|
|
}
|
|
|
|
}
|
2022-03-21 12:29:28 -07:00
|
|
|
|
|
|
|
// generate new order id
|
2022-04-01 06:47:12 -07:00
|
|
|
let order_id = market.gen_order_id(side, price_lots);
|
2022-03-21 12:29:28 -07:00
|
|
|
|
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-04-01 06:47:12 -07:00
|
|
|
let mut remaining_base_lots = max_base_lots;
|
|
|
|
let mut remaining_quote_lots = max_quote_lots;
|
2022-04-01 01:36:04 -07:00
|
|
|
let mut matched_order_changes: Vec<(NodeHandle, i64)> = vec![];
|
|
|
|
let mut matched_order_deletes: Vec<i128> = vec![];
|
2022-03-21 12:29:28 -07:00
|
|
|
let mut number_of_dropped_expired_orders = 0;
|
2022-09-06 05:23:39 -07:00
|
|
|
let opposing_bookside = self.bookside(other_side);
|
2022-04-01 01:36:04 -07:00
|
|
|
for (best_opposing_h, best_opposing) in opposing_bookside.iter_all_including_invalid() {
|
|
|
|
if !best_opposing.is_valid(now_ts) {
|
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,
|
|
|
|
best_opposing.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
|
|
|
best_opposing.owner,
|
|
|
|
best_opposing.quantity,
|
2022-03-24 11:20:56 -07:00
|
|
|
);
|
|
|
|
event_queue.push_back(cast(event)).unwrap();
|
2022-04-01 01:36:04 -07:00
|
|
|
matched_order_deletes.push(best_opposing.key);
|
2022-03-21 12:29:28 -07:00
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-04-01 01:36:04 -07:00
|
|
|
let best_opposing_price = best_opposing.price();
|
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");
|
|
|
|
post_allowed = false;
|
|
|
|
break; // return silently to not fail other instructions in tx
|
|
|
|
} else if limit == 0 {
|
|
|
|
msg!("Order matching limit reached");
|
|
|
|
post_allowed = false;
|
|
|
|
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-04-01 01:36:04 -07:00
|
|
|
.min(best_opposing.quantity)
|
2022-03-21 12:29:28 -07:00
|
|
|
.min(max_match_by_quote);
|
2022-04-01 06:47:12 -07:00
|
|
|
let done =
|
|
|
|
match_base_lots == max_match_by_quote || match_base_lots == remaining_base_lots;
|
2022-03-21 12:29:28 -07:00
|
|
|
|
2022-04-01 06:47:12 -07:00
|
|
|
let match_quote_lots = cm!(match_base_lots * best_opposing_price);
|
2022-09-05 05:14:42 -07:00
|
|
|
cm!(remaining_base_lots -= match_base_lots);
|
|
|
|
cm!(remaining_quote_lots -= match_quote_lots);
|
2022-03-21 12:29:28 -07:00
|
|
|
|
2022-04-01 06:47:12 -07:00
|
|
|
let new_best_opposing_quantity = cm!(best_opposing.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-04-01 01:36:04 -07:00
|
|
|
matched_order_deletes.push(best_opposing.key);
|
2022-03-21 12:29:28 -07:00
|
|
|
} else {
|
2022-04-01 01:36:04 -07:00
|
|
|
matched_order_changes.push((best_opposing_h, new_best_opposing_quantity));
|
2022-03-21 12:29:28 -07:00
|
|
|
}
|
|
|
|
|
2022-04-04 00:23:01 -07:00
|
|
|
// Record the taker trade in the account already, even though it will only be
|
|
|
|
// realized when the fill event gets executed
|
2022-09-09 01:50:09 -07:00
|
|
|
let perp_account = mango_account.perp_position_mut(market.perp_market_index)?;
|
2022-04-04 00:23:01 -07:00
|
|
|
perp_account.add_taker_trade(side, match_base_lots, match_quote_lots);
|
|
|
|
|
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-04-01 01:36:04 -07:00
|
|
|
best_opposing.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
|
|
|
best_opposing.owner,
|
|
|
|
best_opposing.key,
|
|
|
|
best_opposing.client_order_id,
|
2022-03-24 11:20:56 -07:00
|
|
|
market.maker_fee,
|
2022-04-01 01:36:04 -07:00
|
|
|
best_opposing.timestamp,
|
2022-03-24 11:20:56 -07:00
|
|
|
*mango_account_pk,
|
|
|
|
order_id,
|
|
|
|
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;
|
|
|
|
|
|
|
|
if done {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-04-01 06:47:12 -07:00
|
|
|
let total_quote_lots_taken = cm!(max_quote_lots - remaining_quote_lots);
|
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-09-06 05:23:39 -07:00
|
|
|
.node_mut(handle)
|
2022-03-21 12:29:28 -07:00
|
|
|
.unwrap()
|
|
|
|
.as_leaf_mut()
|
|
|
|
.unwrap()
|
|
|
|
.quantity = new_quantity;
|
|
|
|
}
|
2022-04-01 01:36:04 -07:00
|
|
|
for key in matched_order_deletes {
|
|
|
|
let _removed_leaf = opposing_bookside.remove_by_key(key).unwrap();
|
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-05-16 01:34:22 -07:00
|
|
|
msg!("{:?}", post_allowed);
|
2022-03-21 12:29:28 -07:00
|
|
|
if post_allowed && book_base_quantity > 0 {
|
|
|
|
// Drop an expired order if possible
|
2022-09-06 05:23:39 -07:00
|
|
|
let bookside = self.bookside(side);
|
2022-04-01 01:36:04 -07:00
|
|
|
if let Some(expired_order) = bookside.remove_one_expired(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-04-01 01:36:04 -07: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-04-01 01:36:04 -07:00
|
|
|
let worst_order = bookside.remove_worst().unwrap();
|
2022-03-24 11:20:56 -07:00
|
|
|
// MangoErrorCode::OutOfSpace
|
2022-04-01 01:36:04 -07:00
|
|
|
require!(
|
2022-04-01 06:47:12 -07:00
|
|
|
side.is_price_better(price_lots, worst_order.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,
|
|
|
|
client_order_id,
|
|
|
|
now_ts,
|
|
|
|
order_type,
|
|
|
|
time_in_force,
|
|
|
|
);
|
2022-04-01 01:36:04 -07:00
|
|
|
let _result = bookside.insert_leaf(&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-08-18 04:45:31 -07:00
|
|
|
mango_account.add_perp_order(market.perp_market_index, side, &new_order)?;
|
2022-03-26 09:06:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// if there were matched taker quote apply ref fees
|
|
|
|
// we know ref_fee_rate is not None if total_quote_taken > 0
|
2022-04-01 06:47:12 -07:00
|
|
|
if total_quote_lots_taken > 0 {
|
2022-07-25 07:07:53 -07:00
|
|
|
apply_fees(market, mango_account, total_quote_lots_taken)?;
|
2022-03-26 09:06:55 -07:00
|
|
|
}
|
|
|
|
|
2022-09-22 09:55:12 -07:00
|
|
|
// IOC orders have a fee penalty applied regardless of match
|
|
|
|
if order_type == OrderType::ImmediateOrCancel {
|
|
|
|
apply_penalty(market, mango_account)?;
|
|
|
|
}
|
|
|
|
|
2022-03-26 09:06:55 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
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);
|
2022-07-25 07:07:53 -07:00
|
|
|
if oo.order_market == FREE_ORDER_SLOT
|
|
|
|
|| oo.order_market != perp_market.perp_market_index
|
2022-05-16 01:34:22 -07:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-07-25 07:07:53 -07:00
|
|
|
let order_side = oo.order_side;
|
2022-05-16 01:34:22 -07:00
|
|
|
if let Some(side_to_cancel) = side_to_cancel_option {
|
|
|
|
if side_to_cancel != order_side {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-06 05:23:39 -07:00
|
|
|
let order_id = oo.order_id;
|
|
|
|
self.cancel_order(mango_account, order_id, order_side, None)?;
|
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,
|
|
|
|
order_id: i128,
|
|
|
|
side: Side,
|
|
|
|
expected_owner: Option<Pubkey>,
|
|
|
|
) -> Result<LeafNode> {
|
|
|
|
let leaf_node =
|
|
|
|
match side {
|
|
|
|
Side::Bid => self.bids.remove_by_key(order_id).ok_or_else(|| {
|
|
|
|
error_msg!("invalid perp order id {order_id} for side {side:?}")
|
|
|
|
}),
|
|
|
|
Side::Ask => self.asks.remove_by_key(order_id).ok_or_else(|| {
|
|
|
|
error_msg!("invalid perp order id {order_id} for side {side:?}")
|
|
|
|
}),
|
|
|
|
}?;
|
|
|
|
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,
|
2022-07-25 07:07:53 -07:00
|
|
|
mango_account: &mut MangoAccountRefMut,
|
2022-03-25 01:46:38 -07:00
|
|
|
total_quote_taken: i64,
|
|
|
|
) -> Result<()> {
|
|
|
|
let taker_quote_native = I80F48::from_num(
|
|
|
|
market
|
|
|
|
.quote_lot_size
|
|
|
|
.checked_mul(total_quote_taken)
|
|
|
|
.unwrap(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Track 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.
|
|
|
|
// The maker fees apply to the maker's account only when the fill event is consumed.
|
2022-09-22 09:55:12 -07:00
|
|
|
let maker_fees = cm!(taker_quote_native * market.maker_fee);
|
|
|
|
|
|
|
|
let taker_fees = cm!(taker_quote_native * market.taker_fee);
|
2022-03-25 01:46:38 -07:00
|
|
|
|
2022-09-09 01:50:09 -07:00
|
|
|
let perp_account = mango_account.perp_position_mut(market.perp_market_index)?;
|
2022-09-07 08:02:51 -07:00
|
|
|
perp_account.change_quote_position(-taker_fees);
|
2022-09-22 09:55:12 -07:00
|
|
|
cm!(market.fees_accrued += taker_fees + maker_fees);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Applies a fixed penalty fee to the account, and update the market's fees_accrued
|
|
|
|
fn apply_penalty(market: &mut PerpMarket, mango_account: &mut MangoAccountRefMut) -> Result<()> {
|
|
|
|
let perp_account = mango_account.perp_position_mut(market.perp_market_index)?;
|
|
|
|
let fee_penalty = I80F48::from_num(market.fee_penalty);
|
|
|
|
|
|
|
|
perp_account.change_quote_position(-fee_penalty);
|
|
|
|
cm!(market.fees_accrued += fee_penalty);
|
2022-03-25 01:46:38 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|