Skip to main content

bybit/http/
account.rs

1use std::collections::HashMap;
2
3use rust_decimal::{Decimal, serde::str_option::deserialize as option_decimal};
4use serde::{Deserialize, Serialize};
5use serde_aux::prelude::deserialize_number_from_string as number;
6
7use crate::{
8    AccountType, DCPProduct, MarginMode, Second, SpotHedgingStatus, Timestamp, UnifiedMarginStatus,
9    UnifiedUpdateStatus,
10    enums::Category,
11    serde::{Unique, empty_string_as_none, hash_map},
12    ws::WalletMsg,
13};
14
15#[derive(Debug, Serialize, Clone)]
16#[serde(rename_all = "camelCase")]
17pub struct GetWalletBalanceParams {
18    /// Account type
19    /// UTA2.0: UNIFIED
20    /// UTA1.0: UNIFIED, CONTRACT(inverse derivatives wallet)
21    /// Classic account: CONTRACT, SPOT
22    /// To get Funding wallet balance, please go to this endpoint
23    pub account_type: AccountType,
24    /// Coin name, uppercase only
25    /// If not passed, it returns non-zero asset info
26    /// You can pass multiple coins to query, separated by comma. USDT,USDC
27    pub coin: Option<String>,
28}
29
30#[derive(Debug, Deserialize, PartialEq)]
31#[serde(rename_all = "camelCase")]
32pub struct WalletBalance {
33    /// Account type
34    pub account_type: AccountType,
35    /// Account IM rate
36    /// You can refer to this Glossary to understand the below fields calculation and meaning
37    /// All account wide fields are not applicable to
38    /// UTA2.0(isolated margin),
39    /// UTA1.0(isolated margin), UTA1.0(CONTRACT),
40    /// classic account(SPOT, CONTRACT)
41    #[serde(rename = "accountIMRate")]
42    pub account_im_rate: Decimal,
43    /// Account initial margin (USD) calculated by mark price: ∑Asset Total Initial Margin Base Coin calculated by mark price
44    #[serde(rename = "accountIMRateByMp")]
45    pub account_im_rate_by_mp: Decimal,
46    /// Account MM rate
47    #[serde(rename = "accountMMRate")]
48    pub account_mm_rate: Decimal,
49    /// Account maintenance margin (USD) calculated by mark price: ∑ Asset Total Maintenance Margin Base Coin calculated by mark price
50    #[serde(rename = "accountMMRateByMp")]
51    pub account_mm_rate_by_mp: Decimal,
52    /// Account total equity (USD)
53    pub total_equity: Decimal,
54    /// Account wallet balance (USD): ∑Asset Wallet Balance By USD value of each asset
55    pub total_wallet_balance: Decimal,
56    /// Account margin balance (USD): totalWalletBalance + totalPerpUPL
57    pub total_margin_balance: Decimal,
58    /// Account available balance (USD), Cross Margin: totalMarginBalance - totalInitialMargin
59    pub total_available_balance: Decimal,
60    /// Account Perps and Futures unrealised p&l (USD): ∑Each Perp and USDC Futures upl by base coin
61    #[serde(rename = "totalPerpUPL")]
62    pub total_perp_upl: Decimal,
63    /// Account initial margin (USD): ∑Asset Total Initial Margin Base Coin
64    pub total_initial_margin: Decimal,
65    /// Account initial margin (USD) calculated by mark price: ∑Asset Total Initial Margin Base Coin calculated by mark price
66    pub total_initial_margin_by_mp: Decimal,
67    /// Account maintenance margin (USD): ∑ Asset Total Maintenance Margin Base Coin
68    pub total_maintenance_margin: Decimal,
69    /// Account maintenance margin (USD) calculated by mark price: ∑ Asset Total Maintenance Margin Base Coin calculated by mark price
70    pub total_maintenance_margin_by_mp: Decimal,
71    #[serde(deserialize_with = "hash_map")]
72    pub coin: HashMap<String, WalletCoin>,
73}
74
75impl WalletBalance {
76    pub fn update(&mut self, msg: WalletMsg) {
77        self.account_type = msg.account_type;
78        self.account_im_rate = msg.account_im_rate;
79        self.account_mm_rate = msg.account_mm_rate;
80        self.total_equity = msg.total_equity;
81        self.total_wallet_balance = msg.total_wallet_balance;
82        self.total_margin_balance = msg.total_margin_balance;
83        self.total_available_balance = msg.total_available_balance;
84        self.total_perp_upl = msg.total_perp_upl;
85        self.total_initial_margin = msg.total_initial_margin;
86        self.total_maintenance_margin = msg.total_maintenance_margin;
87        self.coin = msg.coin;
88    }
89}
90
91#[derive(Debug, Deserialize, PartialEq, Clone)]
92#[serde(rename_all = "camelCase")]
93pub struct WalletCoin {
94    /// Coin name, such as BTC, ETH, USDT, USDC
95    pub coin: String,
96    /// Equity of coin
97    pub equity: Decimal,
98    /// USD value of coin. If this coin cannot be collateral, then it is 0
99    pub usd_value: Decimal,
100    /// Wallet balance of coin
101    pub wallet_balance: Decimal,
102    /// Locked balance due to the Spot open order
103    pub locked: Decimal,
104    /// The spot asset qty that is used to hedge in the portfolio margin, truncate to 8 decimals and "0" by default. This is a unique field for Unified account.
105    pub spot_hedging_qty: Decimal,
106    /// Borrow amount of current coin
107    pub borrow_amount: Decimal,
108    /// Accrued interest
109    pub accrued_interest: Decimal,
110    /// Pre-occupied margin for order. For portfolio margin mode, it returns ""
111    #[serde(rename = "totalOrderIM", default, deserialize_with = "option_decimal")]
112    pub total_order_im: Option<Decimal>,
113    /// Sum of initial margin of all positions + Pre-occupied liquidation fee. For portfolio margin mode, it returns ""
114    #[serde(
115        rename = "totalPositionIM",
116        default,
117        deserialize_with = "option_decimal"
118    )]
119    pub total_position_im: Option<Decimal>,
120    /// Sum of maintenance margin for all positions. For portfolio margin mode, it returns ""
121    #[serde(
122        rename = "totalPositionMM",
123        default,
124        deserialize_with = "option_decimal"
125    )]
126    pub total_position_mm: Option<Decimal>,
127    /// Unrealised P&L
128    pub unrealised_pnl: Decimal,
129    /// Cumulative Realised P&L
130    pub cum_realised_pnl: Decimal,
131    /// Bonus. This is a unique field for accountType=UNIFIED
132    pub bonus: Decimal,
133    /// Whether the collateral is turned on by user (user), true: ON, false: OFF
134    /// When marginCollateral=true, then collateralSwitch is meaningful
135    pub collateral_switch: bool,
136    /// Whether it can be used as a margin collateral currency (platform), true: YES, false: NO
137    /// When marginCollateral=false, then collateralSwitch is meaningless
138    pub margin_collateral: bool,
139    /// Borrow amount by spot margin trade and manual borrow amount (does not include borrow amount by spot margin active order). spotBorrow field corresponding to spot liabilities is detailed in the announcement.
140    #[serde(default, deserialize_with = "option_decimal")]
141    pub spot_borrow: Option<Decimal>,
142}
143
144impl Unique<String> for WalletCoin {
145    fn unique_key(&self) -> String {
146        self.coin.clone()
147    }
148}
149
150#[derive(Debug, Deserialize, PartialEq)]
151#[serde(rename_all = "camelCase")]
152pub struct AccountInfo {
153    /// Account status
154    pub unified_margin_status: UnifiedMarginStatus,
155    /// ISOLATED_MARGIN, REGULAR_MARGIN, PORTFOLIO_MARGIN
156    pub margin_mode: MarginMode,
157    /// Whether this account is a leader (copytrading). true, false
158    pub is_master_trader: bool,
159    /// Whether the unified account enables Spot hedging. ON, OFF
160    pub spot_hedging_status: SpotHedgingStatus,
161    /// Account data updated timestamp (ms)
162    #[serde(deserialize_with = "number")]
163    pub updated_time: Timestamp,
164}
165
166#[derive(Debug, Deserialize, PartialEq)]
167#[serde(rename_all = "camelCase")]
168pub struct DCPConfiguration {
169    /// DCP config for each product
170    pub dcp_infos: Vec<DCPInfo>,
171}
172
173#[derive(Debug, Deserialize, PartialEq)]
174#[serde(rename_all = "camelCase")]
175pub struct DCPInfo {
176    /// SPOT, DERIVATIVES, OPTIONS
177    pub product: DCPProduct,
178    /// Disconnected-CancelAll-Prevention status: ON
179    #[serde(rename = "dcpStatus")]
180    pub status: String,
181    /// DCP trigger time window which user pre-set. Between [3, 300] seconds, default: 10 sec
182    #[serde(deserialize_with = "number")]
183    pub time_window: Second,
184}
185
186#[derive(Clone, Debug, Serialize)]
187#[serde(rename_all = "camelCase")]
188pub struct GetTransactionLogParams {
189    /// Account Type. UNIFIED
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub account_type: Option<AccountType>,
192    /// Product type spot,linear,option,inverse
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub category: Option<Category>,
195    /// Currency, uppercase only
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub currency: Option<String>,
198    /// BaseCoin, uppercase only. e.g., BTC of BTCPERP
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub base_coin: Option<String>,
201    /// Not documented.
202    /// Settle coin
203    /// linear: either symbol or settleCoin is required. symbol has a higher priority
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub settle_coin: Option<String>,
206    /// Types of transaction logs
207    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
208    pub r#type: Option<String>,
209    /// movePosition, used to filter trans logs of Move Position only
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub trans_sub_type: Option<String>,
212    /// The start timestamp (ms)
213    /// startTime and endTime are not passed, return 24 hours by default
214    /// Only startTime is passed, return range between startTime and startTime+24 hours
215    /// Only endTime is passed, return range between endTime-24 hours and endTime
216    /// If both are passed, the rule is endTime - startTime <= 7 days
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub start_time: Option<Timestamp>,
219    /// The end timestamp (ms)
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub end_time: Option<Timestamp>,
222    /// Limit for data size per page. [1, 50]. Default: 20
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub limit: Option<u64>,
225    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub cursor: Option<String>,
228}
229
230impl GetTransactionLogParams {
231    pub fn new() -> Self {
232        Self {
233            account_type: None,
234            category: None,
235            currency: None,
236            base_coin: None,
237            settle_coin: None,
238            r#type: None,
239            trans_sub_type: None,
240            start_time: None,
241            end_time: None,
242            limit: None,
243            cursor: None,
244        }
245    }
246
247    pub fn with_account_type(mut self, v: AccountType) -> Self {
248        self.account_type = Some(v);
249        self
250    }
251    pub fn with_category(mut self, v: Category) -> Self {
252        self.category = Some(v);
253        self
254    }
255    pub fn with_currency(mut self, v: String) -> Self {
256        self.currency = Some(v);
257        self
258    }
259    pub fn with_base_coin(mut self, v: String) -> Self {
260        self.base_coin = Some(v);
261        self
262    }
263    pub fn with_settle_coin(mut self, v: String) -> Self {
264        self.settle_coin = Some(v);
265        self
266    }
267    pub fn with_type(mut self, v: String) -> Self {
268        self.r#type = Some(v);
269        self
270    }
271    pub fn with_trans_sub_type(mut self, v: String) -> Self {
272        self.trans_sub_type = Some(v);
273        self
274    }
275    pub fn with_start_time(mut self, v: Timestamp) -> Self {
276        self.start_time = Some(v);
277        self
278    }
279    pub fn with_end_time(mut self, v: Timestamp) -> Self {
280        self.end_time = Some(v);
281        self
282    }
283    pub fn with_limit(mut self, v: u64) -> Self {
284        self.limit = Some(v);
285        self
286    }
287    pub fn with_cursor(mut self, v: String) -> Self {
288        self.cursor = Some(v);
289        self
290    }
291}
292
293impl Default for GetTransactionLogParams {
294    fn default() -> Self {
295        Self::new()
296    }
297}
298
299#[derive(Debug, Deserialize, PartialEq)]
300#[serde(rename_all = "camelCase")]
301pub struct TransactionLog {
302    /// Unique id
303    pub id: String,
304    /// Symbol name
305    pub symbol: String,
306    /// Product type
307    pub category: Category,
308    /// Side. Buy,Sell,None
309    pub side: crate::Side,
310    /// Transaction timestamp (ms)
311    #[serde(deserialize_with = "number")]
312    pub transaction_time: Timestamp,
313    /// Type
314    #[serde(rename = "type")]
315    pub r#type: String,
316    /// Transaction sub type, movePosition, used for the logs generated by move position. "" by default
317    pub trans_sub_type: String,
318    /// Quantity
319    /// Spot: the negative means the qty of this currency is decreased, the positive means the qty of this currency is increased
320    /// Perps & Futures: it is the quantity for each trade entry and it does not have direction
321    pub qty: Decimal,
322    /// Size. The rest position size after the trade is executed, and it has direction, i.e., short with "-"
323    pub size: Decimal,
324    /// e.g., USDC, USDT, BTC, ETH
325    pub currency: String,
326    /// Trade price
327    pub trade_price: Decimal,
328    /// Funding fee
329    /// Positive fee value means receive funding; negative fee value means pay funding. This is opposite to the execFee from Get Trade History.
330    /// For USDC Perp, as funding settlement and session settlement occur at the same time, they are represented in a single record at settlement. Please refer to funding to understand funding fee, and cashFlow to understand 8-hour P&L.
331    pub funding: Decimal,
332    /// Trading fee
333    /// Positive fee value means expense
334    /// Negative fee value means rebates
335    pub fee: Decimal,
336    /// Cash flow, e.g., (1) close the position, and unRPL converts to RPL, (2) 8-hour session settlement for USDC Perp and Futures, (3) transfer in or transfer out. This does not include trading fee, funding fee
337    pub cash_flow: Decimal,
338    /// Change = cashFlow + funding - fee
339    pub change: Decimal,
340    /// Cash balance. This is the wallet balance after a cash change
341    pub cash_balance: Decimal,
342    ///
343    /// When type=TRADE, then it is trading fee rate
344    /// When type=SETTLEMENT, it means funding fee rate. For side=Buy, feeRate=market fee rate; For side=Sell, feeRate= - market fee rate
345    pub fee_rate: Decimal,
346    /// The change of bonus
347    pub bonus_change: Decimal,
348    /// Trade ID
349    pub trade_id: String,
350    /// Order ID
351    pub order_id: String,
352    /// User customised order ID
353    #[serde(default, deserialize_with = "crate::serde::empty_string_as_none")]
354    pub order_link_id: Option<String>,
355    /// Trading fee rate information. Currently, this data is returned only for spot orders placed on the Indonesian site or spot fiat currency orders placed on the EU site. In other cases, an empty string is returned. Enum: feeType, subFeeType
356    #[serde(default)]
357    pub extra_fees: Option<serde_json::Value>,
358}
359
360// --- Fee Rate ---
361
362/// Query params for [`Client::get_fee_rate`](crate::http::Client::get_fee_rate).
363#[derive(Debug, Serialize, Clone)]
364#[serde(rename_all = "camelCase")]
365pub struct GetFeeRateParams {
366    pub category: Category,
367    pub symbol: Option<String>,
368    /// Only for `category = option`. Base coin, e.g. `"BTC"`.
369    pub base_coin: Option<String>,
370}
371
372#[derive(Debug, Deserialize, PartialEq)]
373#[serde(rename_all = "camelCase")]
374pub struct FeeRateEntry {
375    /// Symbol name. Spot/linear/inverse only.
376    #[serde(default, deserialize_with = "empty_string_as_none")]
377    pub symbol: Option<String>,
378    /// Base coin. Option only.
379    #[serde(default, deserialize_with = "empty_string_as_none")]
380    pub base_coin: Option<String>,
381    pub taker_fee_rate: Decimal,
382    pub maker_fee_rate: Decimal,
383}
384
385// --- Set Margin Mode ---
386
387/// Request body for [`Client::set_margin_mode`](crate::http::Client::set_margin_mode).
388#[derive(Debug, Serialize, Clone)]
389#[serde(rename_all = "camelCase")]
390pub struct SetMarginModeRequest {
391    pub set_margin_mode: MarginMode,
392}
393
394/// Response for [`Client::set_margin_mode`](crate::http::Client::set_margin_mode).
395///
396/// On success `reasons` is empty. On failure it contains error details
397/// explaining why the switch was rejected.
398#[derive(Debug, Deserialize, PartialEq)]
399pub struct SetMarginModeResponse {
400    pub reasons: Vec<MarginModeFailureReason>,
401}
402
403#[derive(Debug, Deserialize, PartialEq)]
404#[serde(rename_all = "camelCase")]
405pub struct MarginModeFailureReason {
406    pub reason_code: String,
407    pub reason_msg: String,
408}
409
410// --- Upgrade to Unified Account (Pro) ---
411
412/// Response for [`Client::upgrade_to_uta`](crate::http::Client::upgrade_to_uta).
413#[derive(Debug, Deserialize, PartialEq)]
414#[serde(rename_all = "camelCase")]
415pub struct UpgradeToUtaResult {
416    pub unified_update_status: UnifiedUpdateStatus,
417    /// `None` unless `unified_update_status` is `Fail`.
418    pub unified_update_msg: Option<UnifiedUpdateMsg>,
419}
420
421#[derive(Debug, Deserialize, PartialEq)]
422#[serde(rename_all = "camelCase")]
423pub struct UnifiedUpdateMsg {
424    pub msg: Vec<String>,
425}
426
427// --- Get Borrow History ---
428
429/// Query params for [`Client::get_borrow_history`](crate::http::Client::get_borrow_history).
430#[derive(Clone, Debug, Serialize)]
431#[serde(rename_all = "camelCase")]
432pub struct GetBorrowHistoryParams {
433    /// Currency, uppercase only
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub currency: Option<String>,
436    /// The start timestamp (ms). Combined with end_time, cannot exceed 30 days
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub start_time: Option<Timestamp>,
439    /// The end timestamp (ms). Combined with start_time, cannot exceed 30 days
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub end_time: Option<Timestamp>,
442    /// Limit for data size per page. [1, 50]. Default: 20
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub limit: Option<u64>,
445    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub cursor: Option<String>,
448}
449
450impl GetBorrowHistoryParams {
451    pub fn new() -> Self {
452        Self {
453            currency: None,
454            start_time: None,
455            end_time: None,
456            limit: None,
457            cursor: None,
458        }
459    }
460
461    pub fn with_currency(mut self, v: String) -> Self {
462        self.currency = Some(v);
463        self
464    }
465    pub fn with_start_time(mut self, v: Timestamp) -> Self {
466        self.start_time = Some(v);
467        self
468    }
469    pub fn with_end_time(mut self, v: Timestamp) -> Self {
470        self.end_time = Some(v);
471        self
472    }
473    pub fn with_limit(mut self, v: u64) -> Self {
474        self.limit = Some(v);
475        self
476    }
477    pub fn with_cursor(mut self, v: String) -> Self {
478        self.cursor = Some(v);
479        self
480    }
481}
482
483impl Default for GetBorrowHistoryParams {
484    fn default() -> Self {
485        Self::new()
486    }
487}
488
489#[derive(Debug, Deserialize, PartialEq)]
490#[serde(rename_all = "camelCase")]
491pub struct BorrowHistoryEntry {
492    /// Currency
493    pub currency: String,
494    /// Interest record timestamp (ms)
495    #[serde(deserialize_with = "number")]
496    pub created_time: Timestamp,
497    /// Borrow cost, i.e., accrued interest
498    pub borrow_cost: Decimal,
499    /// Hourly borrow rate
500    pub hourly_borrow_rate: Decimal,
501    /// Borrowed amount that accrues interest
502    #[serde(rename = "InterestBearingBorrowSize")]
503    pub interest_bearing_borrow_size: Decimal,
504    /// Amount exempted from interest
505    pub cost_exemption: Decimal,
506    /// Borrowed amount
507    pub borrow_amount: Decimal,
508    /// Unrealised loss
509    pub unrealised_loss: Decimal,
510    /// Amount borrowed within the interest-free threshold
511    #[serde(default, deserialize_with = "option_decimal")]
512    pub free_borrowed_amount: Option<Decimal>,
513}
514
515// --- Get Collateral Info ---
516
517/// Query params for [`Client::get_collateral_info`](crate::http::Client::get_collateral_info).
518#[derive(Debug, Serialize, Clone)]
519#[serde(rename_all = "camelCase")]
520pub struct GetCollateralInfoParams {
521    /// Asset currency of all current collateral, uppercase only
522    #[serde(skip_serializing_if = "Option::is_none")]
523    pub currency: Option<String>,
524}
525
526impl GetCollateralInfoParams {
527    pub fn new() -> Self {
528        Self { currency: None }
529    }
530
531    pub fn with_currency(mut self, v: String) -> Self {
532        self.currency = Some(v);
533        self
534    }
535}
536
537impl Default for GetCollateralInfoParams {
538    fn default() -> Self {
539        Self::new()
540    }
541}
542
543#[derive(Debug, Deserialize, PartialEq)]
544#[serde(rename_all = "camelCase")]
545pub struct CollateralInfoEntry {
546    /// Currency of the collateral
547    pub currency: String,
548    /// Hourly interest rate for borrowing
549    pub hourly_borrow_rate: Decimal,
550    /// Maximum borrowable amount, shared across main and sub accounts
551    pub max_borrowing_amount: Decimal,
552    /// Maximum limit for interest-free borrowing
553    pub free_borrowing_limit: Decimal,
554    /// Amount borrowed within the interest-free threshold
555    pub free_borrow_amount: Decimal,
556    /// Current borrow amount
557    pub borrow_amount: Decimal,
558    /// Borrowing amount from other accounts under the same main account
559    pub other_borrow_amount: Decimal,
560    /// Remaining amount available to borrow
561    pub available_to_borrow: Decimal,
562    /// Whether currency can be borrowed
563    pub borrowable: bool,
564    /// Borrow usage ratio, e.g. 0.5 = 50%
565    pub borrow_usage_rate: Decimal,
566    /// Whether usable as margin collateral
567    pub margin_collateral: bool,
568    /// Whether collateral is enabled by user
569    pub collateral_switch: bool,
570    /// Deprecated. Refer to the tiered collateral ratio endpoint instead
571    #[serde(default, deserialize_with = "option_decimal")]
572    pub collateral_ratio: Option<Decimal>,
573    /// Deprecated. Always empty
574    #[serde(default, deserialize_with = "empty_string_as_none")]
575    pub free_borrowing_amount: Option<String>,
576}