gmsol_model/pool/
delta.rs

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