gmsol_model/
price.rs

1use num_traits::{CheckedAdd, CheckedDiv};
2
3/// Price.
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5#[cfg_attr(
6    feature = "anchor-lang",
7    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
8)]
9#[derive(Debug, Clone, Copy, Default)]
10pub struct Price<T> {
11    /// Minimum Price.
12    pub min: T,
13    /// Maximum Price.
14    pub max: T,
15}
16
17#[cfg(feature = "gmsol-utils")]
18impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for Price<T> {
19    const INIT_SPACE: usize = 2 * T::INIT_SPACE;
20}
21
22impl<T: Clone> Price<T> {
23    /// Set prices for test.
24    #[cfg(test)]
25    pub fn set_price_for_test(&mut self, price: T) {
26        self.min = price.clone();
27        self.max = price;
28    }
29}
30
31impl<T> Price<T>
32where
33    T: Ord,
34{
35    /// Pick price for PnL.
36    pub fn pick_price_for_pnl(&self, is_long: bool, maximize: bool) -> &T {
37        if is_long ^ maximize {
38            &self.min
39        } else {
40            &self.max
41        }
42    }
43
44    /// Pick price.
45    pub fn pick_price(&self, maximize: bool) -> &T {
46        if maximize {
47            &self.max
48        } else {
49            &self.min
50        }
51    }
52}
53
54impl<T: num_traits::Zero> Price<T> {
55    /// Return whether the min price or max price is zero.
56    pub fn has_zero(&self) -> bool {
57        self.min.is_zero() || self.max.is_zero()
58    }
59}
60
61impl<T> Price<T>
62where
63    T: CheckedAdd + CheckedDiv + num_traits::One,
64{
65    /// Get mid price checked.
66    pub fn checked_mid(&self) -> Option<T> {
67        let one = T::one();
68        let two = one.checked_add(&one)?;
69        self.min
70            .checked_add(&self.max)
71            .and_then(|p| p.checked_div(&two))
72    }
73
74    /// Get mid price.
75    ///
76    /// # Panic
77    /// Panic if cannot calculate the mid price.
78    pub fn mid(&self) -> T {
79        self.checked_mid().expect("cannot calculate the mid price")
80    }
81}
82
83impl<T> Price<T>
84where
85    T: num_traits::Zero + CheckedAdd + CheckedDiv + num_traits::One,
86{
87    fn is_valid(&self) -> bool {
88        !self.min.is_zero() && !self.max.is_zero() && self.checked_mid().is_some()
89    }
90}
91
92/// Prices for execution.
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94#[cfg_attr(
95    feature = "anchor-lang",
96    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
97)]
98#[derive(Debug, Clone, Copy, Default)]
99pub struct Prices<T> {
100    /// Index token price.
101    pub index_token_price: Price<T>,
102    /// Long token price.
103    pub long_token_price: Price<T>,
104    /// Short token price.
105    pub short_token_price: Price<T>,
106}
107
108#[cfg(feature = "gmsol-utils")]
109impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for Prices<T> {
110    const INIT_SPACE: usize = 3 * Price::<T>::INIT_SPACE;
111}
112
113impl<T> Prices<T> {
114    /// Create a new [`Prices`].
115    #[cfg(any(test, feature = "test"))]
116    pub fn new_for_test(index: T, long: T, short: T) -> Self
117    where
118        T: Clone,
119    {
120        Self {
121            index_token_price: Price {
122                min: index.clone(),
123                max: index,
124            },
125            long_token_price: Price {
126                min: long.clone(),
127                max: long,
128            },
129            short_token_price: Price {
130                min: short.clone(),
131                max: short,
132            },
133        }
134    }
135
136    /// Get collateral token price.
137    pub fn collateral_token_price(&self, is_long: bool) -> &Price<T> {
138        if is_long {
139            &self.long_token_price
140        } else {
141            &self.short_token_price
142        }
143    }
144}
145
146impl<T> Prices<T>
147where
148    T: num_traits::Zero + CheckedAdd + CheckedDiv + num_traits::One,
149{
150    /// Check if the prices is valid.
151    pub fn is_valid(&self) -> bool {
152        self.index_token_price.is_valid()
153            && self.long_token_price.is_valid()
154            && self.short_token_price.is_valid()
155    }
156
157    /// Validate the prices.
158    pub fn validate(&self) -> crate::Result<()> {
159        if self.is_valid() {
160            Ok(())
161        } else {
162            Err(crate::Error::InvalidArgument("invalid prices"))
163        }
164    }
165}