Skip to main content

ccxt_exchanges/okx/rest/futures/
leverage.rs

1//! Leverage operations for OKX futures/swap.
2
3use super::super::super::{Okx, signed_request::HttpMethod};
4use ccxt_core::{Error, ParseError, Result};
5
6impl Okx {
7    /// Set leverage for a symbol.
8    ///
9    /// Uses OKX POST `/api/v5/account/set-leverage` endpoint.
10    ///
11    /// # Arguments
12    ///
13    /// * `symbol` - Unified symbol (e.g., "BTC/USDT:USDT")
14    /// * `leverage` - Leverage multiplier
15    /// * `margin_mode` - "cross" or "isolated"
16    pub async fn set_leverage_impl(
17        &self,
18        symbol: &str,
19        leverage: u32,
20        margin_mode: Option<&str>,
21    ) -> Result<()> {
22        let inst_id = Self::symbol_to_inst_id(symbol);
23        let mgn_mode = margin_mode.unwrap_or("cross");
24
25        let mut builder = self
26            .signed_request("/api/v5/account/set-leverage")
27            .method(HttpMethod::Post)
28            .param("instId", &inst_id)
29            .param("lever", leverage.to_string())
30            .param("mgnMode", mgn_mode);
31
32        // For isolated mode, we need to specify posSide
33        if mgn_mode == "isolated" {
34            // Set for both long and short sides
35            builder = builder.param("posSide", "net");
36        }
37
38        let data = builder.execute().await?;
39
40        // Check for nested error in data array
41        if let Some(arr) = data["data"].as_array() {
42            if let Some(first) = arr.first() {
43                if let Some(code) = first["sCode"].as_str() {
44                    if code != "0" {
45                        let msg = first["sMsg"].as_str().unwrap_or("Unknown error");
46                        return Err(Error::exchange(code, msg));
47                    }
48                }
49            }
50        }
51
52        Ok(())
53    }
54
55    /// Get current leverage for a symbol.
56    ///
57    /// Uses OKX GET `/api/v5/account/leverage-info` endpoint.
58    pub async fn get_leverage_impl(&self, symbol: &str) -> Result<u32> {
59        let inst_id = Self::symbol_to_inst_id(symbol);
60
61        let data = self
62            .signed_request("/api/v5/account/leverage-info")
63            .param("instId", &inst_id)
64            .param("mgnMode", "cross")
65            .execute()
66            .await?;
67
68        let leverage_array = data["data"].as_array().ok_or_else(|| {
69            Error::from(ParseError::invalid_format("data", "Expected data array"))
70        })?;
71
72        if leverage_array.is_empty() {
73            return Err(Error::from(ParseError::missing_field_owned(format!(
74                "No leverage info found for symbol: {}",
75                symbol
76            ))));
77        }
78
79        let lever_str = leverage_array[0]["lever"]
80            .as_str()
81            .ok_or_else(|| Error::from(ParseError::missing_field("lever")))?;
82
83        lever_str.parse::<f64>().map(|v| v as u32).map_err(|_| {
84            Error::from(ParseError::invalid_value(
85                "lever",
86                format!("Cannot parse leverage: {}", lever_str),
87            ))
88        })
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    #[test]
95    fn test_leverage_parse() {
96        // Test that leverage string parsing works
97        let lever_str = "10";
98        let leverage: u32 = lever_str.parse::<f64>().unwrap() as u32;
99        assert_eq!(leverage, 10);
100
101        let lever_str = "5.00";
102        let leverage: u32 = lever_str.parse::<f64>().unwrap() as u32;
103        assert_eq!(leverage, 5);
104    }
105}