Skip to main content

gmsol_sdk/position/
mod.rs

1use gmsol_model::{
2    num::Unsigned, num_traits::Zero, price::Prices, PerpMarket, PerpMarketExt, Position,
3    PositionExt, PositionState,
4};
5use gmsol_programs::model::PositionModel;
6use status::PositionStatus;
7
8use crate::constants;
9
10/// Position status.
11pub mod status;
12
13/// Options for calculating position status.
14#[derive(Debug, Clone, Default)]
15pub struct CalculatePositionStatusOptions {
16    /// Whether to include virtual inventory impact.
17    pub include_virtual_inventory_impact: bool,
18}
19
20/// Position Calculations.
21pub trait PositionCalculations {
22    /// Calculate position status.
23    fn status(&self, prices: &Prices<u128>) -> crate::Result<PositionStatus> {
24        self.status_with_options(prices, Default::default())
25    }
26
27    /// Calculate position status with options.
28    fn status_with_options(
29        &self,
30        prices: &Prices<u128>,
31        options: CalculatePositionStatusOptions,
32    ) -> crate::Result<PositionStatus>;
33}
34
35impl PositionCalculations for PositionModel {
36    fn status_with_options(
37        &self,
38        prices: &Prices<u128>,
39        options: CalculatePositionStatusOptions,
40    ) -> crate::Result<PositionStatus> {
41        // collateral value
42        let collateral_value = self.collateral_value(prices)?;
43
44        // pnl
45        let position_size_in_tokens = self.size_in_tokens();
46        let position_size_in_usd = self.size_in_usd();
47        let _position_size_in_usd_real = position_size_in_tokens
48            .checked_mul(prices.index_token_price.max)
49            .ok_or(gmsol_model::Error::Computation(
50                "calculating position size in usd real",
51            ))?;
52        let (pending_pnl_value, _uncapped_pnl_value, _size_delta_in_tokens) =
53            self.pnl_value(prices, position_size_in_usd)?;
54        let entry_price = position_size_in_usd
55            .checked_div(*position_size_in_tokens)
56            .ok_or(gmsol_model::Error::Computation("calculating entry price"))?;
57
58        // borrowing fee value
59        let pending_borrowing_fee_value = self.pending_borrowing_fee_value()?;
60
61        // funding fee value
62        let pending_funding_fee = self.pending_funding_fees()?;
63        let pending_funding_fee_value = if self.is_collateral_token_long() {
64            pending_funding_fee
65                .amount()
66                .checked_mul(prices.long_token_price.min)
67                .ok_or(gmsol_model::Error::Computation(
68                    "calculating pending funding fee value",
69                ))?
70        } else {
71            pending_funding_fee
72                .amount()
73                .checked_mul(prices.short_token_price.min)
74                .ok_or(gmsol_model::Error::Computation(
75                    "calculating pending funding fee value",
76                ))?
77        };
78        let pending_claimable_funding_fee_value_in_long_token = pending_funding_fee
79            .claimable_long_token_amount()
80            .checked_mul(prices.long_token_price.min)
81            .ok_or(gmsol_model::Error::Computation(
82                "calculating pending claimable funding fee value in long token",
83            ))?;
84        let pending_claimable_funding_fee_value_in_short_token = pending_funding_fee
85            .claimable_short_token_amount()
86            .checked_mul(prices.short_token_price.min)
87            .ok_or(gmsol_model::Error::Computation(
88                "calculating pending claimable funding fee value in short token",
89            ))?;
90
91        // close order fee value
92        let collateral_token_price = if self.is_collateral_token_long() {
93            prices.long_token_price
94        } else {
95            prices.short_token_price
96        };
97
98        // net value = collateral value +  pending pnl - pending borrowing fee value - nagetive pending funding fee value - close order fee value let mut price_impact_value = self.position_price_impact(&size_delta_usd)?;
99        let size_delta_usd = position_size_in_usd.to_opposite_signed()?;
100        let price_impact =
101            self.position_price_impact(&size_delta_usd, options.include_virtual_inventory_impact)?;
102
103        let mut price_impact_value = price_impact.value;
104        if price_impact_value.is_negative() {
105            self.market().cap_negative_position_price_impact(
106                &size_delta_usd,
107                true,
108                &mut price_impact_value,
109            )?;
110        } else {
111            price_impact_value = Zero::zero();
112        }
113
114        let total_position_fees = self.position_fees(
115            &collateral_token_price,
116            position_size_in_usd,
117            price_impact.balance_change,
118            // Should not account for liquidation fees to determine if position should be liquidated.
119            false,
120        )?;
121
122        let close_order_fee_value = *total_position_fees.order_fees().fee_value();
123
124        let net_value = collateral_value
125            .to_signed()?
126            .checked_add(pending_pnl_value)
127            .ok_or(gmsol_model::Error::Computation("calculating net value"))?
128            .checked_sub(pending_borrowing_fee_value.to_signed()?)
129            .ok_or(gmsol_model::Error::Computation("calculating net value"))?
130            .checked_sub(pending_funding_fee_value.to_signed()?)
131            .ok_or(gmsol_model::Error::Computation("calculating net value"))?
132            .checked_sub(close_order_fee_value.to_signed()?)
133            .ok_or(gmsol_model::Error::Computation("calculating net value"))?
134            .max(Zero::zero());
135
136        // leverage
137        let leverage = if !net_value.is_positive() {
138            None
139        } else {
140            Some(
141                gmsol_model::utils::div_to_factor::<_, { constants::MARKET_DECIMALS }>(
142                    position_size_in_usd,
143                    &net_value.unsigned_abs(),
144                    true,
145                )
146                .ok_or(gmsol_model::Error::Computation("calculating leverage"))?,
147            )
148        };
149
150        // liquidation price
151        let params = self.market().position_params()?;
152        let min_collateral_factor = params.min_collateral_factor();
153        let min_collateral_value = params.min_collateral_value();
154        let liquidation_collateral_usd = gmsol_model::utils::apply_factor::<
155            _,
156            { constants::MARKET_DECIMALS },
157        >(position_size_in_usd, min_collateral_factor)
158        .max(Some(*min_collateral_value))
159        .ok_or(gmsol_model::Error::Computation(
160            "calculating liquidation collateral usd",
161        ))?;
162
163        let liquidation_price = if position_size_in_tokens.is_zero() {
164            None
165        } else {
166            collateral_value
167                .checked_add_signed(price_impact_value)
168                .and_then(|a| a.checked_sub(pending_borrowing_fee_value))
169                .and_then(|a| a.checked_sub(pending_funding_fee_value))
170                .and_then(|a| a.checked_sub(close_order_fee_value))
171                .and_then(|remaining_collateral_usd| {
172                    if self.is_long() {
173                        liquidation_collateral_usd
174                            .checked_add(*position_size_in_usd)?
175                            .checked_sub(remaining_collateral_usd)?
176                            .checked_div(*position_size_in_tokens)
177                    } else {
178                        remaining_collateral_usd
179                            .checked_add(*position_size_in_usd)?
180                            .checked_sub(liquidation_collateral_usd)?
181                            .checked_div(*position_size_in_tokens)
182                    }
183                })
184        };
185
186        Ok(PositionStatus {
187            entry_price,
188            collateral_value,
189            pending_pnl: pending_pnl_value,
190            pending_borrowing_fee_value,
191            pending_funding_fee_value,
192            pending_claimable_funding_fee_value_in_long_token,
193            pending_claimable_funding_fee_value_in_short_token,
194            close_order_fee_value,
195            net_value,
196            leverage,
197            liquidation_price,
198        })
199    }
200}