Merge pull request #234 from blockworks-foundation/cj/ioc_fees

Extra fees for IOC orders
This commit is contained in:
conj0iner 2022-09-28 00:05:25 +08:00 committed by GitHub
commit a97b40a521
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 252 additions and 65 deletions

View File

@ -64,6 +64,7 @@ pub fn perp_create_market(
impact_quantity: i64,
group_insurance_fund: bool,
trusted_market: bool,
fee_penalty: f32,
) -> Result<()> {
let mut perp_market = ctx.accounts.perp_market.load_init()?;
*perp_market = PerpMarket {
@ -103,7 +104,8 @@ pub fn perp_create_market(
padding0: Default::default(),
padding1: Default::default(),
padding2: Default::default(),
reserved: [0; 112],
fee_penalty,
reserved: [0; 108],
};
let mut bids = ctx.accounts.bids.load_init()?;

View File

@ -35,6 +35,7 @@ pub fn perp_edit_market(
impact_quantity_opt: Option<i64>,
group_insurance_fund_opt: Option<bool>,
trusted_market_opt: Option<bool>,
fee_penalty_opt: Option<f32>,
) -> Result<()> {
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
@ -91,6 +92,9 @@ pub fn perp_edit_market(
if let Some(impact_quantity) = impact_quantity_opt {
perp_market.impact_quantity = impact_quantity;
}
if let Some(fee_penalty) = fee_penalty_opt {
perp_market.fee_penalty = fee_penalty;
}
// unchanged -
// long_funding

View File

@ -393,6 +393,7 @@ pub mod mango_v4 {
impact_quantity: i64,
group_insurance_fund: bool,
trusted_market: bool,
fee_penalty: f32,
) -> Result<()> {
instructions::perp_create_market(
ctx,
@ -414,6 +415,7 @@ pub mod mango_v4 {
impact_quantity,
group_insurance_fund,
trusted_market,
fee_penalty,
)
}
@ -435,6 +437,7 @@ pub mod mango_v4 {
impact_quantity_opt: Option<i64>,
group_insurance_fund_opt: Option<bool>,
trusted_market_opt: Option<bool>,
fee_penalty_opt: Option<f32>,
) -> Result<()> {
instructions::perp_edit_market(
ctx,
@ -453,6 +456,7 @@ pub mod mango_v4 {
impact_quantity_opt,
group_insurance_fund_opt,
trusted_market_opt,
fee_penalty_opt,
)
}

View File

@ -400,8 +400,7 @@ pub use account_seeds;
#[cfg(test)]
mod tests {
use crate::state::{OracleConfig, PerpMarket};
use anchor_lang::prelude::Pubkey;
use crate::state::PerpMarket;
use fixed::types::I80F48;
use rand::Rng;
@ -416,52 +415,9 @@ mod tests {
pos
}
fn create_perp_market() -> PerpMarket {
return PerpMarket {
group: Pubkey::new_unique(),
perp_market_index: 0,
group_insurance_fund: 0,
trusted_market: 0,
name: Default::default(),
oracle: Pubkey::new_unique(),
oracle_config: OracleConfig {
conf_filter: I80F48::ZERO,
},
bids: Pubkey::new_unique(),
asks: Pubkey::new_unique(),
event_queue: Pubkey::new_unique(),
quote_lot_size: 1,
base_lot_size: 1,
maint_asset_weight: I80F48::from(1),
init_asset_weight: I80F48::from(1),
maint_liab_weight: I80F48::from(1),
init_liab_weight: I80F48::from(1),
liquidation_fee: I80F48::ZERO,
maker_fee: I80F48::ZERO,
taker_fee: I80F48::ZERO,
min_funding: I80F48::ZERO,
max_funding: I80F48::ZERO,
impact_quantity: 0,
long_funding: I80F48::ZERO,
short_funding: I80F48::ZERO,
funding_last_updated: 0,
open_interest: 0,
seq_num: 0,
fees_accrued: I80F48::ZERO,
fees_settled: I80F48::ZERO,
bump: 0,
base_decimals: 0,
reserved: [0; 112],
padding0: Default::default(),
padding1: Default::default(),
padding2: Default::default(),
registration_time: 0,
};
}
#[test]
fn test_quote_entry_long_increasing_from_zero() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(0, 0, 0);
// Go long 10 @ 10
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-100));
@ -472,7 +428,7 @@ mod tests {
#[test]
fn test_quote_entry_short_increasing_from_zero() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(0, 0, 0);
// Go short 10 @ 10
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(100));
@ -483,7 +439,7 @@ mod tests {
#[test]
fn test_quote_entry_long_increasing_from_long() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(10, -100, -100);
// Go long 10 @ 30
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-300));
@ -494,7 +450,7 @@ mod tests {
#[test]
fn test_quote_entry_short_increasing_from_short() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(-10, 100, 100);
// Go short 10 @ 10
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(300));
@ -505,7 +461,7 @@ mod tests {
#[test]
fn test_quote_entry_long_decreasing_from_short() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(-10, 100, 100);
// Go long 5 @ 50
pos.change_base_and_quote_positions(&mut market, 5, I80F48::from(-250));
@ -516,7 +472,7 @@ mod tests {
#[test]
fn test_quote_entry_short_decreasing_from_long() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(10, -100, -100);
// Go short 5 @ 50
pos.change_base_and_quote_positions(&mut market, -5, I80F48::from(250));
@ -527,7 +483,7 @@ mod tests {
#[test]
fn test_quote_entry_long_close_with_short() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(10, -100, -100);
// Go short 10 @ 50
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(250));
@ -538,7 +494,7 @@ mod tests {
#[test]
fn test_quote_entry_short_close_with_long() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(-10, 100, 100);
// Go long 10 @ 50
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-250));
@ -549,7 +505,7 @@ mod tests {
#[test]
fn test_quote_entry_long_close_short_with_overflow() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(10, -100, -100);
// Go short 15 @ 20
pos.change_base_and_quote_positions(&mut market, -15, I80F48::from(300));
@ -560,7 +516,7 @@ mod tests {
#[test]
fn test_quote_entry_short_close_long_with_overflow() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(-10, 100, 100);
// Go short 15 @ 20
pos.change_base_and_quote_positions(&mut market, 15, I80F48::from(-300));
@ -571,7 +527,7 @@ mod tests {
#[test]
fn test_quote_entry_break_even_price() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(0, 0, 0);
// Buy 11 @ 10,000
pos.change_base_and_quote_positions(&mut market, 11, I80F48::from(-11 * 10_000));
@ -585,7 +541,7 @@ mod tests {
#[test]
fn test_quote_entry_multiple_and_reversed_changes_return_entry_to_zero() {
let mut market = create_perp_market();
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(0, 0, 0);
// Generate array of random trades

View File

@ -377,6 +377,11 @@ impl<'a> Book<'a> {
apply_fees(market, mango_account, total_quote_lots_taken)?;
}
// IOC orders have a fee penalty applied regardless of match
if order_type == OrderType::ImmediateOrCancel {
apply_penalty(market, mango_account)?;
}
Ok(())
}
@ -460,12 +465,24 @@ fn apply_fees(
// 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.
let maker_fees = taker_quote_native * market.maker_fee;
let maker_fees = cm!(taker_quote_native * market.maker_fee);
let taker_fees = cm!(taker_quote_native * market.taker_fee);
let taker_fees = taker_quote_native * market.taker_fee;
let perp_account = mango_account.perp_position_mut(market.perp_market_index)?;
perp_account.change_quote_position(-taker_fees);
market.fees_accrued += taker_fees + maker_fees;
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);
Ok(())
}

View File

@ -375,4 +375,111 @@ mod tests {
match_quote - match_quote * market.taker_fee
);
}
#[test]
fn test_fee_penalty_applied_only_on_limit_order() -> Result<()> {
let (mut market, oracle_price, mut event_queue, bids, asks) = test_setup(1000.0);
let mut book = Book {
bids: bids.borrow_mut(),
asks: asks.borrow_mut(),
};
let buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
let mut account = MangoAccountValue::from_bytes(&buffer).unwrap();
let taker_pk = Pubkey::new_unique();
let now_ts = 1000000;
market.base_lot_size = 1;
market.quote_lot_size = 1;
market.taker_fee = I80F48::from_num(0.01);
market.fee_penalty = 5.0;
account.ensure_perp_position(market.perp_market_index, 0)?;
// Passive order
book.new_order(
Side::Ask,
&mut market,
&mut event_queue,
oracle_price,
&mut account.borrow_mut(),
&taker_pk,
1000,
2,
i64::MAX,
OrderType::Limit,
0,
43,
now_ts,
u8::MAX,
)
.unwrap();
// Partial taker
book.new_order(
Side::Bid,
&mut market,
&mut event_queue,
oracle_price,
&mut account.borrow_mut(),
&taker_pk,
1000,
1,
i64::MAX,
OrderType::Limit,
0,
43,
now_ts,
u8::MAX,
)
.unwrap();
let pos = account.perp_position(market.perp_market_index)?;
assert_eq!(
pos.quote_position_native().round(),
I80F48::from_num(-10),
"Regular fees applied on limit order"
);
assert_eq!(
market.fees_accrued.round(),
I80F48::from_num(10),
"Fees moved to market"
);
// Full taker
book.new_order(
Side::Bid,
&mut market,
&mut event_queue,
oracle_price,
&mut account.borrow_mut(),
&taker_pk,
1000,
1,
i64::MAX,
OrderType::ImmediateOrCancel,
0,
43,
now_ts,
u8::MAX,
)
.unwrap();
let pos = account.perp_position(market.perp_market_index)?;
assert_eq!(
pos.quote_position_native().round(),
I80F48::from_num(-25), // -10 - 5
"Regular fees + fixed penalty applied on IOC order"
);
assert_eq!(
market.fees_accrued.round(),
I80F48::from_num(25), // 10 + 5
"Fees moved to market"
);
Ok(())
}
}

View File

@ -99,12 +99,30 @@ pub struct PerpMarket {
/// Fees settled in native quote currency
pub fees_settled: I80F48,
pub reserved: [u8; 112],
pub fee_penalty: f32,
pub reserved: [u8; 108],
}
const_assert_eq!(
size_of::<PerpMarket>(),
32 + 2 + 2 + 4 + 16 + 32 + 16 + 32 * 3 + 8 * 2 + 16 * 12 + 8 * 2 + 8 * 2 + 16 + 2 + 6 + 8 + 112
32 + 2
+ 2
+ 4
+ 16
+ 32
+ 16
+ 32 * 3
+ 8 * 2
+ 16 * 12
+ 8 * 2
+ 8 * 2
+ 16
+ 2
+ 6
+ 8
+ 4
+ 108
);
const_assert_eq!(size_of::<PerpMarket>() % 8, 0);
@ -225,4 +243,49 @@ impl PerpMarket {
self.short_funding += socialized_loss;
Ok(socialized_loss)
}
/// Creates default market for tests
pub fn default_for_tests() -> PerpMarket {
PerpMarket {
group: Pubkey::new_unique(),
perp_market_index: 0,
name: Default::default(),
oracle: Pubkey::new_unique(),
oracle_config: OracleConfig {
conf_filter: I80F48::ZERO,
},
bids: Pubkey::new_unique(),
asks: Pubkey::new_unique(),
event_queue: Pubkey::new_unique(),
quote_lot_size: 1,
base_lot_size: 1,
maint_asset_weight: I80F48::from(1),
init_asset_weight: I80F48::from(1),
maint_liab_weight: I80F48::from(1),
init_liab_weight: I80F48::from(1),
liquidation_fee: I80F48::ZERO,
maker_fee: I80F48::ZERO,
taker_fee: I80F48::ZERO,
min_funding: I80F48::ZERO,
max_funding: I80F48::ZERO,
impact_quantity: 0,
long_funding: I80F48::ZERO,
short_funding: I80F48::ZERO,
funding_last_updated: 0,
open_interest: 0,
seq_num: 0,
fees_accrued: I80F48::ZERO,
fees_settled: I80F48::ZERO,
bump: 0,
base_decimals: 0,
reserved: [0; 108],
padding0: Default::default(),
padding1: Default::default(),
padding2: Default::default(),
registration_time: 0,
fee_penalty: 0.0,
trusted_market: 0,
group_insurance_fund: 0,
}
}
}

View File

@ -2143,6 +2143,7 @@ pub struct PerpCreateMarketInstruction {
pub taker_fee: f32,
pub group_insurance_fund: bool,
pub trusted_market: bool,
pub fee_penalty: f32,
}
impl PerpCreateMarketInstruction {
pub async fn with_new_book_and_queue(
@ -2195,6 +2196,7 @@ impl ClientInstruction for PerpCreateMarketInstruction {
base_decimals: self.base_decimals,
group_insurance_fund: self.group_insurance_fund,
trusted_market: self.trusted_market,
fee_penalty: self.fee_penalty,
};
let perp_market = Pubkey::find_program_address(

View File

@ -1330,6 +1330,7 @@ export class MangoClient {
liquidationFee: number,
makerFee: number,
takerFee: number,
feePenalty: number,
minFunding: number,
maxFunding: number,
impactQuantity: number,
@ -1364,6 +1365,7 @@ export class MangoClient {
new BN(impactQuantity),
groupInsuranceFund,
trustedMarket,
feePenalty
)
.accounts({
group: group.publicKey,
@ -1430,6 +1432,7 @@ export class MangoClient {
liquidationFee: number,
makerFee: number,
takerFee: number,
feePenalty: number,
minFunding: number,
maxFunding: number,
impactQuantity: number,
@ -1459,6 +1462,7 @@ export class MangoClient {
new BN(impactQuantity),
groupInsuranceFund,
trustedMarket,
feePenalty
)
.accounts({
group: group.publicKey,

View File

@ -2315,6 +2315,10 @@ export type MangoV4 = {
{
"name": "trustedMarket",
"type": "bool"
},
{
"name": "feePenalty",
"type": "f32"
}
]
},
@ -2429,6 +2433,12 @@ export type MangoV4 = {
"type": {
"option": "bool"
}
},
{
"name": "feePenaltyOpt",
"type": {
"option": "f32"
}
}
]
},
@ -4014,12 +4024,16 @@ export type MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "feePenalty",
"type": "f32"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
112
108
]
}
}
@ -8402,6 +8416,10 @@ export const IDL: MangoV4 = {
{
"name": "trustedMarket",
"type": "bool"
},
{
"name": "feePenalty",
"type": "f32"
}
]
},
@ -8516,6 +8534,12 @@ export const IDL: MangoV4 = {
"type": {
"option": "bool"
}
},
{
"name": "feePenaltyOpt",
"type": {
"option": "f32"
}
}
]
},
@ -10101,12 +10125,16 @@ export const IDL: MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "feePenalty",
"type": "f32"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
112
108
]
}
}