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(¶meters);
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(¶meters);
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(¶meters);
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(¶meters);
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(¶meters);
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(¶meters);
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(¶meters);
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(¶meters);
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(¶meters);
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(¶meters);
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}