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}
146
147impl<T: Unsigned> PoolDelta<T> {
148    /// Create a new [`PoolDelta`].
149    pub fn try_new<P>(
150        pool: &P,
151        delta_long_token_usd_value: T::Signed,
152        delta_short_token_usd_value: T::Signed,
153        long_token_price: &T,
154        short_token_price: &T,
155    ) -> crate::Result<Self>
156    where
157        T: CheckedAdd + CheckedSub + CheckedMul,
158        P: Balance<Num = T, Signed = T::Signed> + ?Sized,
159    {
160        let current = PoolValue::try_new(pool, long_token_price, short_token_price)?;
161
162        let next = PoolValue {
163            long_token_usd_value: current
164                .long_token_usd_value
165                .checked_add_with_signed(&delta_long_token_usd_value)
166                .ok_or(crate::Error::Computation("next delta long usd value"))?,
167            short_token_usd_value: current
168                .short_token_usd_value
169                .checked_add_with_signed(&delta_short_token_usd_value)
170                .ok_or(crate::Error::Computation("next delta short usd value"))?,
171        };
172
173        let delta = PoolValue {
174            long_token_usd_value: delta_long_token_usd_value,
175            short_token_usd_value: delta_short_token_usd_value,
176        };
177
178        Ok(Self {
179            current,
180            next,
181            delta,
182        })
183    }
184
185    /// Create a new [`PoolDelta`].
186    pub fn try_from_delta_amounts<P>(
187        pool: &P,
188        long_delta_amount: &T::Signed,
189        short_delta_amount: &T::Signed,
190        long_token_price: &T,
191        short_token_price: &T,
192    ) -> crate::Result<Self>
193    where
194        T: CheckedAdd + CheckedSub + CheckedMul,
195        P: Balance<Num = T, Signed = T::Signed> + ?Sized,
196    {
197        let delta_long_token_usd_value = long_token_price
198            .checked_mul_with_signed(long_delta_amount)
199            .ok_or(crate::Error::Computation("delta long token usd value"))?;
200        let delta_short_token_usd_value = short_token_price
201            .checked_mul_with_signed(short_delta_amount)
202            .ok_or(crate::Error::Computation("delta short token usd value"))?;
203        Self::try_new(
204            pool,
205            delta_long_token_usd_value,
206            delta_short_token_usd_value,
207            long_token_price,
208            short_token_price,
209        )
210    }
211
212    /// Get delta values.
213    pub fn delta(&self) -> &PoolValue<T::Signed> {
214        &self.delta
215    }
216}
217
218impl<T: Unsigned + Clone + Ord> PoolDelta<T> {
219    /// Initial diff usd value.
220    #[inline]
221    pub fn initial_diff_value(&self) -> T {
222        self.current.diff_value()
223    }
224
225    /// Next diff usd value.
226    #[inline]
227    pub fn next_diff_value(&self) -> T {
228        self.next.diff_value()
229    }
230
231    /// Returns whether it is a same side rebalance.
232    #[inline]
233    pub fn is_same_side_rebalance(&self) -> bool {
234        (self.current.long_token_usd_value <= self.current.short_token_usd_value)
235            == (self.next.long_token_usd_value <= self.next.short_token_usd_value)
236    }
237
238    /// Calculate price impact.
239    pub fn price_impact<const DECIMALS: u8>(
240        &self,
241        params: &PriceImpactParams<T>,
242    ) -> crate::Result<PriceImpact<T::Signed>>
243    where
244        T: FixedPointOps<DECIMALS>,
245    {
246        let initial = self.initial_diff_value();
247        let next = self.next_diff_value();
248        let balance_change = match next.cmp(&initial) {
249            Ordering::Equal => BalanceChange::Unchanged,
250            Ordering::Greater => BalanceChange::Worsened,
251            Ordering::Less => BalanceChange::Improved,
252        };
253        let price_impact = if self.is_same_side_rebalance() {
254            Self::price_impact_for_same_side_rebalance(initial, next, params)?
255        } else {
256            Self::price_impact_for_cross_over_rebalance(initial, next, params)?
257        };
258        Ok(PriceImpact {
259            value: price_impact,
260            balance_change,
261        })
262    }
263
264    #[inline]
265    fn price_impact_for_same_side_rebalance<const DECIMALS: u8>(
266        initial: T,
267        next: T,
268        params: &PriceImpactParams<T>,
269    ) -> crate::Result<T::Signed>
270    where
271        T: FixedPointOps<DECIMALS>,
272    {
273        let has_positive_impact = next < initial;
274        let (positive_factor, negative_factor) = params.adjusted_factors();
275
276        let factor = if has_positive_impact {
277            positive_factor
278        } else {
279            negative_factor
280        };
281        let exponent_factor = params.exponent();
282
283        let initial = utils::apply_factors(initial, factor.clone(), exponent_factor.clone())?;
284        let next = utils::apply_factors(next, factor.clone(), exponent_factor.clone())?;
285        let delta: T::Signed = initial
286            .diff(next)
287            .try_into()
288            .map_err(|_| crate::Error::Convert)?;
289        Ok(if has_positive_impact {
290            delta
291        } else {
292            delta.checked_neg().ok_or(crate::Error::Computation(
293                "same side rebalance: negating delta",
294            ))?
295        })
296    }
297
298    #[inline]
299    fn price_impact_for_cross_over_rebalance<const DECIMALS: u8>(
300        initial: T,
301        next: T,
302        params: &PriceImpactParams<T>,
303    ) -> crate::Result<T::Signed>
304    where
305        T: FixedPointOps<DECIMALS>,
306    {
307        let (positive_factor, negative_factor) = params.adjusted_factors();
308        let exponent_factor = params.exponent();
309        let positive_impact =
310            utils::apply_factors(initial, positive_factor.clone(), exponent_factor.clone())?;
311        let negative_impact =
312            utils::apply_factors(next, negative_factor.clone(), exponent_factor.clone())?;
313        let has_positive_impact = positive_impact > negative_impact;
314        let delta: T::Signed = positive_impact
315            .diff(negative_impact)
316            .try_into()
317            .map_err(|_| crate::Error::Convert)?;
318        Ok(if has_positive_impact {
319            delta
320        } else {
321            delta.checked_neg().ok_or(crate::Error::Computation(
322                "cross over rebalance: negating delta",
323            ))?
324        })
325    }
326}