df_hyperliquid_core/trade/
margin.rs1use crate::trade::error::TradeError;
6use crate::trade::leverage::{MarginMode, UpdateLeverage};
7use crate::utils::{Asset, Dec};
8use serde::Deserialize;
9
10pub const FLAT_TABLES_BELOW: u32 = 50;
12
13#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct MarginTable {
16 pub description: String,
18 pub margin_tiers: Vec<MarginTier>,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct MarginTier {
26 pub lower_bound: Dec,
28 pub max_leverage: u32,
29}
30
31impl MarginTable {
32 #[must_use]
34 pub fn flat(max_leverage: u32) -> MarginTable {
35 MarginTable {
36 description: String::new(),
37 margin_tiers: vec![MarginTier { lower_bound: Dec::ZERO, max_leverage }],
38 }
39 }
40
41 #[must_use]
44 pub fn by_id(id: u32, listed: &[(u32, MarginTable)]) -> Option<MarginTable> {
45 match id {
46 0 => None,
47 id if id < FLAT_TABLES_BELOW => Some(MarginTable::flat(id)),
48 id => listed.iter().find(|(at, _)| *at == id).map(|(_, table)| table.clone()),
49 }
50 }
51
52 #[must_use]
57 pub fn max_leverage_at(&self, notional: Dec) -> Option<u32> {
58 let notional = notional.abs();
59 self.margin_tiers
60 .iter()
61 .filter(|tier| tier.lower_bound <= notional)
62 .max_by_key(|tier| tier.lower_bound)
63 .map(|tier| tier.max_leverage)
64 }
65
66 pub fn check(&self, name: &str, leverage: u32, notional: Dec) -> Result<(), LeverageError> {
69 match self.max_leverage_at(notional) {
70 Some(max) if leverage > max => {
71 Err(LeverageError::OverTier { name: name.to_owned(), notional: notional.abs(), max, asked: leverage })
72 }
73 _ => Ok(()),
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
79pub enum LeverageError {
80 #[error("{name} allows at most {max}x, not {asked}x")]
81 TooHigh { name: String, max: u32, asked: u32 },
82 #[error("{name} allows at most {max}x at a notional of {notional}, not {asked}x")]
83 OverTier { name: String, notional: Dec, max: u32, asked: u32 },
84 #[error("{0} is isolated only")]
85 NoCross(String),
86 #[error(transparent)]
87 Trade(#[from] TradeError),
88}
89
90pub fn checked_leverage(
94 asset: Asset,
95 name: &str,
96 max: u32,
97 cross: bool,
98 mode: MarginMode,
99 leverage: u32,
100) -> Result<UpdateLeverage, LeverageError> {
101 if leverage > max {
102 return Err(LeverageError::TooHigh { name: name.to_owned(), max, asked: leverage });
103 }
104 if mode == MarginMode::Cross && !cross {
105 return Err(LeverageError::NoCross(name.to_owned()));
106 }
107 Ok(UpdateLeverage::new(asset, mode, leverage)?)
108}
109
110#[cfg(test)]
111#[path = "tests/margin.rs"]
112mod tests;