Skip to main content

binance_api_client/models/
account.rs

1//! Account and trading models.
2//!
3//! These models represent responses from authenticated account and trading endpoints.
4
5use serde::{Deserialize, Serialize};
6
7use crate::types::{
8    AccountType, CancelReplaceResult, ContingencyType, OcoOrderStatus, OcoStatus, OrderSide,
9    OrderStatus, OrderType, TimeInForce,
10};
11
12use super::market::string_or_float;
13
14/// Account information response.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct AccountInfo {
18    /// Maker commission rate (bps).
19    pub maker_commission: i32,
20    /// Taker commission rate (bps).
21    pub taker_commission: i32,
22    /// Buyer commission rate (bps).
23    pub buyer_commission: i32,
24    /// Seller commission rate (bps).
25    pub seller_commission: i32,
26    /// Commission rates for specific assets.
27    #[serde(default)]
28    pub commission_rates: Option<CommissionRates>,
29    /// Whether trading is enabled.
30    pub can_trade: bool,
31    /// Whether withdrawals are enabled.
32    pub can_withdraw: bool,
33    /// Whether deposits are enabled.
34    pub can_deposit: bool,
35    /// Whether self-trade prevention is being used.
36    #[serde(default)]
37    pub brokered: bool,
38    /// Whether this account requires self-trade prevention.
39    #[serde(default)]
40    pub require_self_trade_prevention: bool,
41    /// Account update time.
42    pub update_time: u64,
43    /// Account type.
44    pub account_type: AccountType,
45    /// Account balances.
46    pub balances: Vec<Balance>,
47    /// Account permissions.
48    #[serde(default)]
49    pub permissions: Vec<AccountType>,
50    /// UID (user ID).
51    #[serde(default)]
52    pub uid: Option<u64>,
53}
54
55/// Commission rates.
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct CommissionRates {
59    /// Maker rate.
60    #[serde(with = "string_or_float")]
61    pub maker: f64,
62    /// Taker rate.
63    #[serde(with = "string_or_float")]
64    pub taker: f64,
65    /// Buyer rate.
66    #[serde(with = "string_or_float")]
67    pub buyer: f64,
68    /// Seller rate.
69    #[serde(with = "string_or_float")]
70    pub seller: f64,
71}
72
73/// Commission rate details for a symbol.
74#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct CommissionRateDetail {
77    /// Maker rate.
78    #[serde(with = "string_or_float")]
79    pub maker: f64,
80    /// Taker rate.
81    #[serde(with = "string_or_float")]
82    pub taker: f64,
83    /// Buyer rate.
84    #[serde(with = "string_or_float")]
85    pub buyer: f64,
86    /// Seller rate.
87    #[serde(with = "string_or_float")]
88    pub seller: f64,
89}
90
91/// Discount commission information.
92#[derive(Debug, Clone, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct CommissionDiscount {
95    /// Whether discount is enabled for account.
96    pub enabled_for_account: bool,
97    /// Whether discount is enabled for symbol.
98    pub enabled_for_symbol: bool,
99    /// Discount asset (e.g., "BNB").
100    pub discount_asset: String,
101    /// Discount rate.
102    #[serde(with = "string_or_float")]
103    pub discount: f64,
104}
105
106/// Account commission rates for a symbol.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct AccountCommission {
110    /// Symbol.
111    pub symbol: String,
112    /// Standard commission rates from the order.
113    pub standard_commission: CommissionRateDetail,
114    /// Special commission rates from the order.
115    pub special_commission: CommissionRateDetail,
116    /// Tax commission rates from the order.
117    pub tax_commission: CommissionRateDetail,
118    /// Discount information for BNB commissions.
119    pub discount: CommissionDiscount,
120}
121
122/// Commission rate detail for SOR test orders.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct OrderCommissionRate {
126    /// Maker rate.
127    #[serde(with = "string_or_float")]
128    pub maker: f64,
129    /// Taker rate.
130    #[serde(with = "string_or_float")]
131    pub taker: f64,
132}
133
134/// Commission rates returned by test SOR order when enabled.
135#[derive(Debug, Clone, Serialize, Deserialize)]
136#[serde(rename_all = "camelCase")]
137pub struct SorOrderCommissionRates {
138    /// Standard commission rates for the order.
139    pub standard_commission_for_order: OrderCommissionRate,
140    /// Tax commission rates for the order.
141    pub tax_commission_for_order: OrderCommissionRate,
142    /// Discount information for BNB commissions.
143    pub discount: CommissionDiscount,
144}
145
146/// Test SOR order response.
147#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(untagged)]
149pub enum SorOrderTestResponse {
150    /// Empty response when commission rates are not requested.
151    Empty(EmptyResponse),
152    /// Commission rates response.
153    Rates(SorOrderCommissionRates),
154}
155
156/// Prevented match entry (self-trade prevention).
157#[derive(Debug, Clone, Serialize, Deserialize)]
158#[serde(rename_all = "camelCase")]
159pub struct PreventedMatch {
160    /// Symbol.
161    pub symbol: String,
162    /// Prevented match ID.
163    pub prevented_match_id: u64,
164    /// Taker order ID.
165    pub taker_order_id: u64,
166    /// Maker symbol.
167    pub maker_symbol: String,
168    /// Maker order ID.
169    pub maker_order_id: u64,
170    /// Trade group ID.
171    pub trade_group_id: u64,
172    /// Self-trade prevention mode.
173    pub self_trade_prevention_mode: String,
174    /// Price.
175    #[serde(with = "string_or_float")]
176    pub price: f64,
177    /// Maker prevented quantity.
178    #[serde(with = "string_or_float")]
179    pub maker_prevented_quantity: f64,
180    /// Transaction time.
181    pub transact_time: u64,
182}
183
184/// Allocation entry from SOR order placement.
185#[derive(Debug, Clone, Serialize, Deserialize)]
186#[serde(rename_all = "camelCase")]
187pub struct Allocation {
188    /// Symbol.
189    pub symbol: String,
190    /// Allocation ID.
191    pub allocation_id: u64,
192    /// Allocation type (e.g., "SOR").
193    pub allocation_type: String,
194    /// Order ID.
195    pub order_id: u64,
196    /// Order list ID.
197    pub order_list_id: i64,
198    /// Price.
199    #[serde(with = "string_or_float")]
200    pub price: f64,
201    /// Quantity.
202    #[serde(with = "string_or_float")]
203    pub qty: f64,
204    /// Quote quantity.
205    #[serde(with = "string_or_float")]
206    pub quote_qty: f64,
207    /// Commission.
208    #[serde(with = "string_or_float")]
209    pub commission: f64,
210    /// Commission asset.
211    pub commission_asset: String,
212    /// Time.
213    pub time: u64,
214    /// Whether the allocation is buyer side.
215    pub is_buyer: bool,
216    /// Whether the allocation is maker side.
217    pub is_maker: bool,
218    /// Whether this allocation is allocator.
219    pub is_allocator: bool,
220}
221
222/// Account balance for a single asset.
223#[derive(Debug, Clone, Serialize, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub struct Balance {
226    /// Asset symbol (e.g., "BTC").
227    pub asset: String,
228    /// Free (available) balance.
229    #[serde(with = "string_or_float")]
230    pub free: f64,
231    /// Locked balance (in orders).
232    #[serde(with = "string_or_float")]
233    pub locked: f64,
234}
235
236impl Balance {
237    /// Get the total balance (free + locked).
238    pub fn total(&self) -> f64 {
239        self.free + self.locked
240    }
241
242    /// Check if the balance is zero.
243    pub fn is_zero(&self) -> bool {
244        self.free == 0.0 && self.locked == 0.0
245    }
246}
247
248/// Order information.
249#[derive(Debug, Clone, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct Order {
252    /// Symbol.
253    pub symbol: String,
254    /// Order ID.
255    pub order_id: u64,
256    /// Order list ID (-1 if not part of an order list).
257    pub order_list_id: i64,
258    /// Client order ID.
259    pub client_order_id: String,
260    /// Price.
261    #[serde(with = "string_or_float")]
262    pub price: f64,
263    /// Original quantity.
264    #[serde(with = "string_or_float")]
265    pub orig_qty: f64,
266    /// Executed quantity.
267    #[serde(with = "string_or_float")]
268    pub executed_qty: f64,
269    /// Cumulative quote quantity.
270    #[serde(with = "string_or_float")]
271    pub cummulative_quote_qty: f64,
272    /// Order status.
273    pub status: OrderStatus,
274    /// Time in force.
275    pub time_in_force: TimeInForce,
276    /// Order type.
277    #[serde(rename = "type")]
278    pub order_type: OrderType,
279    /// Order side.
280    pub side: OrderSide,
281    /// Stop price.
282    #[serde(with = "string_or_float")]
283    pub stop_price: f64,
284    /// Iceberg quantity.
285    #[serde(with = "string_or_float")]
286    pub iceberg_qty: f64,
287    /// Order creation time.
288    pub time: u64,
289    /// Order update time.
290    pub update_time: u64,
291    /// Is the order working.
292    pub is_working: bool,
293    /// Original quote order quantity.
294    #[serde(with = "string_or_float")]
295    pub orig_quote_order_qty: f64,
296    /// Working time.
297    #[serde(default)]
298    pub working_time: Option<u64>,
299    /// Self-trade prevention mode.
300    #[serde(default)]
301    pub self_trade_prevention_mode: Option<String>,
302}
303
304impl Order {
305    /// Get the average fill price.
306    pub fn avg_price(&self) -> Option<f64> {
307        if self.executed_qty > 0.0 {
308            Some(self.cummulative_quote_qty / self.executed_qty)
309        } else {
310            None
311        }
312    }
313
314    /// Check if the order is fully filled.
315    pub fn is_filled(&self) -> bool {
316        self.status == OrderStatus::Filled
317    }
318
319    /// Check if the order is still active.
320    pub fn is_active(&self) -> bool {
321        matches!(self.status, OrderStatus::New | OrderStatus::PartiallyFilled)
322    }
323}
324
325/// New order response (ACK type).
326#[derive(Debug, Clone, Serialize, Deserialize)]
327#[serde(rename_all = "camelCase")]
328pub struct OrderAck {
329    /// Symbol.
330    pub symbol: String,
331    /// Order ID.
332    pub order_id: u64,
333    /// Order list ID.
334    #[serde(default)]
335    pub order_list_id: i64,
336    /// Client order ID.
337    pub client_order_id: String,
338    /// Transaction time.
339    pub transact_time: u64,
340}
341
342/// New order response (RESULT type).
343#[derive(Debug, Clone, Serialize, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct OrderResult {
346    /// Symbol.
347    pub symbol: String,
348    /// Order ID.
349    pub order_id: u64,
350    /// Order list ID.
351    #[serde(default)]
352    pub order_list_id: i64,
353    /// Client order ID.
354    pub client_order_id: String,
355    /// Transaction time.
356    pub transact_time: u64,
357    /// Price.
358    #[serde(with = "string_or_float")]
359    pub price: f64,
360    /// Original quantity.
361    #[serde(with = "string_or_float")]
362    pub orig_qty: f64,
363    /// Executed quantity.
364    #[serde(with = "string_or_float")]
365    pub executed_qty: f64,
366    /// Cumulative quote quantity.
367    #[serde(with = "string_or_float")]
368    pub cummulative_quote_qty: f64,
369    /// Order status.
370    pub status: OrderStatus,
371    /// Time in force.
372    pub time_in_force: TimeInForce,
373    /// Order type.
374    #[serde(rename = "type")]
375    pub order_type: OrderType,
376    /// Order side.
377    pub side: OrderSide,
378    /// Working time.
379    #[serde(default)]
380    pub working_time: Option<u64>,
381    /// Self-trade prevention mode.
382    #[serde(default)]
383    pub self_trade_prevention_mode: Option<String>,
384}
385
386/// New order response (FULL type).
387#[derive(Debug, Clone, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase")]
389pub struct OrderFull {
390    /// Symbol.
391    pub symbol: String,
392    /// Order ID.
393    pub order_id: u64,
394    /// Order list ID.
395    #[serde(default)]
396    pub order_list_id: i64,
397    /// Client order ID.
398    pub client_order_id: String,
399    /// Transaction time.
400    pub transact_time: u64,
401    /// Price.
402    #[serde(with = "string_or_float")]
403    pub price: f64,
404    /// Original quantity.
405    #[serde(with = "string_or_float")]
406    pub orig_qty: f64,
407    /// Executed quantity.
408    #[serde(with = "string_or_float")]
409    pub executed_qty: f64,
410    /// Cumulative quote quantity.
411    #[serde(with = "string_or_float")]
412    pub cummulative_quote_qty: f64,
413    /// Order status.
414    pub status: OrderStatus,
415    /// Time in force.
416    pub time_in_force: TimeInForce,
417    /// Order type.
418    #[serde(rename = "type")]
419    pub order_type: OrderType,
420    /// Order side.
421    pub side: OrderSide,
422    /// Working time.
423    #[serde(default)]
424    pub working_time: Option<u64>,
425    /// Self-trade prevention mode.
426    #[serde(default)]
427    pub self_trade_prevention_mode: Option<String>,
428    /// Fills (trades that filled this order).
429    #[serde(default)]
430    pub fills: Vec<Fill>,
431}
432
433/// Order fill information.
434#[derive(Debug, Clone, Serialize, Deserialize)]
435#[serde(rename_all = "camelCase")]
436pub struct Fill {
437    /// Fill price.
438    #[serde(with = "string_or_float")]
439    pub price: f64,
440    /// Fill quantity.
441    #[serde(rename = "qty", with = "string_or_float")]
442    pub quantity: f64,
443    /// Commission amount.
444    #[serde(with = "string_or_float")]
445    pub commission: f64,
446    /// Commission asset.
447    pub commission_asset: String,
448    /// Trade ID.
449    #[serde(default)]
450    pub trade_id: Option<u64>,
451}
452
453/// Cancel order response.
454#[derive(Debug, Clone, Serialize, Deserialize)]
455#[serde(rename_all = "camelCase")]
456pub struct CancelOrderResponse {
457    /// Symbol.
458    pub symbol: String,
459    /// Original client order ID.
460    pub orig_client_order_id: String,
461    /// Order ID.
462    pub order_id: u64,
463    /// Order list ID.
464    #[serde(default)]
465    pub order_list_id: i64,
466    /// Client order ID.
467    pub client_order_id: String,
468    /// Price.
469    #[serde(with = "string_or_float")]
470    pub price: f64,
471    /// Original quantity.
472    #[serde(with = "string_or_float")]
473    pub orig_qty: f64,
474    /// Executed quantity.
475    #[serde(with = "string_or_float")]
476    pub executed_qty: f64,
477    /// Cumulative quote quantity.
478    #[serde(with = "string_or_float")]
479    pub cummulative_quote_qty: f64,
480    /// Order status.
481    pub status: OrderStatus,
482    /// Time in force.
483    pub time_in_force: TimeInForce,
484    /// Order type.
485    #[serde(rename = "type")]
486    pub order_type: OrderType,
487    /// Order side.
488    pub side: OrderSide,
489    /// Self-trade prevention mode.
490    #[serde(default)]
491    pub self_trade_prevention_mode: Option<String>,
492}
493
494/// Cancel-replace error info.
495#[derive(Debug, Clone, Serialize, Deserialize)]
496#[serde(rename_all = "camelCase")]
497pub struct CancelReplaceErrorInfo {
498    /// Error code.
499    pub code: i32,
500    /// Error message.
501    pub msg: String,
502}
503
504/// Cancel-replace response payload for a failed request.
505#[derive(Debug, Clone, Serialize, Deserialize)]
506#[serde(rename_all = "camelCase")]
507pub struct CancelReplaceErrorData {
508    /// Cancel result.
509    pub cancel_result: CancelReplaceResult,
510    /// New order result.
511    pub new_order_result: CancelReplaceResult,
512    /// Cancel response payload.
513    pub cancel_response: CancelReplaceSideResponse,
514    /// New order response payload.
515    pub new_order_response: Option<CancelReplaceSideResponse>,
516}
517
518/// Cancel-replace error response wrapper.
519#[derive(Debug, Clone, Serialize, Deserialize)]
520#[serde(rename_all = "camelCase")]
521pub struct CancelReplaceErrorResponse {
522    /// Error code.
523    pub code: i32,
524    /// Error message.
525    pub msg: String,
526    /// Error data payload.
527    pub data: CancelReplaceErrorData,
528}
529
530/// Cancel-replace sub-response payloads.
531#[derive(Debug, Clone, Serialize, Deserialize)]
532#[serde(untagged)]
533pub enum CancelReplaceSideResponse {
534    /// Error response.
535    Error(CancelReplaceErrorInfo),
536    /// Cancel order response.
537    Cancel(CancelOrderResponse),
538    /// New order response.
539    Order(OrderResponse),
540}
541
542/// Cancel-replace order response for a successful request.
543#[derive(Debug, Clone, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct CancelReplaceResponse {
546    /// Cancel result.
547    pub cancel_result: CancelReplaceResult,
548    /// New order result.
549    pub new_order_result: CancelReplaceResult,
550    /// Cancel order response.
551    pub cancel_response: CancelOrderResponse,
552    /// New order response.
553    pub new_order_response: OrderResponse,
554}
555
556/// New order response variants for cancel-replace.
557#[derive(Debug, Clone, Serialize, Deserialize)]
558#[serde(untagged)]
559pub enum OrderResponse {
560    /// ACK response.
561    Ack(OrderAck),
562    /// RESULT response.
563    Result(OrderResult),
564    /// FULL response.
565    Full(OrderFull),
566}
567
568/// User trade (my trades).
569#[derive(Debug, Clone, Serialize, Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct UserTrade {
572    /// Symbol.
573    pub symbol: String,
574    /// Trade ID.
575    pub id: u64,
576    /// Order ID.
577    pub order_id: u64,
578    /// Order list ID.
579    #[serde(default)]
580    pub order_list_id: i64,
581    /// Price.
582    #[serde(with = "string_or_float")]
583    pub price: f64,
584    /// Quantity.
585    #[serde(rename = "qty", with = "string_or_float")]
586    pub quantity: f64,
587    /// Quote quantity.
588    #[serde(rename = "quoteQty", with = "string_or_float")]
589    pub quote_quantity: f64,
590    /// Commission.
591    #[serde(with = "string_or_float")]
592    pub commission: f64,
593    /// Commission asset.
594    pub commission_asset: String,
595    /// Trade time.
596    pub time: u64,
597    /// Was the buyer.
598    pub is_buyer: bool,
599    /// Was the maker.
600    pub is_maker: bool,
601    /// Was best match.
602    pub is_best_match: bool,
603}
604
605/// OCO order information.
606#[derive(Debug, Clone, Serialize, Deserialize)]
607#[serde(rename_all = "camelCase")]
608pub struct OcoOrder {
609    /// Order list ID.
610    pub order_list_id: u64,
611    /// Contingency type.
612    pub contingency_type: ContingencyType,
613    /// List status type.
614    pub list_status_type: OcoStatus,
615    /// List order status.
616    pub list_order_status: OcoOrderStatus,
617    /// List client order ID.
618    pub list_client_order_id: String,
619    /// Transaction time.
620    pub transaction_time: u64,
621    /// Symbol.
622    pub symbol: String,
623    /// Orders in this OCO.
624    pub orders: Vec<OcoOrderDetail>,
625    /// Order reports (detailed info about each order).
626    #[serde(default)]
627    pub order_reports: Vec<OcoOrderReport>,
628}
629
630/// OCO order detail (minimal info).
631#[derive(Debug, Clone, Serialize, Deserialize)]
632#[serde(rename_all = "camelCase")]
633pub struct OcoOrderDetail {
634    /// Symbol.
635    pub symbol: String,
636    /// Order ID.
637    pub order_id: u64,
638    /// Client order ID.
639    pub client_order_id: String,
640}
641
642/// OCO order report (detailed info).
643#[derive(Debug, Clone, Serialize, Deserialize)]
644#[serde(rename_all = "camelCase")]
645pub struct OcoOrderReport {
646    /// Symbol.
647    pub symbol: String,
648    /// Order ID.
649    pub order_id: u64,
650    /// Order list ID.
651    pub order_list_id: i64,
652    /// Client order ID.
653    pub client_order_id: String,
654    /// Transaction time.
655    pub transact_time: u64,
656    /// Price.
657    #[serde(with = "string_or_float")]
658    pub price: f64,
659    /// Original quantity.
660    #[serde(with = "string_or_float")]
661    pub orig_qty: f64,
662    /// Executed quantity.
663    #[serde(with = "string_or_float")]
664    pub executed_qty: f64,
665    /// Cumulative quote quantity.
666    #[serde(with = "string_or_float")]
667    pub cummulative_quote_qty: f64,
668    /// Order status.
669    pub status: OrderStatus,
670    /// Time in force.
671    pub time_in_force: TimeInForce,
672    /// Order type.
673    #[serde(rename = "type")]
674    pub order_type: OrderType,
675    /// Order side.
676    pub side: OrderSide,
677    /// Stop price.
678    #[serde(default, with = "super::market::string_or_float_opt")]
679    pub stop_price: Option<f64>,
680    /// Self-trade prevention mode.
681    #[serde(default)]
682    pub self_trade_prevention_mode: Option<String>,
683}
684
685/// User data stream listen key response.
686#[derive(Debug, Clone, Serialize, Deserialize)]
687#[serde(rename_all = "camelCase")]
688pub struct ListenKey {
689    /// The listen key.
690    pub listen_key: String,
691}
692
693/// Empty response (used for endpoints that return {}).
694#[derive(Debug, Clone, Serialize, Deserialize)]
695pub struct EmptyResponse {}
696
697/// Unfilled order count for a rate limit interval.
698#[derive(Debug, Clone, Serialize, Deserialize)]
699#[serde(rename_all = "camelCase")]
700pub struct UnfilledOrderCount {
701    /// Rate limit type (always "ORDERS").
702    pub rate_limit_type: String,
703    /// Time interval (e.g., "SECOND", "DAY").
704    pub interval: String,
705    /// Interval number (e.g., 10 for "10 seconds").
706    pub interval_num: u32,
707    /// Maximum allowed orders in this interval.
708    pub limit: u32,
709    /// Current count of unfilled orders.
710    pub count: u32,
711}
712
713/// Order amendment history entry.
714#[derive(Debug, Clone, Serialize, Deserialize)]
715#[serde(rename_all = "camelCase")]
716pub struct OrderAmendment {
717    /// Symbol.
718    pub symbol: String,
719    /// Order ID.
720    pub order_id: u64,
721    /// Execution ID.
722    pub execution_id: u64,
723    /// Original client order ID.
724    pub orig_client_order_id: String,
725    /// New client order ID after amendment.
726    pub new_client_order_id: String,
727    /// Original quantity before amendment.
728    #[serde(with = "string_or_float")]
729    pub orig_qty: f64,
730    /// New quantity after amendment.
731    #[serde(with = "string_or_float")]
732    pub new_qty: f64,
733    /// Amendment time.
734    pub time: u64,
735}
736
737/// Amended order details returned from amend endpoint.
738#[derive(Debug, Clone, Serialize, Deserialize)]
739#[serde(rename_all = "camelCase")]
740pub struct AmendedOrderInfo {
741    /// Symbol.
742    pub symbol: String,
743    /// Order ID.
744    pub order_id: u64,
745    /// Order list ID (-1 if not part of a list).
746    pub order_list_id: i64,
747    /// Original client order ID.
748    pub orig_client_order_id: String,
749    /// Client order ID after amendment.
750    pub client_order_id: String,
751    /// Order price.
752    #[serde(with = "string_or_float")]
753    pub price: f64,
754    /// Order quantity after amendment.
755    #[serde(rename = "qty", with = "string_or_float")]
756    pub quantity: f64,
757    /// Executed quantity.
758    #[serde(with = "string_or_float")]
759    pub executed_qty: f64,
760    /// Prevented quantity.
761    #[serde(with = "string_or_float")]
762    pub prevented_qty: f64,
763    /// Quote order quantity.
764    #[serde(with = "string_or_float")]
765    pub quote_order_qty: f64,
766    /// Cumulative quote quantity.
767    #[serde(with = "string_or_float")]
768    pub cumulative_quote_qty: f64,
769    /// Order status.
770    pub status: OrderStatus,
771    /// Time in force.
772    pub time_in_force: TimeInForce,
773    /// Order type.
774    #[serde(rename = "type")]
775    pub order_type: OrderType,
776    /// Order side.
777    pub side: OrderSide,
778    /// Working time.
779    #[serde(default)]
780    pub working_time: Option<u64>,
781    /// Self-trade prevention mode.
782    #[serde(default)]
783    pub self_trade_prevention_mode: Option<String>,
784}
785
786/// Order list status for amended orders that are part of order lists.
787#[derive(Debug, Clone, Serialize, Deserialize)]
788#[serde(rename_all = "camelCase")]
789pub struct AmendListStatus {
790    /// Order list ID.
791    pub order_list_id: u64,
792    /// Contingency type (e.g., "OTO", "OCO").
793    pub contingency_type: ContingencyType,
794    /// List order status.
795    pub list_order_status: OcoOrderStatus,
796    /// List client order ID.
797    pub list_client_order_id: String,
798    /// Symbol.
799    pub symbol: String,
800    /// Orders in this list.
801    pub orders: Vec<OcoOrderDetail>,
802}
803
804/// Response from order amend keep priority endpoint.
805#[derive(Debug, Clone, Serialize, Deserialize)]
806#[serde(rename_all = "camelCase")]
807pub struct AmendOrderResponse {
808    /// Transaction time.
809    pub transact_time: u64,
810    /// Execution ID.
811    pub execution_id: u64,
812    /// Amended order details.
813    pub amended_order: AmendedOrderInfo,
814    /// List status (only present for orders that are part of order lists).
815    #[serde(default)]
816    pub list_status: Option<AmendListStatus>,
817}
818
819#[cfg(test)]
820mod tests {
821    use super::*;
822
823    #[test]
824    fn test_balance_deserialize() {
825        let json = r#"{
826            "asset": "BTC",
827            "free": "1.5",
828            "locked": "0.5"
829        }"#;
830        let balance: Balance = serde_json::from_str(json).unwrap();
831        assert_eq!(balance.asset, "BTC");
832        assert_eq!(balance.free, 1.5);
833        assert_eq!(balance.locked, 0.5);
834        assert_eq!(balance.total(), 2.0);
835        assert!(!balance.is_zero());
836    }
837
838    #[test]
839    fn test_balance_zero() {
840        let balance = Balance {
841            asset: "BTC".to_string(),
842            free: 0.0,
843            locked: 0.0,
844        };
845        assert!(balance.is_zero());
846    }
847
848    #[test]
849    fn test_order_deserialize() {
850        let json = r#"{
851            "symbol": "BTCUSDT",
852            "orderId": 12345,
853            "orderListId": -1,
854            "clientOrderId": "test123",
855            "price": "50000.00",
856            "origQty": "1.0",
857            "executedQty": "0.5",
858            "cummulativeQuoteQty": "25000.00",
859            "status": "PARTIALLY_FILLED",
860            "timeInForce": "GTC",
861            "type": "LIMIT",
862            "side": "BUY",
863            "stopPrice": "0.0",
864            "icebergQty": "0.0",
865            "time": 1234567890123,
866            "updateTime": 1234567890123,
867            "isWorking": true,
868            "origQuoteOrderQty": "0.0"
869        }"#;
870        let order: Order = serde_json::from_str(json).unwrap();
871        assert_eq!(order.symbol, "BTCUSDT");
872        assert_eq!(order.order_id, 12345);
873        assert_eq!(order.price, 50000.0);
874        assert_eq!(order.status, OrderStatus::PartiallyFilled);
875        assert!(order.is_active());
876        assert!(!order.is_filled());
877        assert_eq!(order.avg_price(), Some(50000.0));
878    }
879
880    #[test]
881    fn test_order_full_deserialize() {
882        let json = r#"{
883            "symbol": "BTCUSDT",
884            "orderId": 12345,
885            "orderListId": -1,
886            "clientOrderId": "test123",
887            "transactTime": 1234567890123,
888            "price": "50000.00",
889            "origQty": "1.0",
890            "executedQty": "1.0",
891            "cummulativeQuoteQty": "50000.00",
892            "status": "FILLED",
893            "timeInForce": "GTC",
894            "type": "LIMIT",
895            "side": "BUY",
896            "fills": [
897                {
898                    "price": "50000.00",
899                    "qty": "1.0",
900                    "commission": "0.001",
901                    "commissionAsset": "BTC"
902                }
903            ]
904        }"#;
905        let order: OrderFull = serde_json::from_str(json).unwrap();
906        assert_eq!(order.symbol, "BTCUSDT");
907        assert_eq!(order.status, OrderStatus::Filled);
908        assert_eq!(order.fills.len(), 1);
909        assert_eq!(order.fills[0].price, 50000.0);
910        assert_eq!(order.fills[0].commission, 0.001);
911    }
912
913    #[test]
914    fn test_user_trade_deserialize() {
915        let json = r#"{
916            "symbol": "BTCUSDT",
917            "id": 12345,
918            "orderId": 67890,
919            "price": "50000.00",
920            "qty": "1.0",
921            "quoteQty": "50000.00",
922            "commission": "0.001",
923            "commissionAsset": "BTC",
924            "time": 1234567890123,
925            "isBuyer": true,
926            "isMaker": false,
927            "isBestMatch": true
928        }"#;
929        let trade: UserTrade = serde_json::from_str(json).unwrap();
930        assert_eq!(trade.symbol, "BTCUSDT");
931        assert_eq!(trade.id, 12345);
932        assert_eq!(trade.price, 50000.0);
933        assert!(trade.is_buyer);
934        assert!(!trade.is_maker);
935    }
936
937    #[test]
938    fn test_listen_key_deserialize() {
939        let json = r#"{"listenKey": "abc123xyz"}"#;
940        let key: ListenKey = serde_json::from_str(json).unwrap();
941        assert_eq!(key.listen_key, "abc123xyz");
942    }
943
944    #[test]
945    fn test_account_info_deserialize() {
946        let json = r#"{
947            "makerCommission": 10,
948            "takerCommission": 10,
949            "buyerCommission": 0,
950            "sellerCommission": 0,
951            "canTrade": true,
952            "canWithdraw": true,
953            "canDeposit": true,
954            "updateTime": 1234567890123,
955            "accountType": "SPOT",
956            "balances": [
957                {"asset": "BTC", "free": "1.0", "locked": "0.0"}
958            ],
959            "permissions": ["SPOT"]
960        }"#;
961        let account: AccountInfo = serde_json::from_str(json).unwrap();
962        assert_eq!(account.maker_commission, 10);
963        assert!(account.can_trade);
964        assert_eq!(account.account_type, AccountType::Spot);
965        assert_eq!(account.balances.len(), 1);
966        assert_eq!(account.balances[0].asset, "BTC");
967    }
968}