Skip to main content

bybit_client/api/
position.rs

1//! Position API endpoints for position management.
2
3use crate::error::BybitError;
4use crate::http::HttpClient;
5use crate::types::position::*;
6
7/// Position service for position management endpoints.
8#[derive(Debug, Clone)]
9pub struct PositionService {
10    http: HttpClient,
11}
12
13impl PositionService {
14    /// Create a new position service.
15    pub fn new(http: HttpClient) -> Self {
16        Self { http }
17    }
18
19    /// Get position information.
20    ///
21    /// # Example
22    ///
23    /// ```no_run
24    /// # use bybit_client::{BybitClient, Category};
25    /// # use bybit_client::types::position::GetPositionInfoParams;
26    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
27    /// let client = BybitClient::new("api_key", "api_secret")?;
28    ///
29    /// let params = GetPositionInfoParams::new(Category::Linear)
30    ///     .symbol("BTCUSDT");
31    /// let result = client.position().get_position_info(&params).await?;
32    /// for pos in &result.list {
33    ///     println!("{}: {} @ {} (unrealised PnL: {})",
34    ///         pos.symbol, pos.size, pos.avg_price, pos.unrealised_pnl);
35    /// }
36    /// # Ok(())
37    /// # }
38    /// ```
39    pub async fn get_position_info(
40        &self,
41        params: &GetPositionInfoParams,
42    ) -> Result<PositionListResult, BybitError> {
43        self.http.get_signed("/v5/position/list", Some(params)).await
44    }
45
46    /// Set leverage for a symbol.
47    ///
48    /// # Example
49    ///
50    /// ```no_run
51    /// # use bybit_client::{BybitClient, Category};
52    /// # use bybit_client::types::position::SetLeverageParams;
53    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
54    /// let client = BybitClient::new("api_key", "api_secret")?;
55    ///
56    /// // Set 10x leverage (same for buy and sell)
57    /// let params = SetLeverageParams::uniform(Category::Linear, "BTCUSDT", "10");
58    /// client.position().set_leverage(&params).await?;
59    /// # Ok(())
60    /// # }
61    /// ```
62    pub async fn set_leverage(&self, params: &SetLeverageParams) -> Result<(), BybitError> {
63        let _: serde_json::Value = self
64            .http
65            .post_signed("/v5/position/set-leverage", Some(params))
66            .await?;
67        Ok(())
68    }
69
70    /// Switch between cross margin and isolated margin.
71    ///
72    /// # Example
73    ///
74    /// ```no_run
75    /// # use bybit_client::{BybitClient, Category};
76    /// # use bybit_client::types::position::SwitchMarginModeParams;
77    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
78    /// let client = BybitClient::new("api_key", "api_secret")?;
79    ///
80    /// // Switch to isolated margin with 10x leverage
81    /// let params = SwitchMarginModeParams::isolated_margin(
82    ///     Category::Linear, "BTCUSDT", "10", "10"
83    /// );
84    /// client.position().switch_margin_mode(&params).await?;
85    /// # Ok(())
86    /// # }
87    /// ```
88    pub async fn switch_margin_mode(
89        &self,
90        params: &SwitchMarginModeParams,
91    ) -> Result<(), BybitError> {
92        let _: serde_json::Value = self
93            .http
94            .post_signed("/v5/position/switch-isolated", Some(params))
95            .await?;
96        Ok(())
97    }
98
99    /// Switch position mode between one-way and hedge mode.
100    ///
101    /// # Example
102    ///
103    /// ```no_run
104    /// # use bybit_client::{BybitClient, Category};
105    /// # use bybit_client::types::position::SwitchPositionModeParams;
106    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
107    /// let client = BybitClient::new("api_key", "api_secret")?;
108    ///
109    /// // Switch to hedge mode
110    /// let params = SwitchPositionModeParams::hedge_by_symbol(Category::Linear, "BTCUSDT");
111    /// client.position().switch_position_mode(&params).await?;
112    /// # Ok(())
113    /// # }
114    /// ```
115    pub async fn switch_position_mode(
116        &self,
117        params: &SwitchPositionModeParams,
118    ) -> Result<(), BybitError> {
119        let _: serde_json::Value = self
120            .http
121            .post_signed("/v5/position/switch-mode", Some(params))
122            .await?;
123        Ok(())
124    }
125
126    /// Set trading stop (take profit, stop loss, trailing stop).
127    ///
128    /// # Example
129    ///
130    /// ```no_run
131    /// # use bybit_client::{BybitClient, Category, PositionIdx};
132    /// # use bybit_client::types::position::SetTradingStopParams;
133    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
134    /// let client = BybitClient::new("api_key", "api_secret")?;
135    ///
136    /// let params = SetTradingStopParams::new(Category::Linear, "BTCUSDT", PositionIdx::OneWay)
137    ///     .take_profit("55000")
138    ///     .stop_loss("45000");
139    /// client.position().set_trading_stop(&params).await?;
140    /// # Ok(())
141    /// # }
142    /// ```
143    pub async fn set_trading_stop(&self, params: &SetTradingStopParams) -> Result<(), BybitError> {
144        let _: serde_json::Value = self
145            .http
146            .post_signed("/v5/position/trading-stop", Some(params))
147            .await?;
148        Ok(())
149    }
150
151    /// Set auto add margin.
152    ///
153    /// When enabled, the system will automatically add margin to prevent liquidation.
154    ///
155    /// # Example
156    ///
157    /// ```no_run
158    /// # use bybit_client::{BybitClient, Category};
159    /// # use bybit_client::types::position::SetAutoAddMarginParams;
160    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
161    /// let client = BybitClient::new("api_key", "api_secret")?;
162    ///
163    /// let params = SetAutoAddMarginParams::enable(Category::Linear, "BTCUSDT");
164    /// client.position().set_auto_add_margin(&params).await?;
165    /// # Ok(())
166    /// # }
167    /// ```
168    pub async fn set_auto_add_margin(
169        &self,
170        params: &SetAutoAddMarginParams,
171    ) -> Result<(), BybitError> {
172        let _: serde_json::Value = self
173            .http
174            .post_signed("/v5/position/set-auto-add-margin", Some(params))
175            .await?;
176        Ok(())
177    }
178
179    /// Add or reduce margin for a position.
180    ///
181    /// Use positive value to add margin, negative to reduce.
182    ///
183    /// # Example
184    ///
185    /// ```no_run
186    /// # use bybit_client::{BybitClient, Category};
187    /// # use bybit_client::types::position::AddReduceMarginParams;
188    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
189    /// let client = BybitClient::new("api_key", "api_secret")?;
190    ///
191    /// // Add 100 USDT margin
192    /// let params = AddReduceMarginParams::new(Category::Linear, "BTCUSDT", "100");
193    /// let result = client.position().add_or_reduce_margin(&params).await?;
194    /// # Ok(())
195    /// # }
196    /// ```
197    pub async fn add_or_reduce_margin(
198        &self,
199        params: &AddReduceMarginParams,
200    ) -> Result<MarginOperationResult, BybitError> {
201        self.http
202            .post_signed("/v5/position/add-margin", Some(params))
203            .await
204    }
205
206    /// Get closed profit and loss records.
207    ///
208    /// # Example
209    ///
210    /// ```no_run
211    /// # use bybit_client::{BybitClient, Category};
212    /// # use bybit_client::types::position::GetClosedPnlParams;
213    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
214    /// let client = BybitClient::new("api_key", "api_secret")?;
215    ///
216    /// let params = GetClosedPnlParams::new(Category::Linear)
217    ///     .symbol("BTCUSDT")
218    ///     .limit(20);
219    /// let result = client.position().get_closed_pnl(&params).await?;
220    /// for pnl in &result.list {
221    ///     println!("{}: {} (closed PnL: {})", pnl.symbol, pnl.side, pnl.closed_pnl);
222    /// }
223    /// # Ok(())
224    /// # }
225    /// ```
226    pub async fn get_closed_pnl(
227        &self,
228        params: &GetClosedPnlParams,
229    ) -> Result<ClosedPnlListResult, BybitError> {
230        self.http
231            .get_signed("/v5/position/closed-pnl", Some(params))
232            .await
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use crate::types::{Category, PositionIdx};
240
241    #[test]
242    fn test_set_leverage_params_serialization() {
243        let params = SetLeverageParams::uniform(Category::Linear, "BTCUSDT", "10");
244        let json = match serde_json::to_string(&params) {
245            Ok(json) => json,
246            Err(err) => panic!("Failed to serialize leverage params: {}", err),
247        };
248        assert!(json.contains("\"category\":\"linear\""));
249        assert!(json.contains("\"symbol\":\"BTCUSDT\""));
250        assert!(json.contains("\"buyLeverage\":\"10\""));
251        assert!(json.contains("\"sellLeverage\":\"10\""));
252    }
253
254    #[test]
255    fn test_trading_stop_params_serialization() {
256        let params = SetTradingStopParams::new(Category::Linear, "BTCUSDT", PositionIdx::OneWay)
257            .take_profit("55000")
258            .stop_loss("45000");
259
260        let json = match serde_json::to_string(&params) {
261            Ok(json) => json,
262            Err(err) => panic!("Failed to serialize trading stop params: {}", err),
263        };
264        assert!(json.contains("\"takeProfit\":\"55000\""));
265        assert!(json.contains("\"stopLoss\":\"45000\""));
266    }
267
268    #[test]
269    fn test_get_closed_pnl_params_serialization() {
270        let params = GetClosedPnlParams::new(Category::Linear)
271            .symbol("BTCUSDT")
272            .limit(20);
273
274        let query = match serde_urlencoded::to_string(&params) {
275            Ok(query) => query,
276            Err(err) => panic!("Failed to serialize closed PnL params: {}", err),
277        };
278        assert!(query.contains("category=linear"));
279        assert!(query.contains("symbol=BTCUSDT"));
280        assert!(query.contains("limit=20"));
281    }
282}