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:
Christian Kamm 2024-02-01 11:23:45 +01:00 committed by GitHub
parent 719aee37ae
commit ae5907ba3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 454 additions and 545 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
) {}
}

View File

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