df_hyperliquid_core/utils/
rounding.rs1use crate::markets::Side;
7use crate::trade::error::TradeError;
8use crate::utils::{Dec, Round};
9
10pub const SIGNIFICANT_FIGURES: u32 = 5;
12
13pub const PERP_PRICE_PLACES: u32 = 6;
15
16pub const SPOT_PRICE_PLACES: u32 = 8;
18
19pub const MINIMUM_ORDER_VALUE: Dec = Dec::from_int(10);
21
22pub const MAX_SLIPPAGE: Dec = Dec::lit::<1>(5);
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub struct Grid {
29 sz_decimals: u32,
30 ceiling: u32,
31}
32
33impl Grid {
34 #[must_use]
36 pub const fn perp(sz_decimals: u32) -> Grid {
37 Grid { sz_decimals, ceiling: PERP_PRICE_PLACES }
38 }
39
40 #[must_use]
42 pub const fn spot(sz_decimals: u32) -> Grid {
43 Grid { sz_decimals, ceiling: SPOT_PRICE_PLACES }
44 }
45
46 #[must_use]
47 pub const fn sz_decimals(self) -> u32 {
48 self.sz_decimals
49 }
50
51 #[must_use]
54 pub const fn price_places(self) -> u32 {
55 self.ceiling.saturating_sub(self.sz_decimals)
56 }
57
58 #[must_use]
64 pub fn tick(self, price: Dec, round: Round) -> Option<Dec> {
65 if !price.is_positive() {
66 return None;
67 }
68 let places = self.price_places().min(price.sig_places(SIGNIFICANT_FIGURES));
69 let kept = price.round(places, round);
70 kept.is_positive().then_some(kept)
71 }
72
73 #[must_use]
76 pub fn price(self, price: Dec, side: Side) -> Option<Dec> {
77 self.tick(price, side.price_rounding())
78 }
79
80 #[must_use]
82 pub fn is_on_grid(self, price: Dec) -> bool {
83 self.tick(price, Round::Down) == Some(price)
84 }
85
86 #[must_use]
89 pub fn size(self, size: Dec) -> Option<Dec> {
90 if !size.is_positive() {
91 return None;
92 }
93 let kept = size.round(self.sz_decimals, Round::Down);
94 kept.is_positive().then_some(kept)
95 }
96
97 pub fn market_bound(self, reference: Dec, slippage: Dec, side: Side) -> Result<Dec, TradeError> {
101 if slippage.is_negative() || slippage > MAX_SLIPPAGE {
102 return Err(TradeError::Slippage(slippage));
103 }
104 let factor = match side {
105 Side::Buy => Dec::ONE.checked_add(slippage),
106 Side::Sell => Dec::ONE.checked_sub(slippage),
107 };
108 factor
109 .and_then(|factor| reference.checked_mul(factor))
110 .and_then(|moved| self.price(moved, side))
111 .ok_or(TradeError::NoPrice(reference))
112 }
113
114 #[must_use]
117 pub fn least_size(self, price: Dec) -> Option<Dec> {
118 let exact = MINIMUM_ORDER_VALUE.checked_div(price, 12, Round::Up)?;
119 let size = exact.round(self.sz_decimals, Round::Up);
120 size.is_positive().then_some(size)
121 }
122
123 #[must_use]
125 pub fn meets_minimum(price: Dec, size: Dec) -> bool {
126 price.checked_mul(size).is_some_and(|worth| worth >= MINIMUM_ORDER_VALUE)
127 }
128}
129
130#[cfg(test)]
131#[path = "tests/rounding.rs"]
132mod tests;