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