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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms) {
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(¶ms) {
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(¶ms) {
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}