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