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.leverage.to_string());
80        parameters.insert("sellLeverage".into(), req.leverage.to_string());
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        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
202        parameters.insert("category".into(), req.category.as_str().into());
203        parameters.insert("symbol".into(), req.symbol.into());
204        if let Some(v) = req.take_profit {
205            parameters.insert("takeProfit".into(), v.into());
206        }
207        if let Some(v) = req.stop_loss {
208            parameters.insert("stopLoss".into(), v.into());
209        }
210        if let Some(v) = req.tp_trigger_by {
211            parameters.insert("tpTriggerBy".into(), v.into());
212        }
213        if let Some(v) = req.sl_trigger_by {
214            parameters.insert("slTriggerBy".into(), v.into());
215        }
216        if let Some(v) = req.tpsl_mode {
217            parameters.insert("tpslMode".into(), v.into());
218        }
219        if let Some(v) = req.tp_order_type {
220            parameters.insert("tpOrderType".into(), v.as_str().into());
221        }
222        if let Some(v) = req.sl_order_type {
223            parameters.insert("slOrderType".into(), v.as_str().into());
224        }
225        if let Some(v) = req.tp_size {
226            parameters.insert("tpSize".into(), v.into());
227        }
228        if let Some(v) = req.sl_size {
229            parameters.insert("slSize".into(), v.into());
230        }
231        if let Some(v) = req.tp_limit_price {
232            parameters.insert("tpLimitPrice".into(), v.into());
233        }
234        if let Some(v) = req.sl_limit_price {
235            parameters.insert("slLimitPrice".into(), v.into());
236        }
237        parameters.insert("positionIdx".into(), req.position_idx.into());
238        let request = build_json_request(&parameters);
239        let response: TradingStopResponse = self
240            .client
241            .post_signed(
242                API::Position(Position::SetTradingStop),
243                self.recv_window,
244                Some(request),
245            )
246            .await?;
247        Ok(response)
248    }
249
250    /// Set the auto-add margin mode for a given position.
251    ///
252    /// # Arguments
253    ///
254    /// * `req` - The AddMarginRequest containing the necessary information.
255    ///
256    /// # Returns
257    ///
258    /// A result containing the AddMarginResponse.
259    pub async fn set_add_margin<'b>(
260        &self,
261        req: AddMarginRequest<'_>,
262    ) -> Result<AddMarginResponse, BybitError> {
263        // Create a new BTreeMap to store the parameters
264        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
265
266        // Add the category and symbol parameters
267        parameters.insert("category".into(), req.category.as_str().into());
268        parameters.insert("symbol".into(), req.symbol.into());
269
270        // Add the autoAddMargin parameter based on the value of `auto_add`
271        if req.auto_add {
272            parameters.insert("autoAddMargin".into(), 1.into());
273        } else {
274            parameters.insert("autoAddMargin".into(), 0.into());
275        }
276
277        // Add the positionIdx parameter if it is not None
278        if let Some(v) = req.position_idx {
279            parameters.insert("positionIdx".into(), v.into());
280        }
281
282        // Build the JSON request
283        let request = build_json_request(&parameters);
284
285        // Send the POST request to the server
286        let response: AddMarginResponse = self
287            .client
288            .post_signed(
289                API::Position(Position::SetAutoaddMargin),
290                self.recv_window,
291                Some(request),
292            )
293            .await?;
294
295        // Return the response
296        Ok(response)
297    }
298
299    /// Set the auto add margin.
300    ///
301    /// # Arguments
302    ///
303    /// * `req` - The AddReduceMarginRequest containing the necessary information.
304    ///
305    /// # Returns
306    ///
307    /// * Result<AddReduceMarginResponse> - The result of setting the auto add margin.
308    pub async fn add_or_reduce_margin<'b>(
309        &self,
310        req: AddReduceMarginRequest<'_>,
311    ) -> Result<AddReduceMarginResponse, BybitError> {
312        // Create a new BTreeMap to store the parameters
313        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
314
315        // Add the category and symbol parameters
316        parameters.insert("category".into(), req.category.as_str().into());
317        parameters.insert("symbol".into(), req.symbol.into());
318
319        // Add the margin parameter
320        parameters.insert("margin".into(), req.margin.into());
321
322        // Add the positionIdx parameter if it is not None
323        if let Some(v) = req.position_idx {
324            parameters.insert("positionIdx".into(), v.into());
325        }
326
327        // Build the JSON request
328        let request = build_json_request(&parameters);
329
330        // Send the POST request to the server
331        let response: AddReduceMarginResponse = self
332            .client
333            .post_signed(
334                API::Position(Position::AddorReduceMargin),
335                self.recv_window,
336                Some(request),
337            )
338            .await?;
339
340        // Return the response
341        Ok(response)
342    }
343
344    pub async fn get_closed_pnl<'b>(
345        &self,
346        req: ClosedPnlRequest<'_>,
347    ) -> Result<ClosedPnlResponse, BybitError> {
348        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
349        parameters.insert("category".into(), req.category.as_str().into());
350        if let Some(v) = req.symbol {
351            parameters.insert("symbol".into(), v.into());
352        }
353
354        if let Some(start_time) = req.start_time {
355            parameters
356                .entry("startTime".to_owned())
357                .or_insert_with(|| start_time.to_string().into());
358        }
359        if let Some(end_time) = req.end_time {
360            parameters
361                .entry("endTime".to_owned())
362                .or_insert_with(|| end_time.to_string().into());
363        }
364        if let Some(v) = req.limit {
365            parameters.insert("limit".into(), v.into());
366        }
367        let request = build_request(&parameters);
368        let response: ClosedPnlResponse = self
369            .client
370            .get_signed(
371                API::Position(Position::ClosedPnl),
372                self.recv_window,
373                Some(request),
374            )
375            .await?;
376        Ok(response)
377    }
378
379    /// Moves positions from one user to another.
380    ///
381    /// # Arguments
382    ///
383    /// * `req` - The request containing the fromUid, toUid, and list of positions to move.
384    ///
385    /// # Returns
386    ///
387    /// A result containing the response.
388    pub async fn move_position<'b>(
389        &self,
390        req: MovePositionRequest<'_>,
391    ) -> Result<MovePositionResponse, BybitError> {
392        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
393        // The user id of the account to move the position from.
394        parameters.insert("fromUid".into(), req.from_uid.into());
395        // The user id of the account to move the position to.
396        parameters.insert("toUid".into(), req.to_uid.into());
397        // The list of positions to move.
398        parameters.insert("list".into(), json!(req.list));
399        let request = build_json_request(&parameters);
400        let response: MovePositionResponse = self
401            .client
402            .post_signed(
403                API::Position(Position::MovePosition),
404                self.recv_window,
405                Some(request),
406            )
407            .await?;
408        Ok(response)
409    }
410
411    /// Retrieves the history of position movements.
412    ///
413    /// # Arguments
414    ///
415    /// * `req` - The request containing the parameters for the history of position movements.
416    ///
417    /// # Returns
418    ///
419    /// A result containing the response.
420    pub async fn move_position_history<'b>(
421        &self,
422        req: MoveHistoryRequest<'_>,
423    ) -> Result<MoveHistoryResponse, BybitError> {
424        // Create a new BTreeMap to hold the parameters.
425        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
426
427        // If the category is specified, add it to the parameters.
428        if let Some(category) = req.category {
429            parameters.insert("category".into(), category.as_str().into());
430        }
431
432        // If the symbol is specified, add it to the parameters.
433        if let Some(symbol) = req.symbol {
434            parameters.insert("symbol".into(), symbol.into());
435        }
436
437        // If the start time is specified, convert it to milliseconds and insert it into the parameters.
438        if let Some(start_time) = req.start_time {
439            parameters
440                .entry("startTime".to_owned())
441                .or_insert_with(|| start_time.to_string().into());
442        }
443
444        // If the end time is specified, convert it to milliseconds and insert it into the parameters.
445        if let Some(end_time) = req.end_time {
446            parameters
447                .entry("endTime".to_owned())
448                .or_insert_with(|| end_time.to_string().into());
449        }
450
451        // If the status is specified, add it to the parameters.
452        if let Some(status) = req.status {
453            parameters.insert("status".into(), status.into());
454        }
455
456        // If the block trade id is specified, add it to the parameters.
457        if let Some(block_trade_id) = req.block_trade_id {
458            parameters.insert("blockTradeId".into(), block_trade_id.into());
459        }
460
461        // If the limit is specified, add it to the parameters.
462        if let Some(limit) = req.limit {
463            parameters.insert("limit".into(), limit.into());
464        }
465
466        // Build the request using the parameters.
467        let request = build_request(&parameters);
468
469        // Send a GET request to the Bybit API to retrieve the history of position movements.
470        let response: MoveHistoryResponse = self
471            .client
472            .get_signed(
473                API::Position(Position::MovePositionHistory),
474                self.recv_window,
475                Some(request),
476            )
477            .await?;
478
479        // Return the response.
480        Ok(response)
481    }
482}