1use rust_decimal::Decimal;
2use serde::{Deserialize, Serialize};
3
4use crate::spot::{
5 AccountType, ErrorCode, ExchangeFilter, KlineInterval, OrderResponseType, OrderSide,
6 OrderStatus, OrderType, RateLimitInterval, RateLimiter, STPMode, SymbolStatus, TimeInForce,
7 WorkingFloor,
8};
9
10pub type Timestamp = u64;
11
12#[derive(Debug, PartialEq)]
13pub struct Response<T> {
14 pub result: T,
15 pub headers: Headers,
16}
17
18#[derive(Debug, PartialEq)]
19pub struct Headers {
20 pub retry_after: Option<Timestamp>,
21}
22
23#[derive(Debug, Deserialize, PartialEq)]
24pub struct ResponseError {
25 pub code: ErrorCode,
26 pub msg: String,
27}
28
29#[derive(Debug, Deserialize, PartialEq)]
30pub struct TestConnectivity {}
31
32#[derive(Debug, Deserialize, PartialEq)]
33#[serde(rename_all = "camelCase")]
34pub struct ServerTime {
35 pub server_time: Timestamp,
36}
37
38#[derive(Debug, Serialize, PartialEq)]
39#[serde(rename_all = "camelCase")]
40pub struct GetExchangeInfoParams {
41 pub symbol: Option<String>,
43 pub symbols: Option<Vec<String>>,
48 pub permissions: Option<Vec<String>>,
55 pub show_permission_sets: Option<bool>,
57 pub symbol_status: Option<SymbolStatus>,
60}
61
62#[derive(Debug, Deserialize, PartialEq)]
63#[serde(rename_all = "camelCase")]
64pub struct ExchangeInfo {
65 pub timezone: String,
66 pub server_time: Timestamp,
67 pub rate_limits: Vec<RateLimit>,
68 pub exchange_filters: Vec<ExchangeFilter>,
69 pub symbols: Vec<SymbolInfo>,
70 pub sors: Option<Vec<SOR>>,
73}
74
75#[derive(Debug, Deserialize, PartialEq)]
76#[serde(rename_all = "camelCase")]
77pub struct RateLimit {
78 pub rate_limit_type: RateLimiter,
79 pub interval: RateLimitInterval,
80 pub interval_num: u64,
81 pub limit: u64,
82}
83
84#[derive(Debug, Deserialize, PartialEq)]
85#[serde(rename_all = "camelCase")]
86pub struct SymbolInfo {
87 pub symbol: String,
88 pub status: SymbolStatus,
89 pub base_asset: String,
90 pub base_asset_precision: u8, pub quote_asset: String,
92 pub quote_asset_precision: u8, pub base_commission_precision: u8, pub quote_commission_precision: u8, pub order_types: Vec<OrderType>,
97 pub iceberg_allowed: bool,
98 pub oco_allowed: bool,
99 pub oto_allowed: bool,
100 pub quote_order_qty_market_allowed: bool,
101 pub allow_trailing_stop: bool,
102 pub cancel_replace_allowed: bool,
103 pub amend_allowed: bool,
104 pub is_spot_trading_allowed: bool,
105 pub is_margin_trading_allowed: bool,
106 pub filters: Vec<Filter>,
107 pub permissions: Vec<String>,
108 pub permission_sets: Vec<Vec<String>>,
109 pub default_self_trade_prevention_mode: STPMode,
110 pub allowed_self_trade_prevention_modes: Vec<STPMode>,
111}
112
113#[derive(Debug, Deserialize, PartialEq)]
114#[serde(rename_all = "camelCase")]
115pub struct Filter {
116 }
118
119#[derive(Debug, Deserialize, PartialEq)]
121#[serde(rename_all = "camelCase")]
122pub struct SOR {
123 pub base_asset: String,
124 pub symbols: Vec<String>,
125}
126
127#[derive(Debug, Serialize, PartialEq)]
128#[serde(rename_all = "camelCase")]
129pub struct GetOrderBookParams {
130 pub symbol: String,
131 pub limit: Option<u64>,
134}
135
136#[derive(Debug, Deserialize, PartialEq)]
137#[serde(rename_all = "camelCase")]
138pub struct OrderBook {
139 pub last_update_id: i64,
140 pub bids: Vec<OrderLevel>,
141 pub asks: Vec<OrderLevel>,
142}
143
144#[derive(Debug, Deserialize, PartialEq)]
145pub struct OrderLevel(Decimal, Decimal);
146
147impl OrderLevel {
148 pub fn price(&self) -> Decimal {
149 self.0
150 }
151 pub fn qty(&self) -> Decimal {
152 self.0
153 }
154}
155
156#[derive(Debug, Serialize, PartialEq)]
157#[serde(rename_all = "camelCase")]
158pub struct GetRecentTradesParams {
159 pub symbol: String,
160 pub limit: Option<u64>,
162}
163
164#[derive(Debug, Deserialize, PartialEq)]
165#[serde(rename_all = "camelCase")]
166pub struct RecentTrade {
167 pub id: i64,
168 pub price: Decimal,
169 pub qty: Decimal,
170 pub quote_qty: Decimal,
171 pub time: Timestamp,
172 pub is_buyer_maker: bool,
173 pub is_best_match: bool,
174}
175
176#[derive(Debug, Serialize, PartialEq)]
177#[serde(rename_all = "camelCase")]
178pub struct GetOlderTradesParams {
179 pub symbol: String,
180 pub limit: Option<u64>,
182 pub from_id: Option<i64>,
184}
185
186#[derive(Debug, Serialize, PartialEq)]
187#[serde(rename_all = "camelCase")]
188pub struct GetAggregateTradesParams {
189 pub symbol: String,
190 pub from_id: Option<i64>,
192 pub start_time: Option<Timestamp>,
194 pub end_time: Option<Timestamp>,
196 pub limit: Option<u64>,
198}
199
200#[derive(Debug, Deserialize, PartialEq)]
201pub struct AggregateTrade {
202 #[serde(rename = "a")]
204 pub id: i64,
205 #[serde(rename = "p")]
207 pub price: Decimal,
208 #[serde(rename = "q")]
210 pub qty: Decimal,
211 #[serde(rename = "f")]
213 pub first_trade_id: i64,
214 #[serde(rename = "l")]
216 pub last_trade_id: i64,
217 #[serde(rename = "T")]
219 pub time: Timestamp,
220 #[serde(rename = "m")]
222 pub is_buyer_maker: bool,
223 #[serde(rename = "M")]
225 pub is_best_match: bool,
226}
227
228#[derive(Debug, Serialize, PartialEq)]
229#[serde(rename_all = "camelCase")]
230pub struct GetKlineListParams {
231 pub symbol: String,
232 pub interval: KlineInterval,
233 pub start_time: Option<Timestamp>,
234 pub end_time: Option<Timestamp>,
235 pub time_zone: Option<String>,
236 pub limit: Option<u64>,
238}
239
240#[derive(Debug, Deserialize, PartialEq)]
241pub struct Kline(
242 Timestamp, Decimal, Decimal, Decimal, Decimal, Decimal, Timestamp, Decimal, u64, Decimal, Decimal, String, );
255
256impl Kline {
257 pub fn time_open(&self) -> Timestamp {
259 self.0
260 }
261 pub fn open(&self) -> Decimal {
263 self.1
264 }
265 pub fn high(&self) -> Decimal {
267 self.2
268 }
269 pub fn low(&self) -> Decimal {
271 self.3
272 }
273 pub fn close(&self) -> Decimal {
275 self.4
276 }
277 pub fn volume(&self) -> Decimal {
279 self.5
280 }
281 pub fn time_close(&self) -> Timestamp {
283 self.6
284 }
285 pub fn quote_asset_volume(&self) -> Decimal {
287 self.7
288 }
289 pub fn id(&self) -> u64 {
291 self.8
292 }
293 pub fn taker_buy_base_asset_volume(&self) -> Decimal {
295 self.9
296 }
297 pub fn taker_buy_quote_asset_volume(&self) -> Decimal {
299 self.10
300 }
301}
302
303#[derive(Debug, Serialize, PartialEq)]
304#[serde(rename_all = "camelCase")]
305pub struct GetCurrentAveragePriceParams {
306 pub symbol: String,
307}
308
309#[derive(Debug, Deserialize, PartialEq)]
310#[serde(rename_all = "camelCase")]
311pub struct CurrentAveragePrice {
312 pub mins: u64,
314 pub price: Decimal,
316 pub close_time: Timestamp,
318}
319
320#[derive(Debug, Serialize, PartialEq)]
323#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
324pub enum GetTickerPriceChangeStatisticsParams {
325 Mini(SymbolOrSymbols),
326 Full(SymbolOrSymbols),
327}
328
329#[derive(Debug, Serialize, PartialEq)]
330pub struct SymbolOrSymbols {
331 pub symbol: Option<String>,
334 pub symbols: Option<Vec<String>>,
339}
340
341#[derive(Debug, Deserialize, PartialEq)]
342#[serde(untagged)]
343pub enum TickerPriceChangeStatistic {
344 MiniElement(TickerPriceChangeStatisticMini),
345 MiniList(Vec<TickerPriceChangeStatisticMini>),
346 FullElement(TickerPriceChangeStatisticFull),
347 FullList(Vec<TickerPriceChangeStatisticFull>),
348}
349
350#[derive(Debug, Deserialize, PartialEq)]
351#[serde(rename_all = "camelCase")]
352pub struct TickerPriceChangeStatisticFull {
353 pub symbol: String,
354 pub price_change: Decimal,
355 pub price_change_percent: Decimal,
356 pub weighted_avg_price: Decimal,
357 pub prev_close_price: Decimal,
358 pub last_price: Decimal,
359 pub last_qty: Decimal,
360 pub bid_price: Decimal,
361 pub bid_qty: Decimal,
362 pub ask_price: Decimal,
363 pub ask_qty: Decimal,
364 pub open_price: Decimal,
365 pub high_price: Decimal,
366 pub low_price: Decimal,
367 pub volume: Decimal,
368 pub quote_volume: Decimal,
369 pub open_time: Timestamp,
370 pub close_time: Timestamp,
371 pub first_id: i64,
373 pub last_id: i64,
375 pub count: u64,
377}
378
379#[derive(Debug, Deserialize, PartialEq)]
380#[serde(rename_all = "camelCase")]
381pub struct TickerPriceChangeStatisticMini {
382 pub symbol: String,
384 pub open_price: Decimal,
386 pub high_price: Decimal,
388 pub low_price: Decimal,
390 pub last_price: Decimal,
392 pub volume: Decimal,
394 pub quote_volume: Decimal,
396 pub open_time: Timestamp,
398 pub close_time: Timestamp,
400 pub first_id: i64,
402 pub last_id: i64,
404 pub count: u64,
406}
407
408#[derive(Debug, Serialize, PartialEq)]
409#[serde(rename_all = "camelCase")]
410pub struct NewOrderParams {
411 pub symbol: String,
412 pub side: OrderSide,
413 #[serde(rename = "type")]
414 pub order_type: OrderType,
415 pub time_in_force: Option<TimeInForce>,
416 pub quantity: Option<Decimal>,
417 pub quote_order_qty: Option<Decimal>,
418 pub price: Option<Decimal>,
419 pub new_client_order_id: Option<String>,
422 pub strategy_id: Option<i64>,
423 pub strategy_type: Option<i64>,
425 pub stop_price: Option<Decimal>,
427 pub trailing_delta: Option<i64>,
429 pub iceberg_qty: Option<Decimal>,
431 pub new_order_resp_type: OrderResponseType,
434 pub self_trade_prevention_mode: Option<STPMode>,
436 pub recv_window: Option<i64>,
438 pub timestamp: Timestamp,
439}
440
441impl NewOrderParams {
442 pub fn is_valid(&self) -> bool {
443 match self.order_type {
444 OrderType::Limit => {
445 self.time_in_force.is_some() && self.quantity.is_some() && self.price.is_some()
446 }
447 OrderType::Market => {
448 self.quantity.is_some() || self.quote_order_qty.is_some()
456 }
457 OrderType::StopLoss => {
458 self.quantity.is_some()
460 && (self.stop_price.is_some() || self.trailing_delta.is_some())
461 }
462 OrderType::StopLossLimit => {
463 self.time_in_force.is_some()
464 && self.quantity.is_some()
465 && self.price.is_some()
466 && (self.stop_price.is_some() || self.trailing_delta.is_some())
467 }
468 OrderType::TakeProfit => {
469 self.quantity.is_some()
471 && (self.stop_price.is_some() || self.trailing_delta.is_some())
472 }
473 OrderType::TakeProfitLimit => {
474 self.time_in_force.is_some()
475 && self.quantity.is_some()
476 && self.price.is_some()
477 && (self.stop_price.is_some() || self.trailing_delta.is_some())
478 }
479 OrderType::LimitMaker => {
480 self.quantity.is_some() && self.price.is_some()
483 }
484 }
485 }
486}
487
488#[derive(Debug, Deserialize, PartialEq)]
489#[serde(untagged)]
490pub enum NewOrderResponse {
491 Full(NewOrderResponseFull),
492 Result(NewOrderResponseResult),
493 Ack(NewOrderResponseAck),
494}
495
496#[derive(Debug, Deserialize, PartialEq)]
497#[serde(rename_all = "camelCase")]
498pub struct NewOrderResponseAck {
499 pub symbol: String,
500 pub order_id: i64,
501 pub order_list_id: i64,
503 pub client_order_id: String,
504 pub transact_time: Timestamp,
505 pub iceberg_qty: Option<Decimal>,
508 pub prevented_match_id: Option<i64>,
511 pub prevented_quantity: Option<Decimal>,
514 pub stop_price: Option<Decimal>,
517 pub strategy_id: Option<i64>,
520 pub strategy_type: Option<i64>,
523 pub trailing_delta: Option<i64>,
526 pub trailing_time: Option<i64>,
529 pub used_sor: Option<bool>,
532 pub working_floor: Option<WorkingFloor>,
535}
536
537#[derive(Debug, Deserialize, PartialEq)]
538#[serde(rename_all = "camelCase")]
539pub struct NewOrderResponseResult {
540 pub symbol: String,
541 pub order_id: i64,
542 pub order_list_id: i64,
544 pub client_order_id: String,
545 pub transact_time: Timestamp,
546 pub price: Decimal,
547 pub orig_qty: Decimal,
548 pub executed_qty: Decimal,
549 pub orig_quote_order_qty: Decimal,
550 pub cummulative_quote_qty: Decimal,
551 pub status: OrderStatus,
552 pub time_in_force: TimeInForce,
553 #[serde(rename = "type")]
554 pub order_type: OrderType,
555 pub side: OrderSide,
556 pub working_time: Timestamp,
557 pub self_trade_prevention_mode: STPMode,
558 pub iceberg_qty: Option<Decimal>,
561 pub prevented_match_id: Option<i64>,
564 pub prevented_quantity: Option<Decimal>,
567 pub stop_price: Option<Decimal>,
570 pub strategy_id: Option<i64>,
573 pub strategy_type: Option<i64>,
576 pub trailing_delta: Option<i64>,
579 pub trailing_time: Option<i64>,
582 pub used_sor: Option<bool>,
585 pub working_floor: Option<WorkingFloor>,
588}
589
590#[derive(Debug, Deserialize, PartialEq)]
591#[serde(rename_all = "camelCase")]
592pub struct NewOrderResponseFull {
593 pub symbol: String,
594 pub order_id: i64,
595 pub order_list_id: i64,
597 pub client_order_id: String,
598 pub transact_time: Timestamp,
599 pub price: Decimal,
600 pub orig_qty: Decimal,
601 pub executed_qty: Decimal,
602 pub orig_quote_order_qty: Decimal,
603 pub cummulative_quote_qty: Decimal,
604 pub status: OrderStatus,
605 pub time_in_force: TimeInForce,
606 #[serde(rename = "type")]
607 pub order_type: OrderType,
608 pub side: OrderSide,
609 pub working_time: Timestamp,
610 pub self_trade_prevention_mode: STPMode,
611 pub fills: Vec<OrderFill>,
612 pub iceberg_qty: Option<Decimal>,
615 pub prevented_match_id: Option<i64>,
618 pub prevented_quantity: Option<Decimal>,
621 pub stop_price: Option<Decimal>,
624 pub strategy_id: Option<i64>,
627 pub strategy_type: Option<i64>,
630 pub trailing_delta: Option<i64>,
633 pub trailing_time: Option<i64>,
636 pub used_sor: Option<bool>,
639 pub working_floor: Option<WorkingFloor>,
642}
643
644#[derive(Debug, Deserialize, PartialEq)]
645#[serde(rename_all = "camelCase")]
646pub struct OrderFill {
647 pub price: Decimal,
648 pub qty: Decimal,
649 pub commission: Decimal,
650 pub commission_asset: String,
651 pub trade_id: i64,
652}
653
654#[derive(Debug, Deserialize, PartialEq)]
655#[serde(untagged)]
656pub enum TestCommissionRates {
657 Full(TestCommissionRatesFull),
658 Empty(TestCommissionRatesEmpty),
659}
660
661#[derive(Debug, Deserialize, PartialEq)]
662pub struct TestCommissionRatesEmpty {}
663
664#[derive(Debug, Deserialize, PartialEq)]
665#[serde(rename_all = "camelCase")]
666pub struct TestCommissionRatesFull {
667 pub standard_commission_for_order: CommissionForOrder,
669 pub tax_commission_for_order: CommissionForOrder,
671 pub discount: Discount,
673}
674#[derive(Debug, Deserialize, PartialEq)]
675#[serde(rename_all = "camelCase")]
676pub struct CommissionForOrder {
677 pub maker: Decimal,
678 pub taker: Decimal,
679}
680
681#[derive(Debug, Deserialize, PartialEq)]
682#[serde(rename_all = "camelCase")]
683pub struct Discount {
684 pub enabled_for_account: bool,
685 pub enabled_for_symbol: bool,
686 pub discount_asset: String,
687 pub discount: Decimal,
689}
690
691#[derive(Debug, Serialize, PartialEq)]
692#[serde(rename_all = "camelCase")]
693pub struct GetAccountInformationParams {
694 pub omit_zero_balances: Option<bool>,
697 pub recv_window: Option<i64>,
699 pub timestamp: Timestamp,
700}
701
702#[derive(Debug, Deserialize, PartialEq)]
703#[serde(rename_all = "camelCase")]
704pub struct AccountInformation {
705 pub maker_commission: f64,
706 pub taker_commission: f64,
707 pub buyer_commission: f64,
708 pub seller_commission: f64,
709 pub commission_rates: CommissionRates,
710 pub can_trade: bool,
711 pub can_withdraw: bool,
712 pub can_deposit: bool,
713 pub brokered: bool,
714 pub require_self_trade_prevention: bool,
715 pub prevent_sor: bool,
716 pub update_time: Timestamp,
717 pub account_type: AccountType,
718 pub balances: Vec<Balance>,
719 pub permissions: Option<Vec<String>>,
720 pub permission_sets: Option<Vec<Vec<String>>>,
721 pub uid: i64,
722}
723
724#[derive(Debug, Deserialize, PartialEq)]
725#[serde(rename_all = "camelCase")]
726pub struct CommissionRates {
727 pub maker: Decimal,
728 pub taker: Decimal,
729 pub buyer: Decimal,
730 pub seller: Decimal,
731}
732
733#[derive(Debug, Deserialize, PartialEq)]
734#[serde(rename_all = "camelCase")]
735pub struct Balance {
736 pub asset: String,
737 pub free: Decimal,
738 pub locked: Decimal,
739}
740
741#[derive(Debug, Serialize, PartialEq)]
742#[serde(rename_all = "camelCase")]
743pub struct QueryOrderParams {
744 pub symbol: String,
745 pub order_id: Option<i64>,
746 pub orig_client_order_id: Option<String>,
747 pub recv_window: Option<i64>,
749 pub timestamp: Timestamp,
750}
751
752#[derive(Debug, Deserialize, PartialEq)]
753#[serde(rename_all = "camelCase")]
754pub struct Order {
755 pub symbol: String,
756 pub order_id: i64,
757 pub order_list_id: i64,
759 pub client_order_id: String,
760 pub price: Decimal,
761 pub orig_qty: Decimal,
762 pub executed_qty: Decimal,
763 pub cummulative_quote_qty: Decimal,
764 pub status: OrderStatus,
765 pub time_in_force: TimeInForce,
766 #[serde(rename = "type")]
767 pub order_type: OrderType,
768 pub side: OrderSide,
769 pub stop_price: Option<Decimal>,
772 pub iceberg_qty: Option<Decimal>,
775 pub time: Timestamp,
776 pub update_time: Timestamp,
777 pub is_working: bool,
778 pub working_time: Timestamp,
779 pub orig_quote_order_qty: Decimal,
780 pub self_trade_prevention_mode: STPMode,
781}
782
783#[cfg(test)]
784mod tests {
785 use rust_decimal::dec;
786
787 use crate::spot::serde::deserialize_str;
788
789 use super::*;
790
791 #[test]
792 fn deserialize_response_server_time() {
793 let json = r#"{
794 "serverTime": 1499827319559
795 }"#;
796 let expected = ServerTime {
797 server_time: 1499827319559,
798 };
799
800 let current = deserialize_str(json).unwrap();
801
802 assert_eq!(expected, current);
803 }
804
805 #[test]
806 fn deserialize_response_exchange_info() {
807 let json = r#"{
808 "timezone": "UTC",
809 "serverTime": 1565246363776,
810 "rateLimits": [],
811 "exchangeFilters": [],
812 "symbols": [
813 {
814 "symbol": "ETHBTC",
815 "status": "TRADING",
816 "baseAsset": "ETH",
817 "baseAssetPrecision": 8,
818 "quoteAsset": "BTC",
819 "quotePrecision": 8,
820 "quoteAssetPrecision": 8,
821 "baseCommissionPrecision": 8,
822 "quoteCommissionPrecision": 8,
823 "orderTypes": [
824 "LIMIT",
825 "LIMIT_MAKER",
826 "MARKET",
827 "STOP_LOSS",
828 "STOP_LOSS_LIMIT",
829 "TAKE_PROFIT",
830 "TAKE_PROFIT_LIMIT"
831 ],
832 "icebergAllowed": true,
833 "ocoAllowed": true,
834 "otoAllowed": true,
835 "quoteOrderQtyMarketAllowed": true,
836 "allowTrailingStop": false,
837 "cancelReplaceAllowed":false,
838 "amendAllowed":false,
839 "isSpotTradingAllowed": true,
840 "isMarginTradingAllowed": true,
841 "filters": [],
842 "permissions": [],
843 "permissionSets": [
844 [
845 "SPOT",
846 "MARGIN"
847 ]
848 ],
849 "defaultSelfTradePreventionMode": "NONE",
850 "allowedSelfTradePreventionModes": [
851 "NONE"
852 ]
853 }
854 ],
855 "sors": [
856 {
857 "baseAsset": "BTC",
858 "symbols": [
859 "BTCUSDT",
860 "BTCUSDC"
861 ]
862 }
863 ]
864 }"#;
865 let expected = ExchangeInfo {
866 timezone: String::from("UTC"),
867 server_time: 1565246363776,
868 rate_limits: vec![],
869 exchange_filters: vec![],
870 symbols: vec![SymbolInfo {
871 symbol: String::from("ETHBTC"),
872 status: SymbolStatus::Trading,
873 base_asset: String::from("ETH"),
874 base_asset_precision: 8,
875 quote_asset: String::from("BTC"),
876 quote_asset_precision: 8,
877 base_commission_precision: 8,
878 quote_commission_precision: 8,
879 order_types: vec![
880 OrderType::Limit,
881 OrderType::LimitMaker,
882 OrderType::Market,
883 OrderType::StopLoss,
884 OrderType::StopLossLimit,
885 OrderType::TakeProfit,
886 OrderType::TakeProfitLimit,
887 ],
888 iceberg_allowed: true,
889 oco_allowed: true,
890 oto_allowed: true,
891 quote_order_qty_market_allowed: true,
892 allow_trailing_stop: false,
893 cancel_replace_allowed: false,
894 amend_allowed: false,
895 is_spot_trading_allowed: true,
896 is_margin_trading_allowed: true,
897 filters: vec![],
898 permissions: vec![],
899 permission_sets: vec![vec![String::from("SPOT"), String::from("MARGIN")]],
900 default_self_trade_prevention_mode: STPMode::None,
901 allowed_self_trade_prevention_modes: vec![STPMode::None],
902 }],
903 sors: Some(vec![SOR {
904 base_asset: String::from("BTC"),
905 symbols: vec![String::from("BTCUSDT"), String::from("BTCUSDC")],
906 }]),
907 };
908
909 let current = deserialize_str(json).unwrap();
910
911 assert_eq!(expected, current);
912 }
913
914 #[test]
915 fn deserialize_response_order_book() {
916 let json = r#"{
917 "lastUpdateId": 1027024,
918 "bids": [
919 [
920 "4.00000000",
921 "431.00000000"
922 ]
923 ],
924 "asks": [
925 [
926 "4.00000200",
927 "12.00000000"
928 ]
929 ]
930 }"#;
931 let expected = OrderBook {
932 last_update_id: 1027024,
933 bids: vec![OrderLevel(dec!(4.00000000), dec!(431.00000000))],
934 asks: vec![OrderLevel(dec!(4.00000200), dec!(12.00000000))],
935 };
936
937 let current = deserialize_str(json).unwrap();
938
939 assert_eq!(expected, current);
940 }
941
942 #[test]
943 fn deserialize_response_order_ack() {
944 let json = r#"{
945 "symbol": "BTCUSDT",
946 "orderId": 28,
947 "orderListId": -1,
948 "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP",
949 "transactTime": 1507725176595
950 }"#;
951 let response = NewOrderResponseAck {
952 symbol: String::from("BTCUSDT"),
953 order_id: 28,
954 order_list_id: -1,
955 client_order_id: String::from("6gCrw2kRUAF9CvJDGP16IP"),
956 transact_time: 1507725176595,
957 iceberg_qty: None,
958 prevented_match_id: None,
959 prevented_quantity: None,
960 stop_price: None,
961 strategy_id: None,
962 strategy_type: None,
963 trailing_delta: None,
964 trailing_time: None,
965 used_sor: None,
966 working_floor: None,
967 };
968 let expected = NewOrderResponse::Ack(response);
969
970 let current = deserialize_str(json).unwrap();
971
972 assert_eq!(expected, current);
973 }
974
975 #[test]
976 fn deserialize_response_order_result() {
977 let json = r#"{
978 "symbol": "BTCUSDT",
979 "orderId": 28,
980 "orderListId": -1,
981 "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP",
982 "transactTime": 1507725176595,
983 "price": "0.00000000",
984 "origQty": "10.00000000",
985 "executedQty": "10.00000000",
986 "origQuoteOrderQty": "0.000000",
987 "cummulativeQuoteQty": "10.00000000",
988 "status": "FILLED",
989 "timeInForce": "GTC",
990 "type": "MARKET",
991 "side": "SELL",
992 "workingTime": 1507725176595,
993 "selfTradePreventionMode": "NONE"
994 }"#;
995 let response = NewOrderResponseResult {
996 symbol: String::from("BTCUSDT"),
997 order_id: 28,
998 order_list_id: -1,
999 client_order_id: String::from("6gCrw2kRUAF9CvJDGP16IP"),
1000 transact_time: 1507725176595,
1001 price: dec!(0.00000000),
1002 orig_qty: dec!(10.00000000),
1003 executed_qty: dec!(10.00000000),
1004 orig_quote_order_qty: dec!(0.00000000),
1005 cummulative_quote_qty: dec!(10.00000000),
1006 status: OrderStatus::Filled,
1007 time_in_force: TimeInForce::GTC,
1008 order_type: OrderType::Market,
1009 side: OrderSide::SELL,
1010 working_time: 1507725176595,
1011 self_trade_prevention_mode: STPMode::None,
1012 iceberg_qty: None,
1013 prevented_match_id: None,
1014 prevented_quantity: None,
1015 stop_price: None,
1016 strategy_id: None,
1017 strategy_type: None,
1018 trailing_delta: None,
1019 trailing_time: None,
1020 used_sor: None,
1021 working_floor: None,
1022 };
1023 let expected = NewOrderResponse::Result(response);
1024
1025 let current = deserialize_str(json).unwrap();
1026
1027 assert_eq!(expected, current);
1028 }
1029
1030 #[test]
1031 fn deserialize_response_order_full() {
1032 let json = r#"{
1033 "symbol": "BTCUSDT",
1034 "orderId": 28,
1035 "orderListId": -1,
1036 "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP",
1037 "transactTime": 1507725176595,
1038 "price": "0.00000000",
1039 "origQty": "10.00000000",
1040 "executedQty": "10.00000000",
1041 "origQuoteOrderQty": "0.000000",
1042 "cummulativeQuoteQty": "10.00000000",
1043 "status": "FILLED",
1044 "timeInForce": "GTC",
1045 "type": "MARKET",
1046 "side": "SELL",
1047 "workingTime": 1507725176595,
1048 "selfTradePreventionMode": "NONE",
1049 "fills": [
1050 {
1051 "price": "4000.00000000",
1052 "qty": "1.00000000",
1053 "commission": "4.00000000",
1054 "commissionAsset": "USDT",
1055 "tradeId": 56
1056 },
1057 {
1058 "price": "3999.00000000",
1059 "qty": "5.00000000",
1060 "commission": "19.99500000",
1061 "commissionAsset": "USDT",
1062 "tradeId": 57
1063 },
1064 {
1065 "price": "3998.00000000",
1066 "qty": "2.00000000",
1067 "commission": "7.99600000",
1068 "commissionAsset": "USDT",
1069 "tradeId": 58
1070 },
1071 {
1072 "price": "3997.00000000",
1073 "qty": "1.00000000",
1074 "commission": "3.99700000",
1075 "commissionAsset": "USDT",
1076 "tradeId": 59
1077 },
1078 {
1079 "price": "3995.00000000",
1080 "qty": "1.00000000",
1081 "commission": "3.99500000",
1082 "commissionAsset": "USDT",
1083 "tradeId": 60
1084 }
1085 ]
1086 }"#;
1087 let response = NewOrderResponseFull {
1088 symbol: String::from("BTCUSDT"),
1089 order_id: 28,
1090 order_list_id: -1,
1091 client_order_id: String::from("6gCrw2kRUAF9CvJDGP16IP"),
1092 transact_time: 1507725176595,
1093 price: dec!(0.00000000),
1094 orig_qty: dec!(10.00000000),
1095 executed_qty: dec!(10.00000000),
1096 orig_quote_order_qty: dec!(0.00000000),
1097 cummulative_quote_qty: dec!(10.00000000),
1098 status: OrderStatus::Filled,
1099 time_in_force: TimeInForce::GTC,
1100 order_type: OrderType::Market,
1101 side: OrderSide::SELL,
1102 working_time: 1507725176595,
1103 self_trade_prevention_mode: STPMode::None,
1104 fills: vec![
1105 OrderFill {
1106 price: dec!(4000.00000000),
1107 qty: dec!(1.00000000),
1108 commission: dec!(4.00000000),
1109 commission_asset: String::from("USDT"),
1110 trade_id: 56,
1111 },
1112 OrderFill {
1113 price: dec!(3999.00000000),
1114 qty: dec!(5.00000000),
1115 commission: dec!(19.99500000),
1116 commission_asset: String::from("USDT"),
1117 trade_id: 57,
1118 },
1119 OrderFill {
1120 price: dec!(3998.00000000),
1121 qty: dec!(2.00000000),
1122 commission: dec!(7.99600000),
1123 commission_asset: String::from("USDT"),
1124 trade_id: 58,
1125 },
1126 OrderFill {
1127 price: dec!(3997.00000000),
1128 qty: dec!(1.00000000),
1129 commission: dec!(3.99700000),
1130 commission_asset: String::from("USDT"),
1131 trade_id: 59,
1132 },
1133 OrderFill {
1134 price: dec!(3995.00000000),
1135 qty: dec!(1.00000000),
1136 commission: dec!(3.99500000),
1137 commission_asset: String::from("USDT"),
1138 trade_id: 60,
1139 },
1140 ],
1141 iceberg_qty: None,
1142 prevented_match_id: None,
1143 prevented_quantity: None,
1144 stop_price: None,
1145 strategy_id: None,
1146 strategy_type: None,
1147 trailing_delta: None,
1148 trailing_time: None,
1149 used_sor: None,
1150 working_floor: None,
1151 };
1152 let expected = NewOrderResponse::Full(response);
1153
1154 let current = deserialize_str(json).unwrap();
1155
1156 assert_eq!(expected, current);
1157 }
1158
1159 #[test]
1160 fn deserialize_response_test_order_commission_rates_empty() {
1161 let json = r#"{}"#;
1162 let expected = TestCommissionRates::Empty(TestCommissionRatesEmpty {});
1163
1164 let current = deserialize_str(json).unwrap();
1165
1166 assert_eq!(expected, current);
1167 }
1168
1169 #[test]
1170 fn deserialize_response_test_order_commission_rates_full() {
1171 let json = r#"{
1172 "standardCommissionForOrder": {
1173 "maker": "0.00000112",
1174 "taker": "0.00000114"
1175 },
1176 "taxCommissionForOrder": {
1177 "maker": "0.00000112",
1178 "taker": "0.00000114"
1179 },
1180 "discount": {
1181 "enabledForAccount": true,
1182 "enabledForSymbol": true,
1183 "discountAsset": "BNB",
1184 "discount": "0.25000000"
1185 }
1186 }"#;
1187 let rates = TestCommissionRatesFull {
1188 standard_commission_for_order: CommissionForOrder {
1189 maker: dec!(0.00000112),
1190 taker: dec!(0.00000114),
1191 },
1192 tax_commission_for_order: CommissionForOrder {
1193 maker: dec!(0.00000112),
1194 taker: dec!(0.00000114),
1195 },
1196 discount: Discount {
1197 enabled_for_account: true,
1198 enabled_for_symbol: true,
1199 discount_asset: String::from("BNB"),
1200 discount: dec!(0.25000000),
1201 },
1202 };
1203 let expected = TestCommissionRates::Full(rates);
1204
1205 let current = deserialize_str(json).unwrap();
1206
1207 assert_eq!(expected, current);
1208 }
1209
1210 #[test]
1211 fn deserialize_response_account_information() {
1212 let json = r#"{
1213 "makerCommission": 15,
1214 "takerCommission": 15,
1215 "buyerCommission": 0,
1216 "sellerCommission": 0,
1217 "commissionRates": {
1218 "maker": "0.00150000",
1219 "taker": "0.00150000",
1220 "buyer": "0.00000000",
1221 "seller": "0.00000000"
1222 },
1223 "canTrade": true,
1224 "canWithdraw": true,
1225 "canDeposit": true,
1226 "brokered": false,
1227 "requireSelfTradePrevention": false,
1228 "preventSor": false,
1229 "updateTime": 123456789,
1230 "accountType": "SPOT",
1231 "balances": [
1232 {
1233 "asset": "BTC",
1234 "free": "4723846.89208129",
1235 "locked": "0.00000000"
1236 },
1237 {
1238 "asset": "LTC",
1239 "free": "4763368.68006011",
1240 "locked": "0.00000000"
1241 }
1242 ],
1243 "permissions": [
1244 "SPOT"
1245 ],
1246 "uid": 354937868
1247 }"#;
1248 let expected = AccountInformation {
1249 maker_commission: 15.0,
1250 taker_commission: 15.0,
1251 buyer_commission: 0.0,
1252 seller_commission: 0.0,
1253 commission_rates: CommissionRates {
1254 maker: dec!(0.00150000),
1255 taker: dec!(0.00150000),
1256 buyer: dec!(0.00000000),
1257 seller: dec!(0.00000000),
1258 },
1259 can_trade: true,
1260 can_withdraw: true,
1261 can_deposit: true,
1262 brokered: false,
1263 require_self_trade_prevention: false,
1264 prevent_sor: false,
1265 update_time: 123456789,
1266 account_type: AccountType::Spot,
1267 balances: vec![
1268 Balance {
1269 asset: String::from("BTC"),
1270 free: dec!(4723846.89208129),
1271 locked: dec!(0.00000000),
1272 },
1273 Balance {
1274 asset: String::from("LTC"),
1275 free: dec!(4763368.68006011),
1276 locked: dec!(0.00000000),
1277 },
1278 ],
1279 permissions: Some(vec![String::from("SPOT")]),
1280 permission_sets: None,
1281 uid: 354937868,
1282 };
1283
1284 let current = deserialize_str(json).unwrap();
1285
1286 assert_eq!(expected, current);
1287 }
1288
1289 #[test]
1290 fn deserialize_response_query_order() {
1291 let json = r#"{
1292 "symbol": "LTCBTC",
1293 "orderId": 1,
1294 "orderListId": -1,
1295 "clientOrderId": "myOrder1",
1296 "price": "0.1",
1297 "origQty": "1.0",
1298 "executedQty": "0.0",
1299 "cummulativeQuoteQty": "0.0",
1300 "status": "NEW",
1301 "timeInForce": "GTC",
1302 "type": "LIMIT",
1303 "side": "BUY",
1304 "stopPrice": "0.0",
1305 "icebergQty": "0.0",
1306 "time": 1499827319559,
1307 "updateTime": 1499827319559,
1308 "isWorking": true,
1309 "workingTime":1499827319559,
1310 "origQuoteOrderQty": "0.000000",
1311 "selfTradePreventionMode": "NONE"
1312 }"#;
1313 let expected = Order {
1314 symbol: String::from("LTCBTC"),
1315 order_id: 1,
1316 order_list_id: -1,
1317 client_order_id: String::from("myOrder1"),
1318 price: dec!(0.1),
1319 orig_qty: dec!(1.0),
1320 executed_qty: dec!(0.0),
1321 cummulative_quote_qty: dec!(0.0),
1322 status: OrderStatus::New,
1323 time_in_force: TimeInForce::GTC,
1324 order_type: OrderType::Limit,
1325 side: OrderSide::BUY,
1326 stop_price: Some(dec!(0.0)),
1327 iceberg_qty: Some(dec!(0.0)),
1328 time: 1499827319559,
1329 update_time: 1499827319559,
1330 is_working: true,
1331 working_time: 1499827319559,
1332 orig_quote_order_qty: dec!(0.000000),
1333 self_trade_prevention_mode: STPMode::None,
1334 };
1335
1336 let current = deserialize_str(json).unwrap();
1337
1338 assert_eq!(expected, current);
1339 }
1340}