df_hyperliquid_core/trade/orders/
entry.rs1use crate::trade::builder::BuilderSetting;
5use crate::trade::orders::trigger::Exit;
6use crate::trade::orders::{Cloid, Grouping, Order, PlaceOrder, Side, Tif, limit, market, tpsl};
7use crate::trade::ticket::{Amount, Market, Refusal, at_least, routed};
8use crate::utils::Dec;
9use crate::utils::rounding::MINIMUM_ORDER_VALUE;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Price {
14 Market { reference: Dec, slippage: Dec },
17 Limit { price: Dec, tif: Tif },
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct OrderIntent {
25 pub side: Side,
26 pub amount: Amount,
27 pub price: Price,
28 pub reduce_only: bool,
29 pub cloid: Option<Cloid>,
30 pub take_profit: Option<Exit>,
31 pub stop_loss: Option<Exit>,
32}
33
34impl OrderIntent {
35 pub(super) const fn at(side: Side, amount: Amount, price: Price) -> OrderIntent {
36 OrderIntent { side, amount, price, reduce_only: false, cloid: None, take_profit: None, stop_loss: None }
37 }
38
39 #[must_use]
40 pub const fn with_cloid(mut self, cloid: Cloid) -> OrderIntent {
41 self.cloid = Some(cloid);
42 self
43 }
44
45 #[must_use]
47 pub const fn take_profit(mut self, exit: Exit) -> OrderIntent {
48 self.take_profit = Some(exit);
49 self
50 }
51
52 #[must_use]
54 pub const fn stop_loss(mut self, exit: Exit) -> OrderIntent {
55 self.stop_loss = Some(exit);
56 self
57 }
58}
59
60pub fn order(market: &Market, intent: &OrderIntent, builder: Option<&BuilderSetting>) -> Result<PlaceOrder, Refusal> {
64 let has_exits = intent.take_profit.is_some() || intent.stop_loss.is_some();
65 if market.is_spot() && intent.reduce_only {
66 return Err(Refusal::Spot("reduce only"));
67 }
68 if market.is_spot() && has_exits {
69 return Err(Refusal::Spot("a take profit or stop loss"));
70 }
71 let grid = market.grid();
72 let side = intent.side;
73 let (price, valued_at, tif) = match intent.price {
74 Price::Market { reference, slippage } => market::bound(grid, reference, slippage, side)?,
75 Price::Limit { price, tif } => limit::resting(grid, price, tif, side)?,
76 };
77 let size = intent.amount.size_on(grid, valued_at)?;
78 at_least(size, valued_at, MINIMUM_ORDER_VALUE)?;
79 let mut entry = match tif {
80 Tif::FrontendMarket => Order::market(market.asset(), side, price, size),
81 tif => Order::limit(market.asset(), side, price, size, tif),
82 };
83 if intent.reduce_only {
84 entry = entry.reduce_only();
85 }
86 if let Some(cloid) = intent.cloid {
87 entry = entry.with_cloid(cloid);
88 }
89 let mut orders = vec![entry];
90 orders.extend(tpsl::attach(market, side, size, valued_at, intent.take_profit, intent.stop_loss)?);
91 let grouping = match has_exits {
92 true => Grouping::NormalTpsl,
93 false => Grouping::Na,
94 };
95 routed(PlaceOrder::new(orders).grouped(grouping), builder)
96}
97
98#[cfg(test)]
99#[path = "tests/entry.rs"]
100mod tests;