1use num_traits::{CheckedAdd, CheckedDiv};
2
3#[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 pub min: T,
13 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 #[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 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 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 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 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 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#[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 pub index_token_price: Price<T>,
102 pub long_token_price: Price<T>,
104 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 #[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 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 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 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}