1use num_traits::{CheckedAdd, CheckedMul, CheckedNeg, CheckedSub};
2
3use crate::{
4 fixed::FixedPointOps, num::Unsigned, params::PriceImpactParams, utils, Balance, BalanceExt,
5};
6
7#[derive(Debug, Clone, Copy)]
9pub struct Delta<T> {
10 long: Option<T>,
12 short: Option<T>,
14}
15
16impl<T> Delta<T> {
17 pub fn new(long: Option<T>, short: Option<T>) -> Self {
19 Self { long, short }
20 }
21
22 #[inline]
24 pub fn new_with_long(amount: T) -> Self {
25 Self::new(Some(amount), None)
26 }
27
28 #[inline]
30 pub fn new_with_short(amount: T) -> Self {
31 Self::new(None, Some(amount))
32 }
33
34 #[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 #[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 pub fn long(&self) -> Option<&T> {
57 self.long.as_ref()
58 }
59
60 pub fn short(&self) -> Option<&T> {
62 self.short.as_ref()
63 }
64}
65
66pub struct PoolValue<T> {
68 long_token_usd_value: T,
69 short_token_usd_value: T,
70}
71
72impl<T> PoolValue<T> {
73 pub fn long_value(&self) -> &T {
75 &self.long_token_usd_value
76 }
77
78 pub fn short_value(&self) -> &T {
80 &self.short_token_usd_value
81 }
82}
83
84impl<T: Unsigned + Clone> PoolValue<T> {
85 #[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 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
109pub 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 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 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 pub fn delta(&self) -> &PoolValue<T::Signed> {
183 &self.delta
184 }
185}
186
187impl<T: Unsigned + Clone + Ord> PoolDelta<T> {
188 #[inline]
190 pub fn initial_diff_value(&self) -> T {
191 self.current.diff_value()
192 }
193
194 #[inline]
196 pub fn next_diff_value(&self) -> T {
197 self.next.diff_value()
198 }
199
200 #[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 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}