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