commit
3443ab0d70
|
@ -16,7 +16,7 @@ pub struct CreatePerpMarket<'info> {
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
init,
|
init,
|
||||||
seeds = [group.key().as_ref(), b"PerpMarket".as_ref(), &perp_market_index.to_le_bytes().as_ref()],
|
seeds = [group.key().as_ref(), b"PerpMarket".as_ref(), perp_market_index.to_le_bytes().as_ref()],
|
||||||
bump,
|
bump,
|
||||||
payer = payer,
|
payer = payer,
|
||||||
space = 8 + std::mem::size_of::<PerpMarket>(),
|
space = 8 + std::mem::size_of::<PerpMarket>(),
|
||||||
|
@ -50,8 +50,8 @@ pub fn create_perp_market(
|
||||||
oracle: ctx.accounts.oracle.key(),
|
oracle: ctx.accounts.oracle.key(),
|
||||||
bids: ctx.accounts.bids.key(),
|
bids: ctx.accounts.bids.key(),
|
||||||
asks: ctx.accounts.asks.key(),
|
asks: ctx.accounts.asks.key(),
|
||||||
quote_lot_size: quote_lot_size,
|
quote_lot_size,
|
||||||
base_lot_size: base_lot_size,
|
base_lot_size,
|
||||||
seq_num: 0,
|
seq_num: 0,
|
||||||
perp_market_index,
|
perp_market_index,
|
||||||
base_token_index: base_token_index_opt.ok_or(TokenIndex::MAX).unwrap(),
|
base_token_index: base_token_index_opt.ok_or(TokenIndex::MAX).unwrap(),
|
||||||
|
|
|
@ -141,9 +141,9 @@ fn get_pre_cpi_amounts(ctx: &Context<MarginTrade>, cpi_ais: &Vec<AccountInfo>) -
|
||||||
|
|
||||||
fn adjust_for_post_cpi_amounts(
|
fn adjust_for_post_cpi_amounts(
|
||||||
ctx: &Context<MarginTrade>,
|
ctx: &Context<MarginTrade>,
|
||||||
cpi_ais: &Vec<AccountInfo>,
|
cpi_ais: &[AccountInfo],
|
||||||
pre_cpi_amounts: Vec<u64>,
|
pre_cpi_amounts: Vec<u64>,
|
||||||
banks: &mut Vec<AccountInfo>,
|
banks: &mut [AccountInfo],
|
||||||
account: &mut MangoAccount,
|
account: &mut MangoAccount,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let token_accounts_iter = cpi_ais
|
let token_accounts_iter = cpi_ais
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
use crate::state::{
|
use crate::state::{Book, BookSide, Group, MangoAccount, OrderType, PerpMarket};
|
||||||
oracle_price, Book, BookSide, Group, MangoAccount, OrderType, PerpMarket, Side,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct PlacePerpOrder<'info> {
|
pub struct PlacePerpOrder<'info> {
|
||||||
|
@ -32,22 +30,19 @@ pub struct PlacePerpOrder<'info> {
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn place_perp_order(
|
pub fn place_perp_order(
|
||||||
ctx: Context<PlacePerpOrder>,
|
ctx: Context<PlacePerpOrder>,
|
||||||
// TODO side is harcoded for now
|
|
||||||
// maybe new_bid and new_ask can be folded into one function
|
|
||||||
// side: Side,
|
|
||||||
price: i64,
|
price: i64,
|
||||||
max_base_quantity: i64,
|
max_base_quantity: i64,
|
||||||
max_quote_quantity: i64,
|
max_quote_quantity: i64,
|
||||||
client_order_id: u64,
|
client_order_id: u64,
|
||||||
order_type: OrderType,
|
order_type: OrderType,
|
||||||
// TODO reduce_only relies on event queue
|
|
||||||
// reduce_only: bool,
|
|
||||||
expiry_timestamp: u64,
|
expiry_timestamp: u64,
|
||||||
limit: u8,
|
limit: u8,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
// let mut account = ctx.accounts.account.load_mut()?;
|
||||||
let mango_account_pk = ctx.accounts.account.key();
|
let mango_account_pk = ctx.accounts.account.key();
|
||||||
|
|
||||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||||
|
@ -55,7 +50,7 @@ pub fn place_perp_order(
|
||||||
let asks = &ctx.accounts.asks.to_account_info();
|
let asks = &ctx.accounts.asks.to_account_info();
|
||||||
let mut book = Book::load_checked(&bids, &asks, &perp_market)?;
|
let mut book = Book::load_checked(&bids, &asks, &perp_market)?;
|
||||||
|
|
||||||
let oracle_price = oracle_price(&ctx.accounts.oracle.to_account_info())?;
|
// let oracle_price = oracle_price(&ctx.accounts.oracle.to_account_info())?;
|
||||||
|
|
||||||
let now_ts = Clock::get()?.unix_timestamp as u64;
|
let now_ts = Clock::get()?.unix_timestamp as u64;
|
||||||
let time_in_force = if expiry_timestamp != 0 {
|
let time_in_force = if expiry_timestamp != 0 {
|
||||||
|
@ -75,14 +70,9 @@ pub fn place_perp_order(
|
||||||
// TODO reduce_only based on event queue
|
// TODO reduce_only based on event queue
|
||||||
|
|
||||||
book.new_bid(
|
book.new_bid(
|
||||||
// program_id: &Pubkey,
|
|
||||||
// mango_group: &MangoGroup,
|
|
||||||
// mango_group_pk: &Pubkey,
|
|
||||||
// mango_cache: &MangoCache,
|
|
||||||
// event_queue: &mut EventQueue,
|
|
||||||
&mut perp_market,
|
&mut perp_market,
|
||||||
oracle_price,
|
// oracle_price,
|
||||||
&mut account,
|
// &mut account,
|
||||||
&mango_account_pk,
|
&mango_account_pk,
|
||||||
// market_index: usize,
|
// market_index: usize,
|
||||||
price,
|
price,
|
||||||
|
@ -92,7 +82,6 @@ pub fn place_perp_order(
|
||||||
time_in_force,
|
time_in_force,
|
||||||
client_order_id,
|
client_order_id,
|
||||||
now_ts,
|
now_ts,
|
||||||
// referrer_mango_account_ai: Option<&AccountInfo>,
|
|
||||||
limit,
|
limit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub mod instructions;
|
||||||
mod serum3_cpi;
|
mod serum3_cpi;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
use state::{OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex};
|
use state::{OrderType, PerpMarketIndex, Serum3MarketIndex, TokenIndex};
|
||||||
|
|
||||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||||
|
|
||||||
|
@ -145,25 +145,21 @@ pub mod mango_v4 {
|
||||||
|
|
||||||
pub fn place_perp_order(
|
pub fn place_perp_order(
|
||||||
ctx: Context<PlacePerpOrder>,
|
ctx: Context<PlacePerpOrder>,
|
||||||
side: Side,
|
|
||||||
price: i64,
|
price: i64,
|
||||||
max_base_quantity: i64,
|
max_base_quantity: i64,
|
||||||
max_quote_quantity: i64,
|
max_quote_quantity: i64,
|
||||||
client_order_id: u64,
|
client_order_id: u64,
|
||||||
order_type: OrderType,
|
order_type: OrderType,
|
||||||
reduce_only: bool,
|
|
||||||
expiry_timestamp: u64,
|
expiry_timestamp: u64,
|
||||||
limit: u8,
|
limit: u8,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
instructions::place_perp_order(
|
instructions::place_perp_order(
|
||||||
ctx,
|
ctx,
|
||||||
// side,
|
|
||||||
price,
|
price,
|
||||||
max_base_quantity,
|
max_base_quantity,
|
||||||
max_quote_quantity,
|
max_quote_quantity,
|
||||||
client_order_id,
|
client_order_id,
|
||||||
order_type,
|
order_type,
|
||||||
// reduce_only,
|
|
||||||
expiry_timestamp,
|
expiry_timestamp,
|
||||||
limit,
|
limit,
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::state::*;
|
||||||
// MangoAccount size and health compute needs.
|
// MangoAccount size and health compute needs.
|
||||||
const MAX_INDEXED_POSITIONS: usize = 16;
|
const MAX_INDEXED_POSITIONS: usize = 16;
|
||||||
const MAX_SERUM_OPEN_ORDERS: usize = 8;
|
const MAX_SERUM_OPEN_ORDERS: usize = 8;
|
||||||
const MAX_PERP_OPEN_ORDERS: usize = 8;
|
// const MAX_PERP_OPEN_ORDERS: usize = 8;
|
||||||
|
|
||||||
#[zero_copy]
|
#[zero_copy]
|
||||||
pub struct TokenAccount {
|
pub struct TokenAccount {
|
||||||
|
@ -95,7 +95,7 @@ impl TokenAccountMap {
|
||||||
if let Some(i) = pos {
|
if let Some(i) = pos {
|
||||||
self.values[i] = TokenAccount {
|
self.values[i] = TokenAccount {
|
||||||
indexed_value: I80F48::ZERO,
|
indexed_value: I80F48::ZERO,
|
||||||
token_index: token_index,
|
token_index,
|
||||||
in_use_count: 0,
|
in_use_count: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,18 @@ use crate::{
|
||||||
error::MangoError,
|
error::MangoError,
|
||||||
state::{
|
state::{
|
||||||
orderbook::{bookside::BookSide, nodes::LeafNode},
|
orderbook::{bookside::BookSide, nodes::LeafNode},
|
||||||
MangoAccount, PerpMarket,
|
PerpMarket,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use bytemuck::cast;
|
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
use fixed_macro::types::I80F48;
|
use fixed_macro::types::I80F48;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
nodes::NodeHandle,
|
nodes::NodeHandle,
|
||||||
order_type::{OrderType, Side},
|
order_type::{OrderType, Side},
|
||||||
queue::{EventQueue, FillEvent, OutEvent},
|
|
||||||
};
|
};
|
||||||
|
use crate::util::checked_math as cm;
|
||||||
|
|
||||||
pub const CENTIBPS_PER_UNIT: I80F48 = I80F48!(1_000_000);
|
pub const CENTIBPS_PER_UNIT: I80F48 = I80F48!(1_000_000);
|
||||||
// todo move to a constants module or something
|
// todo move to a constants module or something
|
||||||
|
@ -191,147 +190,147 @@ impl<'a> Book<'a> {
|
||||||
|
|
||||||
/// Iterate over the book and return
|
/// Iterate over the book and return
|
||||||
/// return changes to (taker_base, taker_quote, bids_quantity, asks_quantity)
|
/// return changes to (taker_base, taker_quote, bids_quantity, asks_quantity)
|
||||||
pub fn sim_new_bid(
|
// pub fn sim_new_bid(
|
||||||
&self,
|
// &self,
|
||||||
market: &PerpMarket,
|
// market: &PerpMarket,
|
||||||
// info: &PerpMarketInfo,
|
// // info: &PerpMarketInfo,
|
||||||
oracle_price: I80F48,
|
// oracle_price: I80F48,
|
||||||
price: i64,
|
// price: i64,
|
||||||
max_base_quantity: i64, // guaranteed to be greater than zero due to initial check
|
// max_base_quantity: i64, // guaranteed to be greater than zero due to initial check
|
||||||
max_quote_quantity: i64, // guaranteed to be greater than zero due to initial check
|
// max_quote_quantity: i64, // guaranteed to be greater than zero due to initial check
|
||||||
order_type: OrderType,
|
// order_type: OrderType,
|
||||||
now_ts: u64,
|
// now_ts: u64,
|
||||||
) -> std::result::Result<(i64, i64, i64, i64), Error> {
|
// ) -> std::result::Result<(i64, i64, i64, i64), Error> {
|
||||||
let (mut taker_base, mut taker_quote, mut bids_quantity, asks_quantity) = (0, 0, 0i64, 0);
|
// let (mut taker_base, mut taker_quote, mut bids_quantity, asks_quantity) = (0, 0, 0i64, 0);
|
||||||
|
|
||||||
let (post_only, mut post_allowed, price) = match order_type {
|
// let (post_only, mut post_allowed, price) = match order_type {
|
||||||
OrderType::Limit => (false, true, price),
|
// OrderType::Limit => (false, true, price),
|
||||||
OrderType::ImmediateOrCancel => (false, false, price),
|
// OrderType::ImmediateOrCancel => (false, false, price),
|
||||||
OrderType::PostOnly => (true, true, price),
|
// OrderType::PostOnly => (true, true, price),
|
||||||
OrderType::Market => (false, false, i64::MAX),
|
// OrderType::Market => (false, false, i64::MAX),
|
||||||
OrderType::PostOnlySlide => {
|
// OrderType::PostOnlySlide => {
|
||||||
let price = if let Some(best_ask_price) = self.get_best_ask_price(now_ts) {
|
// let price = if let Some(best_ask_price) = self.get_best_ask_price(now_ts) {
|
||||||
price.min(best_ask_price.checked_sub(1).ok_or(MangoError::SomeError)?)
|
// price.min(best_ask_price.checked_sub(1).ok_or(MangoError::SomeError)?)
|
||||||
// math_err
|
// // math_err
|
||||||
} else {
|
// } else {
|
||||||
price
|
// price
|
||||||
};
|
// };
|
||||||
(true, true, price)
|
// (true, true, price)
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
// if post_allowed {
|
// // if post_allowed {
|
||||||
// // price limit check computed lazily to save CU on average
|
// // // price limit check computed lazily to save CU on average
|
||||||
// let native_price = market.lot_to_native_price(price);
|
// // let native_price = market.lot_to_native_price(price);
|
||||||
// if native_price.checked_div(oracle_price).unwrap() > info.maint_liab_weight {
|
// // if native_price.checked_div(oracle_price).unwrap() > info.maint_liab_weight {
|
||||||
// msg!("Posting on book disallowed due to price limits");
|
// // msg!("Posting on book disallowed due to price limits");
|
||||||
// post_allowed = false;
|
// // post_allowed = false;
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// let mut rem_base_quantity = max_base_quantity; // base lots (aka contracts)
|
||||||
|
// let mut rem_quote_quantity = max_quote_quantity;
|
||||||
|
|
||||||
|
// for (_, best_ask) in self.asks.iter_valid(now_ts) {
|
||||||
|
// let best_ask_price = best_ask.price();
|
||||||
|
// if price < best_ask_price {
|
||||||
|
// break;
|
||||||
|
// } else if post_only {
|
||||||
|
// return Ok((taker_base, taker_quote, bids_quantity, asks_quantity));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let max_match_by_quote = rem_quote_quantity / best_ask_price;
|
||||||
|
// let match_quantity = rem_base_quantity
|
||||||
|
// .min(best_ask.quantity)
|
||||||
|
// .min(max_match_by_quote);
|
||||||
|
|
||||||
|
// let match_quote = match_quantity * best_ask_price;
|
||||||
|
// rem_base_quantity -= match_quantity;
|
||||||
|
// rem_quote_quantity -= match_quote;
|
||||||
|
|
||||||
|
// taker_base += match_quantity;
|
||||||
|
// taker_quote -= match_quote;
|
||||||
|
// if match_quantity == max_match_by_quote || rem_base_quantity == 0 {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// let book_base_quantity = rem_base_quantity.min(rem_quote_quantity / price);
|
||||||
|
// if post_allowed && book_base_quantity > 0 {
|
||||||
|
// bids_quantity = bids_quantity.checked_add(book_base_quantity).unwrap();
|
||||||
|
// }
|
||||||
|
// Ok((taker_base, taker_quote, bids_quantity, asks_quantity))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn sim_new_ask(
|
||||||
|
// &self,
|
||||||
|
// market: &PerpMarket,
|
||||||
|
// // info: &PerpMarketInfo,
|
||||||
|
// oracle_price: I80F48,
|
||||||
|
// price: i64,
|
||||||
|
// max_base_quantity: i64, // guaranteed to be greater than zero due to initial check
|
||||||
|
// max_quote_quantity: i64, // guaranteed to be greater than zero due to initial check
|
||||||
|
// order_type: OrderType,
|
||||||
|
// now_ts: u64,
|
||||||
|
// ) -> std::result::Result<(i64, i64, i64, i64), Error> {
|
||||||
|
// let (mut taker_base, mut taker_quote, bids_quantity, mut asks_quantity) = (0, 0, 0, 0i64);
|
||||||
|
|
||||||
|
// let (post_only, mut post_allowed, price) = match order_type {
|
||||||
|
// OrderType::Limit => (false, true, price),
|
||||||
|
// OrderType::ImmediateOrCancel => (false, false, price),
|
||||||
|
// OrderType::PostOnly => (true, true, price),
|
||||||
|
// OrderType::Market => (false, false, 1),
|
||||||
|
// OrderType::PostOnlySlide => {
|
||||||
|
// let price = if let Some(best_bid_price) = self.get_best_bid_price(now_ts) {
|
||||||
|
// price.max(best_bid_price.checked_add(1).ok_or(MangoError::SomeError)?)
|
||||||
|
// // todo math_err
|
||||||
|
// } else {
|
||||||
|
// price
|
||||||
|
// };
|
||||||
|
// (true, true, price)
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// // if post_allowed {
|
||||||
|
// // // price limit check computed lazily to save CU on average
|
||||||
|
// // let native_price = market.lot_to_native_price(price);
|
||||||
|
// // if native_price.checked_div(oracle_price).unwrap() < info.maint_asset_weight {
|
||||||
|
// // msg!("Posting on book disallowed due to price limits");
|
||||||
|
// // post_allowed = false;
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// let mut rem_base_quantity = max_base_quantity; // base lots (aka contracts)
|
||||||
|
// let mut rem_quote_quantity = max_quote_quantity;
|
||||||
|
|
||||||
|
// for (_, best_bid) in self.bids.iter_valid(now_ts) {
|
||||||
|
// let best_bid_price = best_bid.price();
|
||||||
|
// if price > best_bid_price {
|
||||||
|
// break;
|
||||||
|
// } else if post_only {
|
||||||
|
// return Ok((taker_base, taker_quote, bids_quantity, asks_quantity));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let max_match_by_quote = rem_quote_quantity / best_bid_price;
|
||||||
|
// let match_quantity = rem_base_quantity
|
||||||
|
// .min(best_bid.quantity)
|
||||||
|
// .min(max_match_by_quote);
|
||||||
|
|
||||||
|
// let match_quote = match_quantity * best_bid_price;
|
||||||
|
// rem_base_quantity -= match_quantity;
|
||||||
|
// rem_quote_quantity -= match_quote;
|
||||||
|
|
||||||
|
// taker_base -= match_quantity;
|
||||||
|
// taker_quote += match_quote;
|
||||||
|
// if match_quantity == max_match_by_quote || rem_base_quantity == 0 {
|
||||||
|
// break;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let mut rem_base_quantity = max_base_quantity; // base lots (aka contracts)
|
// let book_base_quantity = rem_base_quantity.min(rem_quote_quantity / price);
|
||||||
let mut rem_quote_quantity = max_quote_quantity;
|
// if post_allowed && book_base_quantity > 0 {
|
||||||
|
// asks_quantity = asks_quantity.checked_add(book_base_quantity).unwrap();
|
||||||
for (_, best_ask) in self.asks.iter_valid(now_ts) {
|
|
||||||
let best_ask_price = best_ask.price();
|
|
||||||
if price < best_ask_price {
|
|
||||||
break;
|
|
||||||
} else if post_only {
|
|
||||||
return Ok((taker_base, taker_quote, bids_quantity, asks_quantity));
|
|
||||||
}
|
|
||||||
|
|
||||||
let max_match_by_quote = rem_quote_quantity / best_ask_price;
|
|
||||||
let match_quantity = rem_base_quantity
|
|
||||||
.min(best_ask.quantity)
|
|
||||||
.min(max_match_by_quote);
|
|
||||||
|
|
||||||
let match_quote = match_quantity * best_ask_price;
|
|
||||||
rem_base_quantity -= match_quantity;
|
|
||||||
rem_quote_quantity -= match_quote;
|
|
||||||
|
|
||||||
taker_base += match_quantity;
|
|
||||||
taker_quote -= match_quote;
|
|
||||||
if match_quantity == max_match_by_quote || rem_base_quantity == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let book_base_quantity = rem_base_quantity.min(rem_quote_quantity / price);
|
|
||||||
if post_allowed && book_base_quantity > 0 {
|
|
||||||
bids_quantity = bids_quantity.checked_add(book_base_quantity).unwrap();
|
|
||||||
}
|
|
||||||
Ok((taker_base, taker_quote, bids_quantity, asks_quantity))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sim_new_ask(
|
|
||||||
&self,
|
|
||||||
market: &PerpMarket,
|
|
||||||
// info: &PerpMarketInfo,
|
|
||||||
oracle_price: I80F48,
|
|
||||||
price: i64,
|
|
||||||
max_base_quantity: i64, // guaranteed to be greater than zero due to initial check
|
|
||||||
max_quote_quantity: i64, // guaranteed to be greater than zero due to initial check
|
|
||||||
order_type: OrderType,
|
|
||||||
now_ts: u64,
|
|
||||||
) -> std::result::Result<(i64, i64, i64, i64), Error> {
|
|
||||||
let (mut taker_base, mut taker_quote, bids_quantity, mut asks_quantity) = (0, 0, 0, 0i64);
|
|
||||||
|
|
||||||
let (post_only, mut post_allowed, price) = match order_type {
|
|
||||||
OrderType::Limit => (false, true, price),
|
|
||||||
OrderType::ImmediateOrCancel => (false, false, price),
|
|
||||||
OrderType::PostOnly => (true, true, price),
|
|
||||||
OrderType::Market => (false, false, 1),
|
|
||||||
OrderType::PostOnlySlide => {
|
|
||||||
let price = if let Some(best_bid_price) = self.get_best_bid_price(now_ts) {
|
|
||||||
price.max(best_bid_price.checked_add(1).ok_or(MangoError::SomeError)?)
|
|
||||||
// todo math_err
|
|
||||||
} else {
|
|
||||||
price
|
|
||||||
};
|
|
||||||
(true, true, price)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// if post_allowed {
|
|
||||||
// // price limit check computed lazily to save CU on average
|
|
||||||
// let native_price = market.lot_to_native_price(price);
|
|
||||||
// if native_price.checked_div(oracle_price).unwrap() < info.maint_asset_weight {
|
|
||||||
// msg!("Posting on book disallowed due to price limits");
|
|
||||||
// post_allowed = false;
|
|
||||||
// }
|
// }
|
||||||
|
// Ok((taker_base, taker_quote, bids_quantity, asks_quantity))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let mut rem_base_quantity = max_base_quantity; // base lots (aka contracts)
|
|
||||||
let mut rem_quote_quantity = max_quote_quantity;
|
|
||||||
|
|
||||||
for (_, best_bid) in self.bids.iter_valid(now_ts) {
|
|
||||||
let best_bid_price = best_bid.price();
|
|
||||||
if price > best_bid_price {
|
|
||||||
break;
|
|
||||||
} else if post_only {
|
|
||||||
return Ok((taker_base, taker_quote, bids_quantity, asks_quantity));
|
|
||||||
}
|
|
||||||
|
|
||||||
let max_match_by_quote = rem_quote_quantity / best_bid_price;
|
|
||||||
let match_quantity = rem_base_quantity
|
|
||||||
.min(best_bid.quantity)
|
|
||||||
.min(max_match_by_quote);
|
|
||||||
|
|
||||||
let match_quote = match_quantity * best_bid_price;
|
|
||||||
rem_base_quantity -= match_quantity;
|
|
||||||
rem_quote_quantity -= match_quote;
|
|
||||||
|
|
||||||
taker_base -= match_quantity;
|
|
||||||
taker_quote += match_quote;
|
|
||||||
if match_quantity == max_match_by_quote || rem_base_quantity == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let book_base_quantity = rem_base_quantity.min(rem_quote_quantity / price);
|
|
||||||
if post_allowed && book_base_quantity > 0 {
|
|
||||||
asks_quantity = asks_quantity.checked_add(book_base_quantity).unwrap();
|
|
||||||
}
|
|
||||||
Ok((taker_base, taker_quote, bids_quantity, asks_quantity))
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: can new_bid and new_ask be elegantly folded into one method?
|
// todo: can new_bid and new_ask be elegantly folded into one method?
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn new_bid(
|
pub fn new_bid(
|
||||||
|
@ -342,8 +341,8 @@ impl<'a> Book<'a> {
|
||||||
// mango_cache: &MangoCache,
|
// mango_cache: &MangoCache,
|
||||||
// event_queue: &mut EventQueue,
|
// event_queue: &mut EventQueue,
|
||||||
market: &mut PerpMarket,
|
market: &mut PerpMarket,
|
||||||
oracle_price: I80F48,
|
// oracle_price: I80F48,
|
||||||
mango_account: &mut MangoAccount,
|
// mango_account: &mut MangoAccount,
|
||||||
mango_account_pk: &Pubkey,
|
mango_account_pk: &Pubkey,
|
||||||
// market_index: usize,
|
// market_index: usize,
|
||||||
price: i64,
|
price: i64,
|
||||||
|
@ -438,12 +437,12 @@ impl<'a> Book<'a> {
|
||||||
.min(max_match_by_quote);
|
.min(max_match_by_quote);
|
||||||
let done = match_quantity == max_match_by_quote || match_quantity == rem_base_quantity;
|
let done = match_quantity == max_match_by_quote || match_quantity == rem_base_quantity;
|
||||||
|
|
||||||
let match_quote = match_quantity * best_ask_price;
|
let match_quote = cm!(match_quantity * best_ask_price);
|
||||||
rem_base_quantity -= match_quantity;
|
rem_base_quantity = cm!(rem_base_quantity - match_quantity);
|
||||||
rem_quote_quantity -= match_quote;
|
rem_quote_quantity = cm!(rem_quote_quantity - match_quote);
|
||||||
// mango_account.perp_accounts[market_index].add_taker_trade(match_quantity, -match_quote);
|
// mango_account.perp_accounts[market_index].add_taker_trade(match_quantity, -match_quote);
|
||||||
|
|
||||||
let new_best_ask_quantity = best_ask.quantity - match_quantity;
|
let new_best_ask_quantity = cm!(best_ask.quantity - match_quantity);
|
||||||
let maker_out = new_best_ask_quantity == 0;
|
let maker_out = new_best_ask_quantity == 0;
|
||||||
if maker_out {
|
if maker_out {
|
||||||
ask_deletes.push(best_ask.key);
|
ask_deletes.push(best_ask.key);
|
||||||
|
@ -495,7 +494,7 @@ impl<'a> Book<'a> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let total_quote_taken = max_quote_quantity - rem_quote_quantity;
|
// let total_quote_taken = cm!(max_quote_quantity - rem_quote_quantity);
|
||||||
|
|
||||||
// Apply changes to matched asks (handles invalidate on delete!)
|
// Apply changes to matched asks (handles invalidate on delete!)
|
||||||
for (handle, new_quantity) in ask_changes {
|
for (handle, new_quantity) in ask_changes {
|
||||||
|
@ -514,7 +513,7 @@ impl<'a> Book<'a> {
|
||||||
let book_base_quantity = rem_base_quantity.min(rem_quote_quantity / price);
|
let book_base_quantity = rem_base_quantity.min(rem_quote_quantity / price);
|
||||||
if post_allowed && book_base_quantity > 0 {
|
if post_allowed && book_base_quantity > 0 {
|
||||||
// Drop an expired order if possible
|
// Drop an expired order if possible
|
||||||
if let Some(expired_bid) = self.bids.remove_one_expired(now_ts) {
|
// if let Some(expired_bid) = self.bids.remove_one_expired(now_ts) {
|
||||||
// let event = OutEvent::new(
|
// let event = OutEvent::new(
|
||||||
// Side::Bid,
|
// Side::Bid,
|
||||||
// expired_bid.owner_slot,
|
// expired_bid.owner_slot,
|
||||||
|
@ -524,7 +523,7 @@ impl<'a> Book<'a> {
|
||||||
// expired_bid.quantity,
|
// expired_bid.quantity,
|
||||||
// );
|
// );
|
||||||
// event_queue.push_back(cast(event)).unwrap();
|
// event_queue.push_back(cast(event)).unwrap();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if self.bids.is_full() {
|
if self.bids.is_full() {
|
||||||
// If this bid is higher than lowest bid, boot that bid and insert this one
|
// If this bid is higher than lowest bid, boot that bid and insert this one
|
||||||
|
@ -599,271 +598,271 @@ impl<'a> Book<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(never)]
|
// #[inline(never)]
|
||||||
pub fn new_ask(
|
// pub fn new_ask(
|
||||||
&mut self,
|
// &mut self,
|
||||||
program_id: &Pubkey,
|
// program_id: &Pubkey,
|
||||||
// mango_group: &MangoGroup,
|
// // mango_group: &MangoGroup,
|
||||||
mango_group_pk: &Pubkey,
|
// mango_group_pk: &Pubkey,
|
||||||
// mango_cache: &MangoCache,
|
// // mango_cache: &MangoCache,
|
||||||
event_queue: &mut EventQueue,
|
// event_queue: &mut EventQueue,
|
||||||
market: &mut PerpMarket,
|
// market: &mut PerpMarket,
|
||||||
oracle_price: I80F48,
|
// oracle_price: I80F48,
|
||||||
mango_account: &mut MangoAccount,
|
// mango_account: &mut MangoAccount,
|
||||||
mango_account_pk: &Pubkey,
|
// mango_account_pk: &Pubkey,
|
||||||
market_index: usize,
|
// market_index: usize,
|
||||||
price: i64,
|
// price: i64,
|
||||||
max_base_quantity: i64, // guaranteed to be greater than zero due to initial check
|
// max_base_quantity: i64, // guaranteed to be greater than zero due to initial check
|
||||||
max_quote_quantity: i64, // guaranteed to be greater than zero due to initial check
|
// max_quote_quantity: i64, // guaranteed to be greater than zero due to initial check
|
||||||
order_type: OrderType,
|
// order_type: OrderType,
|
||||||
time_in_force: u8,
|
// time_in_force: u8,
|
||||||
client_order_id: u64,
|
// client_order_id: u64,
|
||||||
now_ts: u64,
|
// now_ts: u64,
|
||||||
referrer_mango_account_ai: Option<&AccountInfo>,
|
// referrer_mango_account_ai: Option<&AccountInfo>,
|
||||||
mut limit: u8, // max number of FillEvents allowed; guaranteed to be greater than 0
|
// mut limit: u8, // max number of FillEvents allowed; guaranteed to be greater than 0
|
||||||
) -> Result<()> {
|
// ) -> Result<()> {
|
||||||
let (post_only, mut post_allowed, price) = match order_type {
|
// let (post_only, mut post_allowed, price) = match order_type {
|
||||||
OrderType::Limit => (false, true, price),
|
// OrderType::Limit => (false, true, price),
|
||||||
OrderType::ImmediateOrCancel => (false, false, price),
|
// OrderType::ImmediateOrCancel => (false, false, price),
|
||||||
OrderType::PostOnly => (true, true, price),
|
// OrderType::PostOnly => (true, true, price),
|
||||||
OrderType::Market => (false, false, 1),
|
// OrderType::Market => (false, false, 1),
|
||||||
OrderType::PostOnlySlide => {
|
// OrderType::PostOnlySlide => {
|
||||||
let price = if let Some(best_bid_price) = self.get_best_bid_price(now_ts) {
|
// let price = if let Some(best_bid_price) = self.get_best_bid_price(now_ts) {
|
||||||
price.max(best_bid_price.checked_add(1).ok_or(MangoError::SomeError)?)
|
// price.max(best_bid_price.checked_add(1).ok_or(MangoError::SomeError)?)
|
||||||
// math_err
|
// // math_err
|
||||||
} else {
|
// } else {
|
||||||
price
|
// price
|
||||||
};
|
// };
|
||||||
(true, true, price)
|
// (true, true, price)
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// let info = &mango_group.perp_markets[market_index];
|
|
||||||
// if post_allowed {
|
|
||||||
// // price limit check computed lazily to save CU on average
|
|
||||||
// let native_price = market.lot_to_native_price(price);
|
|
||||||
// if native_price.checked_div(oracle_price).unwrap() < info.maint_asset_weight {
|
|
||||||
// msg!("Posting on book disallowed due to price limits");
|
|
||||||
// post_allowed = false;
|
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
// referral fee related variables
|
// // let info = &mango_group.perp_markets[market_index];
|
||||||
// let mut ref_fee_rate = None;
|
// // if post_allowed {
|
||||||
// let mut referrer_mango_account_opt = None;
|
// // // price limit check computed lazily to save CU on average
|
||||||
|
// // let native_price = market.lot_to_native_price(price);
|
||||||
|
// // if native_price.checked_div(oracle_price).unwrap() < info.maint_asset_weight {
|
||||||
|
// // msg!("Posting on book disallowed due to price limits");
|
||||||
|
// // post_allowed = false;
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|
||||||
// generate new order id
|
// // referral fee related variables
|
||||||
let order_id = market.gen_order_id(Side::Ask, price);
|
// // let mut ref_fee_rate = None;
|
||||||
|
// // let mut referrer_mango_account_opt = None;
|
||||||
|
|
||||||
// Iterate through book and match against this new ask
|
// // generate new order id
|
||||||
//
|
// let order_id = market.gen_order_id(Side::Ask, price);
|
||||||
// Any changes to matching bids are collected in bid_changes
|
|
||||||
// and then applied after this loop.
|
|
||||||
let mut rem_base_quantity = max_base_quantity; // base lots (aka contracts)
|
|
||||||
let mut rem_quote_quantity = max_quote_quantity;
|
|
||||||
let mut bid_changes: Vec<(NodeHandle, i64)> = vec![];
|
|
||||||
let mut bid_deletes: Vec<i128> = vec![];
|
|
||||||
let mut number_of_dropped_expired_orders = 0;
|
|
||||||
for (best_bid_h, best_bid) in self.bids.iter_all_including_invalid() {
|
|
||||||
if !best_bid.is_valid(now_ts) {
|
|
||||||
// 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;
|
|
||||||
let event = OutEvent::new(
|
|
||||||
Side::Bid,
|
|
||||||
best_bid.owner_slot,
|
|
||||||
now_ts,
|
|
||||||
event_queue.header.seq_num,
|
|
||||||
best_bid.owner,
|
|
||||||
best_bid.quantity,
|
|
||||||
);
|
|
||||||
event_queue.push_back(cast(event)).unwrap();
|
|
||||||
bid_deletes.push(best_bid.key);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let best_bid_price = best_bid.price();
|
// // Iterate through book and match against this new ask
|
||||||
|
// //
|
||||||
if price > best_bid_price {
|
// // Any changes to matching bids are collected in bid_changes
|
||||||
break;
|
// // and then applied after this loop.
|
||||||
} else if post_only {
|
// let mut rem_base_quantity = max_base_quantity; // base lots (aka contracts)
|
||||||
msg!("Order could not be placed due to PostOnly");
|
// let mut rem_quote_quantity = max_quote_quantity;
|
||||||
post_allowed = false;
|
// let mut bid_changes: Vec<(NodeHandle, i64)> = vec![];
|
||||||
break; // return silently to not fail other instructions in tx
|
// let mut bid_deletes: Vec<i128> = vec![];
|
||||||
} else if limit == 0 {
|
// let mut number_of_dropped_expired_orders = 0;
|
||||||
msg!("Order matching limit reached");
|
// for (best_bid_h, best_bid) in self.bids.iter_all_including_invalid() {
|
||||||
post_allowed = false;
|
// if !best_bid.is_valid(now_ts) {
|
||||||
break;
|
// // 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;
|
||||||
let max_match_by_quote = rem_quote_quantity / best_bid_price;
|
// let event = OutEvent::new(
|
||||||
let match_quantity = rem_base_quantity
|
// Side::Bid,
|
||||||
.min(best_bid.quantity)
|
|
||||||
.min(max_match_by_quote);
|
|
||||||
let done = match_quantity == max_match_by_quote || match_quantity == rem_base_quantity;
|
|
||||||
|
|
||||||
let match_quote = match_quantity * best_bid_price;
|
|
||||||
rem_base_quantity -= match_quantity;
|
|
||||||
rem_quote_quantity -= match_quote;
|
|
||||||
// mango_account.perp_accounts[market_index].add_taker_trade(-match_quantity, match_quote);
|
|
||||||
|
|
||||||
let new_best_bid_quantity = best_bid.quantity - match_quantity;
|
|
||||||
let maker_out = new_best_bid_quantity == 0;
|
|
||||||
if maker_out {
|
|
||||||
bid_deletes.push(best_bid.key);
|
|
||||||
} else {
|
|
||||||
bid_changes.push((best_bid_h, new_best_bid_quantity));
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo
|
|
||||||
// if ref_fee_rate is none, determine it
|
|
||||||
// if ref_valid, then pay into referrer, else pay to perp market
|
|
||||||
// if ref_fee_rate.is_none() {
|
|
||||||
// let (a, b) = determine_ref_vars(
|
|
||||||
// program_id,
|
|
||||||
// mango_group,
|
|
||||||
// mango_group_pk,
|
|
||||||
// mango_cache,
|
|
||||||
// mango_account,
|
|
||||||
// referrer_mango_account_ai,
|
|
||||||
// now_ts,
|
|
||||||
// )?;
|
|
||||||
// ref_fee_rate = Some(a);
|
|
||||||
// referrer_mango_account_opt = b;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let fill = FillEvent::new(
|
|
||||||
// Side::Ask,
|
|
||||||
// best_bid.owner_slot,
|
// best_bid.owner_slot,
|
||||||
// maker_out,
|
|
||||||
// now_ts,
|
// now_ts,
|
||||||
// event_queue.header.seq_num,
|
// event_queue.header.seq_num,
|
||||||
// best_bid.owner,
|
// best_bid.owner,
|
||||||
// best_bid.key,
|
// best_bid.quantity,
|
||||||
// best_bid.client_order_id,
|
|
||||||
// info.maker_fee,
|
|
||||||
// best_bid.best_initial,
|
|
||||||
// best_bid.timestamp,
|
|
||||||
// *mango_account_pk,
|
|
||||||
// order_id,
|
|
||||||
// client_order_id,
|
|
||||||
// info.taker_fee + ref_fee_rate.unwrap(),
|
|
||||||
// best_bid_price,
|
|
||||||
// match_quantity,
|
|
||||||
// best_bid.version,
|
|
||||||
// );
|
// );
|
||||||
|
// event_queue.push_back(cast(event)).unwrap();
|
||||||
// event_queue.push_back(cast(fill)).unwrap();
|
// bid_deletes.push(best_bid.key);
|
||||||
limit -= 1;
|
|
||||||
|
|
||||||
if done {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let total_quote_taken = max_quote_quantity - rem_quote_quantity;
|
|
||||||
|
|
||||||
// Apply changes to matched bids (handles invalidate on delete!)
|
|
||||||
for (handle, new_quantity) in bid_changes {
|
|
||||||
self.bids
|
|
||||||
.get_mut(handle)
|
|
||||||
.unwrap()
|
|
||||||
.as_leaf_mut()
|
|
||||||
.unwrap()
|
|
||||||
.quantity = new_quantity;
|
|
||||||
}
|
|
||||||
for key in bid_deletes {
|
|
||||||
let _removed_leaf = self.bids.remove_by_key(key).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are still quantity unmatched, place on the book
|
|
||||||
let book_base_quantity = rem_base_quantity.min(rem_quote_quantity / price);
|
|
||||||
if book_base_quantity > 0 && post_allowed {
|
|
||||||
// Drop an expired order if possible
|
|
||||||
if let Some(expired_ask) = self.asks.remove_one_expired(now_ts) {
|
|
||||||
let event = OutEvent::new(
|
|
||||||
Side::Ask,
|
|
||||||
expired_ask.owner_slot,
|
|
||||||
now_ts,
|
|
||||||
event_queue.header.seq_num,
|
|
||||||
expired_ask.owner,
|
|
||||||
expired_ask.quantity,
|
|
||||||
);
|
|
||||||
event_queue.push_back(cast(event)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.asks.is_full() {
|
|
||||||
// If this asks is lower than highest ask, boot that ask and insert this one
|
|
||||||
let max_ask = self.asks.remove_max().unwrap();
|
|
||||||
require!(price < max_ask.price(), MangoError::SomeError); // OutOfSpace
|
|
||||||
let event = OutEvent::new(
|
|
||||||
Side::Ask,
|
|
||||||
max_ask.owner_slot,
|
|
||||||
now_ts,
|
|
||||||
event_queue.header.seq_num,
|
|
||||||
max_ask.owner,
|
|
||||||
max_ask.quantity,
|
|
||||||
);
|
|
||||||
event_queue.push_back(cast(event)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// let best_initial = if market.meta_data.version == 0 {
|
|
||||||
// match self.get_best_ask_price(now_ts) {
|
|
||||||
// None => price,
|
|
||||||
// Some(p) => p,
|
|
||||||
// }
|
// }
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let best_bid_price = best_bid.price();
|
||||||
|
|
||||||
|
// if price > best_bid_price {
|
||||||
|
// 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;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let max_match_by_quote = rem_quote_quantity / best_bid_price;
|
||||||
|
// let match_quantity = rem_base_quantity
|
||||||
|
// .min(best_bid.quantity)
|
||||||
|
// .min(max_match_by_quote);
|
||||||
|
// let done = match_quantity == max_match_by_quote || match_quantity == rem_base_quantity;
|
||||||
|
|
||||||
|
// let match_quote = match_quantity * best_bid_price;
|
||||||
|
// rem_base_quantity -= match_quantity;
|
||||||
|
// rem_quote_quantity -= match_quote;
|
||||||
|
// // mango_account.perp_accounts[market_index].add_taker_trade(-match_quantity, match_quote);
|
||||||
|
|
||||||
|
// let new_best_bid_quantity = best_bid.quantity - match_quantity;
|
||||||
|
// let maker_out = new_best_bid_quantity == 0;
|
||||||
|
// if maker_out {
|
||||||
|
// bid_deletes.push(best_bid.key);
|
||||||
// } else {
|
// } else {
|
||||||
// let max_depth: i64 = market.liquidity_mining_info.max_depth_bps.to_num();
|
// bid_changes.push((best_bid_h, new_best_bid_quantity));
|
||||||
// self.get_asks_size_below(price, max_depth, now_ts)
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let owner_slot = mango_account
|
|
||||||
// .next_order_slot()
|
|
||||||
// .ok_or(MangoError::SomeError)?; // TooManyOpenOrders
|
|
||||||
let new_ask = LeafNode::new(
|
|
||||||
1, // todo market.meta_data.version,
|
|
||||||
0, // todo owner_slot as u8,
|
|
||||||
order_id,
|
|
||||||
*mango_account_pk,
|
|
||||||
book_base_quantity,
|
|
||||||
client_order_id,
|
|
||||||
now_ts,
|
|
||||||
0, // todo best_initial,
|
|
||||||
order_type,
|
|
||||||
time_in_force,
|
|
||||||
);
|
|
||||||
let _result = self.asks.insert_leaf(&new_ask)?;
|
|
||||||
|
|
||||||
// TODO OPT remove if PlacePerpOrder needs more compute
|
|
||||||
msg!(
|
|
||||||
"ask on book order_id={} quantity={} price={}",
|
|
||||||
order_id,
|
|
||||||
book_base_quantity,
|
|
||||||
price
|
|
||||||
);
|
|
||||||
|
|
||||||
// mango_account.add_order(market_index, Side::Ask, &new_ask)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there were matched taker quote apply ref fees
|
|
||||||
// we know ref_fee_rate is not None if total_quote_taken > 0
|
|
||||||
// if total_quote_taken > 0 {
|
|
||||||
// apply_fees(
|
|
||||||
// market,
|
|
||||||
// info,
|
|
||||||
// mango_account,
|
|
||||||
// mango_account_pk,
|
|
||||||
// market_index,
|
|
||||||
// referrer_mango_account_opt,
|
|
||||||
// referrer_mango_account_ai,
|
|
||||||
// total_quote_taken,
|
|
||||||
// ref_fee_rate.unwrap(),
|
|
||||||
// // &mango_cache.perp_market_cache[market_index],
|
|
||||||
// );
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Ok(())
|
// // todo
|
||||||
}
|
// // if ref_fee_rate is none, determine it
|
||||||
|
// // if ref_valid, then pay into referrer, else pay to perp market
|
||||||
|
// // if ref_fee_rate.is_none() {
|
||||||
|
// // let (a, b) = determine_ref_vars(
|
||||||
|
// // program_id,
|
||||||
|
// // mango_group,
|
||||||
|
// // mango_group_pk,
|
||||||
|
// // mango_cache,
|
||||||
|
// // mango_account,
|
||||||
|
// // referrer_mango_account_ai,
|
||||||
|
// // now_ts,
|
||||||
|
// // )?;
|
||||||
|
// // ref_fee_rate = Some(a);
|
||||||
|
// // referrer_mango_account_opt = b;
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // let fill = FillEvent::new(
|
||||||
|
// // Side::Ask,
|
||||||
|
// // best_bid.owner_slot,
|
||||||
|
// // maker_out,
|
||||||
|
// // now_ts,
|
||||||
|
// // event_queue.header.seq_num,
|
||||||
|
// // best_bid.owner,
|
||||||
|
// // best_bid.key,
|
||||||
|
// // best_bid.client_order_id,
|
||||||
|
// // info.maker_fee,
|
||||||
|
// // best_bid.best_initial,
|
||||||
|
// // best_bid.timestamp,
|
||||||
|
// // *mango_account_pk,
|
||||||
|
// // order_id,
|
||||||
|
// // client_order_id,
|
||||||
|
// // info.taker_fee + ref_fee_rate.unwrap(),
|
||||||
|
// // best_bid_price,
|
||||||
|
// // match_quantity,
|
||||||
|
// // best_bid.version,
|
||||||
|
// // );
|
||||||
|
|
||||||
|
// // event_queue.push_back(cast(fill)).unwrap();
|
||||||
|
// limit -= 1;
|
||||||
|
|
||||||
|
// if done {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// let total_quote_taken = max_quote_quantity - rem_quote_quantity;
|
||||||
|
|
||||||
|
// // Apply changes to matched bids (handles invalidate on delete!)
|
||||||
|
// for (handle, new_quantity) in bid_changes {
|
||||||
|
// self.bids
|
||||||
|
// .get_mut(handle)
|
||||||
|
// .unwrap()
|
||||||
|
// .as_leaf_mut()
|
||||||
|
// .unwrap()
|
||||||
|
// .quantity = new_quantity;
|
||||||
|
// }
|
||||||
|
// for key in bid_deletes {
|
||||||
|
// let _removed_leaf = self.bids.remove_by_key(key).unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // If there are still quantity unmatched, place on the book
|
||||||
|
// let book_base_quantity = rem_base_quantity.min(rem_quote_quantity / price);
|
||||||
|
// if book_base_quantity > 0 && post_allowed {
|
||||||
|
// // Drop an expired order if possible
|
||||||
|
// if let Some(expired_ask) = self.asks.remove_one_expired(now_ts) {
|
||||||
|
// let event = OutEvent::new(
|
||||||
|
// Side::Ask,
|
||||||
|
// expired_ask.owner_slot,
|
||||||
|
// now_ts,
|
||||||
|
// event_queue.header.seq_num,
|
||||||
|
// expired_ask.owner,
|
||||||
|
// expired_ask.quantity,
|
||||||
|
// );
|
||||||
|
// event_queue.push_back(cast(event)).unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if self.asks.is_full() {
|
||||||
|
// // If this asks is lower than highest ask, boot that ask and insert this one
|
||||||
|
// let max_ask = self.asks.remove_max().unwrap();
|
||||||
|
// require!(price < max_ask.price(), MangoError::SomeError); // OutOfSpace
|
||||||
|
// let event = OutEvent::new(
|
||||||
|
// Side::Ask,
|
||||||
|
// max_ask.owner_slot,
|
||||||
|
// now_ts,
|
||||||
|
// event_queue.header.seq_num,
|
||||||
|
// max_ask.owner,
|
||||||
|
// max_ask.quantity,
|
||||||
|
// );
|
||||||
|
// event_queue.push_back(cast(event)).unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // let best_initial = if market.meta_data.version == 0 {
|
||||||
|
// // match self.get_best_ask_price(now_ts) {
|
||||||
|
// // None => price,
|
||||||
|
// // Some(p) => p,
|
||||||
|
// // }
|
||||||
|
// // } else {
|
||||||
|
// // let max_depth: i64 = market.liquidity_mining_info.max_depth_bps.to_num();
|
||||||
|
// // self.get_asks_size_below(price, max_depth, now_ts)
|
||||||
|
// // };
|
||||||
|
|
||||||
|
// // let owner_slot = mango_account
|
||||||
|
// // .next_order_slot()
|
||||||
|
// // .ok_or(MangoError::SomeError)?; // TooManyOpenOrders
|
||||||
|
// let new_ask = LeafNode::new(
|
||||||
|
// 1, // todo market.meta_data.version,
|
||||||
|
// 0, // todo owner_slot as u8,
|
||||||
|
// order_id,
|
||||||
|
// *mango_account_pk,
|
||||||
|
// book_base_quantity,
|
||||||
|
// client_order_id,
|
||||||
|
// now_ts,
|
||||||
|
// 0, // todo best_initial,
|
||||||
|
// order_type,
|
||||||
|
// time_in_force,
|
||||||
|
// );
|
||||||
|
// let _result = self.asks.insert_leaf(&new_ask)?;
|
||||||
|
|
||||||
|
// // TODO OPT remove if PlacePerpOrder needs more compute
|
||||||
|
// msg!(
|
||||||
|
// "ask on book order_id={} quantity={} price={}",
|
||||||
|
// order_id,
|
||||||
|
// book_base_quantity,
|
||||||
|
// price
|
||||||
|
// );
|
||||||
|
|
||||||
|
// // mango_account.add_order(market_index, Side::Ask, &new_ask)?;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // if there were matched taker quote apply ref fees
|
||||||
|
// // we know ref_fee_rate is not None if total_quote_taken > 0
|
||||||
|
// // if total_quote_taken > 0 {
|
||||||
|
// // apply_fees(
|
||||||
|
// // market,
|
||||||
|
// // info,
|
||||||
|
// // mango_account,
|
||||||
|
// // mango_account_pk,
|
||||||
|
// // market_index,
|
||||||
|
// // referrer_mango_account_opt,
|
||||||
|
// // referrer_mango_account_ai,
|
||||||
|
// // total_quote_taken,
|
||||||
|
// // ref_fee_rate.unwrap(),
|
||||||
|
// // // &mango_cache.perp_market_cache[market_index],
|
||||||
|
// // );
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
// pub fn cancel_order(&mut self, order_id: i128, side: Side) -> Result<()> {
|
// pub fn cancel_order(&mut self, order_id: i128, side: Side) -> Result<()> {
|
||||||
// match side {
|
// match side {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use bytemuck::Zeroable;
|
|
||||||
use std::cell::RefMut;
|
use std::cell::RefMut;
|
||||||
|
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
@ -15,8 +14,6 @@ use crate::state::orderbook::nodes::{
|
||||||
};
|
};
|
||||||
use crate::util::LoadZeroCopy;
|
use crate::util::LoadZeroCopy;
|
||||||
|
|
||||||
use super::Book;
|
|
||||||
|
|
||||||
pub const MAX_BOOK_NODES: usize = 1024;
|
pub const MAX_BOOK_NODES: usize = 1024;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use mango_macro::Pod;
|
use mango_macro::Pod;
|
||||||
|
|
||||||
use super::datatype::DataType;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Pod, Default)]
|
#[derive(Copy, Clone, Pod, Default)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
/// Stores meta information about the `Account` on chain
|
/// Stores meta information about the `Account` on chain
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use anchor_lang::prelude::Error;
|
use anchor_lang::prelude::Error;
|
||||||
use bytemuck::{bytes_of, cast_slice_mut, from_bytes_mut, Contiguous, Pod};
|
use bytemuck::{cast_slice_mut, from_bytes_mut, Pod};
|
||||||
|
|
||||||
use solana_program::account_info::AccountInfo;
|
use solana_program::account_info::AccountInfo;
|
||||||
use solana_program::program_error::ProgramError;
|
|
||||||
use solana_program::pubkey::Pubkey;
|
|
||||||
use std::cell::RefMut;
|
use std::cell::RefMut;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use bytemuck::{Pod, Zeroable};
|
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
|
|
@ -1,438 +1,437 @@
|
||||||
use std::cell::RefMut;
|
// use std::cell::RefMut;
|
||||||
use std::mem::size_of;
|
// use std::mem::size_of;
|
||||||
|
|
||||||
use crate::error::MangoError;
|
// use crate::error::MangoError;
|
||||||
use crate::state::orderbook::datatype::DataType;
|
// use crate::state::PerpMarket;
|
||||||
use crate::state::PerpMarket;
|
// use anchor_lang::prelude::*;
|
||||||
use anchor_lang::prelude::*;
|
// use fixed::types::I80F48;
|
||||||
use fixed::types::I80F48;
|
// use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
// use solana_program::account_info::AccountInfo;
|
||||||
use solana_program::account_info::AccountInfo;
|
// use solana_program::pubkey::Pubkey;
|
||||||
use solana_program::pubkey::Pubkey;
|
// use solana_program::sysvar::rent::Rent;
|
||||||
use solana_program::sysvar::rent::Rent;
|
// use static_assertions::const_assert_eq;
|
||||||
use static_assertions::const_assert_eq;
|
|
||||||
|
|
||||||
// use mango_logs::FillLog;
|
// // use mango_logs::FillLog;
|
||||||
use mango_macro::Pod;
|
// use mango_macro::Pod;
|
||||||
|
|
||||||
use super::metadata::MetaData;
|
// use super::metadata::MetaData;
|
||||||
use super::ob_utils::strip_header_mut;
|
// use super::ob_utils::strip_header_mut;
|
||||||
use super::order_type::Side;
|
// use super::order_type::Side;
|
||||||
// use safe_transmute::{self, trivial::TriviallyTransmutable};
|
// // use safe_transmute::{self, trivial::TriviallyTransmutable};
|
||||||
|
|
||||||
// use crate::error::{check_assert, MangoErrorCode, MangoResult, SourceFileId};
|
// // use crate::error::{check_assert, MangoErrorCode, MangoResult, SourceFileId};
|
||||||
// use crate::matching::Side;
|
// // use crate::matching::Side;
|
||||||
// use crate::state::{DataType, MetaData, PerpMarket};
|
// // use crate::state::{DataType, MetaData, PerpMarket};
|
||||||
// use crate::utils::strip_header_mut;
|
// // use crate::utils::strip_header_mut;
|
||||||
|
|
||||||
// Don't want event queue to become single threaded if it's logging liquidations
|
// // Don't want event queue to become single threaded if it's logging liquidations
|
||||||
// Most common scenario will be liqors depositing USDC and withdrawing some other token
|
// // Most common scenario will be liqors depositing USDC and withdrawing some other token
|
||||||
// So tying it to token deposited is not wise
|
// // So tying it to token deposited is not wise
|
||||||
// also can't tie it to token withdrawn because during bull market, liqs will be depositing all base tokens and withdrawing quote
|
// // also can't tie it to token withdrawn because during bull market, liqs will be depositing all base tokens and withdrawing quote
|
||||||
//
|
// //
|
||||||
|
|
||||||
pub trait QueueHeader: bytemuck::Pod {
|
// pub trait QueueHeader: bytemuck::Pod {
|
||||||
type Item: bytemuck::Pod + Copy;
|
// type Item: bytemuck::Pod + Copy;
|
||||||
|
|
||||||
fn head(&self) -> usize;
|
// fn head(&self) -> usize;
|
||||||
fn set_head(&mut self, value: usize);
|
// fn set_head(&mut self, value: usize);
|
||||||
fn count(&self) -> usize;
|
// fn count(&self) -> usize;
|
||||||
fn set_count(&mut self, value: usize);
|
// fn set_count(&mut self, value: usize);
|
||||||
|
|
||||||
fn incr_event_id(&mut self);
|
// fn incr_event_id(&mut self);
|
||||||
fn decr_event_id(&mut self, n: usize);
|
// fn decr_event_id(&mut self, n: usize);
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub struct Queue<'a, H: QueueHeader> {
|
// pub struct Queue<'a, H: QueueHeader> {
|
||||||
pub header: RefMut<'a, H>,
|
// pub header: RefMut<'a, H>,
|
||||||
pub buf: RefMut<'a, [H::Item]>,
|
// pub buf: RefMut<'a, [H::Item]>,
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl<'a, H: QueueHeader> Queue<'a, H> {
|
// impl<'a, H: QueueHeader> Queue<'a, H> {
|
||||||
pub fn new(header: RefMut<'a, H>, buf: RefMut<'a, [H::Item]>) -> Self {
|
// pub fn new(header: RefMut<'a, H>, buf: RefMut<'a, [H::Item]>) -> Self {
|
||||||
Self { header, buf }
|
// Self { header, buf }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn load_mut(account: &'a AccountInfo) -> Result<Self> {
|
// pub fn load_mut(account: &'a AccountInfo) -> Result<Self> {
|
||||||
let (header, buf) = strip_header_mut::<H, H::Item>(account)?;
|
// let (header, buf) = strip_header_mut::<H, H::Item>(account)?;
|
||||||
Ok(Self { header, buf })
|
// Ok(Self { header, buf })
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
// pub fn len(&self) -> usize {
|
||||||
self.header.count()
|
// self.header.count()
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn full(&self) -> bool {
|
// pub fn full(&self) -> bool {
|
||||||
self.header.count() == self.buf.len()
|
// self.header.count() == self.buf.len()
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn empty(&self) -> bool {
|
// pub fn empty(&self) -> bool {
|
||||||
self.header.count() == 0
|
// self.header.count() == 0
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn push_back(&mut self, value: H::Item) -> std::result::Result<(), H::Item> {
|
// pub fn push_back(&mut self, value: H::Item) -> std::result::Result<(), H::Item> {
|
||||||
if self.full() {
|
// if self.full() {
|
||||||
return Err(value);
|
// return Err(value);
|
||||||
}
|
// }
|
||||||
let slot = (self.header.head() + self.header.count()) % self.buf.len();
|
// let slot = (self.header.head() + self.header.count()) % self.buf.len();
|
||||||
self.buf[slot] = value;
|
// self.buf[slot] = value;
|
||||||
|
|
||||||
let count = self.header.count();
|
// let count = self.header.count();
|
||||||
self.header.set_count(count + 1);
|
// self.header.set_count(count + 1);
|
||||||
|
|
||||||
self.header.incr_event_id();
|
// self.header.incr_event_id();
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn peek_front(&self) -> Option<&H::Item> {
|
// pub fn peek_front(&self) -> Option<&H::Item> {
|
||||||
if self.empty() {
|
// if self.empty() {
|
||||||
return None;
|
// return None;
|
||||||
}
|
// }
|
||||||
Some(&self.buf[self.header.head()])
|
// Some(&self.buf[self.header.head()])
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn peek_front_mut(&mut self) -> Option<&mut H::Item> {
|
// pub fn peek_front_mut(&mut self) -> Option<&mut H::Item> {
|
||||||
if self.empty() {
|
// if self.empty() {
|
||||||
return None;
|
// return None;
|
||||||
}
|
// }
|
||||||
Some(&mut self.buf[self.header.head()])
|
// Some(&mut self.buf[self.header.head()])
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn pop_front(&mut self) -> std::result::Result<H::Item, ()> {
|
// pub fn pop_front(&mut self) -> std::result::Result<H::Item, ()> {
|
||||||
if self.empty() {
|
// if self.empty() {
|
||||||
return Err(());
|
// return Err(());
|
||||||
}
|
// }
|
||||||
let value = self.buf[self.header.head()];
|
// let value = self.buf[self.header.head()];
|
||||||
|
|
||||||
let count = self.header.count();
|
// let count = self.header.count();
|
||||||
self.header.set_count(count - 1);
|
// self.header.set_count(count - 1);
|
||||||
|
|
||||||
let head = self.header.head();
|
// let head = self.header.head();
|
||||||
self.header.set_head((head + 1) % self.buf.len());
|
// self.header.set_head((head + 1) % self.buf.len());
|
||||||
|
|
||||||
Ok(value)
|
// Ok(value)
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn revert_pushes(&mut self, desired_len: usize) -> Result<()> {
|
// pub fn revert_pushes(&mut self, desired_len: usize) -> Result<()> {
|
||||||
require!(desired_len <= self.header.count(), MangoError::SomeError);
|
// require!(desired_len <= self.header.count(), MangoError::SomeError);
|
||||||
let len_diff = self.header.count() - desired_len;
|
// let len_diff = self.header.count() - desired_len;
|
||||||
self.header.set_count(desired_len);
|
// self.header.set_count(desired_len);
|
||||||
self.header.decr_event_id(len_diff);
|
// self.header.decr_event_id(len_diff);
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &H::Item> {
|
// pub fn iter(&self) -> impl Iterator<Item = &H::Item> {
|
||||||
QueueIterator {
|
// QueueIterator {
|
||||||
queue: self,
|
// queue: self,
|
||||||
index: 0,
|
// index: 0,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
struct QueueIterator<'a, 'b, H: QueueHeader> {
|
// struct QueueIterator<'a, 'b, H: QueueHeader> {
|
||||||
queue: &'b Queue<'a, H>,
|
// queue: &'b Queue<'a, H>,
|
||||||
index: usize,
|
// index: usize,
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl<'a, 'b, H: QueueHeader> Iterator for QueueIterator<'a, 'b, H> {
|
// impl<'a, 'b, H: QueueHeader> Iterator for QueueIterator<'a, 'b, H> {
|
||||||
type Item = &'b H::Item;
|
// type Item = &'b H::Item;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
// fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.index == self.queue.len() {
|
// if self.index == self.queue.len() {
|
||||||
None
|
// None
|
||||||
} else {
|
// } else {
|
||||||
let item =
|
// let item =
|
||||||
&self.queue.buf[(self.queue.header.head() + self.index) % self.queue.buf.len()];
|
// &self.queue.buf[(self.queue.header.head() + self.index) % self.queue.buf.len()];
|
||||||
self.index += 1;
|
// self.index += 1;
|
||||||
Some(item)
|
// Some(item)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[account(zero_copy)]
|
// #[account(zero_copy)]
|
||||||
pub struct EventQueueHeader {
|
// pub struct EventQueueHeader {
|
||||||
pub meta_data: MetaData,
|
// pub meta_data: MetaData,
|
||||||
head: usize,
|
// head: usize,
|
||||||
count: usize,
|
// count: usize,
|
||||||
pub seq_num: usize,
|
// pub seq_num: usize,
|
||||||
}
|
// }
|
||||||
// unsafe impl TriviallyTransmutable for EventQueueHeader {}
|
// // unsafe impl TriviallyTransmutable for EventQueueHeader {}
|
||||||
|
|
||||||
impl QueueHeader for EventQueueHeader {
|
// impl QueueHeader for EventQueueHeader {
|
||||||
type Item = AnyEvent;
|
// type Item = AnyEvent;
|
||||||
|
|
||||||
fn head(&self) -> usize {
|
// fn head(&self) -> usize {
|
||||||
self.head
|
// self.head
|
||||||
}
|
// }
|
||||||
fn set_head(&mut self, value: usize) {
|
// fn set_head(&mut self, value: usize) {
|
||||||
self.head = value;
|
// self.head = value;
|
||||||
}
|
// }
|
||||||
fn count(&self) -> usize {
|
// fn count(&self) -> usize {
|
||||||
self.count
|
// self.count
|
||||||
}
|
// }
|
||||||
fn set_count(&mut self, value: usize) {
|
// fn set_count(&mut self, value: usize) {
|
||||||
self.count = value;
|
// self.count = value;
|
||||||
}
|
// }
|
||||||
fn incr_event_id(&mut self) {
|
// fn incr_event_id(&mut self) {
|
||||||
self.seq_num += 1;
|
// self.seq_num += 1;
|
||||||
}
|
// }
|
||||||
fn decr_event_id(&mut self, n: usize) {
|
// fn decr_event_id(&mut self, n: usize) {
|
||||||
self.seq_num -= n;
|
// self.seq_num -= n;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub type EventQueue<'a> = Queue<'a, EventQueueHeader>;
|
// pub type EventQueue<'a> = Queue<'a, EventQueueHeader>;
|
||||||
|
|
||||||
impl<'a> EventQueue<'a> {
|
// impl<'a> EventQueue<'a> {
|
||||||
pub fn load_mut_checked(
|
// pub fn load_mut_checked(
|
||||||
account: &'a AccountInfo,
|
// account: &'a AccountInfo,
|
||||||
program_id: &Pubkey,
|
// program_id: &Pubkey,
|
||||||
perp_market: &PerpMarket,
|
// perp_market: &PerpMarket,
|
||||||
) -> Result<Self> {
|
// ) -> Result<Self> {
|
||||||
require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner
|
// require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner
|
||||||
// require!(
|
// // require!(
|
||||||
// &perp_market.event_queue == account.key,
|
// // &perp_market.event_queue == account.key,
|
||||||
// MangoError::SomeError
|
// // MangoError::SomeError
|
||||||
// ); // MangoErrorCode::InvalidAccount
|
// // ); // MangoErrorCode::InvalidAccount
|
||||||
Self::load_mut(account)
|
// Self::load_mut(account)
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn load_and_init(
|
// pub fn load_and_init(
|
||||||
account: &'a AccountInfo,
|
// account: &'a AccountInfo,
|
||||||
program_id: &Pubkey,
|
// program_id: &Pubkey,
|
||||||
rent: &Rent,
|
// rent: &Rent,
|
||||||
) -> Result<Self> {
|
// ) -> Result<Self> {
|
||||||
// NOTE: check this first so we can borrow account later
|
// // NOTE: check this first so we can borrow account later
|
||||||
require!(
|
// require!(
|
||||||
rent.is_exempt(account.lamports(), account.data_len()),
|
// rent.is_exempt(account.lamports(), account.data_len()),
|
||||||
MangoError::SomeError
|
// MangoError::SomeError
|
||||||
); //MangoErrorCode::AccountNotRentExempt
|
// ); //MangoErrorCode::AccountNotRentExempt
|
||||||
|
|
||||||
let mut state = Self::load_mut(account)?;
|
// let mut state = Self::load_mut(account)?;
|
||||||
require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner
|
// require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner
|
||||||
|
|
||||||
// require!(
|
// // require!(
|
||||||
// !state.header.meta_data.is_initialized,
|
// // !state.header.meta_data.is_initialized,
|
||||||
// MangoError::SomeError
|
// // MangoError::SomeError
|
||||||
// );
|
// // );
|
||||||
// state.header.meta_data = MetaData::new(DataType::EventQueue, 0, true);
|
// // state.header.meta_data = MetaData::new(DataType::EventQueue, 0, true);
|
||||||
|
|
||||||
Ok(state)
|
// Ok(state)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Copy, Clone, IntoPrimitive, TryFromPrimitive, Eq, PartialEq)]
|
// #[derive(Copy, Clone, IntoPrimitive, TryFromPrimitive, Eq, PartialEq)]
|
||||||
#[repr(u8)]
|
// #[repr(u8)]
|
||||||
pub enum EventType {
|
// pub enum EventType {
|
||||||
Fill,
|
// Fill,
|
||||||
Out,
|
// Out,
|
||||||
Liquidate,
|
// Liquidate,
|
||||||
}
|
// }
|
||||||
|
|
||||||
const EVENT_SIZE: usize = 200;
|
// const EVENT_SIZE: usize = 200;
|
||||||
#[derive(Copy, Clone, Debug, Pod)]
|
// #[derive(Copy, Clone, Debug, Pod)]
|
||||||
#[repr(C)]
|
// #[repr(C)]
|
||||||
pub struct AnyEvent {
|
// pub struct AnyEvent {
|
||||||
pub event_type: u8,
|
// pub event_type: u8,
|
||||||
pub padding: [u8; EVENT_SIZE - 1],
|
// pub padding: [u8; EVENT_SIZE - 1],
|
||||||
}
|
// }
|
||||||
// unsafe impl TriviallyTransmutable for AnyEvent {}
|
// // unsafe impl TriviallyTransmutable for AnyEvent {}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Pod)]
|
// #[derive(Copy, Clone, Debug, Pod)]
|
||||||
#[repr(C)]
|
// #[repr(C)]
|
||||||
pub struct FillEvent {
|
// pub struct FillEvent {
|
||||||
pub event_type: u8,
|
// pub event_type: u8,
|
||||||
pub taker_side: Side, // side from the taker's POV
|
// pub taker_side: Side, // side from the taker's POV
|
||||||
pub maker_slot: u8,
|
// pub maker_slot: u8,
|
||||||
pub maker_out: bool, // true if maker order quantity == 0
|
// pub maker_out: bool, // true if maker order quantity == 0
|
||||||
pub version: u8,
|
// pub version: u8,
|
||||||
pub market_fees_applied: bool,
|
// pub market_fees_applied: bool,
|
||||||
pub padding: [u8; 2],
|
// pub padding: [u8; 2],
|
||||||
pub timestamp: u64,
|
// pub timestamp: u64,
|
||||||
pub seq_num: usize, // note: usize same as u64
|
// pub seq_num: usize, // note: usize same as u64
|
||||||
|
|
||||||
pub maker: Pubkey,
|
// pub maker: Pubkey,
|
||||||
pub maker_order_id: i128,
|
// pub maker_order_id: i128,
|
||||||
pub maker_client_order_id: u64,
|
// pub maker_client_order_id: u64,
|
||||||
pub maker_fee: I80F48,
|
// pub maker_fee: I80F48,
|
||||||
|
|
||||||
// The best bid/ask at the time the maker order was placed. Used for liquidity incentives
|
// // The best bid/ask at the time the maker order was placed. Used for liquidity incentives
|
||||||
pub best_initial: i64,
|
// pub best_initial: i64,
|
||||||
|
|
||||||
// Timestamp of when the maker order was placed; copied over from the LeafNode
|
// // Timestamp of when the maker order was placed; copied over from the LeafNode
|
||||||
pub maker_timestamp: u64,
|
// pub maker_timestamp: u64,
|
||||||
|
|
||||||
pub taker: Pubkey,
|
// pub taker: Pubkey,
|
||||||
pub taker_order_id: i128,
|
// pub taker_order_id: i128,
|
||||||
pub taker_client_order_id: u64,
|
// pub taker_client_order_id: u64,
|
||||||
pub taker_fee: I80F48,
|
// pub taker_fee: I80F48,
|
||||||
|
|
||||||
pub price: i64,
|
// pub price: i64,
|
||||||
pub quantity: i64, // number of quote lots
|
// pub quantity: i64, // number of quote lots
|
||||||
}
|
// }
|
||||||
// unsafe impl TriviallyTransmutable for FillEvent {}
|
// // unsafe impl TriviallyTransmutable for FillEvent {}
|
||||||
|
|
||||||
impl FillEvent {
|
// impl FillEvent {
|
||||||
pub fn new(
|
// pub fn new(
|
||||||
taker_side: Side,
|
// taker_side: Side,
|
||||||
maker_slot: u8,
|
// maker_slot: u8,
|
||||||
maker_out: bool,
|
// maker_out: bool,
|
||||||
timestamp: u64,
|
// timestamp: u64,
|
||||||
seq_num: usize,
|
// seq_num: usize,
|
||||||
maker: Pubkey,
|
// maker: Pubkey,
|
||||||
maker_order_id: i128,
|
// maker_order_id: i128,
|
||||||
maker_client_order_id: u64,
|
// maker_client_order_id: u64,
|
||||||
maker_fee: I80F48,
|
// maker_fee: I80F48,
|
||||||
best_initial: i64,
|
// best_initial: i64,
|
||||||
maker_timestamp: u64,
|
// maker_timestamp: u64,
|
||||||
|
|
||||||
taker: Pubkey,
|
// taker: Pubkey,
|
||||||
taker_order_id: i128,
|
// taker_order_id: i128,
|
||||||
taker_client_order_id: u64,
|
// taker_client_order_id: u64,
|
||||||
taker_fee: I80F48,
|
// taker_fee: I80F48,
|
||||||
price: i64,
|
// price: i64,
|
||||||
quantity: i64,
|
// quantity: i64,
|
||||||
version: u8,
|
// version: u8,
|
||||||
) -> FillEvent {
|
// ) -> FillEvent {
|
||||||
Self {
|
// Self {
|
||||||
event_type: EventType::Fill as u8,
|
// event_type: EventType::Fill as u8,
|
||||||
taker_side,
|
// taker_side,
|
||||||
maker_slot,
|
// maker_slot,
|
||||||
maker_out,
|
// maker_out,
|
||||||
version,
|
// version,
|
||||||
market_fees_applied: true, // Since mango v3.3.5, market fees are adjusted at matching time
|
// market_fees_applied: true, // Since mango v3.3.5, market fees are adjusted at matching time
|
||||||
padding: [0u8; 2],
|
// padding: [0u8; 2],
|
||||||
timestamp,
|
// timestamp,
|
||||||
seq_num,
|
// seq_num,
|
||||||
maker,
|
// maker,
|
||||||
maker_order_id,
|
// maker_order_id,
|
||||||
maker_client_order_id,
|
// maker_client_order_id,
|
||||||
maker_fee,
|
// maker_fee,
|
||||||
best_initial,
|
// best_initial,
|
||||||
maker_timestamp,
|
// maker_timestamp,
|
||||||
taker,
|
// taker,
|
||||||
taker_order_id,
|
// taker_order_id,
|
||||||
taker_client_order_id,
|
// taker_client_order_id,
|
||||||
taker_fee,
|
// taker_fee,
|
||||||
price,
|
// price,
|
||||||
quantity,
|
// quantity,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn base_quote_change(&self, side: Side) -> (i64, i64) {
|
// pub fn base_quote_change(&self, side: Side) -> (i64, i64) {
|
||||||
match side {
|
// match side {
|
||||||
Side::Bid => (
|
// Side::Bid => (
|
||||||
self.quantity,
|
// self.quantity,
|
||||||
-self.price.checked_mul(self.quantity).unwrap(),
|
// -self.price.checked_mul(self.quantity).unwrap(),
|
||||||
),
|
// ),
|
||||||
Side::Ask => (
|
// Side::Ask => (
|
||||||
-self.quantity,
|
// -self.quantity,
|
||||||
self.price.checked_mul(self.quantity).unwrap(),
|
// self.price.checked_mul(self.quantity).unwrap(),
|
||||||
),
|
// ),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// pub fn to_fill_log(&self, mango_group: Pubkey, market_index: usize) -> FillLog {
|
// // pub fn to_fill_log(&self, mango_group: Pubkey, market_index: usize) -> FillLog {
|
||||||
// FillLog {
|
// // FillLog {
|
||||||
// mango_group,
|
// // mango_group,
|
||||||
// market_index: market_index as u64,
|
// // market_index: market_index as u64,
|
||||||
// taker_side: self.taker_side as u8,
|
// // taker_side: self.taker_side as u8,
|
||||||
// maker_slot: self.maker_slot,
|
// // maker_slot: self.maker_slot,
|
||||||
// maker_out: self.maker_out,
|
// // maker_out: self.maker_out,
|
||||||
// timestamp: self.timestamp,
|
// // timestamp: self.timestamp,
|
||||||
// seq_num: self.seq_num as u64,
|
// // seq_num: self.seq_num as u64,
|
||||||
// maker: self.maker,
|
// // maker: self.maker,
|
||||||
// maker_order_id: self.maker_order_id,
|
// // maker_order_id: self.maker_order_id,
|
||||||
// maker_client_order_id: self.maker_client_order_id,
|
// // maker_client_order_id: self.maker_client_order_id,
|
||||||
// maker_fee: self.maker_fee.to_bits(),
|
// // maker_fee: self.maker_fee.to_bits(),
|
||||||
// best_initial: self.best_initial,
|
// // best_initial: self.best_initial,
|
||||||
// maker_timestamp: self.maker_timestamp,
|
// // maker_timestamp: self.maker_timestamp,
|
||||||
// taker: self.taker,
|
// // taker: self.taker,
|
||||||
// taker_order_id: self.taker_order_id,
|
// // taker_order_id: self.taker_order_id,
|
||||||
// taker_client_order_id: self.taker_client_order_id,
|
// // taker_client_order_id: self.taker_client_order_id,
|
||||||
// taker_fee: self.taker_fee.to_bits(),
|
// // taker_fee: self.taker_fee.to_bits(),
|
||||||
// price: self.price,
|
// // price: self.price,
|
||||||
// quantity: self.quantity,
|
// // quantity: self.quantity,
|
||||||
// }
|
// // }
|
||||||
// }
|
// // }
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Pod)]
|
// #[derive(Copy, Clone, Debug, Pod)]
|
||||||
#[repr(C)]
|
// #[repr(C)]
|
||||||
pub struct OutEvent {
|
// pub struct OutEvent {
|
||||||
pub event_type: u8,
|
// pub event_type: u8,
|
||||||
pub side: Side,
|
// pub side: Side,
|
||||||
pub slot: u8,
|
// pub slot: u8,
|
||||||
padding0: [u8; 5],
|
// padding0: [u8; 5],
|
||||||
pub timestamp: u64,
|
// pub timestamp: u64,
|
||||||
pub seq_num: usize,
|
// pub seq_num: usize,
|
||||||
pub owner: Pubkey,
|
// pub owner: Pubkey,
|
||||||
pub quantity: i64,
|
// pub quantity: i64,
|
||||||
padding1: [u8; EVENT_SIZE - 64],
|
// padding1: [u8; EVENT_SIZE - 64],
|
||||||
}
|
// }
|
||||||
// unsafe impl TriviallyTransmutable for OutEvent {}
|
// // unsafe impl TriviallyTransmutable for OutEvent {}
|
||||||
impl OutEvent {
|
// impl OutEvent {
|
||||||
pub fn new(
|
// pub fn new(
|
||||||
side: Side,
|
// side: Side,
|
||||||
slot: u8,
|
// slot: u8,
|
||||||
timestamp: u64,
|
// timestamp: u64,
|
||||||
seq_num: usize,
|
// seq_num: usize,
|
||||||
owner: Pubkey,
|
// owner: Pubkey,
|
||||||
quantity: i64,
|
// quantity: i64,
|
||||||
) -> Self {
|
// ) -> Self {
|
||||||
Self {
|
// Self {
|
||||||
event_type: EventType::Out.into(),
|
// event_type: EventType::Out.into(),
|
||||||
side,
|
// side,
|
||||||
slot,
|
// slot,
|
||||||
padding0: [0; 5],
|
// padding0: [0; 5],
|
||||||
timestamp,
|
// timestamp,
|
||||||
seq_num,
|
// seq_num,
|
||||||
owner,
|
// owner,
|
||||||
quantity,
|
// quantity,
|
||||||
padding1: [0; EVENT_SIZE - 64],
|
// padding1: [0; EVENT_SIZE - 64],
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Pod)]
|
// #[derive(Copy, Clone, Debug, Pod)]
|
||||||
#[repr(C)]
|
// #[repr(C)]
|
||||||
/// Liquidation for the PerpMarket this EventQueue is for
|
// /// Liquidation for the PerpMarket this EventQueue is for
|
||||||
pub struct LiquidateEvent {
|
// pub struct LiquidateEvent {
|
||||||
pub event_type: u8,
|
// pub event_type: u8,
|
||||||
padding0: [u8; 7],
|
// padding0: [u8; 7],
|
||||||
pub timestamp: u64,
|
// pub timestamp: u64,
|
||||||
pub seq_num: usize,
|
// pub seq_num: usize,
|
||||||
pub liqee: Pubkey,
|
// pub liqee: Pubkey,
|
||||||
pub liqor: Pubkey,
|
// pub liqor: Pubkey,
|
||||||
pub price: I80F48, // oracle price at the time of liquidation
|
// pub price: I80F48, // oracle price at the time of liquidation
|
||||||
pub quantity: i64, // number of contracts that were moved from liqee to liqor
|
// pub quantity: i64, // number of contracts that were moved from liqee to liqor
|
||||||
pub liquidation_fee: I80F48, // liq fee for this earned for this market
|
// pub liquidation_fee: I80F48, // liq fee for this earned for this market
|
||||||
padding1: [u8; EVENT_SIZE - 128],
|
// padding1: [u8; EVENT_SIZE - 128],
|
||||||
}
|
// }
|
||||||
// unsafe impl TriviallyTransmutable for LiquidateEvent {}
|
// // unsafe impl TriviallyTransmutable for LiquidateEvent {}
|
||||||
impl LiquidateEvent {
|
// impl LiquidateEvent {
|
||||||
pub fn new(
|
// pub fn new(
|
||||||
timestamp: u64,
|
// timestamp: u64,
|
||||||
seq_num: usize,
|
// seq_num: usize,
|
||||||
liqee: Pubkey,
|
// liqee: Pubkey,
|
||||||
liqor: Pubkey,
|
// liqor: Pubkey,
|
||||||
price: I80F48,
|
// price: I80F48,
|
||||||
quantity: i64,
|
// quantity: i64,
|
||||||
liquidation_fee: I80F48,
|
// liquidation_fee: I80F48,
|
||||||
) -> Self {
|
// ) -> Self {
|
||||||
Self {
|
// Self {
|
||||||
event_type: EventType::Liquidate.into(),
|
// event_type: EventType::Liquidate.into(),
|
||||||
padding0: [0u8; 7],
|
// padding0: [0u8; 7],
|
||||||
timestamp,
|
// timestamp,
|
||||||
seq_num,
|
// seq_num,
|
||||||
liqee,
|
// liqee,
|
||||||
liqor,
|
// liqor,
|
||||||
price,
|
// price,
|
||||||
quantity,
|
// quantity,
|
||||||
liquidation_fee,
|
// liquidation_fee,
|
||||||
padding1: [0u8; EVENT_SIZE - 128],
|
// padding1: [0u8; EVENT_SIZE - 128],
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
const_assert_eq!(size_of::<AnyEvent>(), size_of::<FillEvent>());
|
// const_assert_eq!(size_of::<AnyEvent>(), size_of::<FillEvent>());
|
||||||
const_assert_eq!(size_of::<AnyEvent>(), size_of::<OutEvent>());
|
// const_assert_eq!(size_of::<AnyEvent>(), size_of::<OutEvent>());
|
||||||
const_assert_eq!(size_of::<AnyEvent>(), size_of::<LiquidateEvent>());
|
// const_assert_eq!(size_of::<AnyEvent>(), size_of::<LiquidateEvent>());
|
||||||
|
|
|
@ -1159,13 +1159,11 @@ impl<'keypair> ClientInstruction for PlacePerpOrderInstruction<'keypair> {
|
||||||
) -> (Self::Accounts, instruction::Instruction) {
|
) -> (Self::Accounts, instruction::Instruction) {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction {
|
let instruction = Self::Instruction {
|
||||||
side: Side::Bid,
|
|
||||||
price: 1,
|
price: 1,
|
||||||
max_base_quantity: 1,
|
max_base_quantity: 1,
|
||||||
max_quote_quantity: 1,
|
max_quote_quantity: 1,
|
||||||
client_order_id: 0,
|
client_order_id: 0,
|
||||||
order_type: OrderType::Limit,
|
order_type: OrderType::Limit,
|
||||||
reduce_only: false,
|
|
||||||
expiry_timestamp: 0,
|
expiry_timestamp: 0,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
};
|
};
|
||||||
|
|
|
@ -109,7 +109,9 @@ impl TestContextBuilder {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// intentionally set to half the limit, to catch potential problems early
|
// intentionally set to half the limit, to catch potential problems early
|
||||||
test.set_compute_max_units(100000);
|
// TODO make configurable
|
||||||
|
// margin trade test just goes above 100000 atm
|
||||||
|
test.set_compute_max_units(101000);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
test,
|
test,
|
||||||
|
|
Loading…
Reference in New Issue