Skip to main content

lnm_sdk/api_v3/rest/models/
cross_leverage.rs

1use std::{convert::TryFrom, fmt};
2
3use serde::{Deserialize, Serialize, de};
4
5use crate::shared::models::{
6    SATS_PER_BTC, leverage::Leverage, margin::Margin, price::Price, quantity::Quantity,
7};
8
9use super::error::CrossLeverageValidationError;
10
11/// A validated leverage value for futures cross positions.
12///
13/// Leverage represents the multiplier applied to the position margin to determine the position size
14/// (quantity).
15/// This type ensures that leverage can be safely used with futures cross orders and positions.
16///
17/// Leverage values must be:
18/// + Integers
19/// + Greater than or equal to [`CrossLeverage::MIN`] (1x)
20/// + Less than or equal to [`CrossLeverage::MAX`] (100x)
21///
22/// # Examples
23///
24/// ```
25/// use lnm_sdk::api_v3::models::CrossLeverage;
26///
27/// // Create a leverage value from an integer
28/// let leverage = CrossLeverage::try_from(10).unwrap();
29/// assert_eq!(leverage.as_u64(), 10);
30///
31/// // Values that are not integers, or that are outside the valid range will fail
32/// assert!(CrossLeverage::try_from(0.9).is_err());
33/// assert!(CrossLeverage::try_from(10.5).is_err());
34/// assert!(CrossLeverage::try_from(101).is_err());
35/// ```
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
37pub struct CrossLeverage(u64);
38
39impl CrossLeverage {
40    /// The minimum allowed leverage value (1x).
41    pub const MIN: Self = Self(1);
42
43    /// The maximum allowed leverage value (100x).
44    pub const MAX: Self = Self(100);
45
46    /// Creates a `CrossLeverage` by rounding and bounding the given value to the valid range.
47    ///
48    /// This method rounds the input to the nearest integer and bounds it to the range
49    /// ([CrossLeverage::MIN], [CrossLeverage::MAX]).
50    /// It should be used to ensure a valid `CrossLeverage` without error handling.
51    ///
52    /// **Note:** In order to check whether a value is a valid leverage and receive an error for
53    /// invalid values, use [`CrossLeverage::try_from`].
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// use lnm_sdk::api_v3::models::CrossLeverage;
59    ///
60    /// // Values within range are rounded
61    /// let lev = CrossLeverage::bounded(25.7);
62    /// assert_eq!(lev.as_u64(), 26);
63    ///
64    /// // Values below minimum are bounded to MIN
65    /// let lev = CrossLeverage::bounded(-1);
66    /// assert_eq!(lev, CrossLeverage::MIN);
67    ///
68    /// // Values above maximum are bounded to MAX
69    /// let lev = CrossLeverage::bounded(150);
70    /// assert_eq!(lev, CrossLeverage::MAX);
71    /// ```
72    pub fn bounded<T>(value: T) -> Self
73    where
74        T: Into<f64>,
75    {
76        let as_f64: f64 = value.into();
77        let rounded = as_f64.round().max(0.0) as u64;
78        let clamped = rounded.clamp(Self::MIN.0, Self::MAX.0);
79
80        Self(clamped)
81    }
82
83    /// Returns the leverage value as its underlying `u64` representation.
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// use lnm_sdk::api_v3::models::CrossLeverage;
89    ///
90    /// let leverage = CrossLeverage::try_from(25.0).unwrap();
91    /// assert_eq!(leverage.as_u64(), 25);
92    /// ```
93    pub fn as_u64(&self) -> u64 {
94        self.0
95    }
96
97    /// Calculates the rounded leverage from quantity (USD), margin (sats), and price (BTC/USD).
98    ///
99    /// The leverage is calculated using the formula:
100    ///
101    /// leverage = (quantity * SATS_PER_BTC) / (margin * price)
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use lnm_sdk::api_v3::models::{CrossLeverage, Quantity, Margin, Price};
107    ///
108    /// let quantity = Quantity::try_from(1_000).unwrap(); // Quantity in USD
109    /// let margin = Margin::try_from(20_000).unwrap(); // Margin in sats
110    /// let price = Price::try_from(100_000.0).unwrap(); // Price in USD/BTC
111    ///
112    /// let leverage = CrossLeverage::try_calculate_rounded(quantity, margin, price).unwrap();
113    /// ```
114    pub fn try_calculate_rounded(
115        quantity: Quantity,
116        margin: Margin,
117        price: Price,
118    ) -> Result<Self, CrossLeverageValidationError> {
119        let leverage_value = quantity.as_f64() * SATS_PER_BTC / (margin.as_f64() * price.as_f64());
120
121        Self::try_from(leverage_value.round())
122    }
123}
124
125impl From<CrossLeverage> for u64 {
126    fn from(value: CrossLeverage) -> u64 {
127        value.0
128    }
129}
130
131impl From<CrossLeverage> for Leverage {
132    fn from(value: CrossLeverage) -> Leverage {
133        Leverage::try_from(value.0 as f64).expect("Must be a valid `Leverage`")
134    }
135}
136
137impl TryFrom<u64> for CrossLeverage {
138    type Error = CrossLeverageValidationError;
139
140    fn try_from(value: u64) -> Result<Self, Self::Error> {
141        if value < Self::MIN.0 {
142            return Err(CrossLeverageValidationError::TooLow { value });
143        }
144
145        if value > Self::MAX.0 {
146            return Err(CrossLeverageValidationError::TooHigh { value });
147        }
148
149        Ok(CrossLeverage(value))
150    }
151}
152
153impl TryFrom<u8> for CrossLeverage {
154    type Error = CrossLeverageValidationError;
155
156    fn try_from(value: u8) -> Result<Self, Self::Error> {
157        Self::try_from(value as u64)
158    }
159}
160
161impl TryFrom<u16> for CrossLeverage {
162    type Error = CrossLeverageValidationError;
163
164    fn try_from(value: u16) -> Result<Self, Self::Error> {
165        Self::try_from(value as u64)
166    }
167}
168
169impl TryFrom<u32> for CrossLeverage {
170    type Error = CrossLeverageValidationError;
171
172    fn try_from(value: u32) -> Result<Self, Self::Error> {
173        Self::try_from(value as u64)
174    }
175}
176
177impl TryFrom<i8> for CrossLeverage {
178    type Error = CrossLeverageValidationError;
179
180    fn try_from(value: i8) -> Result<Self, Self::Error> {
181        Self::try_from(value.max(0) as u64)
182    }
183}
184
185impl TryFrom<i16> for CrossLeverage {
186    type Error = CrossLeverageValidationError;
187
188    fn try_from(value: i16) -> Result<Self, Self::Error> {
189        Self::try_from(value.max(0) as u64)
190    }
191}
192
193impl TryFrom<i32> for CrossLeverage {
194    type Error = CrossLeverageValidationError;
195
196    fn try_from(value: i32) -> Result<Self, Self::Error> {
197        Self::try_from(value.max(0) as u64)
198    }
199}
200
201impl TryFrom<i64> for CrossLeverage {
202    type Error = CrossLeverageValidationError;
203
204    fn try_from(value: i64) -> Result<Self, Self::Error> {
205        Self::try_from(value.max(0) as u64)
206    }
207}
208
209impl TryFrom<usize> for CrossLeverage {
210    type Error = CrossLeverageValidationError;
211
212    fn try_from(value: usize) -> Result<Self, Self::Error> {
213        Self::try_from(value as u64)
214    }
215}
216
217impl TryFrom<isize> for CrossLeverage {
218    type Error = CrossLeverageValidationError;
219
220    fn try_from(value: isize) -> Result<Self, Self::Error> {
221        Self::try_from(value.max(0) as u64)
222    }
223}
224
225impl TryFrom<f32> for CrossLeverage {
226    type Error = CrossLeverageValidationError;
227
228    fn try_from(value: f32) -> Result<Self, Self::Error> {
229        Self::try_from(value as f64)
230    }
231}
232
233impl TryFrom<f64> for CrossLeverage {
234    type Error = CrossLeverageValidationError;
235
236    fn try_from(value: f64) -> Result<Self, Self::Error> {
237        if value.fract() != 0.0 {
238            return Err(CrossLeverageValidationError::NotAnInteger { value });
239        }
240
241        Self::try_from(value.max(0.) as u64)
242    }
243}
244
245impl fmt::Display for CrossLeverage {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        self.0.fmt(f)
248    }
249}
250
251impl Serialize for CrossLeverage {
252    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
253    where
254        S: serde::Serializer,
255    {
256        serializer.serialize_u64(self.0)
257    }
258}
259
260impl<'de> Deserialize<'de> for CrossLeverage {
261    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
262    where
263        D: serde::Deserializer<'de>,
264    {
265        let leverage_u64 = u64::deserialize(deserializer)?;
266        CrossLeverage::try_from(leverage_u64).map_err(|e| de::Error::custom(e.to_string()))
267    }
268}