gmsol_model/pool/
delta.rs

1use std::cmp::Ordering;
2
3use num_traits::{CheckedAdd, CheckedMul, CheckedNeg, CheckedSub, Zero};
4
5use crate::{
6    fixed::FixedPointOps, num::Unsigned, params::PriceImpactParams, utils, Balance, BalanceExt,
7};
8
9/// Represents the change of balance.
10#[derive(Debug, Clone, Copy)]
11pub enum BalanceChange {
12    /// Balance was improved.
13    Improved,
14    /// Balance was worsened.
15    Worsened,
16    /// Balance was unchanged.
17    Unchanged,
18}
19
20/// Represents the effect caused by balance change.
21#[derive(Debug, Clone, Copy)]
22pub struct PriceImpact<T> {
23    /// Price impact value.
24    pub value: T,
25    /// Balance change.
26    pub balance_change: BalanceChange,
27}
28
29impl<T: Zero> Default for PriceImpact<T> {
30    fn default() -> Self {
31        Self {
32            value: Zero::zero(),
33            balance_change: BalanceChange::Unchanged,
34        }
35    }
36}
37
38/// Delta Amounts.
39#[derive(Debug, Clone, Copy)]
40pub struct Delta<T> {
41    /// Long amount.
42    long: Option<T>,
43    /// Short amount.
44    short: Option<T>,
45}
46
47impl<T> Delta<T> {
48    /// Create a new delta amounts.
49    pub fn new(long: Option<T>, short: Option<T>) -> Self {
50        Self { long, short }
51    }
52
53    /// Create a long delta amount.
54    #[inline]
55    pub fn new_with_long(amount: T) -> Self {
56        Self::new(Some(amount), None)
57    }
58
59    /// Create a short delta amount.
60    #[inline]
61    pub fn new_with_short(amount: T) -> Self {
62        Self::new(None, Some(amount))
63    }
64
65    /// Create a delta amount for one side.
66    #[inline]
67    pub fn new_one_side(is_long: bool, amount: T) -> Self {
68        if is_long {
69            Self::new_with_long(amount)
70        } else {
71            Self::new_with_short(amount)
72        }
73    }
74
75    /// Create delta amounts for both sides.
76    #[inline]
77    pub fn new_both_sides(is_long_first: bool, first: T, second: T) -> Self {
78        let (long, short) = if is_long_first {
79            (first, second)
80        } else {
81            (second, first)
82        };
83        Self::new(Some(long), Some(short))
84    }
85
86    /// Get long delta amount.
87    pub fn long(&self) -> Option<&T> {
88        self.long.as_ref()
89    }
90
91    /// Get short delta amount.
92    pub fn short(&self) -> Option<&T> {
93        self.short.as_ref()
94    }
95}
96
97/// Usd values of pool.
98pub struct PoolValue<T> {
99    long_token_usd_value: T,
100    short_token_usd_value: T,
101}
102
103impl<T> PoolValue<T> {
104    /// Get long token usd value.
105    pub fn long_value(&self) -> &T {
106        &self.long_token_usd_value
107    }
108
109    /// Get short token usd value.
110    pub fn short_value(&self) -> &T {
111        &self.short_token_usd_value
112    }
113}
114
115impl<T: Unsigned + Clone> PoolValue<T> {
116    /// Get usd value (abs) difference.
117    #[inline]
118    pub fn diff_value(&self) -> T {
119        self.long_token_usd_value
120            .clone()
121            .diff(self.short_token_usd_value.clone())
122    }
123}
124
125impl<T: Unsigned> PoolValue<T> {
126    /// Create a new [`PoolValue`] from the given pool and prices.
127    pub fn try_new<P>(pool: &P, long_token_price: &T, short_token_price: &T) -> crate::Result<Self>
128    where
129        P: Balance<Num = T, Signed = T::Signed> + ?Sized,
130    {
131        let long_token_usd_value = pool.long_usd_value(long_token_price)?;
132        let short_token_usd_value = pool.short_usd_value(short_token_price)?;
133        Ok(Self {
134            long_token_usd_value,
135            short_token_usd_value,
136        })
137    }
138}
139
140/// Delta of pool usd values.
141pub struct PoolDelta<T: Unsigned> {
142    current: PoolValue<T>,
143    next: PoolValue<T>,
144    delta: PoolValue<T::Signed>,
145    long_token_price: T,
146    short_token_price: T,
147}
148
149impl<T: Unsigned> PoolDelta<T> {
150    /// Create a new [`PoolDelta`].
151    pub fn try_new<P>(
152        pool: &P,
153        delta_long_token_usd_value: T::Signed,
154        delta_short_token_usd_value: T::Signed,
155        long_token_price: &T,
156        short_token_price: &T,
157    ) -> crate::Result<Self>
158    where
159        T: CheckedAdd + CheckedSub + CheckedMul + Clone,
160        P: Balance<Num = T, Signed = T::Signed> + ?Sized,
161    {
162        let current = PoolValue::try_new(pool, long_token_price, short_token_price)?;
163
164        let next = PoolValue {
165            long_token_usd_value: current
166                .long_token_usd_value
167                .checked_add_with_signed(&delta_long_token_usd_value)
168                .ok_or(crate::Error::Computation("next delta long usd value"))?,
169            short_token_usd_value: current
170                .short_token_usd_value
171                .checked_add_with_signed(&delta_short_token_usd_value)
172                .ok_or(crate::Error::Computation("next delta short usd value"))?,
173        };
174
175        let delta = PoolValue {
176            long_token_usd_value: delta_long_token_usd_value,
177            short_token_usd_value: delta_short_token_usd_value,
178        };
179
180        Ok(Self {
181            current,
182            next,
183            delta,
184            long_token_price: long_token_price.clone(),
185            short_token_price: short_token_price.clone(),
186        })
187    }
188
189    /// Create a new [`PoolDelta`].
190    pub fn try_from_delta_amounts<P>(
191        pool: &P,
192        long_delta_amount: &T::Signed,
193        short_delta_amount: &T::Signed,
194        long_token_price: &T,
195        short_token_price: &T,
196    ) -> crate::Result<Self>
197    where
198        T: CheckedAdd + CheckedSub + CheckedMul + Clone,
199        P: Balance<Num = T, Signed = T::Signed> + ?Sized,
200    {
201        let delta_long_token_usd_value = long_token_price
202            .checked_mul_with_signed(long_delta_amount)
203            .ok_or(crate::Error::Computation("delta long token usd value"))?;
204        let delta_short_token_usd_value = short_token_price
205            .checked_mul_with_signed(short_delta_amount)
206            .ok_or(crate::Error::Computation("delta short token usd value"))?;
207        Self::try_new(
208            pool,
209            delta_long_token_usd_value,
210            delta_short_token_usd_value,
211            long_token_price,
212            short_token_price,
213        )
214    }
215
216    /// Get delta values.
217    pub fn delta(&self) -> &PoolValue<T::Signed> {
218        &self.delta
219    }
220
221    /// Returns long token price.
222    pub fn long_token_price(&self) -> &T {
223        &self.long_token_price
224    }
225
226    /// Returns short token price.
227    pub fn short_token_price(&self) -> &T {
228        &self.short_token_price
229    }
230}
231
232impl<T: Unsigned + Clone + Ord> PoolDelta<T> {
233    /// Initial diff usd value.
234    #[inline]
235    pub fn initial_diff_value(&self) -> T {
236        self.current.diff_value()
237    }
238
239    /// Next diff usd value.
240    #[inline]
241    pub fn next_diff_value(&self) -> T {
242        self.next.diff_value()
243    }
244
245    /// Returns whether it is a same side rebalance.
246    #[inline]
247    pub fn is_same_side_rebalance(&self) -> bool {
248        (self.current.long_token_usd_value <= self.current.short_token_usd_value)
249            == (self.next.long_token_usd_value <= self.next.short_token_usd_value)
250    }
251
252    /// Calculate price impact.
253    pub fn price_impact<const DECIMALS: u8>(
254        &self,
255        params: &PriceImpactParams<T>,
256    ) -> crate::Result<PriceImpact<T::Signed>>
257    where
258        T: FixedPointOps<DECIMALS>,
259    {
260        let initial = self.initial_diff_value();
261        let next = self.next_diff_value();
262        let balance_change = match next.cmp(&initial) {
263            Ordering::Equal => BalanceChange::Unchanged,
264            Ordering::Greater => BalanceChange::Worsened,
265            Ordering::Less => BalanceChange::Improved,
266        };
267        let price_impact = if self.is_same_side_rebalance() {
268            Self::price_impact_for_same_side_rebalance(initial, next, params)?
269        } else {
270            Self::price_impact_for_cross_over_rebalance(initial, next, params)?
271        };
272        Ok(PriceImpact {
273            value: price_impact,
274            balance_change,
275        })
276    }
277
278    #[inline]
279    fn price_impact_for_same_side_rebalance<const DECIMALS: u8>(
280        initial: T,
281        next: T,
282        params: &PriceImpactParams<T>,
283    ) -> crate::Result<T::Signed>
284    where
285        T: FixedPointOps<DECIMALS>,
286    {
287        let has_positive_impact = next < initial;
288        let (positive_factor, negative_factor) = params.adjusted_factors();
289
290        let factor = if has_positive_impact {
291            positive_factor
292        } else {
293            negative_factor
294        };
295        let exponent_factor = params.exponent();
296
297        let initial = utils::apply_factors(initial, factor.clone(), exponent_factor.clone())?;
298        let next = utils::apply_factors(next, factor.clone(), exponent_factor.clone())?;
299        let delta: T::Signed = initial
300            .diff(next)
301            .try_into()
302            .map_err(|_| crate::Error::Convert)?;
303        Ok(if has_positive_impact {
304            delta
305        } else {
306            delta.checked_neg().ok_or(crate::Error::Computation(
307                "same side rebalance: negating delta",
308            ))?
309        })
310    }
311
312    #[inline]
313    fn price_impact_for_cross_over_rebalance<const DECIMALS: u8>(
314        initial: T,
315        next: T,
316        params: &PriceImpactParams<T>,
317    ) -> crate::Result<T::Signed>
318    where
319        T: FixedPointOps<DECIMALS>,
320    {
321        let (positive_factor, negative_factor) = params.adjusted_factors();
322        let exponent_factor = params.exponent();
323        let positive_impact =
324            utils::apply_factors(initial, positive_factor.clone(), exponent_factor.clone())?;
325        let negative_impact =
326            utils::apply_factors(next, negative_factor.clone(), exponent_factor.clone())?;
327        let has_positive_impact = positive_impact > negative_impact;
328        let delta: T::Signed = positive_impact
329            .diff(negative_impact)
330            .try_into()
331            .map_err(|_| crate::Error::Convert)?;
332        Ok(if has_positive_impact {
333            delta
334        } else {
335            delta.checked_neg().ok_or(crate::Error::Computation(
336                "cross over rebalance: negating delta",
337            ))?
338        })
339    }
340}