bybit/
account.rs

1use std::collections::BTreeMap;
2
3use crate::api::{Account, API};
4use crate::client::Client;
5use crate::errors::BybitError;
6use crate::model::{
7    AccountInfoResponse, BatchSetCollateralCoinResponse, BorrowHistoryRequest,
8    BorrowHistoryResponse, Category, CollateralInfoResponse, FeeRateResponse,
9    RepayLiabilityResponse, SetCollateralCoinResponse, SetMarginModeResponse, SmpResponse,
10    SpotHedgingResponse, TransactionLogRequest, TransactionLogResponse, UTAResponse,
11    WalletResponse,
12};
13
14use serde_json::{json, Value};
15
16use crate::util::{build_json_request, build_request, date_to_milliseconds};
17
18#[derive(Clone)]
19pub struct AccountManager {
20    pub client: Client,
21    pub recv_window: u16,
22}
23
24impl AccountManager {
25    
26    /// Fetches the wallet balance for a specific account and optional coin.
27    ///
28    /// # Arguments
29    ///
30    /// * `account` - The account type.
31    /// * `coin` - The optional coin.
32    ///
33    /// # Returns
34    ///
35    /// A result containing the wallet balance response or an error.
36    pub async fn get_wallet_balance(
37        &self,
38        account: &str,
39        coin: Option<&str>,
40    ) -> Result<WalletResponse, BybitError> {
41        // Create a new BTreeMap to hold the request parameters.
42        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
43
44        // Add the account type parameter.
45        parameters.insert("accountType".into(), account.into());
46
47        // Add the coin parameter if it is provided.
48        if let Some(c) = coin {
49            parameters.insert("coin".into(), c.into());
50        }
51
52        // Build the request using the parameters.
53        let request = build_request(&parameters);
54
55        // Send the signed request to the Bybit API and await the response.
56        let response: WalletResponse = self
57            .client
58            .get_signed(
59                API::Account(Account::Balance),
60                self.recv_window.into(),
61                Some(request),
62            )
63            .await?;
64
65        // Return the response.
66        Ok(response)
67    }
68
69    
70    /// Upgrades the current account to UTA.
71    ///
72    /// This function sends a POST request to the Bybit API to upgrade the current account to UTA
73    /// (Unified Trading Account). It awaits the response and returns the result.
74    ///
75    /// # Returns
76    ///
77    /// A result containing the UTA response or an error.
78    pub async fn upgrade_to_uta(&self) -> Result<UTAResponse, BybitError> {
79        // Send a signed POST request to the Bybit API to upgrade the account to UTA.
80        // The request does not require any additional parameters.
81        // The response is deserialized into the `UTAResponse` struct.
82        // The `await?` operator awaits the response and returns an error if the request fails.
83
84        // Send the request and await the response.
85        let response: UTAResponse = self
86            .client
87            .post_signed(
88                API::Account(Account::UpgradetoUTA),
89                self.recv_window.into(),
90                None,
91            )
92            .await?;
93
94        // Return the response.
95        Ok(response)
96    }
97
98
99    /// Retrieves the borrow history for the current account.
100    ///
101    /// This function sends a signed GET request to the Bybit API to retrieve the borrow history for
102    /// the current account. The request can be filtered by the `coin` and `start_time` parameters.
103    /// The response is deserialized into the `BorrowHistoryResponse` struct.
104    /// The `await?` operator awaits the response and returns an error if the request fails.
105    ///
106    /// # Arguments
107    ///
108    /// * `req` - A `BorrowHistoryRequest` struct containing the parameters for the request.
109    ///
110    /// # Returns
111    ///
112    /// A result containing the borrow history response or an error.
113    pub async fn get_borrow_history<'b>(
114        &self,
115        req: BorrowHistoryRequest<'_>,
116    ) -> Result<BorrowHistoryResponse, BybitError> {
117        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
118        if let Some(c) = req.coin {
119            parameters.insert("coin".into(), c.into());
120        }
121
122        // If the start time is specified, convert it to milliseconds and insert it into the parameters.
123        if let Some(end_str) = req.start_time.as_ref().map(|s| s.as_ref()) {
124            let end_millis = date_to_milliseconds(end_str);
125            parameters
126                .entry("startTime".to_owned())
127                .or_insert_with(|| end_millis.into());
128        }
129
130        // If the end time is specified, convert it to milliseconds and insert it into the parameters.
131        if let Some(end_str) = req.end_time.as_ref().map(|s| s.as_ref()) {
132            let end_millis = date_to_milliseconds(end_str);
133            parameters
134                .entry("endTime".to_owned())
135                .or_insert_with(|| end_millis.into());
136        }
137
138        // If the limit is specified, insert it into the parameters.
139        if let Some(s) = req.limit {
140            parameters.insert("limit".into(), s.into());
141        }
142
143        let request = build_request(&parameters);
144        let response: BorrowHistoryResponse = self
145            .client
146            .get_signed(
147                API::Account(Account::BorrowHistory),
148                self.recv_window.into(),
149                Some(request),
150            )
151            .await?;
152
153        Ok(response)
154    }
155
156    /// Repays liability for a specific coin.
157    ///
158    /// # Arguments
159    ///
160    /// * `coin` - The coin for which to repay liability. If not specified, all coins are repaid.
161    ///
162    /// # Returns
163    ///
164    /// A result containing a response object or an error.
165    pub async fn repay_liability(
166        &self,
167        coin: Option<&str>,
168    ) -> Result<RepayLiabilityResponse, BybitError> {
169        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
170        if let Some(c) = coin {
171            parameters.insert("coin".into(), c.into());
172        }
173        let request = build_json_request(&parameters);
174        let response: RepayLiabilityResponse = self
175            .client
176            .post_signed(
177                API::Account(Account::RepayLiability),
178                self.recv_window.into(),
179                Some(request),
180            )
181            .await?;
182        Ok(response)
183    }
184
185    /// Sets the collateral coin for a specific coin with the given switch value.
186    ///
187    /// # Arguments
188    ///
189    /// * `coin` - The coin for which to set the collateral.
190    /// * `switch` - The switch value indicating whether to turn collateral on or off.
191    ///
192    /// # Returns
193    ///
194    /// A result containing the set collateral response or an error.
195    pub async fn set_collateral_coin(
196        &self,
197        coin: &str,
198        switch: bool,
199    ) -> Result<SetCollateralCoinResponse, BybitError> {
200        // Create a new BTreeMap to hold the request parameters.
201        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
202        // Insert the coin parameter.
203        parameters.insert("coin".into(), coin.into());
204        // Insert the collateral switch parameter based on the switch value.
205        parameters.insert("collateralSwitch".into(), if switch { "ON".into() } else { "OFF".into() });
206        // Build the request using the parameters.
207        let request = build_json_request(&parameters);
208        // Send the signed request to the Bybit API and await the response.
209        let response: SetCollateralCoinResponse = self
210            .client
211            .post_signed(
212                API::Account(Account::SetCollateral),
213                self.recv_window.into(),
214                Some(request),
215            )
216            .await?;
217        // Return the response.
218        Ok(response)
219    }
220
221    /// Sets the collateral coin for multiple coins in a single request.
222    ///
223    /// # Arguments
224    ///
225    /// * `requests` - A vector of tuples, where each tuple contains the coin and the switch value.
226    ///
227    /// # Returns
228    ///
229    /// A result containing the batch set collateral response or an error.
230    pub async fn batch_set_collateral(
231        &self,
232        requests: Vec<(&str, bool)>,
233    ) -> Result<BatchSetCollateralCoinResponse, BybitError> {
234        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
235        let mut requests_array: Vec<Value> = Vec::new();
236        // Iterate over the requests and build the request array
237        for (coin, switch) in requests {
238            let mut build_switch: BTreeMap<String, Value> = BTreeMap::new();
239            build_switch.insert("coin".into(), coin.into());
240            build_switch.insert("collateralSwitch".into(), switch.into());
241            let build_switches = json!(&build_switch);
242            requests_array.push(build_switches);
243        }
244        // Add the request array to the parameters
245        parameters.insert("request".into(), Value::Array(requests_array));
246        // Build the request
247        let request = build_json_request(&parameters);
248        // Send the signed request to the Bybit API and await the response
249        let response: BatchSetCollateralCoinResponse = self
250            .client
251            .post_signed(
252                API::Account(Account::BatchSetCollateral),
253                self.recv_window.into(),
254                Some(request),
255            )
256            .await?;
257        // Return the response
258        Ok(response)
259    }
260
261    /// Retrieves the collateral information for a specific coin.
262    ///
263    /// # Arguments
264    ///
265    /// * `coin` - The optional coin for which to retrieve the collateral information. If not specified,
266    /// information for all coins is returned.
267    ///
268    /// # Returns
269    ///
270    /// A result containing the collateral information response or an error.
271    pub async fn get_collateral_info(
272        &self,
273        coin: Option<&str>,
274    ) -> Result<CollateralInfoResponse, BybitError> {
275        // Create a new BTreeMap to hold the request parameters.
276        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
277
278        // If a coin is specified, insert it into the parameters.
279        if let Some(v) = coin {
280            parameters.insert("currency".into(), v.into());
281        }
282
283        // Build the request using the parameters.
284        let req = build_request(&parameters);
285
286        // Send the signed request to the Bybit API and await the response.
287        let response: CollateralInfoResponse = self
288            .client
289            .get_signed(
290                API::Account(Account::CollateralInfo),
291                self.recv_window.into(),
292                Some(req),
293            )
294            .await?;
295
296        // Return the response.
297        Ok(response)
298    }
299
300
301    /// Retrieves the fee rate for a given market category and symbol.
302    ///
303    /// # Arguments
304    ///
305    /// * `category` - The market category to fetch the fee rate from.
306    /// * `symbol` - The trading symbol to fetch the fee rate for. If not specified, the fee rate for all symbols in the category is returned.
307    ///
308    /// # Returns
309    ///
310    /// A result containing the fee rate response or an error.
311    pub async fn get_fee_rate(
312        &self,
313        category: Category,
314        symbol: Option<String>,
315    ) -> Result<FeeRateResponse, BybitError> {
316        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
317
318        // Insert the category parameter.
319        parameters.insert("category".into(), category.as_str().into());
320
321        // If a symbol is specified, insert it into the parameters.
322        if let Some(s) = symbol {
323            parameters.insert("symbol".into(), s.into());
324        }
325
326        // Build the request using the parameters.
327        let req = build_request(&parameters);
328
329        // Send the signed request to the Bybit API and await the response.
330        let response: FeeRateResponse = self
331            .client
332            .post_signed(
333                API::Account(Account::FeeRate),
334                self.recv_window.into(),
335                Some(req),
336            )
337            .await?;
338
339        // Return the response.
340        Ok(response)
341    }
342
343    /// Retrieves the account information for the current account.
344    ///
345    /// This function sends a signed GET request to the Bybit API to retrieve the account information.
346    /// The response is deserialized into the `AccountInfoResponse` struct.
347    /// The `await?` operator awaits the response and returns an error if the request fails.
348    ///
349    /// # Returns
350    ///
351    /// A result containing the account information response or an error.
352    pub async fn get_account_info(&self) -> Result<AccountInfoResponse, BybitError> {
353        // Send the signed GET request to the Bybit API to retrieve the account information.
354        // The request does not require any additional parameters.
355        // The response is deserialized into the `AccountInfoResponse` struct.
356        // The `await?` operator awaits the response and returns an error if the request fails.
357
358        // Send the request and await the response.
359        let response: AccountInfoResponse = self
360            .client
361            .get_signed(
362                API::Account(Account::Information),
363                self.recv_window.into(),
364                None,
365            )
366            .await?;
367
368        // Return the response.
369        Ok(response)
370    }
371
372    /// Retrieves the transaction log for the current account.
373    ///
374    /// This function sends a signed GET request to the Bybit API to retrieve the transaction log.
375    /// The request parameters are serialized into a JSON object and sent in the request body.
376    /// The response is deserialized into the `TransactionLogResponse` struct.
377    /// The `await?` operator awaits the response and returns an error if the request fails.
378    ///
379    /// # Arguments
380    ///
381    /// * `req` - An instance of `TransactionLogRequest` containing the request parameters.
382    ///
383    /// # Returns
384    ///
385    /// A result containing the transaction log response or an error.
386    pub async fn get_transaction_log<'b>(
387        &self,
388        req: TransactionLogRequest<'_>,
389    ) -> Result<TransactionLogResponse, BybitError> {
390        // Create a mutable map to store the request parameters.
391        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
392
393        // Add the account type to the request parameters if it is specified.
394        if let Some(v) = req.account_type {
395            parameters.insert("accountType".into(), v.into());
396        }
397
398        // Add the category to the request parameters if it is specified.
399        if let Some(v) = req.category {
400            parameters.insert("category".into(), v.as_str().into());
401        }
402
403        // Add the currency to the request parameters if it is specified.
404        if let Some(s) = req.currency {
405            parameters.insert("currency".into(), s.into());
406        }
407
408        // Add the base coin to the request parameters if it is specified.
409        if let Some(c) = req.base_coin {
410            parameters.insert("baseCoin".into(), c.into());
411        }
412
413        // Add the log type to the request parameters if it is specified.
414        if let Some(t) = req.log_type {
415            parameters.insert("type".into(), t.into());
416        }
417
418        // Add the start time to the request parameters if it is specified.
419        if let Some(start_str) = req.start_time.as_ref().map(|s| s.as_ref()) {
420            let start_millis = date_to_milliseconds(start_str);
421            parameters
422                .entry("startTime".to_owned())
423                .or_insert_with(|| start_millis.into());
424        }
425
426        // Add the end time to the request parameters if it is specified.
427        if let Some(end_str) = req.end_time.as_ref().map(|s| s.as_ref()) {
428            let end_millis = date_to_milliseconds(end_str);
429            parameters
430                .entry("endTime".to_owned())
431                .or_insert_with(|| end_millis.into());
432        }
433
434        // Add the limit to the request parameters if it is specified.
435        if let Some(s) = req.limit {
436            parameters.insert("limit".into(), s.into());
437        }
438
439        // Build the request from the parameters.
440        let request = build_request(&parameters);
441
442        // Send the signed GET request to the Bybit API to retrieve the transaction log.
443        let response: TransactionLogResponse = self
444            .client
445            .get_signed(
446                API::Account(Account::TransactionLog),
447                self.recv_window.into(),
448                Some(request),
449            )
450            .await?;
451
452        // Return the response.
453        Ok(response)
454    }
455
456    /// Retrieves the Server-Market-Portfolio (SMP) group ID for the current user.
457    ///
458    /// # Returns
459    ///
460    /// A result containing the SMP response or an error.
461    pub async fn get_smp_id(&self) -> Result<SmpResponse, BybitError> {
462        // Send a signed GET request to the Bybit API to retrieve the SMP group ID.
463        // The request does not require any additional parameters.
464        let response: SmpResponse = self
465            .client
466            .get_signed(
467                API::Account(Account::SMPGroupID),
468                self.recv_window.into(),
469                None,
470            )
471            .await?;
472
473        // Return the response.
474        Ok(response)
475    }
476
477    /// Sets the margin mode for the current account.
478    ///
479    /// # Arguments
480    ///
481    /// * `margin_mode` - The desired margin mode to set. Can be "CROSS" or "FIXED".
482    ///
483    /// # Returns
484    ///
485    /// A result containing the set margin mode response or an error.
486    pub async fn set_margin_mode(
487        &self,
488        margin_mode: &str,
489    ) -> Result<SetMarginModeResponse, BybitError> {
490        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
491        parameters.insert("setMarginMode".into(), margin_mode.into());
492        let request = build_json_request(&parameters);
493        let response: SetMarginModeResponse = self
494            .client
495            .post_signed(
496                API::Account(Account::SetMarginMode),
497                self.recv_window.into(),
498                Some(request),
499            )
500            .await?;
501        Ok(response)
502    }
503
504    /// Sets the spot hedging mode for the current account.
505    ///
506    /// # Arguments
507    ///
508    /// * `spot_hedging` - The desired spot hedging mode. `true` sets the mode to "ON",
509    ///   `false` sets the mode to "OFF".
510    ///
511    /// # Returns
512    ///
513    /// A result containing the set spot hedging mode response or an error.
514    pub async fn set_spot_hedging(
515        &self,
516        spot_hedging: bool,
517    ) -> Result<SpotHedgingResponse, BybitError> {
518        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
519        parameters.insert(
520            "setHedgingMode".into(),
521            if spot_hedging {
522                "ON".into()
523            } else {
524                "OFF".into()
525            },
526        );
527        let request = build_json_request(&parameters);
528        let response: SpotHedgingResponse = self
529            .client
530            .post_signed(
531                API::Account(Account::SetSpotHedging),
532                self.recv_window.into(),
533                Some(request),
534            )
535            .await?;
536        Ok(response)
537    }
538}