Skip to main content

gmsol_model/
position.rs

1use std::{fmt, ops::Deref};
2
3use num_traits::{CheckedNeg, One, Signed, Zero};
4
5use crate::{
6    action::{
7        decrease_position::{DecreasePosition, DecreasePositionFlags, DecreasePositionSwapType},
8        increase_position::IncreasePosition,
9        swap::SwapReport,
10        update_funding_state::unpack_to_funding_amount_delta,
11    },
12    fixed::FixedPointOps,
13    market::{
14        utils::MarketUtils, BaseMarketExt, BorrowingFeeMarket, BorrowingFeeMarketExt, PerpMarket,
15        PerpMarketExt, PositionImpactMarket,
16    },
17    num::{MulDiv, Num, Unsigned, UnsignedAbs},
18    params::fee::{FundingFees, PositionFees},
19    pool::delta::{BalanceChange, PriceImpact},
20    price::{Price, Prices},
21    BalanceExt, BaseMarket, Delta, PerpMarketMut, PerpMarketMutExt, PnlFactorKind, Pool, PoolExt,
22};
23
24/// Read-only access to the position state.
25pub trait PositionState<const DECIMALS: u8> {
26    /// Unsigned number type.
27    type Num: MulDiv<Signed = Self::Signed> + FixedPointOps<DECIMALS>;
28
29    /// Signed number type.
30    type Signed: UnsignedAbs<Unsigned = Self::Num> + TryFrom<Self::Num> + Num;
31
32    /// Get the collateral amount.
33    fn collateral_amount(&self) -> &Self::Num;
34
35    /// Get a reference to the size (in USD) of the position.
36    fn size_in_usd(&self) -> &Self::Num;
37
38    /// Get a reference to the size (in tokens) of the position.
39    fn size_in_tokens(&self) -> &Self::Num;
40
41    /// Get a reference to last borrowing factor applied by the position.
42    fn borrowing_factor(&self) -> &Self::Num;
43
44    /// Get a reference to the funding fee amount per size.
45    fn funding_fee_amount_per_size(&self) -> &Self::Num;
46
47    /// Get a reference to claimable funding fee amount per size of the given collateral.
48    fn claimable_funding_fee_amount_per_size(&self, is_long_collateral: bool) -> &Self::Num;
49}
50
51/// Mutable access to the position state.
52pub trait PositionStateMut<const DECIMALS: u8>: PositionState<DECIMALS> {
53    /// Get a mutable reference to the collateral amount.
54    fn collateral_amount_mut(&mut self) -> &mut Self::Num;
55
56    /// Get a mutable reference to the size (in USD) of the position.
57    fn size_in_usd_mut(&mut self) -> &mut Self::Num;
58
59    /// Get a mutable reference to the size (in tokens) of the position.
60    fn size_in_tokens_mut(&mut self) -> &mut Self::Num;
61
62    /// Get a mutable reference to last borrowing factor applied by the position.
63    fn borrowing_factor_mut(&mut self) -> &mut Self::Num;
64
65    /// Get a mutable reference to the funding fee amount per size.
66    fn funding_fee_amount_per_size_mut(&mut self) -> &mut Self::Num;
67
68    /// Get a mutable reference to claimable funding fee amount per size of the given collateral.
69    fn claimable_funding_fee_amount_per_size_mut(
70        &mut self,
71        is_long_collateral: bool,
72    ) -> &mut Self::Num;
73}
74
75/// Position with access to its market.
76pub trait Position<const DECIMALS: u8>: PositionState<DECIMALS> {
77    /// Market type.
78    type Market: PerpMarket<DECIMALS, Num = Self::Num, Signed = Self::Signed>;
79
80    /// Get a reference to the market.
81    fn market(&self) -> &Self::Market;
82
83    /// Returns whether the position is a long position.
84    fn is_long(&self) -> bool;
85
86    /// Returns whether the collateral token is the long token of the market.
87    fn is_collateral_token_long(&self) -> bool;
88
89    /// Returns whether the pnl and collateral tokens are the same.
90    fn are_pnl_and_collateral_tokens_the_same(&self) -> bool;
91
92    /// Called from `validate_position` to add supplementary checks.
93    fn on_validate(&self) -> crate::Result<()>;
94}
95
96/// Position with mutable access.
97pub trait PositionMut<const DECIMALS: u8>: Position<DECIMALS> + PositionStateMut<DECIMALS> {
98    /// Get a mutable reference to the market.
99    fn market_mut(&mut self) -> &mut Self::Market;
100
101    /// Increased callback.
102    fn on_increased(&mut self) -> crate::Result<()>;
103
104    /// Decreased callback.
105    fn on_decreased(&mut self) -> crate::Result<()>;
106
107    /// Swapped callback.
108    fn on_swapped(
109        &mut self,
110        ty: DecreasePositionSwapType,
111        report: &SwapReport<Self::Num, <Self::Num as Unsigned>::Signed>,
112    ) -> crate::Result<()>;
113
114    /// Handle swap error.
115    fn on_swap_error(
116        &mut self,
117        ty: DecreasePositionSwapType,
118        error: crate::Error,
119    ) -> crate::Result<()>;
120}
121
122impl<const DECIMALS: u8, P: PositionState<DECIMALS>> PositionState<DECIMALS> for &mut P {
123    type Num = P::Num;
124
125    type Signed = P::Signed;
126
127    fn collateral_amount(&self) -> &Self::Num {
128        (**self).collateral_amount()
129    }
130
131    fn size_in_usd(&self) -> &Self::Num {
132        (**self).size_in_usd()
133    }
134
135    fn size_in_tokens(&self) -> &Self::Num {
136        (**self).size_in_tokens()
137    }
138
139    fn borrowing_factor(&self) -> &Self::Num {
140        (**self).borrowing_factor()
141    }
142
143    fn funding_fee_amount_per_size(&self) -> &Self::Num {
144        (**self).funding_fee_amount_per_size()
145    }
146
147    fn claimable_funding_fee_amount_per_size(&self, is_long_collateral: bool) -> &Self::Num {
148        (**self).claimable_funding_fee_amount_per_size(is_long_collateral)
149    }
150}
151
152impl<const DECIMALS: u8, P: Position<DECIMALS>> Position<DECIMALS> for &mut P {
153    type Market = P::Market;
154
155    fn market(&self) -> &Self::Market {
156        (**self).market()
157    }
158
159    fn is_long(&self) -> bool {
160        (**self).is_long()
161    }
162
163    fn is_collateral_token_long(&self) -> bool {
164        (**self).is_collateral_token_long()
165    }
166
167    fn are_pnl_and_collateral_tokens_the_same(&self) -> bool {
168        (**self).are_pnl_and_collateral_tokens_the_same()
169    }
170
171    fn on_validate(&self) -> crate::Result<()> {
172        (**self).on_validate()
173    }
174}
175
176impl<const DECIMALS: u8, P: PositionStateMut<DECIMALS>> PositionStateMut<DECIMALS> for &mut P {
177    fn collateral_amount_mut(&mut self) -> &mut Self::Num {
178        (**self).collateral_amount_mut()
179    }
180
181    fn size_in_usd_mut(&mut self) -> &mut Self::Num {
182        (**self).size_in_usd_mut()
183    }
184
185    fn size_in_tokens_mut(&mut self) -> &mut Self::Num {
186        (**self).size_in_tokens_mut()
187    }
188
189    fn borrowing_factor_mut(&mut self) -> &mut Self::Num {
190        (**self).borrowing_factor_mut()
191    }
192
193    fn funding_fee_amount_per_size_mut(&mut self) -> &mut Self::Num {
194        (**self).funding_fee_amount_per_size_mut()
195    }
196
197    fn claimable_funding_fee_amount_per_size_mut(
198        &mut self,
199        is_long_collateral: bool,
200    ) -> &mut Self::Num {
201        (**self).claimable_funding_fee_amount_per_size_mut(is_long_collateral)
202    }
203}
204
205impl<const DECIMALS: u8, P: PositionMut<DECIMALS>> PositionMut<DECIMALS> for &mut P {
206    fn market_mut(&mut self) -> &mut Self::Market {
207        (**self).market_mut()
208    }
209
210    fn on_increased(&mut self) -> crate::Result<()> {
211        (**self).on_increased()
212    }
213
214    fn on_decreased(&mut self) -> crate::Result<()> {
215        (**self).on_decreased()
216    }
217
218    fn on_swapped(
219        &mut self,
220        ty: DecreasePositionSwapType,
221        report: &SwapReport<Self::Num, <Self::Num as Unsigned>::Signed>,
222    ) -> crate::Result<()> {
223        (**self).on_swapped(ty, report)
224    }
225
226    fn on_swap_error(
227        &mut self,
228        ty: DecreasePositionSwapType,
229        error: crate::Error,
230    ) -> crate::Result<()> {
231        (**self).on_swap_error(ty, error)
232    }
233}
234
235/// Extension trait for [`PositionState`].
236pub trait PositionStateExt<const DECIMALS: u8>: PositionState<DECIMALS> {
237    /// Return whether the position is considered to be empty **during the decrease position action**.
238    fn is_empty(&self) -> bool {
239        self.size_in_usd().is_zero()
240            && self.size_in_tokens().is_zero()
241            && self.collateral_amount().is_zero()
242    }
243}
244
245impl<const DECIMALS: u8, P: PositionState<DECIMALS> + ?Sized> PositionStateExt<DECIMALS> for P {}
246
247/// Extension trait for [`Position`] with utils.
248pub trait PositionExt<const DECIMALS: u8>: Position<DECIMALS> {
249    /// Check that whether the collateral will be sufficient after paying the given `realized_pnl` and applying `delta_size`.
250    ///
251    /// - Returns the remaining collateral value if sufficient, `None` otherwise.
252    /// - Returns `Err` if failed to finish the calculation.
253    fn will_collateral_be_sufficient(
254        &self,
255        prices: &Prices<Self::Num>,
256        delta: &CollateralDelta<Self::Num>,
257    ) -> crate::Result<WillCollateralBeSufficient<Self::Signed>> {
258        use num_traits::{CheckedAdd, CheckedMul};
259
260        let collateral_price = self.collateral_price(prices);
261
262        let mut remaining_collateral_value = delta
263            .next_collateral_amount
264            .checked_mul(collateral_price.pick_price(false))
265            .ok_or(crate::Error::Computation(
266                "overflow calculating collateral value",
267            ))?
268            .to_signed()?;
269
270        if delta.realized_pnl_value.is_negative() {
271            remaining_collateral_value = remaining_collateral_value
272                .checked_add(&delta.realized_pnl_value)
273                .ok_or(crate::Error::Computation("adding realized pnl"))?;
274        }
275
276        if remaining_collateral_value.is_negative() {
277            return Ok(WillCollateralBeSufficient::Insufficient(
278                remaining_collateral_value,
279            ));
280        }
281
282        let min_collateral_factor = self
283            .market()
284            .min_collateral_factor_for_open_interest(&delta.open_interest_delta, self.is_long())?
285            .max(
286                self.market()
287                    .position_params()?
288                    .min_collateral_factor()
289                    .clone(),
290            );
291
292        match check_collateral(
293            &delta.next_size_in_usd,
294            &min_collateral_factor,
295            None,
296            true,
297            &remaining_collateral_value,
298        )? {
299            CheckCollateralResult::Sufficient => Ok(WillCollateralBeSufficient::Sufficient(
300                remaining_collateral_value,
301            )),
302            CheckCollateralResult::Negative | CheckCollateralResult::MinCollateralForLeverage => {
303                Ok(WillCollateralBeSufficient::Insufficient(
304                    remaining_collateral_value,
305                ))
306            }
307            CheckCollateralResult::MinCollateral | CheckCollateralResult::Zero => unreachable!(),
308        }
309    }
310
311    /// Get collateral price.
312    fn collateral_price<'a>(&self, prices: &'a Prices<Self::Num>) -> &'a Price<Self::Num> {
313        if self.is_collateral_token_long() {
314            &prices.long_token_price
315        } else {
316            &prices.short_token_price
317        }
318    }
319
320    /// Get collateral value.
321    fn collateral_value(&self, prices: &Prices<Self::Num>) -> crate::Result<Self::Num> {
322        use num_traits::CheckedMul;
323
324        let collateral_token_price = self.collateral_price(prices).pick_price(false);
325
326        let collateral_value = self
327            .collateral_amount()
328            .checked_mul(collateral_token_price)
329            .ok_or(crate::Error::Computation(
330                "overflow calculating collateral value",
331            ))?;
332
333        Ok(collateral_value)
334    }
335
336    /// Calculate size delta in tokens when decreased by the given delta size.
337    fn size_delta_in_tokens(&self, size_delta_usd: &Self::Num) -> crate::Result<Self::Num> {
338        let size_delta_in_tokens = if *self.size_in_usd() == *size_delta_usd {
339            self.size_in_tokens().clone()
340        } else if self.is_long() {
341            self.size_in_tokens()
342                .checked_mul_div_ceil(size_delta_usd, self.size_in_usd())
343                .ok_or(crate::Error::Computation(
344                    "calculating size delta in tokens for long",
345                ))?
346        } else {
347            self.size_in_tokens()
348                .checked_mul_div(size_delta_usd, self.size_in_usd())
349                .ok_or(crate::Error::Computation(
350                    "calculating size delta in tokens for short",
351                ))?
352        };
353
354        Ok(size_delta_in_tokens)
355    }
356
357    /// Calculate the pnl value when decreased by the given delta size.
358    ///
359    /// Returns `(pnl_value, uncapped_pnl_value, size_delta_in_tokens)`
360    fn pnl_value(
361        &self,
362        prices: &Prices<Self::Num>,
363        size_delta_usd: &Self::Num,
364    ) -> crate::Result<(Self::Signed, Self::Signed, Self::Num)> {
365        use num_traits::{CheckedMul, CheckedSub};
366
367        let execution_price = &prices
368            .index_token_price
369            .pick_price_for_pnl(self.is_long(), false);
370
371        let position_value: Self::Signed = self
372            .size_in_tokens()
373            .checked_mul(execution_price)
374            .ok_or(crate::Error::Computation(
375                "overflow calculating position value",
376            ))?
377            .try_into()
378            .map_err(|_| crate::Error::Convert)?;
379        let size_in_usd = self
380            .size_in_usd()
381            .clone()
382            .try_into()
383            .map_err(|_| crate::Error::Convert)?;
384        let mut total_pnl = if self.is_long() {
385            position_value.checked_sub(&size_in_usd)
386        } else {
387            size_in_usd.checked_sub(&position_value)
388        }
389        .ok_or(crate::Error::Computation("calculating total pnl"))?;
390        let uncapped_total_pnl = total_pnl.clone();
391
392        if total_pnl.is_positive() {
393            let pool_value =
394                self.market()
395                    .pool_value_without_pnl_for_one_side(prices, self.is_long(), false)?;
396            let pool_pnl = self
397                .market()
398                .pnl(&prices.index_token_price, self.is_long(), true)?;
399            let capped_pool_pnl = self.market().cap_pnl(
400                self.is_long(),
401                &pool_pnl,
402                &pool_value,
403                PnlFactorKind::MaxForTrader,
404            )?;
405
406            // Note: If the PnL is capped at zero, it can still pass this test.
407            // See the `test_zero_max_pnl_factor_for_trader` test for more details.
408            if capped_pool_pnl != pool_pnl
409                && !capped_pool_pnl.is_negative()
410                && pool_pnl.is_positive()
411            {
412                total_pnl = capped_pool_pnl
413                    .unsigned_abs()
414                    .checked_mul_div_with_signed_numerator(&total_pnl, &pool_pnl.unsigned_abs())
415                    .ok_or(crate::Error::Computation("calculating capped total pnl"))?;
416            }
417        }
418
419        let size_delta_in_tokens = self.size_delta_in_tokens(size_delta_usd)?;
420
421        let pnl_usd = size_delta_in_tokens
422            .checked_mul_div_with_signed_numerator(&total_pnl, self.size_in_tokens())
423            .ok_or(crate::Error::Computation("calculating pnl_usd"))?;
424
425        let uncapped_pnl_usd = size_delta_in_tokens
426            .checked_mul_div_with_signed_numerator(&uncapped_total_pnl, self.size_in_tokens())
427            .ok_or(crate::Error::Computation("calculating uncapped_pnl_usd"))?;
428
429        Ok((pnl_usd, uncapped_pnl_usd, size_delta_in_tokens))
430    }
431
432    /// Validate the position.
433    fn validate(
434        &self,
435        prices: &Prices<Self::Num>,
436        should_validate_min_position_size: bool,
437        should_validate_min_collateral_usd: bool,
438    ) -> crate::Result<()> {
439        if self.size_in_usd().is_zero() || self.size_in_tokens().is_zero() {
440            return Err(crate::Error::InvalidPosition(
441                "size_in_usd or size_in_tokens is zero",
442            ));
443        }
444
445        self.on_validate()?;
446
447        if should_validate_min_position_size
448            && self.size_in_usd() < self.market().position_params()?.min_position_size_usd()
449        {
450            return Err(crate::Error::InvalidPosition("size in usd too small"));
451        }
452
453        if let Some(reason) =
454            self.check_liquidatable(prices, should_validate_min_collateral_usd, false)?
455        {
456            return Err(crate::Error::Liquidatable(reason));
457        }
458
459        Ok(())
460    }
461
462    /// Check if the position is liquidatable.
463    ///
464    /// Return [`LiquidatableReason`] if it is liquidatable, `None` otherwise.
465    fn check_liquidatable(
466        &self,
467        prices: &Prices<Self::Num>,
468        should_validate_min_collateral_usd: bool,
469        for_liquidation: bool,
470    ) -> crate::Result<Option<LiquidatableReason>> {
471        use num_traits::{CheckedAdd, CheckedMul, CheckedSub};
472
473        let size_in_usd = self.size_in_usd();
474
475        let (pnl, _, _) = self.pnl_value(prices, size_in_usd)?;
476
477        let collateral_value = self.collateral_value(prices)?;
478        let collateral_price = self.collateral_price(prices);
479
480        let size_delta_usd = size_in_usd.to_opposite_signed()?;
481
482        let PriceImpact {
483            value: mut price_impact_value,
484            balance_change,
485        } = self.position_price_impact(&size_delta_usd, true)?;
486
487        if price_impact_value.is_negative() {
488            self.market().cap_negative_position_price_impact(
489                &size_delta_usd,
490                true,
491                &mut price_impact_value,
492            )?;
493        } else {
494            price_impact_value = Zero::zero();
495        }
496
497        let fees = self.position_fees(
498            collateral_price,
499            size_in_usd,
500            balance_change,
501            // Should not account for liquidation fees to determine if position should be liquidated.
502            false,
503        )?;
504
505        let collateral_cost_value = fees
506            .total_cost_amount()?
507            .checked_mul(collateral_price.pick_price(false))
508            .ok_or(crate::Error::Computation(
509                "overflow calculating collateral cost value",
510            ))?;
511
512        let remaining_collateral_value = collateral_value
513            .to_signed()?
514            .checked_add(&pnl)
515            .and_then(|v| {
516                v.checked_add(&price_impact_value)?
517                    .checked_sub(&collateral_cost_value.to_signed().ok()?)
518            })
519            .ok_or(crate::Error::Computation(
520                "calculating remaining collateral value",
521            ))?;
522
523        let params = self.market().position_params()?;
524
525        let collateral_factor = if for_liquidation {
526            params.min_collateral_factor_for_liquidation()
527        } else {
528            params.min_collateral_factor()
529        };
530
531        match check_collateral(
532            size_in_usd,
533            collateral_factor,
534            should_validate_min_collateral_usd.then(|| params.min_collateral_value()),
535            false,
536            &remaining_collateral_value,
537        )? {
538            CheckCollateralResult::Sufficient => Ok(None),
539            CheckCollateralResult::Zero | CheckCollateralResult::Negative => {
540                Ok(Some(LiquidatableReason::NotPositive))
541            }
542            CheckCollateralResult::MinCollateralForLeverage => {
543                Ok(Some(LiquidatableReason::MinCollateralForLeverage))
544            }
545            CheckCollateralResult::MinCollateral => Ok(Some(LiquidatableReason::MinCollateral)),
546        }
547    }
548
549    /// Get position price impact.
550    fn position_price_impact(
551        &self,
552        size_delta_usd: &Self::Signed,
553        include_virtual_inventory_impact: bool,
554    ) -> crate::Result<PriceImpact<Self::Signed>> {
555        struct ReassignedValues<T> {
556            delta_long_usd_value: T,
557            delta_short_usd_value: T,
558        }
559
560        impl<T: Zero + Clone> ReassignedValues<T> {
561            fn new(is_long: bool, size_delta_usd: &T) -> Self {
562                if is_long {
563                    Self {
564                        delta_long_usd_value: size_delta_usd.clone(),
565                        delta_short_usd_value: Zero::zero(),
566                    }
567                } else {
568                    Self {
569                        delta_long_usd_value: Zero::zero(),
570                        delta_short_usd_value: size_delta_usd.clone(),
571                    }
572                }
573            }
574        }
575
576        // Since the amounts of open interest are already usd amounts,
577        // the price should be `one`.
578        let usd_price = One::one();
579
580        let ReassignedValues {
581            delta_long_usd_value,
582            delta_short_usd_value,
583        } = ReassignedValues::new(self.is_long(), size_delta_usd);
584
585        let params = self.market().position_impact_params()?;
586
587        let impact = self
588            .market()
589            .open_interest()?
590            .pool_delta_with_values(
591                delta_long_usd_value.clone(),
592                delta_short_usd_value.clone(),
593                &usd_price,
594                &usd_price,
595            )?
596            .price_impact(&params)?;
597
598        // The virtual price impact calculation is skipped if the price impact
599        // is positive since the action is helping to balance the pool.
600        //
601        // In case two virtual pools are unbalanced in a different direction
602        // e.g. pool0 has more longs than shorts while pool1 has less longs
603        // than shorts
604        // not skipping the virtual price impact calculation would lead to
605        // a negative price impact for any trade on either pools and would
606        // disincentivise the balancing of pools
607        if !impact.value.is_negative() || !include_virtual_inventory_impact {
608            return Ok(impact);
609        }
610
611        // Calculate virtual price impact.
612        let Some(virtual_inventory) = self.market().virtual_inventory_for_positions_pool()? else {
613            return Ok(impact);
614        };
615
616        let mut leftover = virtual_inventory.checked_cancel_amounts()?;
617
618        // The virtual long and short open interest is adjusted by the `size_delta_usd`
619        // to prevent an underflow in `BalanceExt::pool_delta_with_values`.
620        // Price impact depends on the change in USD balance, so offsetting both
621        // values equally should not change the price impact calculation.
622        if size_delta_usd.is_negative() {
623            let offset = size_delta_usd
624                .checked_neg()
625                .ok_or(crate::Error::Computation(
626                    "calculating virtual open interest offset",
627                ))?;
628            leftover =
629                leftover.checked_apply_delta(Delta::new_both_sides(true, &offset, &offset))?;
630        }
631
632        let virtual_impact = leftover
633            .pool_delta_with_values(
634                delta_long_usd_value,
635                delta_short_usd_value,
636                &usd_price,
637                &usd_price,
638            )?
639            .price_impact(&params)?;
640
641        if virtual_impact.value < impact.value {
642            Ok(virtual_impact)
643        } else {
644            Ok(impact)
645        }
646    }
647
648    /// Get position price impact usd and cap the value if it is positive.
649    #[inline]
650    fn capped_positive_position_price_impact(
651        &self,
652        index_token_price: &Price<Self::Num>,
653        size_delta_usd: &Self::Signed,
654        include_virtual_inventory_impact: bool,
655    ) -> crate::Result<PriceImpact<Self::Signed>> {
656        let mut impact =
657            self.position_price_impact(size_delta_usd, include_virtual_inventory_impact)?;
658        self.market().cap_positive_position_price_impact(
659            index_token_price,
660            size_delta_usd,
661            &mut impact.value,
662        )?;
663        Ok(impact)
664    }
665
666    /// Get capped position price impact usd.
667    ///
668    /// Compare to [`PositionExt::capped_positive_position_price_impact`],
669    /// this method will also cap the negative impact and return the difference before capping.
670    #[inline]
671    fn capped_position_price_impact(
672        &self,
673        index_token_price: &Price<Self::Num>,
674        size_delta_usd: &Self::Signed,
675        include_virtual_inventory_impact: bool,
676    ) -> crate::Result<(PriceImpact<Self::Signed>, Self::Num)> {
677        let mut impact = self.capped_positive_position_price_impact(
678            index_token_price,
679            size_delta_usd,
680            include_virtual_inventory_impact,
681        )?;
682        let impact_diff = self.market().cap_negative_position_price_impact(
683            size_delta_usd,
684            false,
685            &mut impact.value,
686        )?;
687        Ok((impact, impact_diff))
688    }
689
690    /// Get pending borrowing fee value of this position.
691    fn pending_borrowing_fee_value(&self) -> crate::Result<Self::Num> {
692        use crate::utils;
693        use num_traits::CheckedSub;
694
695        let latest_factor = self.market().cumulative_borrowing_factor(self.is_long())?;
696        let diff_factor = latest_factor
697            .checked_sub(self.borrowing_factor())
698            .ok_or(crate::Error::Computation("invalid latest borrowing factor"))?;
699        utils::apply_factor(self.size_in_usd(), &diff_factor)
700            .ok_or(crate::Error::Computation("calculating borrowing fee value"))
701    }
702
703    /// Get pending funding fees.
704    fn pending_funding_fees(&self) -> crate::Result<FundingFees<Self::Num>> {
705        let adjustment = self.market().funding_amount_per_size_adjustment();
706        let fees = FundingFees::builder()
707            .amount(
708                unpack_to_funding_amount_delta(
709                    &adjustment,
710                    &self.market().funding_fee_amount_per_size(
711                        self.is_long(),
712                        self.is_collateral_token_long(),
713                    )?,
714                    self.funding_fee_amount_per_size(),
715                    self.size_in_usd(),
716                    true,
717                )
718                .ok_or(crate::Error::Computation("calculating funding fee amount"))?,
719            )
720            .claimable_long_token_amount(
721                unpack_to_funding_amount_delta(
722                    &adjustment,
723                    &self
724                        .market()
725                        .claimable_funding_fee_amount_per_size(self.is_long(), true)?,
726                    self.claimable_funding_fee_amount_per_size(true),
727                    self.size_in_usd(),
728                    false,
729                )
730                .ok_or(crate::Error::Computation(
731                    "calculating claimable long token funding fee amount",
732                ))?,
733            )
734            .claimable_short_token_amount(
735                unpack_to_funding_amount_delta(
736                    &adjustment,
737                    &self
738                        .market()
739                        .claimable_funding_fee_amount_per_size(self.is_long(), false)?,
740                    self.claimable_funding_fee_amount_per_size(false),
741                    self.size_in_usd(),
742                    false,
743                )
744                .ok_or(crate::Error::Computation(
745                    "calculating claimable short token funding fee amount",
746                ))?,
747            )
748            .build();
749        Ok(fees)
750    }
751
752    /// Calculates the [`PositionFees`] generated by changing the position size by the specified `size_delta_usd`.
753    fn position_fees(
754        &self,
755        collateral_token_price: &Price<Self::Num>,
756        size_delta_usd: &Self::Num,
757        balance_change: BalanceChange,
758        is_liquidation: bool,
759    ) -> crate::Result<PositionFees<Self::Num>> {
760        debug_assert!(!collateral_token_price.has_zero(), "must be non-zero");
761
762        let liquidation_fees = is_liquidation
763            .then(|| {
764                // Although `size_delta_usd` is used here to calculate liquidation fee, partial liquidation is not allowed.
765                // Therefore, `size_delta_usd == size_in_usd` always holds, ensuring consistency with the Solidity version.
766                self.market()
767                    .liquidation_fee_params()?
768                    .fee(size_delta_usd, collateral_token_price)
769            })
770            .transpose()?;
771
772        let fees = self
773            .market()
774            .order_fee_params()?
775            .base_position_fees(collateral_token_price, size_delta_usd, balance_change)?
776            .set_borrowing_fees(
777                self.market().borrowing_fee_params()?.receiver_factor(),
778                collateral_token_price,
779                self.pending_borrowing_fee_value()?,
780            )?
781            .set_funding_fees(self.pending_funding_fees()?)
782            .set_liquidation_fees(liquidation_fees);
783        Ok(fees)
784    }
785}
786
787impl<const DECIMALS: u8, P: Position<DECIMALS>> PositionExt<DECIMALS> for P {}
788
789/// Extension trait for [`PositionMut`] with utils.
790pub trait PositionMutExt<const DECIMALS: u8>: PositionMut<DECIMALS>
791where
792    Self::Market: PerpMarketMut<DECIMALS, Num = Self::Num, Signed = Self::Signed>,
793{
794    /// Create an action to increase the position.
795    fn increase(
796        &mut self,
797        prices: Prices<Self::Num>,
798        collateral_increment_amount: Self::Num,
799        size_delta_usd: Self::Num,
800        acceptable_price: Option<Self::Num>,
801    ) -> crate::Result<IncreasePosition<&mut Self, DECIMALS>>
802    where
803        Self: Sized,
804    {
805        IncreasePosition::try_new(
806            self,
807            prices,
808            collateral_increment_amount,
809            size_delta_usd,
810            acceptable_price,
811        )
812    }
813
814    /// Create an action to decrease the position.
815    fn decrease(
816        &mut self,
817        prices: Prices<Self::Num>,
818        size_delta_usd: Self::Num,
819        acceptable_price: Option<Self::Num>,
820        collateral_withdrawal_amount: Self::Num,
821        flags: DecreasePositionFlags,
822    ) -> crate::Result<DecreasePosition<&mut Self, DECIMALS>>
823    where
824        Self: Sized,
825    {
826        DecreasePosition::try_new(
827            self,
828            prices,
829            size_delta_usd,
830            acceptable_price,
831            collateral_withdrawal_amount,
832            flags,
833        )
834    }
835
836    /// Update global open interest.
837    fn update_open_interest(
838        &mut self,
839        size_delta_usd: &Self::Signed,
840        size_delta_in_tokens: &Self::Signed,
841    ) -> crate::Result<()> {
842        if size_delta_usd.is_zero() {
843            return Ok(());
844        }
845
846        let is_long_collateral = self.is_collateral_token_long();
847        let is_long = self.is_long();
848
849        self.market_mut().apply_delta_to_open_interest(
850            is_long,
851            is_long_collateral,
852            size_delta_usd,
853        )?;
854
855        let open_interest_in_tokens = self
856            .market_mut()
857            .open_interest_in_tokens_pool_mut(is_long)?;
858        if is_long_collateral {
859            open_interest_in_tokens.apply_delta_to_long_amount(size_delta_in_tokens)?;
860        } else {
861            open_interest_in_tokens.apply_delta_to_short_amount(size_delta_in_tokens)?;
862        }
863
864        Ok(())
865    }
866
867    /// Update total borrowing.
868    fn update_total_borrowing(
869        &mut self,
870        next_size_in_usd: &Self::Num,
871        next_borrowing_factor: &Self::Num,
872    ) -> crate::Result<()> {
873        let is_long = self.is_long();
874        let previous = crate::utils::apply_factor(self.size_in_usd(), self.borrowing_factor())
875            .ok_or(crate::Error::Computation("calculating previous borrowing"))?;
876
877        let total_borrowing = self.market_mut().total_borrowing_pool_mut()?;
878
879        let delta = {
880            let next = crate::utils::apply_factor(next_size_in_usd, next_borrowing_factor)
881                .ok_or(crate::Error::Computation("calculating next borrowing"))?;
882            next.checked_signed_sub(previous)?
883        };
884
885        total_borrowing.apply_delta_amount(is_long, &delta)?;
886
887        Ok(())
888    }
889}
890
891impl<const DECIMALS: u8, P: PositionMut<DECIMALS>> PositionMutExt<DECIMALS> for P where
892    P::Market: PerpMarketMut<DECIMALS, Num = Self::Num, Signed = Self::Signed>
893{
894}
895
896/// Collateral Delta Values.
897pub struct CollateralDelta<T: Unsigned> {
898    next_size_in_usd: T,
899    next_collateral_amount: T,
900    realized_pnl_value: T::Signed,
901    open_interest_delta: T::Signed,
902}
903
904impl<T: Unsigned> CollateralDelta<T> {
905    /// Create a new collateral delta.
906    pub fn new(
907        next_size_in_usd: T,
908        next_collateral_amount: T,
909        realized_pnl_value: T::Signed,
910        open_interest_delta: T::Signed,
911    ) -> Self {
912        Self {
913            next_size_in_usd,
914            next_collateral_amount,
915            realized_pnl_value,
916            open_interest_delta,
917        }
918    }
919}
920
921/// Will collateral be sufficient.
922#[derive(Clone, Copy)]
923pub enum WillCollateralBeSufficient<T> {
924    /// Will be sufficient.
925    Sufficient(T),
926    /// Won't be sufficient.
927    Insufficient(T),
928}
929
930impl<T> WillCollateralBeSufficient<T> {
931    /// Returns whether it is sufficient.
932    pub fn is_sufficient(&self) -> bool {
933        matches!(self, Self::Sufficient(_))
934    }
935}
936
937impl<T> Deref for WillCollateralBeSufficient<T> {
938    type Target = T;
939
940    fn deref(&self) -> &Self::Target {
941        match self {
942            Self::Sufficient(v) => v,
943            Self::Insufficient(v) => v,
944        }
945    }
946}
947
948/// Liquidatable reason.
949#[derive(Debug, Clone, Copy)]
950pub enum LiquidatableReason {
951    /// Min collateral.
952    MinCollateral,
953    /// Remaining collateral not positive.
954    NotPositive,
955    /// Min collateral for leverage.
956    MinCollateralForLeverage,
957}
958
959impl fmt::Display for LiquidatableReason {
960    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
961        match self {
962            Self::MinCollateral => write!(f, "min collateral"),
963            Self::NotPositive => write!(f, "<= 0"),
964            Self::MinCollateralForLeverage => write!(f, "min collateral for leverage"),
965        }
966    }
967}
968
969enum CheckCollateralResult {
970    Sufficient,
971    Zero,
972    Negative,
973    MinCollateralForLeverage,
974    MinCollateral,
975}
976
977fn check_collateral<T, const DECIMALS: u8>(
978    size_in_usd: &T,
979    min_collateral_factor: &T,
980    min_collateral_value: Option<&T>,
981    allow_zero_collateral: bool,
982    collateral_value: &T::Signed,
983) -> crate::Result<CheckCollateralResult>
984where
985    T: FixedPointOps<DECIMALS>,
986{
987    if collateral_value.is_negative() {
988        if min_collateral_value.is_some() {
989            // Keep the behavior consistent with the Solidity version.
990            Ok(CheckCollateralResult::MinCollateral)
991        } else {
992            Ok(CheckCollateralResult::Negative)
993        }
994    } else {
995        let collateral_value = collateral_value.unsigned_abs();
996
997        if let Some(min_collateral_value) = min_collateral_value {
998            if collateral_value < *min_collateral_value {
999                return Ok(CheckCollateralResult::MinCollateral);
1000            }
1001        }
1002
1003        if !allow_zero_collateral && collateral_value.is_zero() {
1004            return Ok(CheckCollateralResult::Zero);
1005        }
1006
1007        let min_collateral_usd_for_leverage =
1008            crate::utils::apply_factor(size_in_usd, min_collateral_factor).ok_or(
1009                crate::Error::Computation("calculating min collateral usd for leverage"),
1010            )?;
1011
1012        if collateral_value < min_collateral_usd_for_leverage {
1013            return Ok(CheckCollateralResult::MinCollateralForLeverage);
1014        }
1015
1016        Ok(CheckCollateralResult::Sufficient)
1017    }
1018}
1019
1020/// Insolvent Close Step.
1021#[derive(Debug, Clone, Copy)]
1022#[cfg_attr(
1023    feature = "anchor-lang",
1024    derive(
1025        anchor_lang::AnchorDeserialize,
1026        anchor_lang::AnchorSerialize,
1027        anchor_lang::InitSpace
1028    )
1029)]
1030#[non_exhaustive]
1031pub enum InsolventCloseStep {
1032    /// PnL.
1033    Pnl,
1034    /// Fees.
1035    Fees,
1036    /// Funding fees.
1037    Funding,
1038    /// Price impact.
1039    Impact,
1040    /// Price impact diff.
1041    Diff,
1042}