use std::ops::{Deref, DerefMut};
use crate::{
fixed::FixedPointOps,
num::{MulDiv, Num, Unsigned, UnsignedAbs},
pool::{balance::Merged, Balance, BalanceExt, Pool},
price::{Price, Prices},
Delta, PoolExt,
};
use num_traits::{CheckedAdd, CheckedSub, Signed, Zero};
use super::get_msg_by_side;
pub trait BaseMarket<const DECIMALS: u8> {
type Num: MulDiv<Signed = Self::Signed> + FixedPointOps<DECIMALS>;
type Signed: UnsignedAbs<Unsigned = Self::Num> + TryFrom<Self::Num> + Num;
type Pool: Pool<Num = Self::Num, Signed = Self::Signed>;
fn liquidity_pool(&self) -> crate::Result<&Self::Pool>;
fn claimable_fee_pool(&self) -> crate::Result<&Self::Pool>;
fn swap_impact_pool(&self) -> crate::Result<&Self::Pool>;
fn open_interest_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
fn open_interest_in_tokens_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
fn collateral_sum_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
fn virtual_inventory_for_swaps_pool(
&self,
) -> crate::Result<Option<impl Deref<Target = Self::Pool>>>;
fn virtual_inventory_for_positions_pool(
&self,
) -> crate::Result<Option<impl Deref<Target = Self::Pool>>>;
fn usd_to_amount_divisor(&self) -> Self::Num;
fn max_pool_amount(&self, is_long_token: bool) -> crate::Result<Self::Num>;
fn pnl_factor_config(&self, kind: PnlFactorKind, is_long: bool) -> crate::Result<Self::Num>;
fn reserve_factor(&self) -> crate::Result<Self::Num>;
fn open_interest_reserve_factor(&self) -> crate::Result<Self::Num>;
fn max_open_interest(&self, is_long: bool) -> crate::Result<Self::Num>;
fn ignore_open_interest_for_usage_factor(&self) -> crate::Result<bool>;
}
pub trait BaseMarketMut<const DECIMALS: u8>: BaseMarket<DECIMALS> {
fn liquidity_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
fn claimable_fee_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
fn virtual_inventory_for_swaps_pool_mut(
&mut self,
) -> crate::Result<Option<impl DerefMut<Target = Self::Pool>>>;
}
impl<M: BaseMarket<DECIMALS>, const DECIMALS: u8> BaseMarket<DECIMALS> for &mut M {
type Num = M::Num;
type Signed = M::Signed;
type Pool = M::Pool;
fn liquidity_pool(&self) -> crate::Result<&Self::Pool> {
(**self).liquidity_pool()
}
fn swap_impact_pool(&self) -> crate::Result<&Self::Pool> {
(**self).swap_impact_pool()
}
fn claimable_fee_pool(&self) -> crate::Result<&Self::Pool> {
(**self).claimable_fee_pool()
}
fn open_interest_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
(**self).open_interest_pool(is_long)
}
fn open_interest_in_tokens_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
(**self).open_interest_in_tokens_pool(is_long)
}
fn collateral_sum_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
(**self).collateral_sum_pool(is_long)
}
fn virtual_inventory_for_swaps_pool(
&self,
) -> crate::Result<Option<impl Deref<Target = Self::Pool>>> {
(**self).virtual_inventory_for_swaps_pool()
}
fn virtual_inventory_for_positions_pool(
&self,
) -> crate::Result<Option<impl Deref<Target = Self::Pool>>> {
(**self).virtual_inventory_for_positions_pool()
}
fn usd_to_amount_divisor(&self) -> Self::Num {
(**self).usd_to_amount_divisor()
}
fn max_pool_amount(&self, is_long_token: bool) -> crate::Result<Self::Num> {
(**self).max_pool_amount(is_long_token)
}
fn pnl_factor_config(&self, kind: PnlFactorKind, is_long: bool) -> crate::Result<Self::Num> {
(**self).pnl_factor_config(kind, is_long)
}
fn reserve_factor(&self) -> crate::Result<Self::Num> {
(**self).reserve_factor()
}
fn open_interest_reserve_factor(&self) -> crate::Result<Self::Num> {
(**self).open_interest_reserve_factor()
}
fn max_open_interest(&self, is_long: bool) -> crate::Result<Self::Num> {
(**self).max_open_interest(is_long)
}
fn ignore_open_interest_for_usage_factor(&self) -> crate::Result<bool> {
(**self).ignore_open_interest_for_usage_factor()
}
}
impl<M: BaseMarketMut<DECIMALS>, const DECIMALS: u8> BaseMarketMut<DECIMALS> for &mut M {
fn liquidity_pool_mut(&mut self) -> crate::Result<&mut Self::Pool> {
(**self).liquidity_pool_mut()
}
fn claimable_fee_pool_mut(&mut self) -> crate::Result<&mut Self::Pool> {
(**self).claimable_fee_pool_mut()
}
fn virtual_inventory_for_swaps_pool_mut(
&mut self,
) -> crate::Result<Option<impl DerefMut<Target = Self::Pool>>> {
(**self).virtual_inventory_for_swaps_pool_mut()
}
}
pub trait BaseMarketExt<const DECIMALS: u8>: BaseMarket<DECIMALS> {
#[inline]
fn pool_value_without_pnl_for_one_side(
&self,
prices: &Prices<Self::Num>,
is_long: bool,
maximize: bool,
) -> crate::Result<Self::Num> {
if is_long {
self.liquidity_pool()?
.long_usd_value(prices.long_token_price.pick_price(maximize))
} else {
self.liquidity_pool()?
.short_usd_value(prices.short_token_price.pick_price(maximize))
}
}
fn open_interest(&self) -> crate::Result<Merged<&Self::Pool, &Self::Pool>> {
Ok(self
.open_interest_pool(true)?
.merge(self.open_interest_pool(false)?))
}
fn open_interest_in_tokens(&self) -> crate::Result<Merged<&Self::Pool, &Self::Pool>> {
Ok(self
.open_interest_in_tokens_pool(true)?
.merge(self.open_interest_in_tokens_pool(false)?))
}
fn pnl(
&self,
index_token_price: &Price<Self::Num>,
is_long: bool,
maximize: bool,
) -> crate::Result<Self::Signed> {
use num_traits::CheckedMul;
let open_interest = self.open_interest()?.amount(is_long)?;
let open_interest_in_tokens = self.open_interest_in_tokens()?.amount(is_long)?;
if open_interest.is_zero() && open_interest_in_tokens.is_zero() {
return Ok(Zero::zero());
}
let price = index_token_price.pick_price_for_pnl(is_long, maximize);
let open_interest_value = open_interest_in_tokens
.checked_mul(price)
.ok_or(crate::Error::Computation("calculating open interest value"))?;
if is_long {
open_interest_value
.to_signed()?
.checked_sub(&open_interest.to_signed()?)
.ok_or(crate::Error::Computation("calculating pnl for long"))
} else {
open_interest
.to_signed()?
.checked_sub(&open_interest_value.to_signed()?)
.ok_or(crate::Error::Computation("calculating pnl for short"))
}
}
fn pnl_factor_with_pool_value(
&self,
prices: &Prices<Self::Num>,
is_long: bool,
maximize: bool,
) -> crate::Result<(Self::Signed, Self::Num)> {
let pool_value = self.pool_value_without_pnl_for_one_side(prices, is_long, !maximize)?;
let pnl = self.pnl(&prices.index_token_price, is_long, maximize)?;
crate::utils::div_to_factor_signed(&pnl, &pool_value)
.ok_or(crate::Error::Computation("calculating pnl factor"))
.map(|factor| (factor, pool_value))
}
fn pnl_factor(
&self,
prices: &Prices<Self::Num>,
is_long: bool,
maximize: bool,
) -> crate::Result<Self::Signed> {
Ok(self
.pnl_factor_with_pool_value(prices, is_long, maximize)?
.0)
}
fn validate_pool_amount(&self, is_long_token: bool) -> crate::Result<()> {
let amount = self.liquidity_pool()?.amount(is_long_token)?;
let max_pool_amount = self.max_pool_amount(is_long_token)?;
if amount > max_pool_amount {
Err(crate::Error::MaxPoolAmountExceeded(get_msg_by_side(
is_long_token,
)))
} else {
Ok(())
}
}
fn pnl_factor_exceeded(
&self,
prices: &Prices<Self::Num>,
kind: PnlFactorKind,
is_long: bool,
) -> crate::Result<Option<PnlFactorExceeded<Self::Num>>> {
let (pnl_factor, pool_value) = self.pnl_factor_with_pool_value(prices, is_long, true)?;
let max_pnl_factor = self.pnl_factor_config(kind, is_long)?;
let is_exceeded = pnl_factor.is_positive() && pnl_factor.unsigned_abs() > max_pnl_factor;
Ok(is_exceeded.then(|| PnlFactorExceeded {
pnl_factor,
max_pnl_factor,
pool_value,
}))
}
fn validate_pnl_factor(
&self,
prices: &Prices<Self::Num>,
kind: PnlFactorKind,
is_long: bool,
) -> crate::Result<()> {
if self.pnl_factor_exceeded(prices, kind, is_long)?.is_some() {
Err(crate::Error::PnlFactorExceeded(
kind,
get_msg_by_side(is_long),
))
} else {
Ok(())
}
}
fn validate_max_pnl(
&self,
prices: &Prices<Self::Num>,
long_kind: PnlFactorKind,
short_kind: PnlFactorKind,
) -> crate::Result<()> {
self.validate_pnl_factor(prices, long_kind, true)?;
self.validate_pnl_factor(prices, short_kind, false)?;
Ok(())
}
fn reserved_value(
&self,
index_token_price: &Price<Self::Num>,
is_long: bool,
) -> crate::Result<Self::Num> {
if is_long {
self.open_interest_in_tokens()?
.long_usd_value(index_token_price.pick_price(true))
} else {
self.open_interest()?.short_amount()
}
}
fn validate_reserve(&self, prices: &Prices<Self::Num>, is_long: bool) -> crate::Result<()> {
let pool_value = self.pool_value_without_pnl_for_one_side(prices, is_long, false)?;
let max_reserved_value =
crate::utils::apply_factor(&pool_value, &self.reserve_factor()?)
.ok_or(crate::Error::Computation("calculating max reserved value"))?;
let reserved_value = self.reserved_value(&prices.index_token_price, is_long)?;
if reserved_value > max_reserved_value {
Err(crate::Error::InsufficientReserve(
reserved_value.to_string(),
max_reserved_value.to_string(),
))
} else {
Ok(())
}
}
fn expected_min_token_balance_excluding_collateral_amount_for_one_token_side(
&self,
is_long_side: bool,
) -> crate::Result<Self::Num> {
let mut balance = self.liquidity_pool()?.amount(is_long_side)?;
balance = balance
.checked_add(&self.swap_impact_pool()?.amount(is_long_side)?)
.ok_or(crate::Error::Computation(
"overflow adding swap impact pool amount",
))?;
balance = balance
.checked_add(&self.claimable_fee_pool()?.amount(is_long_side)?)
.ok_or(crate::Error::Computation(
"overflow adding claimable fee amount",
))?;
Ok(balance)
}
fn total_collateral_amount_for_one_token_side(
&self,
is_long_side: bool,
) -> crate::Result<Self::Num> {
let mut collateral_amount = self.collateral_sum_pool(true)?.amount(is_long_side)?;
collateral_amount = collateral_amount
.checked_add(&self.collateral_sum_pool(false)?.amount(is_long_side)?)
.ok_or(crate::Error::Computation(
"calculating total collateral sum for one side",
))?;
Ok(collateral_amount)
}
fn checked_apply_delta(
&self,
delta: Delta<&Self::Signed>,
) -> crate::Result<(Self::Pool, Option<Self::Pool>)> {
let liquidity_pool = self.liquidity_pool()?.checked_apply_delta(delta)?;
let virtual_inventory_for_swaps_pool = self
.virtual_inventory_for_swaps_pool()?
.map(|p| p.checked_apply_delta(delta))
.transpose()?;
Ok((liquidity_pool, virtual_inventory_for_swaps_pool))
}
}
impl<M: BaseMarket<DECIMALS> + ?Sized, const DECIMALS: u8> BaseMarketExt<DECIMALS> for M {}
pub trait BaseMarketMutExt<const DECIMALS: u8>: BaseMarketMut<DECIMALS> {
fn apply_delta(&mut self, is_long_token: bool, delta: &Self::Signed) -> crate::Result<()> {
let delta = if is_long_token {
Delta::new_with_long(delta)
} else {
Delta::new_with_short(delta)
};
let (liquidity_pool, virtual_inventory_for_swaps_pool) = self.checked_apply_delta(delta)?;
*self
.liquidity_pool_mut()
.expect("liquidity pool must be valid") = liquidity_pool;
if let Some(virtual_inventory_for_swaps_pool) = virtual_inventory_for_swaps_pool {
*self
.virtual_inventory_for_swaps_pool_mut()
.expect("virtual inventory for_swaps pool must be valid")
.expect("virtual inventory for_swaps pool must exist") =
virtual_inventory_for_swaps_pool;
}
Ok(())
}
fn apply_delta_to_claimable_fee_pool(
&mut self,
is_long_token: bool,
delta: &Self::Signed,
) -> crate::Result<()> {
self.claimable_fee_pool_mut()?
.apply_delta_amount(is_long_token, delta)?;
Ok(())
}
}
impl<M: BaseMarketMut<DECIMALS> + ?Sized, const DECIMALS: u8> BaseMarketMutExt<DECIMALS> for M {}
#[derive(
Debug,
Clone,
Copy,
num_enum::TryFromPrimitive,
num_enum::IntoPrimitive,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
)]
#[cfg_attr(
feature = "strum",
derive(strum::EnumIter, strum::EnumString, strum::Display)
)]
#[cfg_attr(feature = "strum", strum(serialize_all = "snake_case"))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "js", derive(tsify_next::Tsify))]
#[repr(u8)]
#[non_exhaustive]
pub enum PnlFactorKind {
MaxAfterDeposit,
MaxAfterWithdrawal,
MaxForTrader,
ForAdl,
MinAfterAdl,
}
pub struct PnlFactorExceeded<T: Unsigned> {
pub pnl_factor: T::Signed,
pub max_pnl_factor: T,
pub pool_value: T,
}
impl<T: Unsigned> PnlFactorExceeded<T> {
pub fn exceeded_pnl<const DECIMALS: u8>(&self) -> Option<T>
where
T: CheckedSub,
T: FixedPointOps<DECIMALS>,
{
if !self.pnl_factor.is_positive() || self.pool_value.is_zero() {
return None;
}
let pnl_factor = self.pnl_factor.unsigned_abs();
let diff_factor = pnl_factor.checked_sub(&self.max_pnl_factor)?;
crate::utils::apply_factor(&self.pool_value, &diff_factor)
}
}