1use crate::rest::client::{RestClient, SecType, ServerResponse};
2use crate::rest::BybitResult as Result;
3use serde_json::json;
4
5#[derive(Clone)]
6pub struct PositionClient {
7 client: RestClient,
8}
9
10impl PositionClient {
11 pub fn new(client: RestClient) -> Self {
12 PositionClient { client }
13 }
14
15 pub async fn get_position_info(
20 &self,
21 category: &str,
22 symbol: Option<&str>,
23 base_coin: Option<&str>,
24 settle_coin: Option<&str>,
25 limit: Option<i32>,
26 cursor: Option<&str>,
27 ) -> Result<ServerResponse<serde_json::Value>> {
28 let endpoint = "v5/position/list";
29 let mut params = json!({
30 "category": category,
31 });
32
33 if let Some(symbol) = symbol {
34 params["symbol"] = json!(symbol);
35 }
36 if let Some(base_coin) = base_coin {
37 params["baseCoin"] = json!(base_coin);
38 }
39 if let Some(settle_coin) = settle_coin {
40 params["settleCoin"] = json!(settle_coin);
41 }
42 if let Some(limit) = limit {
43 params["limit"] = json!(limit);
44 }
45 if let Some(cursor) = cursor {
46 params["cursor"] = json!(cursor);
47 }
48
49 let response = self.client.get(endpoint, params, SecType::Signed).await?;
50 Ok(response)
51 }
52
53 pub async fn set_leverage(
58 &self,
59 category: &str,
60 symbol: &str,
61 buy_leverage: &str,
62 sell_leverage: &str,
63 ) -> Result<ServerResponse<serde_json::Value>> {
64 let endpoint = "v5/position/set-leverage";
65 let body = json!({
66 "category": category,
67 "symbol": symbol,
68 "buyLeverage": buy_leverage,
69 "sellLeverage": sell_leverage,
70 });
71
72 let response = self.client.post(endpoint, body, SecType::Signed).await?;
73 Ok(response)
74 }
75
76 pub async fn switch_margin_mode(
81 &self,
82 category: &str,
83 symbol: &str,
84 trade_mode: i32, buy_leverage: &str,
86 sell_leverage: &str,
87 ) -> Result<ServerResponse<serde_json::Value>> {
88 let endpoint = "v5/position/switch-isolated";
89 let body = json!({
90 "category": category,
91 "symbol": symbol,
92 "tradeMode": trade_mode,
93 "buyLeverage": buy_leverage,
94 "sellLeverage": sell_leverage,
95 });
96
97 let response = self.client.post(endpoint, body, SecType::Signed).await?;
98 Ok(response)
99 }
100
101 pub async fn switch_position_mode(
106 &self,
107 category: &str,
108 mode: i32, symbol: Option<&str>,
110 coin: Option<&str>,
111 ) -> Result<ServerResponse<serde_json::Value>> {
112 let endpoint = "v5/position/switch-mode";
113 let mut body = json!({
114 "category": category,
115 "mode": mode,
116 });
117
118 if let Some(symbol) = symbol {
119 body["symbol"] = json!(symbol);
120 }
121 if let Some(coin) = coin {
122 body["coin"] = json!(coin);
123 }
124
125 let response = self.client.post(endpoint, body, SecType::Signed).await?;
126 Ok(response)
127 }
128
129 pub async fn set_trading_stop(
134 &self,
135 category: &str,
136 symbol: &str,
137 position_idx: i32,
138 take_profit: Option<&str>,
139 stop_loss: Option<&str>,
140 trailing_stop: Option<&str>,
141 tp_trigger_by: Option<&str>,
142 sl_trigger_by: Option<&str>,
143 active_price: Option<&str>,
144 tp_size: Option<&str>,
145 sl_size: Option<&str>,
146 tp_limit_price: Option<&str>,
147 sl_limit_price: Option<&str>,
148 tp_order_type: Option<&str>,
149 sl_order_type: Option<&str>,
150 ) -> Result<ServerResponse<serde_json::Value>> {
151 let endpoint = "v5/position/trading-stop";
152 let mut body = json!({
153 "category": category,
154 "symbol": symbol,
155 "positionIdx": position_idx,
156 });
157
158 if let Some(take_profit) = take_profit {
159 body["takeProfit"] = json!(take_profit);
160 }
161 if let Some(stop_loss) = stop_loss {
162 body["stopLoss"] = json!(stop_loss);
163 }
164 if let Some(trailing_stop) = trailing_stop {
165 body["trailingStop"] = json!(trailing_stop);
166 }
167 if let Some(tp_trigger_by) = tp_trigger_by {
168 body["tpTriggerBy"] = json!(tp_trigger_by);
169 }
170 if let Some(sl_trigger_by) = sl_trigger_by {
171 body["slTriggerBy"] = json!(sl_trigger_by);
172 }
173 if let Some(active_price) = active_price {
174 body["activePrice"] = json!(active_price);
175 }
176 if let Some(tp_size) = tp_size {
177 body["tpSize"] = json!(tp_size);
178 }
179 if let Some(sl_size) = sl_size {
180 body["slSize"] = json!(sl_size);
181 }
182 if let Some(tp_limit_price) = tp_limit_price {
183 body["tpLimitPrice"] = json!(tp_limit_price);
184 }
185 if let Some(sl_limit_price) = sl_limit_price {
186 body["slLimitPrice"] = json!(sl_limit_price);
187 }
188 if let Some(tp_order_type) = tp_order_type {
189 body["tpOrderType"] = json!(tp_order_type);
190 }
191 if let Some(sl_order_type) = sl_order_type {
192 body["slOrderType"] = json!(sl_order_type);
193 }
194
195 let response = self.client.post(endpoint, body, SecType::Signed).await?;
196 Ok(response)
197 }
198
199 pub async fn set_auto_add_margin(
204 &self,
205 category: &str,
206 symbol: &str,
207 auto_add_margin: i32, position_idx: Option<i32>,
209 ) -> Result<ServerResponse<serde_json::Value>> {
210 let endpoint = "v5/position/set-auto-add-margin";
211 let mut body = json!({
212 "category": category,
213 "symbol": symbol,
214 "autoAddMargin": auto_add_margin,
215 });
216
217 if let Some(position_idx) = position_idx {
218 body["positionIdx"] = json!(position_idx);
219 }
220
221 let response = self.client.post(endpoint, body, SecType::Signed).await?;
222 Ok(response)
223 }
224
225 pub async fn get_closed_pnl(
230 &self,
231 category: &str,
232 symbol: Option<&str>,
233 start_time: Option<i64>,
234 end_time: Option<i64>,
235 limit: Option<i32>,
236 cursor: Option<&str>,
237 ) -> Result<ServerResponse<serde_json::Value>> {
238 let endpoint = "v5/position/closed-pnl";
239 let mut params = json!({
240 "category": category,
241 });
242
243 if let Some(symbol) = symbol {
244 params["symbol"] = json!(symbol);
245 }
246 if let Some(start_time) = start_time {
247 params["startTime"] = json!(start_time);
248 }
249 if let Some(end_time) = end_time {
250 params["endTime"] = json!(end_time);
251 }
252 if let Some(limit) = limit {
253 params["limit"] = json!(limit);
254 }
255 if let Some(cursor) = cursor {
256 params["cursor"] = json!(cursor);
257 }
258
259 let response = self.client.get(endpoint, params, SecType::Signed).await?;
260 Ok(response)
261 }
262
263 pub async fn set_tpsl_mode(
268 &self,
269 category: &str,
270 symbol: &str,
271 tp_sl_mode: &str, ) -> Result<ServerResponse<serde_json::Value>> {
273 let endpoint = "v5/position/set-tpsl-mode";
274 let body = json!({
275 "category": category,
276 "symbol": symbol,
277 "tpSlMode": tp_sl_mode,
278 });
279
280 let response = self.client.post(endpoint, body, SecType::Signed).await?;
281 Ok(response)
282 }
283
284 pub async fn set_risk_limit(
289 &self,
290 category: &str,
291 symbol: &str,
292 risk_id: i32,
293 position_idx: Option<i32>,
294 ) -> Result<ServerResponse<serde_json::Value>> {
295 let endpoint = "v5/position/set-risk-limit";
296 let mut body = json!({
297 "category": category,
298 "symbol": symbol,
299 "riskId": risk_id,
300 });
301
302 if let Some(position_idx) = position_idx {
303 body["positionIdx"] = json!(position_idx);
304 }
305
306 let response = self.client.post(endpoint, body, SecType::Signed).await?;
307 Ok(response)
308 }
309
310 pub async fn move_positions(
315 &self,
316 from_uid: &str,
317 to_uid: &str,
318 list: Vec<serde_json::Value>,
319 ) -> Result<ServerResponse<serde_json::Value>> {
320 let endpoint = "v5/position/move-positions";
321 let body = json!({
322 "fromUid": from_uid,
323 "toUid": to_uid,
324 "list": list,
325 });
326
327 let response = self.client.post(endpoint, body, SecType::Signed).await?;
328 Ok(response)
329 }
330
331 pub async fn get_move_position_history(
336 &self,
337 category: Option<&str>,
338 symbol: Option<&str>,
339 start_time: Option<i64>,
340 end_time: Option<i64>,
341 status: Option<&str>,
342 block_trade_id: Option<&str>,
343 limit: Option<i32>,
344 cursor: Option<&str>,
345 ) -> Result<ServerResponse<serde_json::Value>> {
346 let endpoint = "v5/position/move-history";
347 let mut params = json!({});
348
349 if let Some(category) = category {
350 params["category"] = json!(category);
351 }
352 if let Some(symbol) = symbol {
353 params["symbol"] = json!(symbol);
354 }
355 if let Some(start_time) = start_time {
356 params["startTime"] = json!(start_time);
357 }
358 if let Some(end_time) = end_time {
359 params["endTime"] = json!(end_time);
360 }
361 if let Some(status) = status {
362 params["status"] = json!(status);
363 }
364 if let Some(block_trade_id) = block_trade_id {
365 params["blockTradeId"] = json!(block_trade_id);
366 }
367 if let Some(limit) = limit {
368 params["limit"] = json!(limit);
369 }
370 if let Some(cursor) = cursor {
371 params["cursor"] = json!(cursor);
372 }
373
374 let response = self.client.get(endpoint, params, SecType::Signed).await?;
375 Ok(response)
376 }
377
378 pub async fn confirm_new_risk_limit(
383 &self,
384 category: &str,
385 symbol: &str,
386 ) -> Result<ServerResponse<serde_json::Value>> {
387 let endpoint = "v5/position/confirm-pending-mmr";
388 let body = json!({
389 "category": category,
390 "symbol": symbol,
391 });
392
393 let response = self.client.post(endpoint, body, SecType::Signed).await?;
394 Ok(response)
395 }
396
397 pub async fn update_margin(
402 &self,
403 category: &str,
404 symbol: &str,
405 margin: &str, position_idx: Option<i32>,
407 ) -> Result<ServerResponse<serde_json::Value>> {
408 let endpoint = "v5/position/add-margin";
409 let mut body = json!({
410 "category": category,
411 "symbol": symbol,
412 "margin": margin,
413 });
414
415 if let Some(position_idx) = position_idx {
416 body["positionIdx"] = json!(position_idx);
417 }
418
419 let response = self.client.post(endpoint, body, SecType::Signed).await?;
420 Ok(response)
421 }
422
423 pub async fn get_execution(
428 &self,
429 category: &str,
430 symbol: Option<&str>,
431 order_id: Option<&str>,
432 order_link_id: Option<&str>,
433 base_coin: Option<&str>,
434 start_time: Option<i64>,
435 end_time: Option<i64>,
436 exec_type: Option<&str>,
437 limit: Option<i32>,
438 cursor: Option<&str>,
439 ) -> Result<ServerResponse<serde_json::Value>> {
440 let endpoint = "v5/execution/list";
441 let mut params = json!({
442 "category": category,
443 });
444
445 if let Some(symbol) = symbol {
446 params["symbol"] = json!(symbol);
447 }
448 if let Some(order_id) = order_id {
449 params["orderId"] = json!(order_id);
450 }
451 if let Some(order_link_id) = order_link_id {
452 params["orderLinkId"] = json!(order_link_id);
453 }
454 if let Some(base_coin) = base_coin {
455 params["baseCoin"] = json!(base_coin);
456 }
457 if let Some(start_time) = start_time {
458 params["startTime"] = json!(start_time);
459 }
460 if let Some(end_time) = end_time {
461 params["endTime"] = json!(end_time);
462 }
463 if let Some(exec_type) = exec_type {
464 params["execType"] = json!(exec_type);
465 }
466 if let Some(limit) = limit {
467 params["limit"] = json!(limit);
468 }
469 if let Some(cursor) = cursor {
470 params["cursor"] = json!(cursor);
471 }
472
473 let response = self.client.get(endpoint, params, SecType::Signed).await?;
474 Ok(response)
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481 use crate::rest::ApiKeyPair;
482
483 fn create_test_client() -> PositionClient {
484 let api_key_pair = ApiKeyPair::new(
485 "test".to_string(),
486 "test_key".to_string(),
487 "test_secret".to_string(),
488 );
489 let rest_client =
490 RestClient::new(api_key_pair, "https://api-testnet.bybit.com".to_string());
491 PositionClient::new(rest_client)
492 }
493
494 #[test]
495 fn test_position_client_creation() {
496 let _client = create_test_client();
497 }
498
499 #[tokio::test]
500 async fn test_position_info_params() {
501 let category = "linear";
502 let symbol = Some("BTCUSDT");
503 let base_coin: Option<&str> = None;
504 let settle_coin: Option<&str> = None;
505 let limit = Some(50);
506 let cursor: Option<&str> = None;
507
508 assert_eq!(category, "linear");
509 assert_eq!(symbol, Some("BTCUSDT"));
510 assert!(base_coin.is_none());
511 assert!(settle_coin.is_none());
512 assert_eq!(limit, Some(50));
513 assert!(cursor.is_none());
514 }
515
516 #[tokio::test]
517 async fn test_set_leverage_params() {
518 let category = "linear";
519 let symbol = "BTCUSDT";
520 let buy_leverage = "10";
521 let sell_leverage = "10";
522
523 assert_eq!(category, "linear");
524 assert_eq!(symbol, "BTCUSDT");
525 assert_eq!(buy_leverage, "10");
526 assert_eq!(sell_leverage, "10");
527 }
528
529 #[tokio::test]
530 async fn test_switch_margin_mode_params() {
531 let category = "linear";
532 let symbol = "BTCUSDT";
533 let trade_mode = 1; let buy_leverage = "5";
535 let sell_leverage = "5";
536
537 assert_eq!(category, "linear");
538 assert_eq!(symbol, "BTCUSDT");
539 assert_eq!(trade_mode, 1);
540 assert_eq!(buy_leverage, "5");
541 assert_eq!(sell_leverage, "5");
542 }
543
544 #[tokio::test]
545 async fn test_set_trading_stop_params() {
546 let category = "linear";
547 let symbol = "BTCUSDT";
548 let position_idx = 0;
549 let take_profit = Some("50000");
550 let stop_loss = Some("40000");
551 let trailing_stop: Option<&str> = None;
552
553 assert_eq!(category, "linear");
554 assert_eq!(symbol, "BTCUSDT");
555 assert_eq!(position_idx, 0);
556 assert_eq!(take_profit, Some("50000"));
557 assert_eq!(stop_loss, Some("40000"));
558 assert!(trailing_stop.is_none());
559 }
560
561 #[tokio::test]
562 async fn test_closed_pnl_params() {
563 let category = "linear";
564 let symbol = Some("BTCUSDT");
565 let start_time = Some(1234567890i64);
566 let end_time = Some(1234567899i64);
567 let limit = Some(100);
568 let cursor: Option<&str> = None;
569
570 assert_eq!(category, "linear");
571 assert_eq!(symbol, Some("BTCUSDT"));
572 assert_eq!(start_time, Some(1234567890));
573 assert_eq!(end_time, Some(1234567899));
574 assert_eq!(limit, Some(100));
575 assert!(cursor.is_none());
576 }
577}