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