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:
Christian Kamm 2022-09-07 17:02:51 +02:00
parent e56ed3776c
commit 6c32077d1a
12 changed files with 119 additions and 102 deletions

View File

@ -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

View File

@ -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();

View File

@ -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,

View File

@ -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![

View File

@ -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(())
}

View File

@ -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);

View File

@ -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(())

View File

@ -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
);
}

View File

@ -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>(

View File

@ -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,

View File

@ -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"
);

View File

@ -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"
);