fix perp settle limit materialization (#865)
Previously, we tried to keep track of "other" and "trade" realized pnl. An issue occured when a perp base position went to zero: the way we computed the trade pnl included potential non-trade unsettled pnl. That caused follow-up trouble because the value could change sign and reset the settle limit for trade pnl. This change aims to simplify in some ways: - explicitly talk about oneshot-settleable pnl (fees, funding, liquidation) and recurring-settleable pnl (materialization of settle limit derived from the stable value of the base position when reducing the base position) - instead of directly tracking realized settleable amounts (which doesn't really work), just decrease the recurring settleable amount when it exceeds the remaining unsettled pnl - get rid of the directionality to avoid bugs of that kind - stop tracking unsettled-realized trade pnl (it was wrong before, and no client uses it) - we already track position-lifetime realized trade pnl
This commit is contained in:
parent
719aee37ae
commit
ae5907ba3a
|
@ -9334,36 +9334,44 @@
|
|||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "realizedTradePnlNative",
|
||||
"name": "deprecatedRealizedTradePnlNative",
|
||||
"docs": [
|
||||
"Amount of pnl that was realized by bringing the base position closer to 0.",
|
||||
"",
|
||||
"The settlement of this type of pnl is limited by settle_pnl_limit_realized_trade.",
|
||||
"Settling pnl reduces this value once other_pnl below is exhausted."
|
||||
"Deprecated field: Amount of pnl that was realized by bringing the base position closer to 0."
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "realizedOtherPnlNative",
|
||||
"name": "oneshotSettlePnlAllowance",
|
||||
"docs": [
|
||||
"Amount of pnl realized from fees, funding and liquidation.",
|
||||
"Amount of pnl that can be settled once.",
|
||||
"",
|
||||
"This type of realized pnl is always settleable.",
|
||||
"Settling pnl reduces this value first."
|
||||
"- The value is signed: a negative number means negative pnl can be settled.",
|
||||
"- A settlement in the right direction will decrease this amount.",
|
||||
"",
|
||||
"Typically added for fees, funding and liquidation."
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "settlePnlLimitRealizedTrade",
|
||||
"name": "recurringSettlePnlAllowance",
|
||||
"docs": [
|
||||
"Settle limit contribution from realized pnl.",
|
||||
"Amount of pnl that can be settled in each settle window.",
|
||||
"",
|
||||
"Every time pnl is realized, this is increased by a fraction of the stable",
|
||||
"value of the realization. It magnitude decreases when realized pnl drops below its value."
|
||||
"- Unsigned, the settlement can happen in both directions. Value is >= 0.",
|
||||
"- Previously stored a similar value that was signed, so in migration cases",
|
||||
"this value can be negative and should be .abs()ed.",
|
||||
"- If this value exceeds the current stable-upnl, it should be decreased,",
|
||||
"see apply_recurring_settle_pnl_allowance_constraint()",
|
||||
"",
|
||||
"When the base position is reduced, the settle limit contribution from the reduced",
|
||||
"base position is materialized into this value. When the base position increases,",
|
||||
"some of the allowance is taken away.",
|
||||
"",
|
||||
"This also gets increased when a liquidator takes over pnl."
|
||||
],
|
||||
"type": "i64"
|
||||
},
|
||||
|
|
|
@ -74,40 +74,37 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
|||
group,
|
||||
event_queue
|
||||
);
|
||||
let before_pnl = maker_taker
|
||||
.perp_position(perp_market_index)?
|
||||
.realized_trade_pnl_native;
|
||||
maker_taker.execute_perp_maker(
|
||||
let maker_realized_pnl = maker_taker.execute_perp_maker(
|
||||
perp_market_index,
|
||||
&mut perp_market,
|
||||
fill,
|
||||
&group,
|
||||
)?;
|
||||
maker_taker.execute_perp_taker(perp_market_index, &mut perp_market, fill)?;
|
||||
let taker_realized_pnl = maker_taker.execute_perp_taker(
|
||||
perp_market_index,
|
||||
&mut perp_market,
|
||||
fill,
|
||||
)?;
|
||||
emit_perp_balances(
|
||||
group_key,
|
||||
fill.maker,
|
||||
maker_taker.perp_position(perp_market_index).unwrap(),
|
||||
&perp_market,
|
||||
);
|
||||
let after_pnl = maker_taker
|
||||
.perp_position(perp_market_index)?
|
||||
.realized_trade_pnl_native;
|
||||
let closed_pnl = after_pnl - before_pnl;
|
||||
let closed_pnl = maker_realized_pnl + taker_realized_pnl;
|
||||
(closed_pnl, closed_pnl)
|
||||
} else {
|
||||
load_mango_account!(maker, fill.maker, mango_account_ais, group, event_queue);
|
||||
load_mango_account!(taker, fill.taker, mango_account_ais, group, event_queue);
|
||||
|
||||
let maker_before_pnl = maker
|
||||
.perp_position(perp_market_index)?
|
||||
.realized_trade_pnl_native;
|
||||
let taker_before_pnl = taker
|
||||
.perp_position(perp_market_index)?
|
||||
.realized_trade_pnl_native;
|
||||
|
||||
maker.execute_perp_maker(perp_market_index, &mut perp_market, fill, &group)?;
|
||||
taker.execute_perp_taker(perp_market_index, &mut perp_market, fill)?;
|
||||
let maker_realized_pnl = maker.execute_perp_maker(
|
||||
perp_market_index,
|
||||
&mut perp_market,
|
||||
fill,
|
||||
&group,
|
||||
)?;
|
||||
let taker_realized_pnl =
|
||||
taker.execute_perp_taker(perp_market_index, &mut perp_market, fill)?;
|
||||
emit_perp_balances(
|
||||
group_key,
|
||||
fill.maker,
|
||||
|
@ -120,16 +117,8 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
|||
taker.perp_position(perp_market_index).unwrap(),
|
||||
&perp_market,
|
||||
);
|
||||
let maker_after_pnl = maker
|
||||
.perp_position(perp_market_index)?
|
||||
.realized_trade_pnl_native;
|
||||
let taker_after_pnl = taker
|
||||
.perp_position(perp_market_index)?
|
||||
.realized_trade_pnl_native;
|
||||
|
||||
let maker_closed_pnl = maker_after_pnl - maker_before_pnl;
|
||||
let taker_closed_pnl = taker_after_pnl - taker_before_pnl;
|
||||
(maker_closed_pnl, taker_closed_pnl)
|
||||
(maker_realized_pnl, taker_realized_pnl)
|
||||
};
|
||||
emit_stack(FillLogV3 {
|
||||
mango_group: group_key,
|
||||
|
|
|
@ -598,7 +598,7 @@ pub(crate) fn liquidation_action(
|
|||
let token_transfer = pnl_transfer * spot_gain_per_settled;
|
||||
|
||||
liqor_perp_position.record_liquidation_pnl_takeover(pnl_transfer, limit_transfer);
|
||||
liqee_perp_position.record_settle(pnl_transfer);
|
||||
liqee_perp_position.record_settle(pnl_transfer, &perp_market);
|
||||
|
||||
// Update the accounts' perp_spot_transfer statistics.
|
||||
let transfer_i64 = token_transfer.round_to_zero().to_num::<i64>();
|
||||
|
@ -1027,7 +1027,7 @@ mod tests {
|
|||
init_liqee_base,
|
||||
I80F48::from_num(init_liqee_quote),
|
||||
);
|
||||
p.realized_other_pnl_native = p
|
||||
p.oneshot_settle_pnl_allowance = p
|
||||
.unsettled_pnl(setup.perp_market.data(), I80F48::ONE)
|
||||
.unwrap();
|
||||
|
||||
|
@ -1072,7 +1072,7 @@ mod tests {
|
|||
// The settle limit taken over matches the quote pos when removing the
|
||||
// quote gains from giving away base lots
|
||||
assert_eq_f!(
|
||||
I80F48::from_num(liqor_perp.settle_pnl_limit_realized_trade),
|
||||
I80F48::from_num(liqor_perp.recurring_settle_pnl_allowance),
|
||||
liqor_perp.quote_position_native.to_num::<f64>()
|
||||
+ liqor_perp.base_position_lots as f64,
|
||||
1.1
|
||||
|
|
|
@ -267,7 +267,7 @@ pub(crate) fn liquidation_action(
|
|||
.max(I80F48::ZERO);
|
||||
if settlement > 0 {
|
||||
liqor_perp_position.record_liquidation_quote_change(-settlement);
|
||||
liqee_perp_position.record_settle(-settlement);
|
||||
liqee_perp_position.record_settle(-settlement, &perp_market);
|
||||
|
||||
// Update the accounts' perp_spot_transfer statistics.
|
||||
let settlement_i64 = settlement.round_to_zero().to_num::<i64>();
|
||||
|
@ -380,7 +380,7 @@ pub(crate) fn liquidation_action(
|
|||
|
||||
// transfer perp quote loss from the liqee to the liqor
|
||||
let liqor_perp_position = liqor.perp_position_mut(perp_market_index)?;
|
||||
liqee_perp_position.record_settle(-insurance_liab_transfer);
|
||||
liqee_perp_position.record_settle(-insurance_liab_transfer, &perp_market);
|
||||
liqor_perp_position.record_liquidation_quote_change(-insurance_liab_transfer);
|
||||
|
||||
msg!(
|
||||
|
@ -399,7 +399,7 @@ pub(crate) fn liquidation_action(
|
|||
(perp_market.long_funding, perp_market.short_funding);
|
||||
if insurance_fund_exhausted && remaining_liab > 0 {
|
||||
perp_market.socialize_loss(-remaining_liab)?;
|
||||
liqee_perp_position.record_settle(-remaining_liab);
|
||||
liqee_perp_position.record_settle(-remaining_liab, &perp_market);
|
||||
socialized_loss = remaining_liab;
|
||||
msg!("socialized loss: {}", socialized_loss);
|
||||
}
|
||||
|
@ -760,7 +760,7 @@ mod tests {
|
|||
{
|
||||
let p = perp_p(&mut setup.liqee);
|
||||
p.quote_position_native = I80F48::from_num(init_perp);
|
||||
p.settle_pnl_limit_realized_trade = -settle_limit;
|
||||
p.recurring_settle_pnl_allowance = (settle_limit as i64).abs();
|
||||
|
||||
let settle_bank = setup.settle_bank.data();
|
||||
settle_bank
|
||||
|
|
|
@ -68,7 +68,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
|
|||
.min(I80F48::from(max_settle_amount));
|
||||
require!(settlement >= 0, MangoError::SettlementAmountMustBePositive);
|
||||
|
||||
perp_position.record_settle(-settlement); // settle the negative pnl on the user perp position
|
||||
perp_position.record_settle(-settlement, &perp_market); // settle the negative pnl on the user perp position
|
||||
perp_market.fees_accrued -= settlement;
|
||||
|
||||
emit_perp_balances(
|
||||
|
|
|
@ -143,8 +143,8 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
b_max_settle,
|
||||
);
|
||||
|
||||
a_perp_position.record_settle(settlement);
|
||||
b_perp_position.record_settle(-settlement);
|
||||
a_perp_position.record_settle(settlement, &perp_market);
|
||||
b_perp_position.record_settle(-settlement, &perp_market);
|
||||
emit_perp_balances(
|
||||
ctx.accounts.group.key(),
|
||||
ctx.accounts.account_a.key(),
|
||||
|
|
|
@ -1237,13 +1237,14 @@ impl<
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns amount of realized trade pnl for the maker
|
||||
pub fn execute_perp_maker(
|
||||
&mut self,
|
||||
perp_market_index: PerpMarketIndex,
|
||||
perp_market: &mut PerpMarket,
|
||||
fill: &FillEvent,
|
||||
group: &Group,
|
||||
) -> Result<()> {
|
||||
) -> Result<I80F48> {
|
||||
let side = fill.taker_side().invert_side();
|
||||
let (base_change, quote_change) = fill.base_quote_change(side);
|
||||
let quote = I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change);
|
||||
|
@ -1257,7 +1258,7 @@ impl<
|
|||
let pa = self.perp_position_mut(perp_market_index)?;
|
||||
pa.settle_funding(perp_market);
|
||||
pa.record_trading_fee(fees);
|
||||
pa.record_trade(perp_market, base_change, quote);
|
||||
let realized_pnl = pa.record_trade(perp_market, base_change, quote);
|
||||
|
||||
pa.maker_volume += quote.abs().to_num::<u64>();
|
||||
|
||||
|
@ -1288,15 +1289,16 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(realized_pnl)
|
||||
}
|
||||
|
||||
/// Returns amount of realized trade pnl for the taker
|
||||
pub fn execute_perp_taker(
|
||||
&mut self,
|
||||
perp_market_index: PerpMarketIndex,
|
||||
perp_market: &mut PerpMarket,
|
||||
fill: &FillEvent,
|
||||
) -> Result<()> {
|
||||
) -> Result<I80F48> {
|
||||
let pa = self.perp_position_mut(perp_market_index)?;
|
||||
pa.settle_funding(perp_market);
|
||||
|
||||
|
@ -1305,11 +1307,11 @@ impl<
|
|||
// fees are assessed at time of trade; no need to assess fees here
|
||||
let quote_change_native =
|
||||
I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change);
|
||||
pa.record_trade(perp_market, base_change, quote_change_native);
|
||||
let realized_pnl = pa.record_trade(perp_market, base_change, quote_change_native);
|
||||
|
||||
pa.taker_volume += quote_change_native.abs().to_num::<u64>();
|
||||
|
||||
Ok(())
|
||||
Ok(realized_pnl)
|
||||
}
|
||||
|
||||
pub fn execute_perp_out_event(
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -112,8 +112,8 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
|||
taker_fee: 0.0,
|
||||
group_insurance_fund: true,
|
||||
// adjust this factur such that we get the desired settle limit in the end
|
||||
settle_pnl_limit_factor: (settle_limit as f32 + 0.1).min(0.0)
|
||||
/ (-1.0 * 100.0 * adj_price) as f32,
|
||||
settle_pnl_limit_factor: (settle_limit as f32 - 0.1).max(0.0)
|
||||
/ (1.0 * 100.0 * adj_price) as f32,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, base_token).await
|
||||
},
|
||||
|
@ -227,7 +227,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
|||
let account_data = solana.get_account::<MangoAccount>(account).await;
|
||||
assert_eq!(account_data.perps[0].quote_position_native(), pnl);
|
||||
assert_eq!(
|
||||
account_data.perps[0].settle_pnl_limit_realized_trade,
|
||||
account_data.perps[0].recurring_settle_pnl_allowance,
|
||||
settle_limit
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -277,7 +277,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
|||
};
|
||||
|
||||
{
|
||||
let (perp_market, account, liqor) = setup_perp(-28, -50, -10).await;
|
||||
let (perp_market, account, liqor) = setup_perp(-28, -50, 10).await;
|
||||
let liqor_quote_before = account_position(solana, liqor, quote_token.bank).await;
|
||||
|
||||
send_tx(
|
||||
|
@ -310,7 +310,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
|||
}
|
||||
|
||||
{
|
||||
let (perp_market, account, liqor) = setup_perp(-28, -50, -10).await;
|
||||
let (perp_market, account, liqor) = setup_perp(-28, -50, 10).await;
|
||||
fund_insurance(2).await;
|
||||
let liqor_quote_before = account_position(solana, liqor, quote_token.bank).await;
|
||||
|
||||
|
@ -348,7 +348,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
|||
}
|
||||
|
||||
{
|
||||
let (perp_market, account, liqor) = setup_perp(-28, -50, -10).await;
|
||||
let (perp_market, account, liqor) = setup_perp(-28, -50, 10).await;
|
||||
fund_insurance(5).await;
|
||||
|
||||
send_tx(
|
||||
|
@ -371,7 +371,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
|||
|
||||
// no insurance
|
||||
{
|
||||
let (perp_market, account, liqor) = setup_perp(-28, -50, -10).await;
|
||||
let (perp_market, account, liqor) = setup_perp(-28, -50, 10).await;
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
|
@ -390,7 +390,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
|||
|
||||
// no settlement: no settle health
|
||||
{
|
||||
let (perp_market, account, liqor) = setup_perp(-200, -50, -10).await;
|
||||
let (perp_market, account, liqor) = setup_perp(-200, -50, 10).await;
|
||||
fund_insurance(5).await;
|
||||
|
||||
send_tx(
|
||||
|
@ -430,7 +430,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
|||
|
||||
// no socialized loss: fully covered by insurance fund
|
||||
{
|
||||
let (perp_market, account, liqor) = setup_perp(-40, -50, -5).await;
|
||||
let (perp_market, account, liqor) = setup_perp(-40, -50, 5).await;
|
||||
fund_insurance(42).await;
|
||||
|
||||
send_tx(
|
||||
|
|
|
@ -230,12 +230,12 @@ async fn test_liq_perps_base_and_bankruptcy() -> Result<(), TransportError> {
|
|||
0.1
|
||||
));
|
||||
assert!(assert_equal(
|
||||
liqee_data.perps[0].realized_trade_pnl_native,
|
||||
liqee_data.perps[0].realized_pnl_for_position_native,
|
||||
liqee_amount - 1000.0,
|
||||
0.1
|
||||
));
|
||||
// stable price is 1.0, so 0.2 * 1000
|
||||
assert_eq!(liqee_data.perps[0].settle_pnl_limit_realized_trade, -201);
|
||||
assert_eq!(liqee_data.perps[0].recurring_settle_pnl_allowance, 201);
|
||||
assert!(assert_equal(
|
||||
perp_market_after.fees_accrued - perp_market_before.fees_accrued,
|
||||
liqor_amount - liqee_amount,
|
||||
|
@ -521,7 +521,7 @@ async fn test_liq_perps_base_and_bankruptcy() -> Result<(), TransportError> {
|
|||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: liqee_quote_deposits_before as u64 - 100,
|
||||
amount: liqee_quote_deposits_before as u64 - 200,
|
||||
allow_borrow: false,
|
||||
account: account_1,
|
||||
owner,
|
||||
|
@ -572,9 +572,9 @@ async fn test_liq_perps_base_and_bankruptcy() -> Result<(), TransportError> {
|
|||
0.1
|
||||
));
|
||||
assert!(assert_equal(
|
||||
liqor_data.tokens[0].native(&settle_bank),
|
||||
liqor_before.tokens[0].native(&settle_bank).to_num::<f64>()
|
||||
- liqee_settle_limit_before as f64 * 100.0, // 100 is base lot size
|
||||
liqor_data.tokens[1].native(&settle_bank),
|
||||
liqor_before.tokens[1].native(&settle_bank).to_num::<f64>()
|
||||
- liqee_settle_limit_before as f64,
|
||||
0.1
|
||||
));
|
||||
|
||||
|
|
|
@ -1100,14 +1100,6 @@ async fn test_perp_pnl_settle_limit() -> 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].realized_trade_pnl_native,
|
||||
I80F48::from(200_000 - 80_000)
|
||||
);
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].realized_trade_pnl_native,
|
||||
I80F48::from(-200_000 + 80_000)
|
||||
);
|
||||
// neither account has any settle limit left (check for 1 because of the ceil()ing)
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].available_settle_limit(&market).1,
|
||||
|
@ -1119,7 +1111,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
|||
);
|
||||
// check that realized pnl settle limit was set up correctly
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].settle_pnl_limit_realized_trade,
|
||||
mango_account_0.perps[0].recurring_settle_pnl_allowance,
|
||||
(0.8 * 1.0 * 100.0 * 1000.0) as i64 + 1
|
||||
); // +1 just for rounding
|
||||
|
||||
|
@ -1152,7 +1144,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
|||
// This time account 0's realized pnl settle limit kicks in.
|
||||
//
|
||||
let account_1_quote_before = mango_account_1.perps[0].quote_position_native();
|
||||
let account_0_realized_limit = mango_account_0.perps[0].settle_pnl_limit_realized_trade;
|
||||
let account_0_realized_limit = mango_account_0.perps[0].recurring_settle_pnl_allowance;
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
|
@ -1186,12 +1178,13 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
|||
mango_account_1.perps[0].quote_position_native() - account_1_quote_before,
|
||||
I80F48::from(account_0_realized_limit)
|
||||
);
|
||||
// account0's limit gets reduced to the realized pnl amount left over
|
||||
// account0's limit gets reduced to the pnl amount left over
|
||||
let perp_market_data = solana.get_account::<PerpMarket>(perp_market).await;
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].settle_pnl_limit_realized_trade,
|
||||
mango_account_0.perps[0].recurring_settle_pnl_allowance,
|
||||
mango_account_0.perps[0]
|
||||
.realized_trade_pnl_native
|
||||
.to_num::<i64>()
|
||||
.unsettled_pnl(&perp_market_data, I80F48::from_num(1.0))
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
// can't settle again
|
||||
|
@ -1213,7 +1206,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
|||
//
|
||||
|
||||
let account_1_quote_before = mango_account_1.perps[0].quote_position_native();
|
||||
let account_0_realized_limit = mango_account_0.perps[0].settle_pnl_limit_realized_trade;
|
||||
let account_0_realized_limit = mango_account_0.perps[0].recurring_settle_pnl_allowance;
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
|
@ -1248,13 +1241,13 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
|||
I80F48::from(account_0_realized_limit)
|
||||
);
|
||||
// account0's limit gets reduced to the realized pnl amount left over
|
||||
assert_eq!(mango_account_0.perps[0].settle_pnl_limit_realized_trade, 0);
|
||||
assert_eq!(mango_account_0.perps[0].recurring_settle_pnl_allowance, 0);
|
||||
assert_eq!(
|
||||
mango_account_0.perps[0].realized_trade_pnl_native,
|
||||
mango_account_0.perps[0].realized_pnl_for_position_native,
|
||||
I80F48::from(0)
|
||||
);
|
||||
assert_eq!(
|
||||
mango_account_1.perps[0].realized_trade_pnl_native,
|
||||
mango_account_1.perps[0].realized_pnl_for_position_native,
|
||||
I80F48::from(0)
|
||||
);
|
||||
|
||||
|
|
|
@ -1325,9 +1325,9 @@ export class PerpPosition {
|
|||
dto.takerVolume,
|
||||
dto.perpSpotTransfers,
|
||||
dto.avgEntryPricePerBaseLot,
|
||||
I80F48.from(dto.realizedTradePnlNative),
|
||||
I80F48.from(dto.realizedOtherPnlNative),
|
||||
dto.settlePnlLimitRealizedTrade,
|
||||
I80F48.from(dto.deprecatedRealizedTradePnlNative),
|
||||
I80F48.from(dto.oneshotSettlePnlAllowance),
|
||||
dto.recurringSettlePnlAllowance,
|
||||
I80F48.from(dto.realizedPnlForPositionNative),
|
||||
);
|
||||
}
|
||||
|
@ -1380,9 +1380,9 @@ export class PerpPosition {
|
|||
public takerVolume: BN,
|
||||
public perpSpotTransfers: BN,
|
||||
public avgEntryPricePerBaseLot: number,
|
||||
public realizedTradePnlNative: I80F48,
|
||||
public realizedOtherPnlNative: I80F48,
|
||||
public settlePnlLimitRealizedTrade: BN,
|
||||
public deprecatedRealizedTradePnlNative: I80F48,
|
||||
public oneshotSettlePnlAllowance: I80F48,
|
||||
public recurringSettlePnlAllowance: BN,
|
||||
public realizedPnlForPositionNative: I80F48,
|
||||
) {}
|
||||
|
||||
|
@ -1636,28 +1636,25 @@ export class PerpPosition {
|
|||
.mul(baseNative)
|
||||
.toNumber();
|
||||
const unrealized = new BN(perpMarket.settlePnlLimitFactor * positionValue);
|
||||
|
||||
let maxPnl = unrealized.add(this.recurringSettlePnlAllowance.abs());
|
||||
let minPnl = maxPnl.neg();
|
||||
|
||||
const oneshot = this.oneshotSettlePnlAllowance;
|
||||
if (!oneshot.isNeg()) {
|
||||
maxPnl = maxPnl.add(new BN(oneshot.ceil().toNumber()));
|
||||
} else {
|
||||
minPnl = minPnl.add(new BN(oneshot.floor().toNumber()));
|
||||
}
|
||||
|
||||
const used = new BN(
|
||||
this.settlePnlLimitSettledInCurrentWindowNative.toNumber(),
|
||||
);
|
||||
|
||||
let minPnl = unrealized.neg().sub(used);
|
||||
let maxPnl = unrealized.sub(used);
|
||||
const availableMin = BN.min(minPnl.sub(used), new BN(0));
|
||||
const availableMax = BN.max(maxPnl.sub(used), new BN(0));
|
||||
|
||||
const realizedTrade = this.settlePnlLimitRealizedTrade;
|
||||
if (realizedTrade.gte(new BN(0))) {
|
||||
maxPnl = maxPnl.add(realizedTrade);
|
||||
} else {
|
||||
minPnl = minPnl.add(realizedTrade);
|
||||
}
|
||||
|
||||
const realizedOther = new BN(this.realizedOtherPnlNative.toNumber());
|
||||
if (realizedOther.gte(new BN(0))) {
|
||||
maxPnl = maxPnl.add(realizedOther);
|
||||
} else {
|
||||
minPnl = minPnl.add(realizedOther);
|
||||
}
|
||||
|
||||
return [BN.min(minPnl, new BN(0)), BN.max(maxPnl, new BN(0))];
|
||||
return [availableMin, availableMax];
|
||||
}
|
||||
|
||||
public applyPnlSettleLimit(pnl: I80F48, perpMarket: PerpMarket): I80F48 {
|
||||
|
@ -1782,8 +1779,10 @@ export class PerpPosition {
|
|||
this.getNotionalValueUi(perpMarket!).toString() +
|
||||
', cumulative pnl over position lifetime ui - ' +
|
||||
this.cumulativePnlOverPositionLifetimeUi(perpMarket!).toString() +
|
||||
', realized other pnl native ui - ' +
|
||||
toUiDecimalsForQuote(this.realizedOtherPnlNative) +
|
||||
', oneshot settleable native ui - ' +
|
||||
toUiDecimalsForQuote(this.oneshotSettlePnlAllowance) +
|
||||
', recurring settleable native ui - ' +
|
||||
toUiDecimalsForQuote(this.recurringSettlePnlAllowance) +
|
||||
', cumulative long funding ui - ' +
|
||||
toUiDecimalsForQuote(this.cumulativeLongFunding) +
|
||||
', cumulative short funding ui - ' +
|
||||
|
@ -1812,9 +1811,9 @@ export class PerpPositionDto {
|
|||
public takerVolume: BN,
|
||||
public perpSpotTransfers: BN,
|
||||
public avgEntryPricePerBaseLot: number,
|
||||
public realizedTradePnlNative: I80F48Dto,
|
||||
public realizedOtherPnlNative: I80F48Dto,
|
||||
public settlePnlLimitRealizedTrade: BN,
|
||||
public deprecatedRealizedTradePnlNative: I80F48Dto,
|
||||
public oneshotSettlePnlAllowance: I80F48Dto,
|
||||
public recurringSettlePnlAllowance: BN,
|
||||
public realizedPnlForPositionNative: I80F48Dto,
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -9334,36 +9334,44 @@ export type MangoV4 = {
|
|||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "realizedTradePnlNative",
|
||||
"name": "deprecatedRealizedTradePnlNative",
|
||||
"docs": [
|
||||
"Amount of pnl that was realized by bringing the base position closer to 0.",
|
||||
"",
|
||||
"The settlement of this type of pnl is limited by settle_pnl_limit_realized_trade.",
|
||||
"Settling pnl reduces this value once other_pnl below is exhausted."
|
||||
"Deprecated field: Amount of pnl that was realized by bringing the base position closer to 0."
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "realizedOtherPnlNative",
|
||||
"name": "oneshotSettlePnlAllowance",
|
||||
"docs": [
|
||||
"Amount of pnl realized from fees, funding and liquidation.",
|
||||
"Amount of pnl that can be settled once.",
|
||||
"",
|
||||
"This type of realized pnl is always settleable.",
|
||||
"Settling pnl reduces this value first."
|
||||
"- The value is signed: a negative number means negative pnl can be settled.",
|
||||
"- A settlement in the right direction will decrease this amount.",
|
||||
"",
|
||||
"Typically added for fees, funding and liquidation."
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "settlePnlLimitRealizedTrade",
|
||||
"name": "recurringSettlePnlAllowance",
|
||||
"docs": [
|
||||
"Settle limit contribution from realized pnl.",
|
||||
"Amount of pnl that can be settled in each settle window.",
|
||||
"",
|
||||
"Every time pnl is realized, this is increased by a fraction of the stable",
|
||||
"value of the realization. It magnitude decreases when realized pnl drops below its value."
|
||||
"- Unsigned, the settlement can happen in both directions. Value is >= 0.",
|
||||
"- Previously stored a similar value that was signed, so in migration cases",
|
||||
"this value can be negative and should be .abs()ed.",
|
||||
"- If this value exceeds the current stable-upnl, it should be decreased,",
|
||||
"see apply_recurring_settle_pnl_allowance_constraint()",
|
||||
"",
|
||||
"When the base position is reduced, the settle limit contribution from the reduced",
|
||||
"base position is materialized into this value. When the base position increases,",
|
||||
"some of the allowance is taken away.",
|
||||
"",
|
||||
"This also gets increased when a liquidator takes over pnl."
|
||||
],
|
||||
"type": "i64"
|
||||
},
|
||||
|
@ -23360,36 +23368,44 @@ export const IDL: MangoV4 = {
|
|||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "realizedTradePnlNative",
|
||||
"name": "deprecatedRealizedTradePnlNative",
|
||||
"docs": [
|
||||
"Amount of pnl that was realized by bringing the base position closer to 0.",
|
||||
"",
|
||||
"The settlement of this type of pnl is limited by settle_pnl_limit_realized_trade.",
|
||||
"Settling pnl reduces this value once other_pnl below is exhausted."
|
||||
"Deprecated field: Amount of pnl that was realized by bringing the base position closer to 0."
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "realizedOtherPnlNative",
|
||||
"name": "oneshotSettlePnlAllowance",
|
||||
"docs": [
|
||||
"Amount of pnl realized from fees, funding and liquidation.",
|
||||
"Amount of pnl that can be settled once.",
|
||||
"",
|
||||
"This type of realized pnl is always settleable.",
|
||||
"Settling pnl reduces this value first."
|
||||
"- The value is signed: a negative number means negative pnl can be settled.",
|
||||
"- A settlement in the right direction will decrease this amount.",
|
||||
"",
|
||||
"Typically added for fees, funding and liquidation."
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "settlePnlLimitRealizedTrade",
|
||||
"name": "recurringSettlePnlAllowance",
|
||||
"docs": [
|
||||
"Settle limit contribution from realized pnl.",
|
||||
"Amount of pnl that can be settled in each settle window.",
|
||||
"",
|
||||
"Every time pnl is realized, this is increased by a fraction of the stable",
|
||||
"value of the realization. It magnitude decreases when realized pnl drops below its value."
|
||||
"- Unsigned, the settlement can happen in both directions. Value is >= 0.",
|
||||
"- Previously stored a similar value that was signed, so in migration cases",
|
||||
"this value can be negative and should be .abs()ed.",
|
||||
"- If this value exceeds the current stable-upnl, it should be decreased,",
|
||||
"see apply_recurring_settle_pnl_allowance_constraint()",
|
||||
"",
|
||||
"When the base position is reduced, the settle limit contribution from the reduced",
|
||||
"base position is materialized into this value. When the base position increases,",
|
||||
"some of the allowance is taken away.",
|
||||
"",
|
||||
"This also gets increased when a liquidator takes over pnl."
|
||||
],
|
||||
"type": "i64"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue