use fix::prelude::*;
use super::exo::ExoExchangeContext;
use super::lst::LstExchangeContext;
use super::ExchangeContext;
use crate::calculus::{chain_rule, positive, positive_rate, quotient_rule};
use crate::error::CoreError;
use crate::fees::controller::FeeController;
use crate::fees::curve_controller::{narrow_cr, InterpolatedFeeController};
use crate::lst::sol_price::LstSolPrice;
use crate::pyth::PriceRange;
use crate::rebalance::pricing::RebalancePriceController;
use crate::solana_clock::SolanaClock;
impl<C: SolanaClock> ExoExchangeContext<C> {
pub fn stablecoin_mint_marginal(
&self,
collateral_amount: UFix64<N9>,
) -> Result<f64, CoreError> {
let projected = self.projected_mint_state(collateral_amount)?;
let cr = narrow_cr(projected.collateral_ratio)?;
let fee = self.stablecoin_mint_fees.fee_inner(cr)?.to_f64();
let fee_slope = self.stablecoin_mint_fees.fee_slope(cr)?.to_f64();
let collateral_usd_lower = positive(self.collateral_usd_price.lower)?;
let nav = positive(self.stablecoin_nav()?)?;
let nav_rate = collateral_usd_lower.get() / nav.get();
let d_total_collateral = 1.0;
let d_stablecoin_supply = nav_rate;
let d_cr = quotient_rule(
positive(projected.total_collateral)?,
d_total_collateral,
positive(projected.stablecoin_supply)?,
d_stablecoin_supply,
collateral_usd_lower,
);
let d_fee = chain_rule(fee_slope, d_cr);
let rate = nav_rate * (1.0 - fee);
positive_rate(rate - collateral_amount.to_f64() * nav_rate * d_fee)
}
pub fn stablecoin_redeem_marginal(
&self,
amount_stablecoin: UFix64<N6>,
) -> Result<f64, CoreError> {
let nav = positive(self.stablecoin_nav()?)?;
let collateral_out = self
.exo_conversion()
.token_to_exo(amount_stablecoin, self.stablecoin_nav()?)?;
let projected = self.projected_redeem_state(collateral_out)?;
let cr = narrow_cr(projected.collateral_ratio)?;
let fee = self.stablecoin_redeem_fees.fee_inner(cr)?.to_f64();
let fee_slope = self.stablecoin_redeem_fees.fee_slope(cr)?.to_f64();
let collateral_usd_lower = positive(self.collateral_usd_price.lower)?;
let collateral_usd_upper = positive(self.collateral_usd_price.upper)?;
let nav_rate = nav.get() / collateral_usd_upper.get();
let d_total_collateral = -nav_rate;
let d_stablecoin_supply =
-(collateral_usd_lower.get() / collateral_usd_upper.get());
let d_cr = quotient_rule(
positive(projected.total_collateral)?,
d_total_collateral,
positive(projected.stablecoin_supply)?,
d_stablecoin_supply,
collateral_usd_lower,
);
let d_fee = chain_rule(fee_slope, d_cr);
let rate = nav_rate * (1.0 - fee);
positive_rate(rate - amount_stablecoin.to_f64() * nav_rate * d_fee)
}
pub fn rebalance_buy_marginal(
&self,
usdc_usd_price: PriceRange<N9>,
collateral_amount: UFix64<N9>,
) -> Result<f64, CoreError> {
let projected = self.projected_rebalance_buy_state(collateral_amount)?;
let curve = self.rebalance_buy_curve()?;
let curve_price = curve.price(projected.collateral_ratio)?.to_f64();
let curve_slope = curve.price_slope(projected.collateral_ratio)?.to_f64();
let collateral_spot = positive(self.collateral_oracle_price().spot)?;
let nav = positive(self.stablecoin_nav()?)?;
let usdc_usd_upper = positive(usdc_usd_price.upper)?;
let d_total_collateral = 1.0;
let d_stablecoin_supply = collateral_spot.get() / nav.get();
let d_cr = quotient_rule(
positive(projected.total_collateral)?,
d_total_collateral,
positive(projected.stablecoin_supply)?,
d_stablecoin_supply,
collateral_spot,
);
let d_curve_price = chain_rule(curve_slope, d_cr);
let marginal = (curve_price + collateral_amount.to_f64() * d_curve_price)
/ usdc_usd_upper.get();
positive_rate(marginal)
}
pub fn rebalance_sell_marginal(
&self,
usdc_usd_price: PriceRange<N9>,
usdc_amount: UFix64<N9>,
) -> Result<f64, CoreError> {
let projected =
self.projected_rebalance_sell_state(usdc_usd_price, usdc_amount)?;
let curve = self.rebalance_sell_curve()?;
let curve_price = positive(curve.price(projected.collateral_ratio)?)?;
let curve_slope = curve.price_slope(projected.collateral_ratio)?.to_f64();
let collateral_spot = positive(self.collateral_oracle_price().spot)?;
let nav = positive(self.stablecoin_nav()?)?;
let usdc_usd_lower = positive(usdc_usd_price.lower)?;
let d_total_collateral = -(usdc_usd_lower.get() / collateral_spot.get());
let d_stablecoin_supply = -(usdc_usd_lower.get() / nav.get());
let d_cr = quotient_rule(
positive(projected.total_collateral)?,
d_total_collateral,
positive(projected.stablecoin_supply)?,
d_stablecoin_supply,
collateral_spot,
);
let d_curve_price = chain_rule(curve_slope, d_cr);
let left = usdc_usd_lower.get() / curve_price.get();
let right = usdc_usd_lower.get() * usdc_amount.to_f64() * d_curve_price
/ (curve_price.get() * curve_price.get());
positive_rate(left - right)
}
pub fn levercoin_mint_marginal(
&self,
collateral_amount: UFix64<N9>,
) -> Result<f64, CoreError> {
let new_total = self
.total_collateral
.checked_add(&collateral_amount)
.ok_or(CoreError::DestinationCollateral)?;
let projected = self
.projected_rebalance_mode(new_total, self.virtual_stablecoin_supply()?)?;
let mode = self.select_rebalance_mode_for_fees(projected);
let fee = self.levercoin_fees().mint_fee(mode)?.to_f64();
let collateral_usd_lower = positive(self.collateral_usd_price.lower)?;
let nav = positive(self.levercoin_mint_nav()?)?;
positive_rate((1.0 - fee) * collateral_usd_lower.get() / nav.get())
}
pub fn levercoin_redeem_marginal(
&self,
amount_levercoin: UFix64<N6>,
) -> Result<f64, CoreError> {
let nav = positive(self.levercoin_redeem_nav()?)?;
let collateral_out = self
.exo_conversion()
.token_to_exo(amount_levercoin, self.levercoin_redeem_nav()?)?;
let new_total = self
.total_collateral
.checked_sub(&collateral_out)
.ok_or(CoreError::DestinationCollateral)?;
let projected = self
.projected_rebalance_mode(new_total, self.virtual_stablecoin_supply()?)?;
let mode = self.select_rebalance_mode_for_fees(projected);
let fee = self.levercoin_fees().redeem_fee(mode)?.to_f64();
let collateral_usd_upper = positive(self.collateral_usd_price.upper)?;
positive_rate(nav.get() / collateral_usd_upper.get() * (1.0 - fee))
}
}
impl<C: SolanaClock> LstExchangeContext<C> {
pub fn stablecoin_mint_marginal(
&self,
lst_sol_price: &LstSolPrice,
amount_lst: UFix64<N9>,
) -> Result<f64, CoreError> {
let projected = self.projected_mint_state(lst_sol_price, amount_lst)?;
let cr = narrow_cr(projected.collateral_ratio)?;
let fee = self.stablecoin_mint_fees.fee_inner(cr)?.to_f64();
let fee_slope = self.stablecoin_mint_fees.fee_slope(cr)?.to_f64();
let lst_sol = positive(lst_sol_price.get_epoch_price(self.clock.epoch())?)?;
let sol_usd_lower = positive(self.sol_usd_price.lower)?;
let nav = positive(self.stablecoin_nav()?)?;
let nav_rate = lst_sol.get() * sol_usd_lower.get() / nav.get();
let d_total_collateral = lst_sol.get();
let d_stablecoin_supply = nav_rate;
let d_cr = quotient_rule(
positive(projected.total_collateral)?,
d_total_collateral,
positive(projected.stablecoin_supply)?,
d_stablecoin_supply,
sol_usd_lower,
);
let d_fee = chain_rule(fee_slope, d_cr);
let rate = nav_rate * (1.0 - fee);
positive_rate(rate - amount_lst.to_f64() * nav_rate * d_fee)
}
pub fn stablecoin_redeem_marginal(
&self,
lst_sol_price: &LstSolPrice,
amount_stablecoin: UFix64<N6>,
) -> Result<f64, CoreError> {
let nav = positive(self.stablecoin_nav()?)?;
let lst_out = self
.token_conversion(lst_sol_price)?
.token_to_lst(amount_stablecoin, self.stablecoin_nav()?)?;
let projected = self.projected_redeem_state(lst_sol_price, lst_out)?;
let cr = narrow_cr(projected.collateral_ratio)?;
let fee = self.stablecoin_redeem_fees.fee_inner(cr)?.to_f64();
let fee_slope = self.stablecoin_redeem_fees.fee_slope(cr)?.to_f64();
let lst_sol = positive(lst_sol_price.get_epoch_price(self.clock.epoch())?)?;
let sol_usd_lower = positive(self.sol_usd_price.lower)?;
let sol_usd_upper = positive(self.sol_usd_price.upper)?;
let nav_rate = nav.get() / (sol_usd_upper.get() * lst_sol.get());
let d_total_collateral = -(nav.get() / sol_usd_upper.get());
let d_stablecoin_supply = -(sol_usd_lower.get() / sol_usd_upper.get());
let d_cr = quotient_rule(
positive(projected.total_collateral)?,
d_total_collateral,
positive(projected.stablecoin_supply)?,
d_stablecoin_supply,
sol_usd_lower,
);
let d_fee = chain_rule(fee_slope, d_cr);
let rate = nav_rate * (1.0 - fee);
positive_rate(rate - amount_stablecoin.to_f64() * nav_rate * d_fee)
}
pub fn rebalance_buy_marginal(
&self,
lst_sol_price: &LstSolPrice,
usdc_usd_price: PriceRange<N9>,
lst_amount: UFix64<N9>,
) -> Result<f64, CoreError> {
let projected =
self.projected_rebalance_buy_state(lst_sol_price, lst_amount)?;
let curve = self.rebalance_buy_curve()?;
let curve_price = curve.price(projected.collateral_ratio)?.to_f64();
let curve_slope = curve.price_slope(projected.collateral_ratio)?.to_f64();
let lst_sol = positive(lst_sol_price.get_epoch_price(self.clock.epoch())?)?;
let sol_spot = positive(self.collateral_oracle_price().spot)?;
let nav = positive(self.stablecoin_nav()?)?;
let usdc_usd_upper = positive(usdc_usd_price.upper)?;
let d_total_collateral = lst_sol.get();
let d_stablecoin_supply = lst_sol.get() * sol_spot.get() / nav.get();
let d_cr = quotient_rule(
positive(projected.total_collateral)?,
d_total_collateral,
positive(projected.stablecoin_supply)?,
d_stablecoin_supply,
sol_spot,
);
let d_curve_price = chain_rule(curve_slope, d_cr);
let marginal = lst_sol.get()
* (curve_price + lst_amount.to_f64() * d_curve_price)
/ usdc_usd_upper.get();
positive_rate(marginal)
}
pub fn rebalance_sell_marginal(
&self,
lst_sol_price: &LstSolPrice,
usdc_usd_price: PriceRange<N9>,
usdc_amount: UFix64<N9>,
) -> Result<f64, CoreError> {
let projected = self.projected_rebalance_sell_state(
lst_sol_price,
usdc_usd_price,
usdc_amount,
)?;
let curve = self.rebalance_sell_curve()?;
let curve_price = positive(curve.price(projected.collateral_ratio)?)?;
let curve_slope = curve.price_slope(projected.collateral_ratio)?.to_f64();
let lst_sol = positive(lst_sol_price.get_epoch_price(self.clock.epoch())?)?;
let sol_spot = positive(self.collateral_oracle_price().spot)?;
let nav = positive(self.stablecoin_nav()?)?;
let usdc_usd_lower = positive(usdc_usd_price.lower)?;
let d_total_collateral = -(usdc_usd_lower.get() / sol_spot.get());
let d_stablecoin_supply = -(usdc_usd_lower.get() / nav.get());
let d_cr = quotient_rule(
positive(projected.total_collateral)?,
d_total_collateral,
positive(projected.stablecoin_supply)?,
d_stablecoin_supply,
sol_spot,
);
let d_curve_price = chain_rule(curve_slope, d_cr);
let left = usdc_usd_lower.get() / (curve_price.get() * lst_sol.get());
let right = usdc_usd_lower.get() * usdc_amount.to_f64() * d_curve_price
/ (curve_price.get() * curve_price.get() * lst_sol.get());
positive_rate(left - right)
}
pub fn levercoin_mint_marginal(
&self,
lst_sol_price: &LstSolPrice,
amount_lst: UFix64<N9>,
) -> Result<f64, CoreError> {
let new_sol =
lst_sol_price.convert_lst_to_sol(amount_lst, self.clock.epoch())?;
let new_total_sol = self
.total_sol
.checked_add(&new_sol)
.ok_or(CoreError::DestinationCollateral)?;
let projected = self.projected_rebalance_mode(
new_total_sol,
self.virtual_stablecoin_supply()?,
)?;
let mode = self.select_rebalance_mode_for_fees(projected);
let fee = self.levercoin_fees().mint_fee(mode)?.to_f64();
let lst_sol = positive(lst_sol_price.get_epoch_price(self.clock.epoch())?)?;
let sol_usd_lower = positive(self.sol_usd_price.lower)?;
let nav = positive(self.levercoin_mint_nav()?)?;
positive_rate((1.0 - fee) * lst_sol.get() * sol_usd_lower.get() / nav.get())
}
pub fn levercoin_redeem_marginal(
&self,
lst_sol_price: &LstSolPrice,
amount_levercoin: UFix64<N6>,
) -> Result<f64, CoreError> {
let nav = positive(self.levercoin_redeem_nav()?)?;
let lst_out = self
.token_conversion(lst_sol_price)?
.token_to_lst(amount_levercoin, self.levercoin_redeem_nav()?)?;
let sol_rm =
lst_sol_price.convert_lst_to_sol(lst_out, self.clock.epoch())?;
let new_total_sol = self
.total_sol
.checked_sub(&sol_rm)
.ok_or(CoreError::DestinationCollateral)?;
let projected = self.projected_rebalance_mode(
new_total_sol,
self.virtual_stablecoin_supply()?,
)?;
let mode = self.select_rebalance_mode_for_fees(projected);
let fee = self.levercoin_fees().redeem_fee(mode)?.to_f64();
let lst_sol = positive(lst_sol_price.get_epoch_price(self.clock.epoch())?)?;
let sol_usd_upper = positive(self.sol_usd_price.upper)?;
positive_rate(
nav.get() / (sol_usd_upper.get() * lst_sol.get()) * (1.0 - fee),
)
}
}
pub trait SwapMarginals: ExchangeContext {
fn stablecoin_to_levercoin_marginal(
&self,
amount_stablecoin: UFix64<N6>,
) -> Result<f64, CoreError> {
let new_stablecoin = self
.virtual_stablecoin_supply()?
.checked_sub(&amount_stablecoin)
.ok_or(CoreError::DestinationStablecoin)?;
let projected =
self.projected_rebalance_mode(self.total_collateral(), new_stablecoin)?;
let mode = self.select_rebalance_mode_for_fees(projected);
let fee = self
.levercoin_fees()
.convert_from_stablecoin_fee(mode)?
.to_f64();
let stablecoin_nav = positive(self.stablecoin_nav()?)?;
let levercoin_nav_upper = positive(self.levercoin_mint_nav()?)?;
positive_rate(
(1.0 - fee) * stablecoin_nav.get() / levercoin_nav_upper.get(),
)
}
fn levercoin_to_stablecoin_marginal(
&self,
amount_levercoin: UFix64<N6>,
) -> Result<f64, CoreError> {
let converted =
self.swap_conversion()?.lever_to_stable(amount_levercoin)?;
let new_stablecoin = self
.virtual_stablecoin_supply()?
.checked_add(&converted)
.ok_or(CoreError::DestinationStablecoin)?;
let projected =
self.projected_rebalance_mode(self.total_collateral(), new_stablecoin)?;
let mode = self.select_rebalance_mode_for_fees(projected);
let fee = self
.levercoin_fees()
.convert_to_stablecoin_fee(mode)?
.to_f64();
let levercoin_nav_lower = positive(self.levercoin_redeem_nav()?)?;
let stablecoin_nav = positive(self.stablecoin_nav()?)?;
positive_rate(
levercoin_nav_lower.get() / stablecoin_nav.get() * (1.0 - fee),
)
}
}
impl<T: ExchangeContext> SwapMarginals for T {}