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 long_token_price: T,
146 short_token_price: T,
147}
148
149impl<T: Unsigned> PoolDelta<T> {
150 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 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 pub fn delta(&self) -> &PoolValue<T::Signed> {
218 &self.delta
219 }
220
221 pub fn long_token_price(&self) -> &T {
223 &self.long_token_price
224 }
225
226 pub fn short_token_price(&self) -> &T {
228 &self.short_token_price
229 }
230}
231
232impl<T: Unsigned + Clone + Ord> PoolDelta<T> {
233 #[inline]
235 pub fn initial_diff_value(&self) -> T {
236 self.current.diff_value()
237 }
238
239 #[inline]
241 pub fn next_diff_value(&self) -> T {
242 self.next.diff_value()
243 }
244
245 #[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 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}