Perps: Let all base lot changes go through a function
Direct access to base_position_lots and quote_position_native is not allowed anymore. Fixes an issue where quote_lots were used instead of quote_native, and also takes fees into account for the entry price.
This commit is contained in:
parent
e56ed3776c
commit
6c32077d1a
|
@ -58,7 +58,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: I80F48)
|
|||
|
||||
// Calculate PnL for each account
|
||||
let base_native = perp_position.base_position_native(&perp_market);
|
||||
let pnl: I80F48 = cm!(perp_position.quote_position_native + base_native * oracle_price);
|
||||
let pnl: I80F48 = cm!(perp_position.quote_position_native() + base_native * oracle_price);
|
||||
|
||||
// Account perp position must have a loss to be able to settle against the fee account
|
||||
require!(pnl.is_negative(), MangoError::ProfitabilityMismatch);
|
||||
|
@ -72,7 +72,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: I80F48)
|
|||
.abs()
|
||||
.min(perp_market.fees_accrued.abs())
|
||||
.min(max_settle_amount);
|
||||
perp_position.quote_position_native = cm!(perp_position.quote_position_native + settlement);
|
||||
perp_position.change_quote_position(settlement);
|
||||
perp_market.fees_accrued = cm!(perp_market.fees_accrued - settlement);
|
||||
|
||||
// Update the account's net_settled with the new PnL
|
||||
|
|
|
@ -64,8 +64,8 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>, max_settle_amount: I80F48) -
|
|||
perp_market.oracle_price(&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?)?;
|
||||
|
||||
// Fetch perp positions for accounts
|
||||
let mut a_perp_position = account_a.perp_position_mut(perp_market.perp_market_index)?;
|
||||
let mut b_perp_position = account_b.perp_position_mut(perp_market.perp_market_index)?;
|
||||
let a_perp_position = account_a.perp_position_mut(perp_market.perp_market_index)?;
|
||||
let b_perp_position = account_b.perp_position_mut(perp_market.perp_market_index)?;
|
||||
|
||||
// Settle funding before settling any PnL
|
||||
a_perp_position.settle_funding(&perp_market);
|
||||
|
@ -74,8 +74,8 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>, max_settle_amount: I80F48) -
|
|||
// Calculate PnL for each account
|
||||
let a_base_native = a_perp_position.base_position_native(&perp_market);
|
||||
let b_base_native = b_perp_position.base_position_native(&perp_market);
|
||||
let a_pnl: I80F48 = cm!(a_perp_position.quote_position_native + a_base_native * oracle_price);
|
||||
let b_pnl: I80F48 = cm!(b_perp_position.quote_position_native + b_base_native * oracle_price);
|
||||
let a_pnl: I80F48 = cm!(a_perp_position.quote_position_native() + a_base_native * oracle_price);
|
||||
let b_pnl: I80F48 = cm!(b_perp_position.quote_position_native() + b_base_native * oracle_price);
|
||||
|
||||
// Account A must be profitable, and B must be unprofitable
|
||||
// PnL must be opposite signs for there to be a settlement
|
||||
|
@ -84,8 +84,8 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>, max_settle_amount: I80F48) -
|
|||
|
||||
// Settle for the maximum possible capped to max_settle_amount
|
||||
let settlement = a_pnl.abs().min(b_pnl.abs()).min(max_settle_amount);
|
||||
cm!(a_perp_position.quote_position_native -= settlement);
|
||||
cm!(b_perp_position.quote_position_native += settlement);
|
||||
a_perp_position.change_quote_position(-settlement);
|
||||
b_perp_position.change_quote_position(settlement);
|
||||
|
||||
// Update the account's net_settled with the new PnL
|
||||
let settlement_i64 = settlement.checked_to_num::<i64>().unwrap();
|
||||
|
|
|
@ -18,8 +18,8 @@ pub fn emit_perp_balances(
|
|||
mango_group,
|
||||
mango_account,
|
||||
market_index,
|
||||
base_position: pp.base_position_lots,
|
||||
quote_position: pp.quote_position_native.to_bits(),
|
||||
base_position: pp.base_position_lots(),
|
||||
quote_position: pp.quote_position_native().to_bits(),
|
||||
long_settled_funding: pp.long_settled_funding.to_bits(),
|
||||
short_settled_funding: pp.short_settled_funding.to_bits(),
|
||||
price,
|
||||
|
|
|
@ -498,14 +498,14 @@ impl PerpInfo {
|
|||
let base_info = &token_infos[base_index];
|
||||
|
||||
let base_lot_size = I80F48::from(perp_market.base_lot_size);
|
||||
let base_lots = cm!(perp_position.base_position_lots + perp_position.taker_base_lots);
|
||||
let base_lots = cm!(perp_position.base_position_lots() + perp_position.taker_base_lots);
|
||||
|
||||
let unsettled_funding = perp_position.unsettled_funding(&perp_market);
|
||||
let taker_quote = I80F48::from(cm!(
|
||||
perp_position.taker_quote_lots * perp_market.quote_lot_size
|
||||
));
|
||||
let quote_current =
|
||||
cm!(perp_position.quote_position_native - unsettled_funding + taker_quote);
|
||||
cm!(perp_position.quote_position_native() - unsettled_funding + taker_quote);
|
||||
|
||||
// Two scenarios:
|
||||
// 1. The price goes low and all bids execute, converting to base.
|
||||
|
@ -1226,8 +1226,7 @@ mod tests {
|
|||
|
||||
let mut perp1 = mock_perp_market(group, 9, 4, 0.2, 0.1);
|
||||
let perpaccount = account.ensure_perp_position(9).unwrap().0;
|
||||
perpaccount.base_position_lots = 3;
|
||||
perpaccount.quote_position_native = -I80F48::from(310u16);
|
||||
perpaccount.change_base_and_quote_positions(perp1.data(), 3, -I80F48::from(310u16));
|
||||
perpaccount.bids_base_lots = 7;
|
||||
perpaccount.asks_base_lots = 11;
|
||||
perpaccount.taker_base_lots = 1;
|
||||
|
@ -1398,8 +1397,11 @@ mod tests {
|
|||
|
||||
let mut perp1 = mock_perp_market(group, 9, 4, 0.2, 0.1);
|
||||
let perpaccount = account.ensure_perp_position(9).unwrap().0;
|
||||
perpaccount.base_position_lots = testcase.perp1.0;
|
||||
perpaccount.quote_position_native = I80F48::from(testcase.perp1.1);
|
||||
perpaccount.change_base_and_quote_positions(
|
||||
perp1.data(),
|
||||
testcase.perp1.0,
|
||||
I80F48::from(testcase.perp1.1),
|
||||
);
|
||||
perpaccount.bids_base_lots = testcase.perp1.2;
|
||||
perpaccount.asks_base_lots = testcase.perp1.3;
|
||||
|
||||
|
@ -1708,8 +1710,7 @@ mod tests {
|
|||
let mut perp1 = mock_perp_market(group, 9, 1, 0.2, 0.1);
|
||||
perp1.data().long_funding = I80F48::from_num(10.1);
|
||||
let perpaccount = account.ensure_perp_position(9).unwrap().0;
|
||||
perpaccount.base_position_lots = 10; // 100 base native
|
||||
perpaccount.quote_position_native = I80F48::from(-110);
|
||||
perpaccount.change_base_and_quote_positions(perp1.data(), 10, I80F48::from(-110));
|
||||
perpaccount.long_settled_funding = I80F48::from_num(10.0);
|
||||
|
||||
let ais = vec![
|
||||
|
|
|
@ -712,10 +712,9 @@ impl<
|
|||
if raw_index_opt.is_none() {
|
||||
raw_index_opt = self.all_perp_positions().position(|p| !p.is_active());
|
||||
if let Some(raw_index) = raw_index_opt {
|
||||
*(self.perp_position_mut_by_raw_index(raw_index)) = PerpPosition {
|
||||
market_index: perp_market_index,
|
||||
..Default::default()
|
||||
};
|
||||
let perp_position = self.perp_position_mut_by_raw_index(raw_index);
|
||||
*perp_position = PerpPosition::default();
|
||||
perp_position.market_index = perp_market_index;
|
||||
}
|
||||
}
|
||||
if let Some(raw_index) = raw_index_opt {
|
||||
|
@ -796,18 +795,13 @@ impl<
|
|||
|
||||
let side = fill.taker_side.invert_side();
|
||||
let (base_change, quote_change) = fill.base_quote_change(side);
|
||||
pa.change_base_and_entry_positions(perp_market, base_change, quote_change);
|
||||
let quote = I80F48::from_num(
|
||||
perp_market
|
||||
.quote_lot_size
|
||||
.checked_mul(quote_change)
|
||||
.unwrap(),
|
||||
);
|
||||
let fees = quote.abs() * fill.maker_fee;
|
||||
let quote = cm!(I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change));
|
||||
let fees = cm!(quote.abs() * fill.maker_fee);
|
||||
if !fill.market_fees_applied {
|
||||
perp_market.fees_accrued += fees;
|
||||
cm!(perp_market.fees_accrued += fees);
|
||||
}
|
||||
pa.quote_position_native = pa.quote_position_native.checked_add(quote - fees).unwrap();
|
||||
let quote_change_native = cm!(quote - fees);
|
||||
pa.change_base_and_quote_positions(perp_market, base_change, quote_change_native);
|
||||
|
||||
if fill.maker_out {
|
||||
self.remove_perp_order(fill.maker_slot as usize, base_change.abs())
|
||||
|
@ -836,12 +830,11 @@ impl<
|
|||
|
||||
let (base_change, quote_change) = fill.base_quote_change(fill.taker_side);
|
||||
pa.remove_taker_trade(base_change, quote_change);
|
||||
pa.change_base_and_entry_positions(perp_market, base_change, quote_change);
|
||||
let quote = I80F48::from_num(perp_market.quote_lot_size * quote_change);
|
||||
|
||||
// fees are assessed at time of trade; no need to assess fees here
|
||||
let quote_change_native =
|
||||
cm!(I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change));
|
||||
pa.change_base_and_quote_positions(perp_market, base_change, quote_change_native);
|
||||
|
||||
pa.quote_position_native += quote;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -153,10 +153,10 @@ pub struct PerpPosition {
|
|||
pub padding: [u8; 6],
|
||||
|
||||
/// Active position size, measured in base lots
|
||||
pub base_position_lots: i64,
|
||||
base_position_lots: i64,
|
||||
/// Active position in quote (conversation rate is that of the time the order was settled)
|
||||
/// measured in native quote
|
||||
pub quote_position_native: I80F48,
|
||||
quote_position_native: I80F48,
|
||||
|
||||
/// Tracks what the position is to calculate average entry & break even price
|
||||
pub quote_entry_native: i64,
|
||||
|
@ -240,8 +240,16 @@ impl PerpPosition {
|
|||
I80F48::from(cm!(self.base_position_lots * market.base_lot_size))
|
||||
}
|
||||
|
||||
pub fn base_position_lots(&self) -> i64 {
|
||||
self.base_position_lots
|
||||
}
|
||||
|
||||
pub fn quote_position_native(&self) -> I80F48 {
|
||||
self.quote_position_native
|
||||
}
|
||||
|
||||
/// This assumes settle_funding was already called
|
||||
pub fn change_base_position(&mut self, perp_market: &mut PerpMarket, base_change: i64) {
|
||||
fn change_base_position(&mut self, perp_market: &mut PerpMarket, base_change: i64) {
|
||||
let start = self.base_position_lots;
|
||||
self.base_position_lots += base_change;
|
||||
perp_market.open_interest += self.base_position_lots.abs() - start.abs();
|
||||
|
@ -271,25 +279,24 @@ impl PerpPosition {
|
|||
}
|
||||
|
||||
/// Update the quote entry position
|
||||
pub fn change_quote_entry(&mut self, base_change: i64, quote_change: i64) {
|
||||
fn update_entry_price(&mut self, base_change: i64, quote_change_native: i64) {
|
||||
if base_change == 0 {
|
||||
return;
|
||||
}
|
||||
let old_position = self.base_position_lots;
|
||||
let is_increasing = old_position == 0 || old_position.signum() == base_change.signum();
|
||||
cm!(self.quote_running_native += quote_change);
|
||||
cm!(self.quote_running_native += quote_change_native);
|
||||
match is_increasing {
|
||||
true => {
|
||||
cm!(self.quote_entry_native += quote_change);
|
||||
cm!(self.quote_entry_native += quote_change_native);
|
||||
}
|
||||
false => {
|
||||
let new_position = cm!(old_position + base_change);
|
||||
let changes_side = old_position.signum() == -new_position.signum();
|
||||
self.quote_entry_native = if changes_side {
|
||||
cm!(
|
||||
((new_position as f64) * (quote_change as f64) / (base_change as f64))
|
||||
.round()
|
||||
) as i64
|
||||
cm!(((new_position as f64) * (quote_change_native as f64)
|
||||
/ (base_change as f64))
|
||||
.round()) as i64
|
||||
} else {
|
||||
let remaining_frac =
|
||||
(1f64 - (base_change.abs() as f64) / (old_position.abs() as f64)).max(0f64);
|
||||
|
@ -301,14 +308,22 @@ impl PerpPosition {
|
|||
}
|
||||
|
||||
/// Change the base and quote positions as the result of a trade
|
||||
pub fn change_base_and_entry_positions(
|
||||
pub fn change_base_and_quote_positions(
|
||||
&mut self,
|
||||
perp_market: &mut PerpMarket,
|
||||
base_change: i64,
|
||||
quote_change: i64,
|
||||
quote_change_native: I80F48,
|
||||
) {
|
||||
self.change_quote_entry(base_change, quote_change);
|
||||
self.update_entry_price(
|
||||
base_change,
|
||||
quote_change_native.round().checked_to_num().unwrap(),
|
||||
);
|
||||
self.change_base_position(perp_market, base_change);
|
||||
self.change_quote_position(quote_change_native);
|
||||
}
|
||||
|
||||
pub fn change_quote_position(&mut self, quote_change_native: I80F48) {
|
||||
cm!(self.quote_position_native += quote_change_native);
|
||||
}
|
||||
|
||||
/// Calculate the average entry price of the position
|
||||
|
@ -439,7 +454,7 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(0, 0, 0);
|
||||
// Go long 10 @ 10
|
||||
pos.change_base_and_entry_positions(&mut market, 10, -100);
|
||||
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-100));
|
||||
assert_eq!(pos.quote_entry_native, -100);
|
||||
assert_eq!(pos.quote_running_native, -100);
|
||||
assert_eq!(pos.avg_entry_price(), I80F48::from(10));
|
||||
|
@ -450,7 +465,7 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(0, 0, 0);
|
||||
// Go short 10 @ 10
|
||||
pos.change_base_and_entry_positions(&mut market, -10, 100);
|
||||
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(100));
|
||||
assert_eq!(pos.quote_entry_native, 100);
|
||||
assert_eq!(pos.quote_running_native, 100);
|
||||
assert_eq!(pos.avg_entry_price(), I80F48::from(10));
|
||||
|
@ -461,7 +476,7 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(10, -100, -100);
|
||||
// Go long 10 @ 30
|
||||
pos.change_base_and_entry_positions(&mut market, 10, -300);
|
||||
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-300));
|
||||
assert_eq!(pos.quote_entry_native, -400);
|
||||
assert_eq!(pos.quote_running_native, -400);
|
||||
assert_eq!(pos.avg_entry_price(), I80F48::from(20));
|
||||
|
@ -472,7 +487,7 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(-10, 100, 100);
|
||||
// Go short 10 @ 10
|
||||
pos.change_base_and_entry_positions(&mut market, -10, 300);
|
||||
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(300));
|
||||
assert_eq!(pos.quote_entry_native, 400);
|
||||
assert_eq!(pos.quote_running_native, 400);
|
||||
assert_eq!(pos.avg_entry_price(), I80F48::from(20));
|
||||
|
@ -483,7 +498,7 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(-10, 100, 100);
|
||||
// Go long 5 @ 50
|
||||
pos.change_base_and_entry_positions(&mut market, 5, -250);
|
||||
pos.change_base_and_quote_positions(&mut market, 5, I80F48::from(-250));
|
||||
assert_eq!(pos.quote_entry_native, 50);
|
||||
assert_eq!(pos.quote_running_native, -150);
|
||||
assert_eq!(pos.avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing
|
||||
|
@ -494,7 +509,7 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(10, -100, -100);
|
||||
// Go short 5 @ 50
|
||||
pos.change_base_and_entry_positions(&mut market, -5, 250);
|
||||
pos.change_base_and_quote_positions(&mut market, -5, I80F48::from(250));
|
||||
assert_eq!(pos.quote_entry_native, -50);
|
||||
assert_eq!(pos.quote_running_native, 150);
|
||||
assert_eq!(pos.avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing
|
||||
|
@ -505,7 +520,7 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(10, -100, -100);
|
||||
// Go short 10 @ 50
|
||||
pos.change_base_and_entry_positions(&mut market, -10, 250);
|
||||
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(250));
|
||||
assert_eq!(pos.quote_entry_native, 0);
|
||||
assert_eq!(pos.quote_running_native, 150);
|
||||
assert_eq!(pos.avg_entry_price(), I80F48::from(0)); // Entry price zero when no position
|
||||
|
@ -516,7 +531,7 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(-10, 100, 100);
|
||||
// Go long 10 @ 50
|
||||
pos.change_base_and_entry_positions(&mut market, 10, -250);
|
||||
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-250));
|
||||
assert_eq!(pos.quote_entry_native, 0);
|
||||
assert_eq!(pos.quote_running_native, -150);
|
||||
assert_eq!(pos.avg_entry_price(), I80F48::from(0)); // Entry price zero when no position
|
||||
|
@ -527,7 +542,7 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(10, -100, -100);
|
||||
// Go short 15 @ 20
|
||||
pos.change_base_and_entry_positions(&mut market, -15, 300);
|
||||
pos.change_base_and_quote_positions(&mut market, -15, I80F48::from(300));
|
||||
assert_eq!(pos.quote_entry_native, 100);
|
||||
assert_eq!(pos.quote_running_native, 200);
|
||||
assert_eq!(pos.avg_entry_price(), I80F48::from(20)); // Entry price zero when no position
|
||||
|
@ -538,7 +553,7 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(-10, 100, 100);
|
||||
// Go short 15 @ 20
|
||||
pos.change_base_and_entry_positions(&mut market, 15, -300);
|
||||
pos.change_base_and_quote_positions(&mut market, 15, I80F48::from(-300));
|
||||
assert_eq!(pos.quote_entry_native, -100);
|
||||
assert_eq!(pos.quote_running_native, -200);
|
||||
assert_eq!(pos.avg_entry_price(), I80F48::from(20)); // Entry price zero when no position
|
||||
|
@ -549,9 +564,9 @@ mod tests {
|
|||
let mut market = create_perp_market();
|
||||
let mut pos = create_perp_position(0, 0, 0);
|
||||
// Buy 11 @ 10,000
|
||||
pos.change_base_and_entry_positions(&mut market, 11, -11 * 10_000);
|
||||
pos.change_base_and_quote_positions(&mut market, 11, I80F48::from(-11 * 10_000));
|
||||
// Sell 1 @ 12,000
|
||||
pos.change_base_and_entry_positions(&mut market, -1, 12_000);
|
||||
pos.change_base_and_quote_positions(&mut market, -1, I80F48::from(12_000));
|
||||
assert_eq!(pos.quote_entry_native, -10 * 10_000);
|
||||
assert_eq!(pos.quote_running_native, -98_000);
|
||||
assert_eq!(pos.base_position_lots, 10);
|
||||
|
@ -574,7 +589,7 @@ mod tests {
|
|||
}
|
||||
// Apply all of the trades going forward
|
||||
trades.iter().for_each(|[qty, quote]| {
|
||||
pos.change_base_and_entry_positions(&mut market, *qty, *quote);
|
||||
pos.change_base_and_quote_positions(&mut market, *qty, I80F48::from(*quote));
|
||||
});
|
||||
// base_position should be sum of all base quantities
|
||||
assert_eq!(
|
||||
|
@ -583,7 +598,7 @@ mod tests {
|
|||
);
|
||||
// Reverse out all the trades
|
||||
trades.iter().for_each(|[qty, quote]| {
|
||||
pos.change_base_and_entry_positions(&mut market, -*qty, -*quote);
|
||||
pos.change_base_and_quote_positions(&mut market, -*qty, I80F48::from(-*quote));
|
||||
});
|
||||
// base position should be 0
|
||||
assert_eq!(pos.base_position_lots, 0);
|
||||
|
|
|
@ -468,7 +468,7 @@ fn apply_fees(
|
|||
let perp_account = mango_account
|
||||
.ensure_perp_position(market.perp_market_index)?
|
||||
.0;
|
||||
perp_account.quote_position_native -= taker_fees;
|
||||
perp_account.change_quote_position(-taker_fees);
|
||||
market.fees_accrued += taker_fees + maker_fees;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -247,11 +247,11 @@ mod tests {
|
|||
assert_eq!(maker.perp_position_by_raw_index(0).asks_base_lots, 0);
|
||||
assert_eq!(maker.perp_position_by_raw_index(0).taker_base_lots, 0);
|
||||
assert_eq!(maker.perp_position_by_raw_index(0).taker_quote_lots, 0);
|
||||
assert_eq!(maker.perp_position_by_raw_index(0).base_position_lots, 0);
|
||||
assert_eq!(maker.perp_position_by_raw_index(0).base_position_lots(), 0);
|
||||
assert_eq!(
|
||||
maker
|
||||
.perp_position_by_raw_index(0)
|
||||
.quote_position_native
|
||||
.quote_position_native()
|
||||
.to_num::<u32>(),
|
||||
0
|
||||
);
|
||||
|
@ -305,9 +305,9 @@ mod tests {
|
|||
taker.perp_position_by_raw_index(0).taker_quote_lots,
|
||||
match_quantity * price
|
||||
);
|
||||
assert_eq!(taker.perp_position_by_raw_index(0).base_position_lots, 0);
|
||||
assert_eq!(taker.perp_position_by_raw_index(0).base_position_lots(), 0);
|
||||
assert_eq!(
|
||||
taker.perp_position_by_raw_index(0).quote_position_native,
|
||||
taker.perp_position_by_raw_index(0).quote_position_native(),
|
||||
-match_quote * market.taker_fee
|
||||
);
|
||||
|
||||
|
@ -343,11 +343,11 @@ mod tests {
|
|||
assert_eq!(maker.perp_position_by_raw_index(0).taker_base_lots, 0);
|
||||
assert_eq!(maker.perp_position_by_raw_index(0).taker_quote_lots, 0);
|
||||
assert_eq!(
|
||||
maker.perp_position_by_raw_index(0).base_position_lots,
|
||||
maker.perp_position_by_raw_index(0).base_position_lots(),
|
||||
match_quantity
|
||||
);
|
||||
assert_eq!(
|
||||
maker.perp_position_by_raw_index(0).quote_position_native,
|
||||
maker.perp_position_by_raw_index(0).quote_position_native(),
|
||||
-match_quote - match_quote * market.maker_fee
|
||||
);
|
||||
|
||||
|
@ -356,11 +356,11 @@ mod tests {
|
|||
assert_eq!(taker.perp_position_by_raw_index(0).taker_base_lots, 0);
|
||||
assert_eq!(taker.perp_position_by_raw_index(0).taker_quote_lots, 0);
|
||||
assert_eq!(
|
||||
taker.perp_position_by_raw_index(0).base_position_lots,
|
||||
taker.perp_position_by_raw_index(0).base_position_lots(),
|
||||
-match_quantity
|
||||
);
|
||||
assert_eq!(
|
||||
taker.perp_position_by_raw_index(0).quote_position_native,
|
||||
taker.perp_position_by_raw_index(0).quote_position_native(),
|
||||
match_quote - match_quote * market.taker_fee
|
||||
);
|
||||
}
|
||||
|
|
|
@ -88,8 +88,8 @@ pub fn get_pnl_native(
|
|||
) -> I80F48 {
|
||||
let contract_size = perp_market.base_lot_size;
|
||||
let new_quote_pos =
|
||||
I80F48::from_num(-perp_position.base_position_lots * contract_size) * oracle_price;
|
||||
perp_position.quote_position_native - new_quote_pos
|
||||
I80F48::from_num(-perp_position.base_position_lots() * contract_size) * oracle_price;
|
||||
perp_position.quote_position_native() - new_quote_pos
|
||||
}
|
||||
|
||||
pub fn assert_mango_error<T>(
|
||||
|
|
|
@ -341,12 +341,12 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
.unwrap();
|
||||
|
||||
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
||||
assert_eq!(mango_account_0.perps[0].base_position_lots, 1);
|
||||
assert!(mango_account_0.perps[0].quote_position_native < -100.019);
|
||||
assert_eq!(mango_account_0.perps[0].base_position_lots(), 1);
|
||||
assert!(mango_account_0.perps[0].quote_position_native() < -100.019);
|
||||
|
||||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(mango_account_1.perps[0].base_position_lots, -1);
|
||||
assert_eq!(mango_account_1.perps[0].quote_position_native, 100);
|
||||
assert_eq!(mango_account_1.perps[0].base_position_lots(), -1);
|
||||
assert_eq!(mango_account_1.perps[0].quote_position_native(), 100);
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
|
|
|
@ -302,13 +302,13 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
||||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
||||
|
||||
assert_eq!(mango_account_0.perps[0].base_position_lots, 1);
|
||||
assert_eq!(mango_account_1.perps[0].base_position_lots, -1);
|
||||
assert_eq!(mango_account_0.perps[0].base_position_lots(), 1);
|
||||
assert_eq!(mango_account_1.perps[0].base_position_lots(), -1);
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].quote_position_native.round(),
|
||||
mango_account_0.perps[0].quote_position_native().round(),
|
||||
-100_020
|
||||
);
|
||||
assert_eq!(mango_account_1.perps[0].quote_position_native, 100_000);
|
||||
assert_eq!(mango_account_1.perps[0].quote_position_native(), 100_000);
|
||||
}
|
||||
|
||||
// Bank must be valid for quote currency
|
||||
|
@ -545,21 +545,23 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
||||
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].base_position_lots, 1,
|
||||
mango_account_0.perps[0].base_position_lots(),
|
||||
1,
|
||||
"base position unchanged for account 0"
|
||||
);
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].base_position_lots, -1,
|
||||
mango_account_1.perps[0].base_position_lots(),
|
||||
-1,
|
||||
"base position unchanged for account 1"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].quote_position_native.round(),
|
||||
mango_account_0.perps[0].quote_position_native().round(),
|
||||
I80F48::from(-100_020) - partial_settle_amount,
|
||||
"quote position reduced for profitable position by max_settle_amount"
|
||||
);
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].quote_position_native.round(),
|
||||
mango_account_1.perps[0].quote_position_native().round(),
|
||||
I80F48::from(100_000) + partial_settle_amount,
|
||||
"quote position increased for losing position by opposite of first account"
|
||||
);
|
||||
|
@ -607,21 +609,23 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
||||
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].base_position_lots, 1,
|
||||
mango_account_0.perps[0].base_position_lots(),
|
||||
1,
|
||||
"base position unchanged for account 0"
|
||||
);
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].base_position_lots, -1,
|
||||
mango_account_1.perps[0].base_position_lots(),
|
||||
-1,
|
||||
"base position unchanged for account 1"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].quote_position_native.round(),
|
||||
mango_account_0.perps[0].quote_position_native().round(),
|
||||
I80F48::from(-100_020) - expected_pnl_0,
|
||||
"quote position reduced for profitable position"
|
||||
);
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].quote_position_native.round(),
|
||||
mango_account_1.perps[0].quote_position_native().round(),
|
||||
I80F48::from(100_000) + expected_pnl_0,
|
||||
"quote position increased for losing position by opposite of first account"
|
||||
);
|
||||
|
@ -700,21 +704,23 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
||||
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].base_position_lots, 1,
|
||||
mango_account_0.perps[0].base_position_lots(),
|
||||
1,
|
||||
"base position unchanged for account 0"
|
||||
);
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].base_position_lots, -1,
|
||||
mango_account_1.perps[0].base_position_lots(),
|
||||
-1,
|
||||
"base position unchanged for account 1"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].quote_position_native.round(),
|
||||
mango_account_0.perps[0].quote_position_native().round(),
|
||||
I80F48::from(-100_500) + expected_pnl_1,
|
||||
"quote position increased for losing position"
|
||||
);
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].quote_position_native.round(),
|
||||
mango_account_1.perps[0].quote_position_native().round(),
|
||||
I80F48::from(100_480) - expected_pnl_1,
|
||||
"quote position reduced for losing position by opposite of first account"
|
||||
);
|
||||
|
|
|
@ -299,15 +299,15 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
.unwrap();
|
||||
|
||||
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
||||
assert_eq!(mango_account_0.perps[0].base_position_lots, 1);
|
||||
assert_eq!(mango_account_0.perps[0].base_position_lots(), 1);
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].quote_position_native.round(),
|
||||
mango_account_0.perps[0].quote_position_native().round(),
|
||||
-100_020
|
||||
);
|
||||
|
||||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(mango_account_1.perps[0].base_position_lots, -1);
|
||||
assert_eq!(mango_account_1.perps[0].quote_position_native, 100_000);
|
||||
assert_eq!(mango_account_1.perps[0].base_position_lots(), -1);
|
||||
assert_eq!(mango_account_1.perps[0].quote_position_native(), 100_000);
|
||||
|
||||
// Bank must be valid for quote currency
|
||||
let result = send_tx(
|
||||
|
@ -528,12 +528,13 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
|
||||
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].base_position_lots, -1,
|
||||
mango_account_1.perps[0].base_position_lots(),
|
||||
-1,
|
||||
"base position unchanged for account 1"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].quote_position_native.round(),
|
||||
mango_account_1.perps[0].quote_position_native().round(),
|
||||
I80F48::from(100_000) + partial_settle_amount,
|
||||
"quote position increased for losing position by fee settle amount"
|
||||
);
|
||||
|
@ -582,12 +583,13 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
|
||||
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].base_position_lots, -1,
|
||||
mango_account_1.perps[0].base_position_lots(),
|
||||
-1,
|
||||
"base position unchanged for account 1"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].quote_position_native.round(),
|
||||
mango_account_1.perps[0].quote_position_native().round(),
|
||||
I80F48::from(100_000) + initial_fees,
|
||||
"quote position increased for losing position by fees settled"
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue