use num_traits::{CheckedAdd, CheckedDiv};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "anchor-lang",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[derive(Debug, Clone, Copy, Default)]
pub struct Price<T> {
pub min: T,
pub max: T,
}
#[cfg(feature = "gmsol-utils")]
impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for Price<T> {
const INIT_SPACE: usize = 2 * T::INIT_SPACE;
}
#[cfg(feature = "gmsol-utils")]
impl<'a> From<&'a gmsol_utils::price::Price> for Price<u128> {
fn from(value: &'a gmsol_utils::price::Price) -> Self {
Self {
min: value.min.to_unit_price(),
max: value.max.to_unit_price(),
}
}
}
impl<T: Clone> Price<T> {
#[cfg(test)]
pub fn set_price_for_test(&mut self, price: T) {
self.min = price.clone();
self.max = price;
}
}
impl<T> Price<T>
where
T: Ord,
{
pub fn pick_price_for_pnl(&self, is_long: bool, maximize: bool) -> &T {
if is_long ^ maximize {
&self.min
} else {
&self.max
}
}
pub fn pick_price(&self, maximize: bool) -> &T {
if maximize {
&self.max
} else {
&self.min
}
}
}
impl<T: num_traits::Zero> Price<T> {
pub fn has_zero(&self) -> bool {
self.min.is_zero() || self.max.is_zero()
}
}
impl<T> Price<T>
where
T: CheckedAdd + CheckedDiv + num_traits::One,
{
pub fn checked_mid(&self) -> Option<T> {
let one = T::one();
let two = one.checked_add(&one)?;
self.min
.checked_add(&self.max)
.and_then(|p| p.checked_div(&two))
}
pub fn mid(&self) -> T {
self.checked_mid().expect("cannot calculate the mid price")
}
}
impl<T> Price<T>
where
T: num_traits::Zero + CheckedAdd + CheckedDiv + num_traits::One,
{
fn is_valid(&self) -> bool {
!self.min.is_zero() && !self.max.is_zero() && self.checked_mid().is_some()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "anchor-lang",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[derive(Debug, Clone, Copy, Default)]
pub struct Prices<T> {
pub index_token_price: Price<T>,
pub long_token_price: Price<T>,
pub short_token_price: Price<T>,
}
#[cfg(feature = "gmsol-utils")]
impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for Prices<T> {
const INIT_SPACE: usize = 3 * Price::<T>::INIT_SPACE;
}
impl<T> Prices<T> {
#[cfg(any(test, feature = "test"))]
pub fn new_for_test(index: T, long: T, short: T) -> Self
where
T: Clone,
{
Self {
index_token_price: Price {
min: index.clone(),
max: index,
},
long_token_price: Price {
min: long.clone(),
max: long,
},
short_token_price: Price {
min: short.clone(),
max: short,
},
}
}
pub fn collateral_token_price(&self, is_long: bool) -> &Price<T> {
if is_long {
&self.long_token_price
} else {
&self.short_token_price
}
}
}
impl<T> Prices<T>
where
T: num_traits::Zero + CheckedAdd + CheckedDiv + num_traits::One,
{
pub fn is_valid(&self) -> bool {
self.index_token_price.is_valid()
&& self.long_token_price.is_valid()
&& self.short_token_price.is_valid()
}
pub fn validate(&self) -> crate::Result<()> {
if self.is_valid() {
Ok(())
} else {
Err(crate::Error::InvalidArgument("invalid prices"))
}
}
}