levana_perpswap_cosmos/
compat.rs

1//! Backwards compatibility helpers
2// this file should be completely deleted when max_gains is deleted
3#![allow(missing_docs)]
4
5use crate::prelude::*;
6use crate::storage::{MaxGainsInQuote, PricePoint};
7
8/// Backwards compatible take profit calculation
9pub struct BackwardsCompatTakeProfit<'a> {
10    pub collateral: NonZero<Collateral>,
11    pub direction: DirectionToBase,
12    pub leverage: LeverageToBase,
13    pub market_type: MarketType,
14    pub price_point: &'a PricePoint,
15    pub max_gains: MaxGainsInQuote,
16    pub take_profit: Option<PriceBaseInQuote>,
17}
18
19impl<'a> BackwardsCompatTakeProfit<'a> {
20    pub fn calc(self) -> Result<TakeProfitTrader> {
21        let BackwardsCompatTakeProfit {
22            collateral,
23            direction,
24            leverage,
25            market_type,
26            price_point,
27            max_gains,
28            take_profit,
29        } = self;
30        match take_profit {
31            Some(take_profit) => Ok(TakeProfitTrader::Finite(take_profit.into_non_zero())),
32            None => match max_gains {
33                MaxGainsInQuote::PosInfinity => Ok(TakeProfitTrader::PosInfinity),
34                MaxGainsInQuote::Finite(_) => {
35                    let leverage_to_notional =
36                        leverage.into_signed(direction).into_notional(market_type)?;
37
38                    let notional_size_in_collateral =
39                        leverage_to_notional.checked_mul_collateral(collateral)?;
40
41                    let counter_collateral = max_gains.calculate_counter_collateral(
42                        market_type,
43                        collateral,
44                        notional_size_in_collateral,
45                        leverage_to_notional,
46                    )?;
47
48                    TakeProfitFromCounterCollateral {
49                        counter_collateral,
50                        market_type,
51                        collateral,
52                        leverage_to_base: self.leverage,
53                        price_point,
54                        direction,
55                    }
56                    .calc()
57                }
58            },
59        }
60    }
61}
62
63pub struct TakeProfitFromCounterCollateral<'a> {
64    pub market_type: MarketType,
65    pub collateral: NonZero<Collateral>,
66    pub counter_collateral: NonZero<Collateral>,
67    pub leverage_to_base: LeverageToBase,
68    pub price_point: &'a PricePoint,
69    pub direction: DirectionToBase,
70}
71impl<'a> TakeProfitFromCounterCollateral<'a> {
72    pub fn calc(&self) -> Result<TakeProfitTrader> {
73        let Self {
74            market_type,
75            collateral,
76            counter_collateral,
77            leverage_to_base,
78            price_point,
79            direction,
80        } = self;
81
82        let notional_size = calc_notional_size(
83            *leverage_to_base,
84            *direction,
85            *market_type,
86            price_point,
87            *collateral,
88        )?;
89
90        let take_profit_price_raw = price_point.price_notional.into_number().checked_add(
91            counter_collateral
92                .into_number()
93                .checked_div(notional_size.into_number())?,
94        )?;
95
96        let take_profit_price = if take_profit_price_raw.approx_eq(Number::ZERO)? {
97            None
98        } else {
99            debug_assert!(
100                take_profit_price_raw.is_positive_or_zero(),
101                "There should never be a calculated take profit price which is negative. In production, this is treated as 0 to indicate infinite max gains."
102            );
103            Price::try_from_number(take_profit_price_raw).ok()
104        };
105
106        match take_profit_price {
107            Some(price) => Ok(TakeProfitTrader::Finite(price.into_base_price(*market_type).into_non_zero())),
108            None =>
109            match market_type {
110                // Infinite max gains results in a notional take profit price of 0
111                MarketType::CollateralIsBase => Ok(TakeProfitTrader::PosInfinity),
112                MarketType::CollateralIsQuote => Err(anyhow!("Calculated a take profit price of {take_profit_price_raw} in a collateral-is-quote market. Spot notional price: {}. Counter collateral: {}. Notional size: {}.", price_point.price_notional, self.counter_collateral,notional_size)),
113            }
114        }
115    }
116}
117
118pub fn calc_notional_size(
119    leverage: LeverageToBase,
120    direction: DirectionToBase,
121    market_type: MarketType,
122    price_point: &PricePoint,
123    collateral: NonZero<Collateral>,
124) -> Result<Signed<Notional>> {
125    let leverage_to_base = leverage.into_signed(direction);
126
127    let leverage_to_notional = leverage_to_base.into_notional(market_type)?;
128
129    let notional_size_in_collateral = leverage_to_notional.checked_mul_collateral(collateral)?;
130
131    Ok(notional_size_in_collateral.map(|x| price_point.collateral_to_notional(x)))
132}