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#[derive(Debug, Clone, Copy)]
11pub enum BalanceChange {
12 Improved,
14 Worsened,
16 Unchanged,
18}
19
20#[derive(Debug, Clone, Copy)]
22pub struct PriceImpact<T> {
23 pub value: T,
25 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#[derive(Debug, Clone, Copy)]
40pub struct Delta<T> {
41 long: Option<T>,
43 short: Option<T>,
45}
46
47impl<T> Delta<T> {
48 pub fn new(long: Option<T>, short: Option<T>) -> Self {
50 Self { long, short }
51 }
52
53 #[inline]
55 pub fn new_with_long(amount: T) -> Self {
56 Self::new(Some(amount), None)
57 }
58
59 #[inline]
61 pub fn new_with_short(amount: T) -> Self {
62 Self::new(None, Some(amount))
63 }
64
65 #[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 #[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 pub fn long(&self) -> Option<&T> {
88 self.long.as_ref()
89 }
90
91 pub fn short(&self) -> Option<&T> {
93 self.short.as_ref()
94 }
95}
96
97pub struct PoolValue<T> {
99 long_token_usd_value: T,
100 short_token_usd_value: T,
101}
102
103impl<T> PoolValue<T> {
104 pub fn long_value(&self) -> &T {
106 &self.long_token_usd_value
107 }
108
109 pub fn short_value(&self) -> &T {
111 &self.short_token_usd_value
112 }
113}
114
115impl<T: Unsigned + Clone> PoolValue<T> {
116 #[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 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
140pub 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 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 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 pub fn delta(&self) -> &PoolValue<T::Signed> {
214 &self.delta
215 }
216}
217
218impl<T: Unsigned + Clone + Ord> PoolDelta<T> {
219 #[inline]
221 pub fn initial_diff_value(&self) -> T {
222 self.current.diff_value()
223 }
224
225 #[inline]
227 pub fn next_diff_value(&self) -> T {
228 self.next.diff_value()
229 }
230
231 #[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 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}