Skip to main content

df_hyperliquid_core/trade/orders/
mod.rs

1//! Orders: the wire every order type ends in ([`Order`], [`PlaceOrder`]), what an order's `t`
2//! says, its client id, and one module per type. Keys go in the order the venue hashes:
3//! `a b p s r t c` for an order, then `grouping`, then `builder` last.
4
5pub mod cloid;
6pub mod close;
7pub mod entry;
8pub mod limit;
9pub mod market;
10pub mod order_type;
11pub mod scale;
12pub mod tpsl;
13pub mod trigger;
14pub mod twap;
15
16pub use cloid::Cloid;
17pub use close::{Close, close};
18pub use entry::{OrderIntent, Price, order};
19pub use order_type::{Grouping, OrderType, Tif, Tpsl};
20pub use scale::{LEAST_LEVELS, MOST_LEVELS, Scale, scale};
21pub use tpsl::{Covers, TriggerIntent, trigger};
22pub use trigger::{Exit, ExitPrice, TRIGGER_SLIPPAGE};
23pub use twap::{MINIMUM_TWAP_VALUE, Twap, TwapCancel, TwapIntent, twap};
24
25use crate::trade::builder::{Builder, OverCap};
26use crate::utils::sign::{L1Action, Map, Value};
27use crate::utils::{Asset, AssetKind, Dec};
28
29/// Which way an order trades, the same `B` and `A` the reads and the socket carry.
30pub use crate::markets::Side;
31
32/// One order on any market. Price and size go on the wire exactly as given,
33/// so put them on the asset's grid first ([`crate::utils::Grid`]).
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct Order {
36    asset: Asset,
37    side: Side,
38    price: Dec,
39    size: Dec,
40    reduce_only: bool,
41    order_type: OrderType,
42    cloid: Option<Cloid>,
43}
44
45impl Order {
46    /// A limit order that rests or crosses as `tif` says.
47    #[must_use]
48    pub const fn limit(asset: Asset, side: Side, price: Dec, size: Dec, tif: Tif) -> Order {
49        Order { asset, side, price, size, reduce_only: false, order_type: OrderType::Limit(tif), cloid: None }
50    }
51
52    /// A market order: `FrontendMarket`, always, at `bound`, the worst price
53    /// the trader takes (the reference moved by the slippage, on the grid; on
54    /// an outcome, the edge of the range). Never `Gtc`: a `Gtc` at the bound
55    /// would rest on the book for whatever did not fill.
56    #[must_use]
57    pub const fn market(asset: Asset, side: Side, bound: Dec, size: Dec) -> Order {
58        Order::limit(asset, side, bound, size, Tif::FrontendMarket)
59    }
60
61    /// A take profit or stop loss: fires when the mark crosses `trigger`, as
62    /// a market order bounded by `price` or as a limit at it. A size of zero
63    /// on a reduce-only trigger means the whole position.
64    #[must_use]
65    pub const fn trigger(
66        asset: Asset,
67        side: Side,
68        price: Dec,
69        size: Dec,
70        trigger: Dec,
71        tpsl: Tpsl,
72        is_market: bool,
73    ) -> Order {
74        Order {
75            asset,
76            side,
77            price,
78            size,
79            reduce_only: false,
80            order_type: OrderType::Trigger { is_market, trigger, tpsl },
81            cloid: None,
82        }
83    }
84
85    /// Only ever shrinks the position.
86    #[must_use]
87    pub const fn reduce_only(mut self) -> Order {
88        self.reduce_only = true;
89        self
90    }
91
92    #[must_use]
93    pub const fn with_cloid(mut self, cloid: Cloid) -> Order {
94        self.cloid = Some(cloid);
95        self
96    }
97
98    #[must_use]
99    pub const fn asset(&self) -> Asset {
100        self.asset
101    }
102
103    #[must_use]
104    pub const fn side(&self) -> Side {
105        self.side
106    }
107
108    #[must_use]
109    pub const fn price(&self) -> Dec {
110        self.price
111    }
112
113    #[must_use]
114    pub const fn size(&self) -> Dec {
115        self.size
116    }
117
118    #[must_use]
119    pub const fn is_reduce_only(&self) -> bool {
120        self.reduce_only
121    }
122
123    #[must_use]
124    pub const fn order_type(&self) -> OrderType {
125        self.order_type
126    }
127
128    #[must_use]
129    pub const fn cloid(&self) -> Option<Cloid> {
130        self.cloid
131    }
132
133    /// A limit order's TIF; `None` for a trigger.
134    #[must_use]
135    pub const fn tif(&self) -> Option<Tif> {
136        match self.order_type {
137            OrderType::Limit(tif) => Some(tif),
138            OrderType::Trigger { .. } => None,
139        }
140    }
141
142    #[must_use]
143    pub const fn is_market(&self) -> bool {
144        matches!(self.order_type, OrderType::Limit(Tif::FrontendMarket))
145    }
146
147    #[must_use]
148    pub const fn is_trigger(&self) -> bool {
149        matches!(self.order_type, OrderType::Trigger { .. })
150    }
151
152    /// `a b p s r t c`, in the order the venue hashes; `c` only when there is one.
153    #[must_use]
154    pub fn wire(&self) -> Map {
155        Map::new()
156            .put("a", self.asset)
157            .put("b", self.side.is_buy())
158            .put("p", self.price.to_wire())
159            .put("s", self.size.to_wire())
160            .put("r", self.reduce_only)
161            .put("t", self.order_type.wire())
162            .put_some("c", self.cloid.map(Cloid::to_hex))
163    }
164}
165
166/// `order`: one or more orders, grouped, optionally through a builder.
167#[derive(Debug, Clone, PartialEq, Eq)]
168pub struct PlaceOrder {
169    orders: Vec<Order>,
170    grouping: Grouping,
171    builder: Option<Builder>,
172}
173
174impl PlaceOrder {
175    /// A scale is one action with an order a level.
176    #[must_use]
177    pub fn new(orders: Vec<Order>) -> PlaceOrder {
178        PlaceOrder { orders, grouping: Grouping::Na, builder: None }
179    }
180
181    #[must_use]
182    pub fn one(order: Order) -> PlaceOrder {
183        PlaceOrder::new(vec![order])
184    }
185
186    #[must_use]
187    pub fn grouped(mut self, grouping: Grouping) -> PlaceOrder {
188        self.grouping = grouping;
189        self
190    }
191
192    #[must_use]
193    pub fn through(mut self, builder: Builder) -> PlaceOrder {
194        self.builder = Some(builder);
195        self
196    }
197
198    #[must_use]
199    pub fn orders(&self) -> &[Order] {
200        &self.orders
201    }
202
203    #[must_use]
204    pub const fn grouping(&self) -> Grouping {
205        self.grouping
206    }
207
208    #[must_use]
209    pub const fn builder(&self) -> Option<Builder> {
210        self.builder
211    }
212
213    /// The builder's fee, when it is more than the venue lets a builder take
214    /// on these orders. One perp order holds the whole action to the perp cap.
215    #[must_use]
216    pub fn over_builder_cap(&self) -> Option<OverCap> {
217        let fee = self.builder?.fee();
218        let kind = AssetKind::strictest(self.orders.iter().map(Order::asset))?;
219        let cap = kind.builder_cap();
220        (fee > cap).then_some(OverCap { fee, cap, kind })
221    }
222}
223
224impl L1Action for PlaceOrder {
225    fn action(&self) -> Map {
226        Map::new()
227            .put("type", "order")
228            .put("orders", Value::array(self.orders.iter().map(Order::wire)))
229            .put("grouping", self.grouping.wire())
230            .put_some("builder", self.builder.as_ref().map(Builder::wire))
231    }
232}
233
234#[cfg(test)]
235#[path = "tests/order/mod.rs"]
236mod tests;