use crate::trade::error::TradeError;
use crate::trade::margin::{MarginMode, UpdateLeverage};
use crate::utils::{Asset, Dec};
use serde::Deserialize;
pub const FLAT_TABLES_BELOW: u32 = 50;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarginTable {
pub description: String,
pub margin_tiers: Vec<MarginTier>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarginTier {
pub lower_bound: Dec,
pub max_leverage: u32,
}
impl MarginTable {
#[must_use]
pub fn flat(max_leverage: u32) -> MarginTable {
MarginTable {
description: String::new(),
margin_tiers: vec![MarginTier { lower_bound: Dec::ZERO, max_leverage }],
}
}
#[must_use]
pub fn by_id(id: u32, listed: &[(u32, MarginTable)]) -> Option<MarginTable> {
match id {
0 => None,
id if id < FLAT_TABLES_BELOW => Some(MarginTable::flat(id)),
id => listed.iter().find(|(at, _)| *at == id).map(|(_, table)| table.clone()),
}
}
#[must_use]
pub fn max_leverage_at(&self, notional: Dec) -> Option<u32> {
let notional = notional.abs();
self.margin_tiers
.iter()
.filter(|tier| tier.lower_bound <= notional)
.max_by_key(|tier| tier.lower_bound)
.map(|tier| tier.max_leverage)
}
pub fn check(&self, name: &str, leverage: u32, notional: Dec) -> Result<(), LeverageError> {
match self.max_leverage_at(notional) {
Some(max) if leverage > max => {
Err(LeverageError::OverTier { name: name.to_owned(), notional: notional.abs(), max, asked: leverage })
}
_ => Ok(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum LeverageError {
#[error("{name} allows at most {max}x, not {asked}x")]
TooHigh { name: String, max: u32, asked: u32 },
#[error("{name} allows at most {max}x at a notional of {notional}, not {asked}x")]
OverTier { name: String, notional: Dec, max: u32, asked: u32 },
#[error("{0} is isolated only")]
NoCross(String),
#[error(transparent)]
Trade(#[from] TradeError),
}
pub fn checked_leverage(
asset: Asset,
name: &str,
max: u32,
cross: bool,
mode: MarginMode,
leverage: u32,
) -> Result<UpdateLeverage, LeverageError> {
if leverage > max {
return Err(LeverageError::TooHigh { name: name.to_owned(), max, asked: leverage });
}
if mode == MarginMode::Cross && !cross {
return Err(LeverageError::NoCross(name.to_owned()));
}
Ok(UpdateLeverage::new(asset, mode, leverage)?)
}
#[cfg(test)]
#[path = "tests/tables.rs"]
mod tests;