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 // Calculate PnL for each account
let base_native = perp_position.base_position_native(&perp_market); 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 // Account perp position must have a loss to be able to settle against the fee account
require!(pnl.is_negative(), MangoError::ProfitabilityMismatch); require!(pnl.is_negative(), MangoError::ProfitabilityMismatch);
@ -72,7 +72,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: I80F48)
.abs() .abs()
.min(perp_market.fees_accrued.abs()) .min(perp_market.fees_accrued.abs())
.min(max_settle_amount); .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); perp_market.fees_accrued = cm!(perp_market.fees_accrued - settlement);
// Update the account's net_settled with the new PnL // 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())?)?; perp_market.oracle_price(&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?)?;
// Fetch perp positions for accounts // Fetch perp positions for accounts
let mut a_perp_position = account_a.perp_position_mut(perp_market.perp_market_index)?; let 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 b_perp_position = account_b.perp_position_mut(perp_market.perp_market_index)?;
// Settle funding before settling any PnL // Settle funding before settling any PnL
a_perp_position.settle_funding(&perp_market); 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 // Calculate PnL for each account
let a_base_native = a_perp_position.base_position_native(&perp_market); 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 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 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 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 // Account A must be profitable, and B must be unprofitable
// PnL must be opposite signs for there to be a settlement // 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 // Settle for the maximum possible capped to max_settle_amount
let settlement = a_pnl.abs().min(b_pnl.abs()).min(max_settle_amount); let settlement = a_pnl.abs().min(b_pnl.abs()).min(max_settle_amount);
cm!(a_perp_position.quote_position_native -= settlement); a_perp_position.change_quote_position(-settlement);
cm!(b_perp_position.quote_position_native += settlement); b_perp_position.change_quote_position(settlement);
// Update the account's net_settled with the new PnL // Update the account's net_settled with the new PnL
let settlement_i64 = settlement.checked_to_num::<i64>().unwrap(); let settlement_i64 = settlement.checked_to_num::<i64>().unwrap();

View File

@ -18,8 +18,8 @@ pub fn emit_perp_balances(
mango_group, mango_group,
mango_account, mango_account,
market_index, market_index,
base_position: pp.base_position_lots, base_position: pp.base_position_lots(),
quote_position: pp.quote_position_native.to_bits(), quote_position: pp.quote_position_native().to_bits(),
long_settled_funding: pp.long_settled_funding.to_bits(), long_settled_funding: pp.long_settled_funding.to_bits(),
short_settled_funding: pp.short_settled_funding.to_bits(), short_settled_funding: pp.short_settled_funding.to_bits(),
price, price,

View File

@ -498,14 +498,14 @@ impl PerpInfo {
let base_info = &token_infos[base_index]; let base_info = &token_infos[base_index];
let base_lot_size = I80F48::from(perp_market.base_lot_size); 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 unsettled_funding = perp_position.unsettled_funding(&perp_market);
let taker_quote = I80F48::from(cm!( let taker_quote = I80F48::from(cm!(
perp_position.taker_quote_lots * perp_market.quote_lot_size perp_position.taker_quote_lots * perp_market.quote_lot_size
)); ));
let quote_current = 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: // Two scenarios:
// 1. The price goes low and all bids execute, converting to base. // 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 mut perp1 = mock_perp_market(group, 9, 4, 0.2, 0.1);
let perpaccount = account.ensure_perp_position(9).unwrap().0; let perpaccount = account.ensure_perp_position(9).unwrap().0;
perpaccount.base_position_lots = 3; perpaccount.change_base_and_quote_positions(perp1.data(), 3, -I80F48::from(310u16));
perpaccount.quote_position_native = -I80F48::from(310u16);
perpaccount.bids_base_lots = 7; perpaccount.bids_base_lots = 7;
perpaccount.asks_base_lots = 11; perpaccount.asks_base_lots = 11;
perpaccount.taker_base_lots = 1; 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 mut perp1 = mock_perp_market(group, 9, 4, 0.2, 0.1);
let perpaccount = account.ensure_perp_position(9).unwrap().0; let perpaccount = account.ensure_perp_position(9).unwrap().0;
perpaccount.base_position_lots = testcase.perp1.0; perpaccount.change_base_and_quote_positions(
perpaccount.quote_position_native = I80F48::from(testcase.perp1.1); perp1.data(),
testcase.perp1.0,
I80F48::from(testcase.perp1.1),
);
perpaccount.bids_base_lots = testcase.perp1.2; perpaccount.bids_base_lots = testcase.perp1.2;
perpaccount.asks_base_lots = testcase.perp1.3; 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); let mut perp1 = mock_perp_market(group, 9, 1, 0.2, 0.1);
perp1.data().long_funding = I80F48::from_num(10.1); perp1.data().long_funding = I80F48::from_num(10.1);
let perpaccount = account.ensure_perp_position(9).unwrap().0; let perpaccount = account.ensure_perp_position(9).unwrap().0;
perpaccount.base_position_lots = 10; // 100 base native perpaccount.change_base_and_quote_positions(perp1.data(), 10, I80F48::from(-110));
perpaccount.quote_position_native = I80F48::from(-110);
perpaccount.long_settled_funding = I80F48::from_num(10.0); perpaccount.long_settled_funding = I80F48::from_num(10.0);
let ais = vec![ let ais = vec![

View File

@ -712,10 +712,9 @@ impl<
if raw_index_opt.is_none() { if raw_index_opt.is_none() {
raw_index_opt = self.all_perp_positions().position(|p| !p.is_active()); raw_index_opt = self.all_perp_positions().position(|p| !p.is_active());
if let Some(raw_index) = raw_index_opt { if let Some(raw_index) = raw_index_opt {
*(self.perp_position_mut_by_raw_index(raw_index)) = PerpPosition { let perp_position = self.perp_position_mut_by_raw_index(raw_index);
market_index: perp_market_index, *perp_position = PerpPosition::default();
..Default::default() perp_position.market_index = perp_market_index;
};
} }
} }
if let Some(raw_index) = raw_index_opt { if let Some(raw_index) = raw_index_opt {
@ -796,18 +795,13 @@ impl<
let side = fill.taker_side.invert_side(); let side = fill.taker_side.invert_side();
let (base_change, quote_change) = fill.base_quote_change(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 = cm!(I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change));
let quote = I80F48::from_num( let fees = cm!(quote.abs() * fill.maker_fee);
perp_market
.quote_lot_size
.checked_mul(quote_change)
.unwrap(),
);
let fees = quote.abs() * fill.maker_fee;
if !fill.market_fees_applied { 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 { if fill.maker_out {
self.remove_perp_order(fill.maker_slot as usize, base_change.abs()) 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); let (base_change, quote_change) = fill.base_quote_change(fill.taker_side);
pa.remove_taker_trade(base_change, quote_change); 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 // 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(()) Ok(())
} }

View File

@ -153,10 +153,10 @@ pub struct PerpPosition {
pub padding: [u8; 6], pub padding: [u8; 6],
/// Active position size, measured in base lots /// 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) /// Active position in quote (conversation rate is that of the time the order was settled)
/// measured in native quote /// 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 /// Tracks what the position is to calculate average entry & break even price
pub quote_entry_native: i64, pub quote_entry_native: i64,
@ -240,8 +240,16 @@ impl PerpPosition {
I80F48::from(cm!(self.base_position_lots * market.base_lot_size)) 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 /// 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; let start = self.base_position_lots;
self.base_position_lots += base_change; self.base_position_lots += base_change;
perp_market.open_interest += self.base_position_lots.abs() - start.abs(); perp_market.open_interest += self.base_position_lots.abs() - start.abs();
@ -271,25 +279,24 @@ impl PerpPosition {
} }
/// Update the quote entry position /// 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 { if base_change == 0 {
return; return;
} }
let old_position = self.base_position_lots; let old_position = self.base_position_lots;
let is_increasing = old_position == 0 || old_position.signum() == base_change.signum(); 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 { match is_increasing {
true => { true => {
cm!(self.quote_entry_native += quote_change); cm!(self.quote_entry_native += quote_change_native);
} }
false => { false => {
let new_position = cm!(old_position + base_change); let new_position = cm!(old_position + base_change);
let changes_side = old_position.signum() == -new_position.signum(); let changes_side = old_position.signum() == -new_position.signum();
self.quote_entry_native = if changes_side { self.quote_entry_native = if changes_side {
cm!( cm!(((new_position as f64) * (quote_change_native as f64)
((new_position as f64) * (quote_change as f64) / (base_change as f64)) / (base_change as f64))
.round() .round()) as i64
) as i64
} else { } else {
let remaining_frac = let remaining_frac =
(1f64 - (base_change.abs() as f64) / (old_position.abs() as f64)).max(0f64); (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 /// 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, &mut self,
perp_market: &mut PerpMarket, perp_market: &mut PerpMarket,
base_change: i64, 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_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 /// Calculate the average entry price of the position
@ -439,7 +454,7 @@ mod tests {
let mut market = create_perp_market(); let mut market = create_perp_market();
let mut pos = create_perp_position(0, 0, 0); let mut pos = create_perp_position(0, 0, 0);
// Go long 10 @ 10 // 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_entry_native, -100);
assert_eq!(pos.quote_running_native, -100); assert_eq!(pos.quote_running_native, -100);
assert_eq!(pos.avg_entry_price(), I80F48::from(10)); assert_eq!(pos.avg_entry_price(), I80F48::from(10));
@ -450,7 +465,7 @@ mod tests {
let mut market = create_perp_market(); let mut market = create_perp_market();
let mut pos = create_perp_position(0, 0, 0); let mut pos = create_perp_position(0, 0, 0);
// Go short 10 @ 10 // 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_entry_native, 100);
assert_eq!(pos.quote_running_native, 100); assert_eq!(pos.quote_running_native, 100);
assert_eq!(pos.avg_entry_price(), I80F48::from(10)); assert_eq!(pos.avg_entry_price(), I80F48::from(10));
@ -461,7 +476,7 @@ mod tests {
let mut market = create_perp_market(); let mut market = create_perp_market();
let mut pos = create_perp_position(10, -100, -100); let mut pos = create_perp_position(10, -100, -100);
// Go long 10 @ 30 // 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_entry_native, -400);
assert_eq!(pos.quote_running_native, -400); assert_eq!(pos.quote_running_native, -400);
assert_eq!(pos.avg_entry_price(), I80F48::from(20)); assert_eq!(pos.avg_entry_price(), I80F48::from(20));
@ -472,7 +487,7 @@ mod tests {
let mut market = create_perp_market(); let mut market = create_perp_market();
let mut pos = create_perp_position(-10, 100, 100); let mut pos = create_perp_position(-10, 100, 100);
// Go short 10 @ 10 // 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_entry_native, 400);
assert_eq!(pos.quote_running_native, 400); assert_eq!(pos.quote_running_native, 400);
assert_eq!(pos.avg_entry_price(), I80F48::from(20)); assert_eq!(pos.avg_entry_price(), I80F48::from(20));
@ -483,7 +498,7 @@ mod tests {
let mut market = create_perp_market(); let mut market = create_perp_market();
let mut pos = create_perp_position(-10, 100, 100); let mut pos = create_perp_position(-10, 100, 100);
// Go long 5 @ 50 // 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_entry_native, 50);
assert_eq!(pos.quote_running_native, -150); assert_eq!(pos.quote_running_native, -150);
assert_eq!(pos.avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing 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 market = create_perp_market();
let mut pos = create_perp_position(10, -100, -100); let mut pos = create_perp_position(10, -100, -100);
// Go short 5 @ 50 // 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_entry_native, -50);
assert_eq!(pos.quote_running_native, 150); assert_eq!(pos.quote_running_native, 150);
assert_eq!(pos.avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing 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 market = create_perp_market();
let mut pos = create_perp_position(10, -100, -100); let mut pos = create_perp_position(10, -100, -100);
// Go short 10 @ 50 // 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_entry_native, 0);
assert_eq!(pos.quote_running_native, 150); assert_eq!(pos.quote_running_native, 150);
assert_eq!(pos.avg_entry_price(), I80F48::from(0)); // Entry price zero when no position 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 market = create_perp_market();
let mut pos = create_perp_position(-10, 100, 100); let mut pos = create_perp_position(-10, 100, 100);
// Go long 10 @ 50 // 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_entry_native, 0);
assert_eq!(pos.quote_running_native, -150); assert_eq!(pos.quote_running_native, -150);
assert_eq!(pos.avg_entry_price(), I80F48::from(0)); // Entry price zero when no position 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 market = create_perp_market();
let mut pos = create_perp_position(10, -100, -100); let mut pos = create_perp_position(10, -100, -100);
// Go short 15 @ 20 // 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_entry_native, 100);
assert_eq!(pos.quote_running_native, 200); assert_eq!(pos.quote_running_native, 200);
assert_eq!(pos.avg_entry_price(), I80F48::from(20)); // Entry price zero when no position 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 market = create_perp_market();
let mut pos = create_perp_position(-10, 100, 100); let mut pos = create_perp_position(-10, 100, 100);
// Go short 15 @ 20 // 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_entry_native, -100);
assert_eq!(pos.quote_running_native, -200); assert_eq!(pos.quote_running_native, -200);
assert_eq!(pos.avg_entry_price(), I80F48::from(20)); // Entry price zero when no position 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 market = create_perp_market();
let mut pos = create_perp_position(0, 0, 0); let mut pos = create_perp_position(0, 0, 0);
// Buy 11 @ 10,000 // 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 // 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_entry_native, -10 * 10_000);
assert_eq!(pos.quote_running_native, -98_000); assert_eq!(pos.quote_running_native, -98_000);
assert_eq!(pos.base_position_lots, 10); assert_eq!(pos.base_position_lots, 10);
@ -574,7 +589,7 @@ mod tests {
} }
// Apply all of the trades going forward // Apply all of the trades going forward
trades.iter().for_each(|[qty, quote]| { 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 // base_position should be sum of all base quantities
assert_eq!( assert_eq!(
@ -583,7 +598,7 @@ mod tests {
); );
// Reverse out all the trades // Reverse out all the trades
trades.iter().for_each(|[qty, quote]| { 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 // base position should be 0
assert_eq!(pos.base_position_lots, 0); assert_eq!(pos.base_position_lots, 0);

View File

@ -468,7 +468,7 @@ fn apply_fees(
let perp_account = mango_account let perp_account = mango_account
.ensure_perp_position(market.perp_market_index)? .ensure_perp_position(market.perp_market_index)?
.0; .0;
perp_account.quote_position_native -= taker_fees; perp_account.change_quote_position(-taker_fees);
market.fees_accrued += taker_fees + maker_fees; market.fees_accrued += taker_fees + maker_fees;
Ok(()) 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).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_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).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!( assert_eq!(
maker maker
.perp_position_by_raw_index(0) .perp_position_by_raw_index(0)
.quote_position_native .quote_position_native()
.to_num::<u32>(), .to_num::<u32>(),
0 0
); );
@ -305,9 +305,9 @@ mod tests {
taker.perp_position_by_raw_index(0).taker_quote_lots, taker.perp_position_by_raw_index(0).taker_quote_lots,
match_quantity * price 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!( 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 -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_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).taker_quote_lots, 0);
assert_eq!( assert_eq!(
maker.perp_position_by_raw_index(0).base_position_lots, maker.perp_position_by_raw_index(0).base_position_lots(),
match_quantity match_quantity
); );
assert_eq!( 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 -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_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).taker_quote_lots, 0);
assert_eq!( assert_eq!(
taker.perp_position_by_raw_index(0).base_position_lots, taker.perp_position_by_raw_index(0).base_position_lots(),
-match_quantity -match_quantity
); );
assert_eq!( 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 match_quote - match_quote * market.taker_fee
); );
} }

View File

@ -88,8 +88,8 @@ pub fn get_pnl_native(
) -> I80F48 { ) -> I80F48 {
let contract_size = perp_market.base_lot_size; let contract_size = perp_market.base_lot_size;
let new_quote_pos = let new_quote_pos =
I80F48::from_num(-perp_position.base_position_lots * contract_size) * oracle_price; I80F48::from_num(-perp_position.base_position_lots() * contract_size) * oracle_price;
perp_position.quote_position_native - new_quote_pos perp_position.quote_position_native() - new_quote_pos
} }
pub fn assert_mango_error<T>( pub fn assert_mango_error<T>(

View File

@ -341,12 +341,12 @@ async fn test_perp() -> Result<(), TransportError> {
.unwrap(); .unwrap();
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await; 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!(mango_account_0.perps[0].quote_position_native < -100.019); assert!(mango_account_0.perps[0].quote_position_native() < -100.019);
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await; 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].base_position_lots(), -1);
assert_eq!(mango_account_1.perps[0].quote_position_native, 100); assert_eq!(mango_account_1.perps[0].quote_position_native(), 100);
send_tx( send_tx(
solana, 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_0 = solana.get_account::<MangoAccount>(account_0).await;
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).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_0.perps[0].base_position_lots(), 1);
assert_eq!(mango_account_1.perps[0].base_position_lots, -1); assert_eq!(mango_account_1.perps[0].base_position_lots(), -1);
assert_eq!( assert_eq!(
mango_account_0.perps[0].quote_position_native.round(), mango_account_0.perps[0].quote_position_native().round(),
-100_020 -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 // 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; let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
assert_eq!( 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" "base position unchanged for account 0"
); );
assert_eq!( 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" "base position unchanged for account 1"
); );
assert_eq!( 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, I80F48::from(-100_020) - partial_settle_amount,
"quote position reduced for profitable position by max_settle_amount" "quote position reduced for profitable position by max_settle_amount"
); );
assert_eq!( 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, I80F48::from(100_000) + partial_settle_amount,
"quote position increased for losing position by opposite of first account" "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; let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
assert_eq!( 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" "base position unchanged for account 0"
); );
assert_eq!( 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" "base position unchanged for account 1"
); );
assert_eq!( 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, I80F48::from(-100_020) - expected_pnl_0,
"quote position reduced for profitable position" "quote position reduced for profitable position"
); );
assert_eq!( 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, I80F48::from(100_000) + expected_pnl_0,
"quote position increased for losing position by opposite of first account" "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; let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
assert_eq!( 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" "base position unchanged for account 0"
); );
assert_eq!( 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" "base position unchanged for account 1"
); );
assert_eq!( 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, I80F48::from(-100_500) + expected_pnl_1,
"quote position increased for losing position" "quote position increased for losing position"
); );
assert_eq!( 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, I80F48::from(100_480) - expected_pnl_1,
"quote position reduced for losing position by opposite of first account" "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(); .unwrap();
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await; 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!( assert_eq!(
mango_account_0.perps[0].quote_position_native.round(), mango_account_0.perps[0].quote_position_native().round(),
-100_020 -100_020
); );
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await; 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].base_position_lots(), -1);
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 // Bank must be valid for quote currency
let result = send_tx( 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; let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
assert_eq!( 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" "base position unchanged for account 1"
); );
assert_eq!( 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, I80F48::from(100_000) + partial_settle_amount,
"quote position increased for losing position by fee 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; let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
assert_eq!( 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" "base position unchanged for account 1"
); );
assert_eq!( 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, I80F48::from(100_000) + initial_fees,
"quote position increased for losing position by fees settled" "quote position increased for losing position by fees settled"
); );