Skip to main content

df_hyperliquid_core/trade/orders/
close.rs

1//! Closing a position at market: reduce only, the other way, `FrontendMarket`.
2
3use crate::trade::builder::BuilderSetting;
4use crate::trade::error::TradeError;
5use crate::trade::orders::{Cloid, Order, PlaceOrder, Side};
6use crate::trade::ticket::{Market, Refusal, at_least, positive, routed};
7use crate::utils::Dec;
8use crate::utils::rounding::MINIMUM_ORDER_VALUE;
9
10/// A position closed at market: all of it, or `size` of it.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct Close {
13    /// The position, signed as `clearinghouseState` writes `szi`: below
14    /// zero is short.
15    pub position: Dec,
16    pub size: Option<Dec>,
17    pub reference: Dec,
18    pub slippage: Dec,
19    pub cloid: Option<Cloid>,
20}
21
22impl Close {
23    /// The whole `position`, bounded `slippage` past `reference`.
24    #[must_use]
25    pub const fn new(position: Dec, reference: Dec, slippage: Dec) -> Close {
26        Close { position, size: None, reference, slippage, cloid: None }
27    }
28
29    #[must_use]
30    pub const fn with_cloid(mut self, cloid: Cloid) -> Close {
31        self.cloid = Some(cloid);
32        self
33    }
34}
35
36/// A position closed at market: reduce only, the other way, `FrontendMarket`.
37/// A whole position closes whatever it is worth; part of one is held to the
38/// minimum as any order is.
39pub fn close(market: &Market, intent: &Close, builder: Option<&BuilderSetting>) -> Result<PlaceOrder, Refusal> {
40    if market.is_spot() {
41        return Err(Refusal::Spot("a position"));
42    }
43    if intent.position.is_zero() {
44        return Err(Refusal::NoPosition);
45    }
46    let side = match intent.position.is_positive() {
47        true => Side::Sell,
48        false => Side::Buy,
49    };
50    let held = intent.position.abs();
51    let wanted = match intent.size {
52        None => held,
53        Some(size) if positive(size, "a size")? > held => {
54            return Err(Refusal::CloseTooLarge { size, position: held });
55        }
56        Some(size) => size,
57    };
58    let size = market.grid().size(wanted).ok_or(Refusal::Trade(TradeError::NoSize(wanted)))?;
59    let reference = positive(intent.reference, "a reference price")?;
60    let bound = market.grid().market_bound(reference, intent.slippage, side)?;
61    if size != held {
62        at_least(size, reference, MINIMUM_ORDER_VALUE)?;
63    }
64    let order = Order::market(market.asset(), side, bound, size).reduce_only();
65    let order = match intent.cloid {
66        Some(cloid) => order.with_cloid(cloid),
67        None => order,
68    };
69    routed(PlaceOrder::one(order), builder)
70}
71
72#[cfg(test)]
73#[path = "tests/close.rs"]
74mod tests;