Skip to main content

df_hyperliquid_core/trade/margin/
isolated.rs

1//! `updateIsolatedMargin`: USDC into or out of an isolated position.
2
3use crate::trade::error::TradeError;
4use crate::trade::margin::MarginMode;
5use crate::trade::orders::Side;
6use crate::trade::ticket::{Market, Refusal};
7use crate::utils::sign::{L1Action, Map};
8use crate::utils::{Asset, BadUnits, Dec, Round};
9
10/// `updateIsolatedMargin`: `{type, asset, isBuy, ntli}`. `ntli` is USDC in
11/// millionths, negative to take margin out; `isBuy` names the position's
12/// side, long or short.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct UpdateIsolatedMargin {
15    asset: Asset,
16    position: Side,
17    ntli: i64,
18}
19
20impl UpdateIsolatedMargin {
21    /// `usdc` positive to add margin and negative to remove it, at most six
22    /// places: refused rather than rounded when finer.
23    pub fn new(asset: Asset, position: Side, usdc: Dec) -> Result<UpdateIsolatedMargin, TradeError> {
24        let bad = |error: BadUnits| TradeError::Margin(error);
25        if usdc.places() > 6 {
26            return Err(bad(BadUnits::TooFine { value: usdc, places: 6 }));
27        }
28        let ntli = usdc.units_at(6, Round::Down).and_then(|units| i64::try_from(units).ok());
29        match ntli {
30            Some(0) => Err(bad(BadUnits::NotPositive(usdc))),
31            Some(ntli) => Ok(UpdateIsolatedMargin { asset, position, ntli }),
32            None => Err(bad(BadUnits::TooLarge(usdc))),
33        }
34    }
35
36    #[must_use]
37    pub const fn ntli(&self) -> i64 {
38        self.ntli
39    }
40}
41
42impl L1Action for UpdateIsolatedMargin {
43    fn action(&self) -> Map {
44        Map::new()
45            .put("type", "updateIsolatedMargin")
46            .put("asset", self.asset)
47            .put("isBuy", self.position.is_buy())
48            .put("ntli", self.ntli)
49    }
50}
51
52/// More USDC into an isolated position, long (`Side::Buy`) or short.
53pub fn add_margin(asset: Asset, position: Side, usdc: Dec) -> Result<UpdateIsolatedMargin, TradeError> {
54    match usdc.is_positive() {
55        true => UpdateIsolatedMargin::new(asset, position, usdc),
56        false => Err(TradeError::Margin(BadUnits::NotPositive(usdc))),
57    }
58}
59
60/// USDC out of an isolated position: sent as a negative `ntli`.
61pub fn remove_margin(asset: Asset, position: Side, usdc: Dec) -> Result<UpdateIsolatedMargin, TradeError> {
62    match usdc.is_positive() {
63        true => UpdateIsolatedMargin::new(asset, position, usdc.negate()),
64        false => Err(TradeError::Margin(BadUnits::NotPositive(usdc))),
65    }
66}
67
68/// `updateIsolatedMargin` on an isolated `position` (signed `szi`): `usdc`
69/// above zero adds margin, below zero takes it out.
70pub fn margin(market: &Market, position: Dec, mode: MarginMode, usdc: Dec) -> Result<UpdateIsolatedMargin, Refusal> {
71    if market.is_spot() {
72        return Err(Refusal::Spot("margin"));
73    }
74    if position.is_zero() {
75        return Err(Refusal::NoPosition);
76    }
77    if mode == MarginMode::Cross {
78        return Err(Refusal::CrossPosition);
79    }
80    let side = match position.is_positive() {
81        true => Side::Buy,
82        false => Side::Sell,
83    };
84    Ok(UpdateIsolatedMargin::new(market.asset(), side, usdc)?)
85}
86
87#[cfg(test)]
88#[path = "tests/isolated.rs"]
89mod tests;