Skip to main content

bybit/
position.rs

1use crate::prelude::*;
2
3#[derive(Clone)]
4pub struct PositionManager {
5    pub client: Client,
6    pub recv_window: u16,
7}
8
9impl PositionManager {
10    /// Asynchronously retrieves information about a position based on the provided request.
11    ///
12    /// # Arguments
13    ///
14    /// * `req` - The position request containing the category, symbol, base coin, settle coin, and limit.
15    ///
16    /// # Returns
17    ///
18    /// A `Result` containing a vector of `PositionInfo` if the operation is successful, or an error if it fails.
19    ///
20    /// # Example
21    ///
22    /// ```
23    /// use crate::model::{PositionRequest, Category};
24    /// use crate::errors::Result;
25    /// use crate::api::PositionInfo;
26    /// use my_module::PositionManager;
27    ///
28    /// #[tokio::main]
29    /// async fn main() -> Result<()> {
30    ///     let position_manager = PositionManager::new();
31    ///     let request = PositionRequest::new(Category::Linear, Some("symbol"), Some("base_coin"), Some("settle_coin"), Some(10));
32    ///     let position_info = position_manager.get_info(request).await?;
33    ///     Ok(())
34    /// }
35    /// ```
36    pub async fn get_info<'b>(&self, req: PositionRequest<'_>) -> Result<InfoResponse, BybitError> {
37        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
38        parameters.insert("category".into(), req.category.as_str().into());
39        if let Some(v) = req.symbol {
40            parameters.insert("symbol".into(), v.into());
41        }
42        if let Some(v) = req.base_coin {
43            parameters.insert("baseCoin".into(), v.into());
44        }
45        if let Some(v) = req.settle_coin {
46            parameters.insert("settleCoin".into(), v.into());
47        }
48        if let Some(v) = req.limit {
49            parameters.insert("limit".into(), v.to_string());
50        }
51        let request = build_request(&parameters);
52        let response: InfoResponse = self
53            .client
54            .get_signed(
55                API::Position(Position::Information),
56                self.recv_window,
57                Some(request),
58            )
59            .await?;
60        Ok(response)
61    }
62
63    // Sets the leverage for a given symbol.
64    ///
65    /// # Arguments
66    ///
67    /// * `req` - The leverage request containing category, symbol, and leverage.
68    ///
69    /// # Returns
70    ///
71    /// A result containing the leverage response.
72    pub async fn set_leverage<'b>(
73        &self,
74        req: LeverageRequest<'_>,
75    ) -> Result<LeverageResponse, BybitError> {
76        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
77        parameters.insert("category".into(), req.category.as_str().into());
78        parameters.insert("symbol".into(), req.symbol.into());
79        parameters.insert("buyLeverage".into(), req.buy_leverage);
80        parameters.insert("sellLeverage".into(), req.sell_leverage);
81        let request = build_json_request(&parameters);
82        let response: LeverageResponse = self
83            .client
84            .post_signed(
85                API::Position(Position::SetLeverage),
86                self.recv_window,
87                Some(request),
88            )
89            .await?;
90        Ok(response)
91    }
92
93    /// Set the margin mode.
94    ///
95    /// # Arguments
96    ///
97    /// * `req` - The ChangeMarginRequest containing the necessary information.
98    ///
99    /// # Returns
100    ///
101    /// * Result<ChangeMarginResponse> - The result of setting the margin mode.
102    pub async fn set_margin_mode<'b>(
103        &self,
104        req: ChangeMarginRequest<'_>,
105    ) -> Result<ChangeMarginResponse, BybitError> {
106        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
107        parameters.insert("category".into(), req.category.as_str().into());
108        parameters.insert("symbol".into(), req.symbol.into());
109        parameters.insert("tradeMode".into(), req.trade_mode.into());
110        let request = build_json_request(&parameters);
111        let response: ChangeMarginResponse = self
112            .client
113            .post_signed(
114                API::Position(Position::SwitchIsolated),
115                self.recv_window,
116                Some(request),
117            )
118            .await?;
119        Ok(response)
120    }
121
122    /// Set the position mode.
123    ///
124    /// # Arguments
125    ///
126    /// * `req` - The MarginModeRequest containing the necessary information.
127    ///
128    /// # Returns
129    ///
130    /// * Result<MarginModeResponse> - The result of setting the position mode.
131    pub async fn set_position_mode<'b>(
132        &self,
133        req: MarginModeRequest<'_>,
134    ) -> Result<MarginModeResponse, BybitError> {
135        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
136        parameters.insert("category".into(), req.category.as_str().into());
137        if let Some(v) = req.symbol {
138            parameters.insert("symbol".into(), v.into());
139        }
140        if let Some(v) = req.coin {
141            parameters.insert("coin".into(), v.into());
142        }
143        parameters.insert("mode".into(), req.mode.into());
144        let request = build_json_request(&parameters);
145        let response: MarginModeResponse = self
146            .client
147            .post_signed(
148                API::Position(Position::SwitchMode),
149                self.recv_window,
150                Some(request),
151            )
152            .await?;
153        Ok(response)
154    }
155
156    /// Set the risk limit.
157    ///
158    /// # Arguments
159    ///
160    /// * `req` - The SetRiskLimitRequest containing the necessary information.
161    ///
162    /// # Returns
163    ///
164    /// * Result<SetRiskLimitResult> - The result of setting the risk limit.
165    pub async fn set_risk_limit<'b>(
166        &self,
167        req: SetRiskLimit<'_>,
168    ) -> Result<SetRiskLimitResponse, BybitError> {
169        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
170        parameters.insert("category".into(), req.category.as_str().into());
171        parameters.insert("symbol".into(), req.symbol.into());
172        parameters.insert("riskId".into(), req.risk_id.into());
173        if let Some(v) = req.position_idx {
174            parameters.insert("positionIdx".into(), v.into());
175        }
176        let request = build_json_request(&parameters);
177        let response: SetRiskLimitResponse = self
178            .client
179            .post_signed(
180                API::Position(Position::SetRiskLimit),
181                self.recv_window,
182                Some(request),
183            )
184            .await?;
185        Ok(response)
186    }
187
188    /// Set the trading stop.
189    ///
190    /// # Arguments
191    ///
192    /// * `req` - The TradingStopRequest containing the necessary information.
193    ///
194    /// # Returns
195    ///
196    /// * Result<TradingStopResponse> - The result of setting the trading stop.
197    pub async fn set_trading_stop<'b>(
198        &self,
199        req: TradingStopRequest<'_>,
200    ) -> Result<TradingStopResponse, BybitError> {
201        // Validate request parameters
202        if let Err(e) = req.validate() {
203            return Err(BybitError::ValidationError(e));
204        }
205
206        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
207        parameters.insert("category".into(), req.category.as_str().into());
208        parameters.insert("symbol".into(), req.symbol.into());
209        parameters.insert("tpslMode".into(), req.tpsl_mode.into());
210        parameters.insert("positionIdx".into(), req.position_idx.into());
211
212        if let Some(v) = req.take_profit {
213            parameters.insert("takeProfit".into(), v.into());
214        }
215        if let Some(v) = req.stop_loss {
216            parameters.insert("stopLoss".into(), v.into());
217        }
218        if let Some(v) = req.trailing_stop {
219            parameters.insert("trailingStop".into(), v.into());
220        }
221        if let Some(v) = req.tp_trigger_by {
222            parameters.insert("tpTriggerBy".into(), v.into());
223        }
224        if let Some(v) = req.sl_trigger_by {
225            parameters.insert("slTriggerBy".into(), v.into());
226        }
227        if let Some(v) = req.active_price {
228            parameters.insert("activePrice".into(), v.into());
229        }
230        if let Some(v) = req.tp_size {
231            parameters.insert("tpSize".into(), v.into());
232        }
233        if let Some(v) = req.sl_size {
234            parameters.insert("slSize".into(), v.into());
235        }
236        if let Some(v) = req.tp_limit_price {
237            parameters.insert("tpLimitPrice".into(), v.into());
238        }
239        if let Some(v) = req.sl_limit_price {
240            parameters.insert("slLimitPrice".into(), v.into());
241        }
242        if let Some(v) = req.tp_order_type {
243            parameters.insert("tpOrderType".into(), v.as_str().into());
244        }
245        if let Some(v) = req.sl_order_type {
246            parameters.insert("slOrderType".into(), v.as_str().into());
247        }
248
249        let request = build_json_request(&parameters);
250        let response: TradingStopResponse = self
251            .client
252            .post_signed(
253                API::Position(Position::SetTradingStop),
254                self.recv_window,
255                Some(request),
256            )
257            .await?;
258        Ok(response)
259    }
260
261    /// Set the auto-add margin mode for a given position.
262    ///
263    /// # Arguments
264    ///
265    /// * `req` - The AddMarginRequest containing the necessary information.
266    ///
267    /// # Returns
268    ///
269    /// A result containing the AddMarginResponse.
270    pub async fn set_add_margin<'b>(
271        &self,
272        req: AddMarginRequest<'_>,
273    ) -> Result<AddMarginResponse, BybitError> {
274        // Create a new BTreeMap to store the parameters
275        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
276
277        // Add the category and symbol parameters
278        parameters.insert("category".into(), req.category.as_str().into());
279        parameters.insert("symbol".into(), req.symbol.into());
280
281        // Add the autoAddMargin parameter based on the value of `auto_add`
282        if req.auto_add {
283            parameters.insert("autoAddMargin".into(), 1.into());
284        } else {
285            parameters.insert("autoAddMargin".into(), 0.into());
286        }
287
288        // Add the positionIdx parameter if it is not None
289        if let Some(v) = req.position_idx {
290            parameters.insert("positionIdx".into(), v.into());
291        }
292
293        // Build the JSON request
294        let request = build_json_request(&parameters);
295
296        // Send the POST request to the server
297        let response: AddMarginResponse = self
298            .client
299            .post_signed(
300                API::Position(Position::SetAutoaddMargin),
301                self.recv_window,
302                Some(request),
303            )
304            .await?;
305
306        // Return the response
307        Ok(response)
308    }
309
310    /// Set the auto add margin.
311    ///
312    /// # Arguments
313    ///
314    /// * `req` - The AddReduceMarginRequest containing the necessary information.
315    ///
316    /// # Returns
317    ///
318    /// * Result<AddReduceMarginResponse> - The result of setting the auto add margin.
319    pub async fn add_or_reduce_margin<'b>(
320        &self,
321        req: AddReduceMarginRequest<'_>,
322    ) -> Result<AddReduceMarginResponse, BybitError> {
323        // Create a new BTreeMap to store the parameters
324        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
325
326        // Add the category and symbol parameters
327        parameters.insert("category".into(), req.category.as_str().into());
328        parameters.insert("symbol".into(), req.symbol.into());
329
330        // Add the margin parameter
331        parameters.insert("margin".into(), req.margin.into());
332
333        // Add the positionIdx parameter if it is not None
334        if let Some(v) = req.position_idx {
335            parameters.insert("positionIdx".into(), v.into());
336        }
337
338        // Build the JSON request
339        let request = build_json_request(&parameters);
340
341        // Send the POST request to the server
342        let response: AddReduceMarginResponse = self
343            .client
344            .post_signed(
345                API::Position(Position::AddorReduceMargin),
346                self.recv_window,
347                Some(request),
348            )
349            .await?;
350
351        // Return the response
352        Ok(response)
353    }
354
355    pub async fn get_closed_pnl<'b>(
356        &self,
357        req: ClosedPnlRequest<'_>,
358    ) -> Result<ClosedPnlResponse, BybitError> {
359        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
360        parameters.insert("category".into(), req.category.as_str().into());
361        if let Some(v) = req.symbol {
362            parameters.insert("symbol".into(), v.into());
363        }
364
365        if let Some(start_time) = req.start_time {
366            parameters
367                .entry("startTime".to_owned())
368                .or_insert_with(|| start_time.to_string().into());
369        }
370        if let Some(end_time) = req.end_time {
371            parameters
372                .entry("endTime".to_owned())
373                .or_insert_with(|| end_time.to_string().into());
374        }
375        if let Some(v) = req.limit {
376            parameters.insert("limit".into(), v.into());
377        }
378        let request = build_request(&parameters);
379        let response: ClosedPnlResponse = self
380            .client
381            .get_signed(
382                API::Position(Position::ClosedPnl),
383                self.recv_window,
384                Some(request),
385            )
386            .await?;
387        Ok(response)
388    }
389
390    /// Get closed options positions.
391    ///
392    /// # Arguments
393    ///
394    /// * `req` - The ClosedOptionsPositionsRequest containing the necessary information.
395    ///
396    /// # Returns
397    ///
398    /// A result containing the closed options positions response.
399    pub async fn get_closed_options_positions<'b>(
400        &self,
401        req: ClosedOptionsPositionsRequest<'_>,
402    ) -> Result<ClosedOptionsPositionsResult, BybitError> {
403        // Validate request parameters
404        if let Err(e) = req.validate() {
405            return Err(BybitError::ValidationError(e));
406        }
407
408        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
409        parameters.insert("category".into(), req.category.as_str().into());
410
411        if let Some(v) = req.symbol {
412            parameters.insert("symbol".into(), v.into());
413        }
414
415        if let Some(v) = req.start_time {
416            parameters.insert("startTime".into(), v.to_string().into());
417        }
418
419        if let Some(v) = req.end_time {
420            parameters.insert("endTime".into(), v.to_string().into());
421        }
422
423        if let Some(v) = req.limit {
424            parameters.insert("limit".into(), v.to_string().into());
425        }
426
427        if let Some(v) = req.cursor {
428            parameters.insert("cursor".into(), v.into());
429        }
430
431        let request = build_request(&parameters);
432        let response: ClosedOptionsPositionsResult = self
433            .client
434            .get_signed(
435                API::Position(Position::GetClosedOptionsPositions),
436                self.recv_window,
437                Some(request),
438            )
439            .await?;
440        Ok(response)
441    }
442
443    /// Confirm new risk limit (MMR - Maintenance Margin Rate).
444    ///
445    /// # Arguments
446    ///
447    /// * `req` - The ConfirmPendingMmrRequest containing the necessary information.
448    ///
449    /// # Returns
450    ///
451    /// A result containing the confirm pending MMR response.
452    ///
453    /// # Bybit API Reference
454    /// This endpoint is only applicable when the user is marked as only reducing positions
455    /// (see the `isReduceOnly` field in the Get Position Info interface).
456    /// After the user actively adjusts the risk level, this interface is called to try to
457    /// calculate the adjusted risk level. If the call passes (retCode=0), the system will
458    /// remove the position reduceOnly mark.
459    pub async fn confirm_pending_mmr<'b>(
460        &self,
461        req: ConfirmPendingMmrRequest<'_>,
462    ) -> Result<ConfirmPendingMmrResponse, BybitError> {
463        // Validate request parameters
464        if let Err(e) = req.validate() {
465            return Err(BybitError::ValidationError(e));
466        }
467
468        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
469        parameters.insert("category".into(), req.category.as_str().into());
470        parameters.insert("symbol".into(), req.symbol.into());
471
472        let request = build_json_request(&parameters);
473        let response: ConfirmPendingMmrResponse = self
474            .client
475            .post_signed(
476                API::Position(Position::ConfirmPendingMmr),
477                self.recv_window,
478                Some(request),
479            )
480            .await?;
481        Ok(response)
482    }
483
484    /// Moves positions from one user to another.
485    ///
486    /// # Arguments
487    ///
488    /// * `req` - The request containing the fromUid, toUid, and list of positions to move.
489    ///
490    /// # Returns
491    ///
492    /// A result containing the response.
493    pub async fn move_position<'b>(
494        &self,
495        req: MovePositionRequest<'_>,
496    ) -> Result<MovePositionResponse, BybitError> {
497        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
498        // The user id of the account to move the position from.
499        parameters.insert("fromUid".into(), req.from_uid.into());
500        // The user id of the account to move the position to.
501        parameters.insert("toUid".into(), req.to_uid.into());
502        // The list of positions to move.
503        parameters.insert("list".into(), json!(req.list));
504        let request = build_json_request(&parameters);
505        let response: MovePositionResponse = self
506            .client
507            .post_signed(
508                API::Position(Position::MovePosition),
509                self.recv_window,
510                Some(request),
511            )
512            .await?;
513        Ok(response)
514    }
515
516    /// Retrieves the history of position movements.
517    ///
518    /// # Arguments
519    ///
520    /// * `req` - The request containing the parameters for the history of position movements.
521    ///
522    /// # Returns
523    ///
524    /// A result containing the response.
525    pub async fn move_position_history<'b>(
526        &self,
527        req: MoveHistoryRequest<'_>,
528    ) -> Result<MoveHistoryResponse, BybitError> {
529        // Create a new BTreeMap to hold the parameters.
530        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
531
532        // If the category is specified, add it to the parameters.
533        if let Some(category) = req.category {
534            parameters.insert("category".into(), category.as_str().into());
535        }
536
537        // If the symbol is specified, add it to the parameters.
538        if let Some(symbol) = req.symbol {
539            parameters.insert("symbol".into(), symbol.into());
540        }
541
542        // If the start time is specified, convert it to milliseconds and insert it into the parameters.
543        if let Some(start_time) = req.start_time {
544            parameters
545                .entry("startTime".to_owned())
546                .or_insert_with(|| start_time.to_string().into());
547        }
548
549        // If the end time is specified, convert it to milliseconds and insert it into the parameters.
550        if let Some(end_time) = req.end_time {
551            parameters
552                .entry("endTime".to_owned())
553                .or_insert_with(|| end_time.to_string().into());
554        }
555
556        // If the status is specified, add it to the parameters.
557        if let Some(status) = req.status {
558            parameters.insert("status".into(), status.into());
559        }
560
561        // If the block trade id is specified, add it to the parameters.
562        if let Some(block_trade_id) = req.block_trade_id {
563            parameters.insert("blockTradeId".into(), block_trade_id.into());
564        }
565
566        // If the limit is specified, add it to the parameters.
567        if let Some(limit) = req.limit {
568            parameters.insert("limit".into(), limit.into());
569        }
570
571        // Build the request using the parameters.
572        let request = build_request(&parameters);
573
574        // Send a GET request to the Bybit API to retrieve the history of position movements.
575        let response: MoveHistoryResponse = self
576            .client
577            .get_signed(
578                API::Position(Position::MovePositionHistory),
579                self.recv_window,
580                Some(request),
581            )
582            .await?;
583
584        // Return the response.
585        Ok(response)
586    }
587}