gmsol_sdk/position/
mod.rs1use 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
10pub mod status;
12
13#[derive(Debug, Clone, Default)]
15pub struct CalculatePositionStatusOptions {
16 pub include_virtual_inventory_impact: bool,
18}
19
20pub trait PositionCalculations {
22 fn status(&self, prices: &Prices<u128>) -> crate::Result<PositionStatus> {
24 self.status_with_options(prices, Default::default())
25 }
26
27 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 let collateral_value = self.collateral_value(prices)?;
43
44 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 let pending_borrowing_fee_value = self.pending_borrowing_fee_value()?;
60
61 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 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 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 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 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 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}