use crate::trade::error::{TradeError, units};
use crate::trade::orders::intent::MAX_SLIPPAGE_BPS;
use crate::utils::{Dec, Grid, Round, Side};
const BPS_DECIMALS: u32 = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Fit {
#[default]
Exact,
Favour,
Round(Round),
}
impl Fit {
pub fn size(self, grid: Grid, size: Dec) -> Result<u64, TradeError> {
let placed = match self {
Fit::Exact => size,
Fit::Favour => size.round(grid.size_decimals(), Round::Down),
Fit::Round(round) => size.round(grid.size_decimals(), round),
};
units("BaseAmount", grid.base_amount(placed))
}
pub fn price(self, grid: Grid, price: Dec, side: Side) -> Result<u32, TradeError> {
let placed = match self {
Fit::Exact => price,
Fit::Favour => price.round(grid.price_decimals(), toward_less_cost(side)),
Fit::Round(round) => price.round(grid.price_decimals(), round),
};
units("Price", grid.price_units(placed))
}
pub fn trigger(self, grid: Grid, trigger: Dec) -> Result<u32, TradeError> {
let placed = match self {
Fit::Exact => trigger,
Fit::Favour => trigger.round(grid.price_decimals(), Round::Nearest),
Fit::Round(round) => trigger.round(grid.price_decimals(), round),
};
units("TriggerPrice", grid.price_units(placed))
}
pub fn size_for(self, grid: Grid, quote: Dec, price: Dec) -> Result<u64, TradeError> {
if !price.is_positive() {
return Err(TradeError::NoPrice("a quote-sized order"));
}
let round = match self {
Fit::Round(round) => round,
Fit::Exact | Fit::Favour => Round::Down,
};
let size =
quote.checked_div(price, grid.size_decimals(), round).ok_or(TradeError::NoPrice("a quote-sized order"))?;
units("BaseAmount", grid.base_amount(size))
}
}
const fn toward_less_cost(side: Side) -> Round {
match side {
Side::Bid => Round::Down,
Side::Ask => Round::Up,
}
}
pub fn worst_price(grid: Grid, side: Side, reference: Dec, slippage_bps: u32) -> Result<Dec, TradeError> {
if !reference.is_positive() {
return Err(TradeError::NoPrice("a reference"));
}
if slippage_bps > MAX_SLIPPAGE_BPS {
return Err(TradeError::Slippage(slippage_bps));
}
let fraction = Dec::new(i128::from(slippage_bps), BPS_DECIMALS).ok_or(TradeError::Slippage(slippage_bps))?;
let moved = reference.checked_mul(fraction).and_then(|off| match side {
Side::Bid => reference.checked_add(off),
Side::Ask => reference.checked_sub(off),
});
moved.and_then(|moved| grid.price(moved, toward_less_cost(side))).ok_or(TradeError::NoPrice("a reference"))
}
#[cfg(test)]
#[path = "tests/fit.rs"]
mod tests;