1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct AccountInfo {
18 pub maker_commission: i32,
20 pub taker_commission: i32,
22 pub buyer_commission: i32,
24 pub seller_commission: i32,
26 #[serde(default)]
28 pub commission_rates: Option<CommissionRates>,
29 pub can_trade: bool,
31 pub can_withdraw: bool,
33 pub can_deposit: bool,
35 #[serde(default)]
37 pub brokered: bool,
38 #[serde(default)]
40 pub require_self_trade_prevention: bool,
41 pub update_time: u64,
43 pub account_type: AccountType,
45 pub balances: Vec<Balance>,
47 #[serde(default)]
49 pub permissions: Vec<AccountType>,
50 #[serde(default)]
52 pub uid: Option<u64>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct CommissionRates {
59 #[serde(with = "string_or_float")]
61 pub maker: f64,
62 #[serde(with = "string_or_float")]
64 pub taker: f64,
65 #[serde(with = "string_or_float")]
67 pub buyer: f64,
68 #[serde(with = "string_or_float")]
70 pub seller: f64,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct CommissionRateDetail {
77 #[serde(with = "string_or_float")]
79 pub maker: f64,
80 #[serde(with = "string_or_float")]
82 pub taker: f64,
83 #[serde(with = "string_or_float")]
85 pub buyer: f64,
86 #[serde(with = "string_or_float")]
88 pub seller: f64,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct CommissionDiscount {
95 pub enabled_for_account: bool,
97 pub enabled_for_symbol: bool,
99 pub discount_asset: String,
101 #[serde(with = "string_or_float")]
103 pub discount: f64,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct AccountCommission {
110 pub symbol: String,
112 pub standard_commission: CommissionRateDetail,
114 pub special_commission: CommissionRateDetail,
116 pub tax_commission: CommissionRateDetail,
118 pub discount: CommissionDiscount,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct OrderCommissionRate {
126 #[serde(with = "string_or_float")]
128 pub maker: f64,
129 #[serde(with = "string_or_float")]
131 pub taker: f64,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136#[serde(rename_all = "camelCase")]
137pub struct SorOrderCommissionRates {
138 pub standard_commission_for_order: OrderCommissionRate,
140 pub tax_commission_for_order: OrderCommissionRate,
142 pub discount: CommissionDiscount,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(untagged)]
149pub enum SorOrderTestResponse {
150 Empty(EmptyResponse),
152 Rates(SorOrderCommissionRates),
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158#[serde(rename_all = "camelCase")]
159pub struct PreventedMatch {
160 pub symbol: String,
162 pub prevented_match_id: u64,
164 pub taker_order_id: u64,
166 pub maker_symbol: String,
168 pub maker_order_id: u64,
170 pub trade_group_id: u64,
172 pub self_trade_prevention_mode: String,
174 #[serde(with = "string_or_float")]
176 pub price: f64,
177 #[serde(with = "string_or_float")]
179 pub maker_prevented_quantity: f64,
180 pub transact_time: u64,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
186#[serde(rename_all = "camelCase")]
187pub struct Allocation {
188 pub symbol: String,
190 pub allocation_id: u64,
192 pub allocation_type: String,
194 pub order_id: u64,
196 pub order_list_id: i64,
198 #[serde(with = "string_or_float")]
200 pub price: f64,
201 #[serde(with = "string_or_float")]
203 pub qty: f64,
204 #[serde(with = "string_or_float")]
206 pub quote_qty: f64,
207 #[serde(with = "string_or_float")]
209 pub commission: f64,
210 pub commission_asset: String,
212 pub time: u64,
214 pub is_buyer: bool,
216 pub is_maker: bool,
218 pub is_allocator: bool,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub struct Balance {
226 pub asset: String,
228 #[serde(with = "string_or_float")]
230 pub free: f64,
231 #[serde(with = "string_or_float")]
233 pub locked: f64,
234}
235
236impl Balance {
237 pub fn total(&self) -> f64 {
239 self.free + self.locked
240 }
241
242 pub fn is_zero(&self) -> bool {
244 self.free == 0.0 && self.locked == 0.0
245 }
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct Order {
252 pub symbol: String,
254 pub order_id: u64,
256 pub order_list_id: i64,
258 pub client_order_id: String,
260 #[serde(with = "string_or_float")]
262 pub price: f64,
263 #[serde(with = "string_or_float")]
265 pub orig_qty: f64,
266 #[serde(with = "string_or_float")]
268 pub executed_qty: f64,
269 #[serde(with = "string_or_float")]
271 pub cummulative_quote_qty: f64,
272 pub status: OrderStatus,
274 pub time_in_force: TimeInForce,
276 #[serde(rename = "type")]
278 pub order_type: OrderType,
279 pub side: OrderSide,
281 #[serde(with = "string_or_float")]
283 pub stop_price: f64,
284 #[serde(with = "string_or_float")]
286 pub iceberg_qty: f64,
287 pub time: u64,
289 pub update_time: u64,
291 pub is_working: bool,
293 #[serde(with = "string_or_float")]
295 pub orig_quote_order_qty: f64,
296 #[serde(default)]
298 pub working_time: Option<u64>,
299 #[serde(default)]
301 pub self_trade_prevention_mode: Option<String>,
302}
303
304impl Order {
305 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 pub fn is_filled(&self) -> bool {
316 self.status == OrderStatus::Filled
317 }
318
319 pub fn is_active(&self) -> bool {
321 matches!(self.status, OrderStatus::New | OrderStatus::PartiallyFilled)
322 }
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
327#[serde(rename_all = "camelCase")]
328pub struct OrderAck {
329 pub symbol: String,
331 pub order_id: u64,
333 #[serde(default)]
335 pub order_list_id: i64,
336 pub client_order_id: String,
338 pub transact_time: u64,
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct OrderResult {
346 pub symbol: String,
348 pub order_id: u64,
350 #[serde(default)]
352 pub order_list_id: i64,
353 pub client_order_id: String,
355 pub transact_time: u64,
357 #[serde(with = "string_or_float")]
359 pub price: f64,
360 #[serde(with = "string_or_float")]
362 pub orig_qty: f64,
363 #[serde(with = "string_or_float")]
365 pub executed_qty: f64,
366 #[serde(with = "string_or_float")]
368 pub cummulative_quote_qty: f64,
369 pub status: OrderStatus,
371 pub time_in_force: TimeInForce,
373 #[serde(rename = "type")]
375 pub order_type: OrderType,
376 pub side: OrderSide,
378 #[serde(default)]
380 pub working_time: Option<u64>,
381 #[serde(default)]
383 pub self_trade_prevention_mode: Option<String>,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase")]
389pub struct OrderFull {
390 pub symbol: String,
392 pub order_id: u64,
394 #[serde(default)]
396 pub order_list_id: i64,
397 pub client_order_id: String,
399 pub transact_time: u64,
401 #[serde(with = "string_or_float")]
403 pub price: f64,
404 #[serde(with = "string_or_float")]
406 pub orig_qty: f64,
407 #[serde(with = "string_or_float")]
409 pub executed_qty: f64,
410 #[serde(with = "string_or_float")]
412 pub cummulative_quote_qty: f64,
413 pub status: OrderStatus,
415 pub time_in_force: TimeInForce,
417 #[serde(rename = "type")]
419 pub order_type: OrderType,
420 pub side: OrderSide,
422 #[serde(default)]
424 pub working_time: Option<u64>,
425 #[serde(default)]
427 pub self_trade_prevention_mode: Option<String>,
428 #[serde(default)]
430 pub fills: Vec<Fill>,
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
435#[serde(rename_all = "camelCase")]
436pub struct Fill {
437 #[serde(with = "string_or_float")]
439 pub price: f64,
440 #[serde(rename = "qty", with = "string_or_float")]
442 pub quantity: f64,
443 #[serde(with = "string_or_float")]
445 pub commission: f64,
446 pub commission_asset: String,
448 #[serde(default)]
450 pub trade_id: Option<u64>,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
455#[serde(rename_all = "camelCase")]
456pub struct CancelOrderResponse {
457 pub symbol: String,
459 pub orig_client_order_id: String,
461 pub order_id: u64,
463 #[serde(default)]
465 pub order_list_id: i64,
466 pub client_order_id: String,
468 #[serde(with = "string_or_float")]
470 pub price: f64,
471 #[serde(with = "string_or_float")]
473 pub orig_qty: f64,
474 #[serde(with = "string_or_float")]
476 pub executed_qty: f64,
477 #[serde(with = "string_or_float")]
479 pub cummulative_quote_qty: f64,
480 pub status: OrderStatus,
482 pub time_in_force: TimeInForce,
484 #[serde(rename = "type")]
486 pub order_type: OrderType,
487 pub side: OrderSide,
489 #[serde(default)]
491 pub self_trade_prevention_mode: Option<String>,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize)]
496#[serde(rename_all = "camelCase")]
497pub struct CancelReplaceErrorInfo {
498 pub code: i32,
500 pub msg: String,
502}
503
504#[derive(Debug, Clone, Serialize, Deserialize)]
506#[serde(rename_all = "camelCase")]
507pub struct CancelReplaceErrorData {
508 pub cancel_result: CancelReplaceResult,
510 pub new_order_result: CancelReplaceResult,
512 pub cancel_response: CancelReplaceSideResponse,
514 pub new_order_response: Option<CancelReplaceSideResponse>,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize)]
520#[serde(rename_all = "camelCase")]
521pub struct CancelReplaceErrorResponse {
522 pub code: i32,
524 pub msg: String,
526 pub data: CancelReplaceErrorData,
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
532#[serde(untagged)]
533pub enum CancelReplaceSideResponse {
534 Error(CancelReplaceErrorInfo),
536 Cancel(CancelOrderResponse),
538 Order(OrderResponse),
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct CancelReplaceResponse {
546 pub cancel_result: CancelReplaceResult,
548 pub new_order_result: CancelReplaceResult,
550 pub cancel_response: CancelOrderResponse,
552 pub new_order_response: OrderResponse,
554}
555
556#[derive(Debug, Clone, Serialize, Deserialize)]
558#[serde(untagged)]
559pub enum OrderResponse {
560 Ack(OrderAck),
562 Result(OrderResult),
564 Full(OrderFull),
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct UserTrade {
572 pub symbol: String,
574 pub id: u64,
576 pub order_id: u64,
578 #[serde(default)]
580 pub order_list_id: i64,
581 #[serde(with = "string_or_float")]
583 pub price: f64,
584 #[serde(rename = "qty", with = "string_or_float")]
586 pub quantity: f64,
587 #[serde(rename = "quoteQty", with = "string_or_float")]
589 pub quote_quantity: f64,
590 #[serde(with = "string_or_float")]
592 pub commission: f64,
593 pub commission_asset: String,
595 pub time: u64,
597 pub is_buyer: bool,
599 pub is_maker: bool,
601 pub is_best_match: bool,
603}
604
605#[derive(Debug, Clone, Serialize, Deserialize)]
607#[serde(rename_all = "camelCase")]
608pub struct OcoOrder {
609 pub order_list_id: u64,
611 pub contingency_type: ContingencyType,
613 pub list_status_type: OcoStatus,
615 pub list_order_status: OcoOrderStatus,
617 pub list_client_order_id: String,
619 pub transaction_time: u64,
621 pub symbol: String,
623 pub orders: Vec<OcoOrderDetail>,
625 #[serde(default)]
627 pub order_reports: Vec<OcoOrderReport>,
628}
629
630#[derive(Debug, Clone, Serialize, Deserialize)]
632#[serde(rename_all = "camelCase")]
633pub struct OcoOrderDetail {
634 pub symbol: String,
636 pub order_id: u64,
638 pub client_order_id: String,
640}
641
642#[derive(Debug, Clone, Serialize, Deserialize)]
644#[serde(rename_all = "camelCase")]
645pub struct OcoOrderReport {
646 pub symbol: String,
648 pub order_id: u64,
650 pub order_list_id: i64,
652 pub client_order_id: String,
654 pub transact_time: u64,
656 #[serde(with = "string_or_float")]
658 pub price: f64,
659 #[serde(with = "string_or_float")]
661 pub orig_qty: f64,
662 #[serde(with = "string_or_float")]
664 pub executed_qty: f64,
665 #[serde(with = "string_or_float")]
667 pub cummulative_quote_qty: f64,
668 pub status: OrderStatus,
670 pub time_in_force: TimeInForce,
672 #[serde(rename = "type")]
674 pub order_type: OrderType,
675 pub side: OrderSide,
677 #[serde(default, with = "super::market::string_or_float_opt")]
679 pub stop_price: Option<f64>,
680 #[serde(default)]
682 pub self_trade_prevention_mode: Option<String>,
683}
684
685#[derive(Debug, Clone, Serialize, Deserialize)]
687#[serde(rename_all = "camelCase")]
688pub struct ListenKey {
689 pub listen_key: String,
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize)]
695pub struct EmptyResponse {}
696
697#[derive(Debug, Clone, Serialize, Deserialize)]
699#[serde(rename_all = "camelCase")]
700pub struct UnfilledOrderCount {
701 pub rate_limit_type: String,
703 pub interval: String,
705 pub interval_num: u32,
707 pub limit: u32,
709 pub count: u32,
711}
712
713#[derive(Debug, Clone, Serialize, Deserialize)]
715#[serde(rename_all = "camelCase")]
716pub struct OrderAmendment {
717 pub symbol: String,
719 pub order_id: u64,
721 pub execution_id: u64,
723 pub orig_client_order_id: String,
725 pub new_client_order_id: String,
727 #[serde(with = "string_or_float")]
729 pub orig_qty: f64,
730 #[serde(with = "string_or_float")]
732 pub new_qty: f64,
733 pub time: u64,
735}
736
737#[derive(Debug, Clone, Serialize, Deserialize)]
739#[serde(rename_all = "camelCase")]
740pub struct AmendedOrderInfo {
741 pub symbol: String,
743 pub order_id: u64,
745 pub order_list_id: i64,
747 pub orig_client_order_id: String,
749 pub client_order_id: String,
751 #[serde(with = "string_or_float")]
753 pub price: f64,
754 #[serde(rename = "qty", with = "string_or_float")]
756 pub quantity: f64,
757 #[serde(with = "string_or_float")]
759 pub executed_qty: f64,
760 #[serde(with = "string_or_float")]
762 pub prevented_qty: f64,
763 #[serde(with = "string_or_float")]
765 pub quote_order_qty: f64,
766 #[serde(with = "string_or_float")]
768 pub cumulative_quote_qty: f64,
769 pub status: OrderStatus,
771 pub time_in_force: TimeInForce,
773 #[serde(rename = "type")]
775 pub order_type: OrderType,
776 pub side: OrderSide,
778 #[serde(default)]
780 pub working_time: Option<u64>,
781 #[serde(default)]
783 pub self_trade_prevention_mode: Option<String>,
784}
785
786#[derive(Debug, Clone, Serialize, Deserialize)]
788#[serde(rename_all = "camelCase")]
789pub struct AmendListStatus {
790 pub order_list_id: u64,
792 pub contingency_type: ContingencyType,
794 pub list_order_status: OcoOrderStatus,
796 pub list_client_order_id: String,
798 pub symbol: String,
800 pub orders: Vec<OcoOrderDetail>,
802}
803
804#[derive(Debug, Clone, Serialize, Deserialize)]
806#[serde(rename_all = "camelCase")]
807pub struct AmendOrderResponse {
808 pub transact_time: u64,
810 pub execution_id: u64,
812 pub amended_order: AmendedOrderInfo,
814 #[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}