df_hyperliquid_core/trade/orders/
scale.rs1use crate::trade::builder::BuilderSetting;
5use crate::trade::error::TradeError;
6use crate::trade::orders::limit::limit_on;
7use crate::trade::orders::{Order, PlaceOrder, Side, Tif};
8use crate::trade::ticket::{Market, Refusal, at_least, positive, routed};
9use crate::utils::rounding::MINIMUM_ORDER_VALUE;
10use crate::utils::{Dec, Round};
11
12pub const LEAST_LEVELS: u32 = 2;
14
15pub const MOST_LEVELS: u32 = 50;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct Scale {
22 pub side: Side,
23 pub size: Dec,
25 pub from: Dec,
26 pub to: Dec,
27 pub levels: u32,
28 pub tif: Tif,
29 pub reduce_only: bool,
30}
31
32impl Scale {
33 #[must_use]
34 pub const fn new(side: Side, size: Dec, from: Dec, to: Dec, levels: u32, tif: Tif) -> Scale {
35 Scale { side, size, from, to, levels, tif, reduce_only: false }
36 }
37}
38
39pub fn scale(market: &Market, intent: &Scale, builder: Option<&BuilderSetting>) -> Result<PlaceOrder, Refusal> {
41 if market.is_spot() && intent.reduce_only {
42 return Err(Refusal::Spot("reduce only"));
43 }
44 if !matches!(intent.tif, Tif::Gtc | Tif::Alo) {
45 return Err(Refusal::ScaleTif(intent.tif));
46 }
47 let levels = intent.levels;
48 if !(LEAST_LEVELS..=MOST_LEVELS).contains(&levels) {
49 return Err(Refusal::Levels(levels));
50 }
51 let grid = market.grid();
52 let total = positive(intent.size, "a size")?;
53 let from = positive(intent.from, "a from price")?;
54 let to = positive(intent.to, "a to price")?;
55 let count = Dec::from_int(i64::from(levels));
56 let raw = total.checked_div(count, 12, Round::Down).ok_or(Refusal::Trade(TradeError::NoSize(total)))?;
57 let each = grid.size(raw).ok_or(Refusal::Trade(TradeError::NoSize(raw)))?;
58 let span = to.checked_sub(from).ok_or(Refusal::Trade(TradeError::NoPrice(to)))?;
59 let step = span
60 .checked_div(Dec::from_int(i64::from(levels - 1)), 12, Round::Down)
61 .ok_or(Refusal::Trade(TradeError::NoPrice(to)))?;
62 let mut orders = Vec::new();
63 for at in 0..levels {
64 let raw = match at + 1 == levels {
65 true => to,
66 false => step
67 .checked_mul(Dec::from_int(i64::from(at)))
68 .and_then(|moved| from.checked_add(moved))
69 .ok_or(Refusal::Trade(TradeError::NoPrice(from)))?,
70 };
71 let price = limit_on(grid, raw, intent.side, "a level's price")?;
72 at_least(each, price, MINIMUM_ORDER_VALUE)?;
73 let order = Order::limit(market.asset(), intent.side, price, each, intent.tif);
74 orders.push(match intent.reduce_only {
75 true => order.reduce_only(),
76 false => order,
77 });
78 }
79 routed(PlaceOrder::new(orders), builder)
80}
81
82#[cfg(test)]
83#[path = "tests/scale.rs"]
84mod tests;