Skip to main content

binance/spot/http/
api.rs

1use rust_decimal::Decimal;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    Timestamp,
6    serde::serialize_option_as_json,
7    spot::{
8        AccountType, ExchangeFilter, KlineInterval, OrderResponseType, OrderSide, OrderStatus,
9        OrderType, RateLimitInterval, RateLimiter, STPMode, SymbolStatus, TimeInForce,
10        WorkingFloor,
11    },
12};
13
14#[derive(Debug, PartialEq)]
15pub struct Response<T> {
16    pub result: T,
17    pub headers: Headers,
18}
19
20#[derive(Debug, PartialEq)]
21pub struct Headers {
22    pub retry_after: Option<Timestamp>,
23}
24
25#[derive(Debug, Deserialize, PartialEq)]
26pub struct TestConnectivity {}
27
28#[derive(Debug, Deserialize, PartialEq)]
29#[serde(rename_all = "camelCase")]
30pub struct ServerTime {
31    pub server_time: Timestamp,
32}
33
34#[derive(Debug, Default, Serialize, PartialEq)]
35#[serde(rename_all = "camelCase")]
36pub struct GetExchangeInfoParams {
37    /// Single symbol: `?symbol=BNBBTC`. Cannot be combined with `symbols`.
38    symbol: Option<String>,
39    /// Multiple symbols, sent as a JSON-array literal:
40    /// `?symbols=["BTCUSDT","BNBBTC"]` (URL-encoded). Cannot be combined with `symbol`.
41    #[serde(serialize_with = "serialize_option_as_json")]
42    symbols: Option<Vec<String>>,
43    /// Permission filter, sent as a JSON-array literal:
44    /// `?permissions=["MARGIN","LEVERAGED"]` (URL-encoded). A single
45    /// permission may also be sent as a one-element vec.
46    #[serde(serialize_with = "serialize_option_as_json")]
47    permissions: Option<Vec<String>>,
48    /// Controls whether the content of the permissionSets field is populated or not. Defaults to true
49    show_permission_sets: Option<bool>,
50    /// Filters symbols that have this tradingStatus. Valid values: TRADING, HALT, BREAK
51    /// Cannot be used in combination with symbols or symbol.
52    symbol_status: Option<SymbolStatus>,
53}
54
55impl GetExchangeInfoParams {
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
61        self.symbol = Some(symbol.into());
62        self
63    }
64
65    pub fn symbols(mut self, symbols: Vec<String>) -> Self {
66        self.symbols = Some(symbols);
67        self
68    }
69
70    pub fn permissions(mut self, permissions: Vec<String>) -> Self {
71        self.permissions = Some(permissions);
72        self
73    }
74
75    pub fn show_permission_sets(mut self, value: bool) -> Self {
76        self.show_permission_sets = Some(value);
77        self
78    }
79
80    pub fn symbol_status(mut self, value: SymbolStatus) -> Self {
81        self.symbol_status = Some(value);
82        self
83    }
84}
85
86#[derive(Debug, Deserialize, PartialEq)]
87#[serde(rename_all = "camelCase")]
88pub struct ExchangeInfo {
89    pub timezone: String,
90    pub server_time: Timestamp,
91    pub rate_limits: Vec<RateLimit>,
92    pub exchange_filters: Vec<ExchangeFilter>,
93    pub symbols: Vec<SymbolInfo>,
94    /// Optional field. Present only when SOR is available.
95    /// LINK: https://github.com/binance/binance-spot-api-docs/blob/master/faqs/sor_faq.md
96    pub sors: Option<Vec<SOR>>,
97}
98
99#[derive(Debug, Deserialize, PartialEq)]
100#[serde(rename_all = "camelCase")]
101pub struct RateLimit {
102    pub rate_limit_type: RateLimiter,
103    pub interval: RateLimitInterval,
104    pub interval_num: u64,
105    pub limit: u64,
106}
107
108#[derive(Debug, Deserialize, PartialEq)]
109#[serde(rename_all = "camelCase")]
110pub struct SymbolInfo {
111    pub symbol: String,
112    pub status: SymbolStatus,
113    pub base_asset: String,
114    pub base_asset_precision: u8, // value range: [0:8]
115    pub quote_asset: String,
116    // INFO: 'quote_precision' will be removed in future api versions (v4+)
117    pub quote_asset_precision: u8,      // value range: [0:8]
118    pub base_commission_precision: u8,  // value range: [0:8]
119    pub quote_commission_precision: u8, // value range: [0:8]
120    pub order_types: Vec<OrderType>,
121    pub iceberg_allowed: bool,
122    pub oco_allowed: bool,
123    pub oto_allowed: bool,
124    pub quote_order_qty_market_allowed: bool,
125    pub allow_trailing_stop: bool,
126    pub cancel_replace_allowed: bool,
127    pub amend_allowed: bool,
128    pub is_spot_trading_allowed: bool,
129    pub is_margin_trading_allowed: bool,
130    pub filters: Vec<Filter>,
131    pub permissions: Vec<String>,
132    pub permission_sets: Vec<Vec<String>>,
133    pub default_self_trade_prevention_mode: STPMode,
134    pub allowed_self_trade_prevention_modes: Vec<STPMode>,
135}
136
137/// Per-symbol trading filters from `/api/v3/exchangeInfo`.
138///
139/// Reference: <https://developers.binance.com/docs/binance-spot-api-docs/filters>
140#[derive(Debug, Deserialize, PartialEq)]
141#[serde(tag = "filterType")]
142pub enum Filter {
143    #[serde(rename = "PRICE_FILTER", rename_all = "camelCase")]
144    PriceFilter {
145        min_price: Decimal,
146        max_price: Decimal,
147        tick_size: Decimal,
148    },
149    #[serde(rename = "PERCENT_PRICE_BY_SIDE", rename_all = "camelCase")]
150    PercentPriceBySide {
151        bid_multiplier_up: Decimal,
152        bid_multiplier_down: Decimal,
153        ask_multiplier_up: Decimal,
154        ask_multiplier_down: Decimal,
155        avg_price_mins: u64,
156    },
157    #[serde(rename = "LOT_SIZE", rename_all = "camelCase")]
158    LotSize {
159        min_qty: Decimal,
160        max_qty: Decimal,
161        step_size: Decimal,
162    },
163    #[serde(rename = "MIN_NOTIONAL", rename_all = "camelCase")]
164    MinNotional {
165        min_notional: Decimal,
166        apply_to_market: bool,
167        avg_price_mins: u64,
168    },
169    #[serde(rename = "NOTIONAL", rename_all = "camelCase")]
170    Notional {
171        min_notional: Decimal,
172        apply_min_to_market: bool,
173        max_notional: Decimal,
174        apply_max_to_market: bool,
175        avg_price_mins: u64,
176    },
177    #[serde(rename = "ICEBERG_PARTS", rename_all = "camelCase")]
178    IcebergParts { limit: u64 },
179    #[serde(rename = "MARKET_LOT_SIZE", rename_all = "camelCase")]
180    MarketLotSize {
181        min_qty: Decimal,
182        max_qty: Decimal,
183        step_size: Decimal,
184    },
185    #[serde(rename = "MAX_NUM_ORDERS", rename_all = "camelCase")]
186    MaxNumOrders { max_num_orders: u64 },
187    #[serde(rename = "MAX_NUM_ALGO_ORDERS", rename_all = "camelCase")]
188    MaxNumAlgoOrders { max_num_algo_orders: u64 },
189    #[serde(rename = "MAX_NUM_ICEBERG_ORDERS", rename_all = "camelCase")]
190    MaxNumIcebergOrders { max_num_iceberg_orders: u64 },
191    #[serde(rename = "MAX_POSITION", rename_all = "camelCase")]
192    MaxPosition { max_position: Decimal },
193    #[serde(rename = "TRAILING_DELTA", rename_all = "camelCase")]
194    TrailingDelta {
195        min_trailing_above_delta: u64,
196        max_trailing_above_delta: u64,
197        min_trailing_below_delta: u64,
198        max_trailing_below_delta: u64,
199    },
200    /// Catch-all for filter types not yet modelled. Lets new Binance filter
201    /// types deserialize without breaking existing callers.
202    #[serde(other)]
203    Unknown,
204}
205
206/// Smart Order Routing (SOR).
207#[derive(Debug, Deserialize, PartialEq)]
208#[serde(rename_all = "camelCase")]
209pub struct SOR {
210    pub base_asset: String,
211    pub symbols: Vec<String>,
212}
213
214#[derive(Debug, Serialize, PartialEq)]
215#[serde(rename_all = "camelCase")]
216pub struct GetOrderBookParams {
217    symbol: String,
218    /// Default: 100; Maximum: 5000.
219    /// If limit > 5000, only 5000 entries will be returned.
220    limit: Option<u64>,
221}
222
223impl GetOrderBookParams {
224    pub fn new(symbol: impl Into<String>) -> Self {
225        Self {
226            symbol: symbol.into(),
227            limit: None,
228        }
229    }
230
231    pub fn limit(mut self, limit: u64) -> Self {
232        self.limit = Some(limit);
233        self
234    }
235}
236
237#[derive(Debug, Deserialize, PartialEq, Clone)]
238#[serde(rename_all = "camelCase")]
239pub struct OrderBook {
240    pub last_update_id: i64,
241    pub bids: Vec<OrderLevel>,
242    pub asks: Vec<OrderLevel>,
243}
244
245#[derive(Debug, Deserialize, PartialEq, Clone)]
246pub struct OrderLevel(Decimal, Decimal);
247
248impl OrderLevel {
249    pub fn price(&self) -> Decimal {
250        self.0
251    }
252    pub fn qty(&self) -> Decimal {
253        self.1
254    }
255}
256
257#[derive(Debug, Serialize, PartialEq)]
258#[serde(rename_all = "camelCase")]
259pub struct GetRecentTradesParams {
260    symbol: String,
261    /// Default: 500; Maximum: 1000.
262    limit: Option<u64>,
263}
264
265impl GetRecentTradesParams {
266    pub fn new(symbol: impl Into<String>) -> Self {
267        Self {
268            symbol: symbol.into(),
269            limit: None,
270        }
271    }
272
273    pub fn limit(mut self, limit: u64) -> Self {
274        self.limit = Some(limit);
275        self
276    }
277}
278
279#[derive(Debug, Deserialize, PartialEq)]
280#[serde(rename_all = "camelCase")]
281pub struct RecentTrade {
282    pub id: i64,
283    pub price: Decimal,
284    pub qty: Decimal,
285    pub quote_qty: Decimal,
286    pub time: Timestamp,
287    pub is_buyer_maker: bool,
288    pub is_best_match: bool,
289}
290
291#[derive(Debug, Serialize, PartialEq)]
292#[serde(rename_all = "camelCase")]
293pub struct GetOlderTradesParams {
294    symbol: String,
295    /// Default: 500; Maximum: 1000.
296    limit: Option<u64>,
297    /// TradeId to fetch from. Default gets most recent trades.
298    from_id: Option<i64>,
299}
300
301impl GetOlderTradesParams {
302    pub fn new(symbol: impl Into<String>) -> Self {
303        Self {
304            symbol: symbol.into(),
305            limit: None,
306            from_id: None,
307        }
308    }
309
310    pub fn limit(mut self, limit: u64) -> Self {
311        self.limit = Some(limit);
312        self
313    }
314
315    pub fn from_id(mut self, from_id: i64) -> Self {
316        self.from_id = Some(from_id);
317        self
318    }
319}
320
321#[derive(Debug, Serialize, PartialEq)]
322#[serde(rename_all = "camelCase")]
323pub struct GetAggregateTradesParams {
324    symbol: String,
325    /// ID to get aggregate trades from INCLUSIVE.
326    from_id: Option<i64>,
327    /// Timestamp in ms to get aggregate trades from INCLUSIVE.
328    start_time: Option<Timestamp>,
329    /// Timestamp in ms to get aggregate trades until INCLUSIVE.
330    end_time: Option<Timestamp>,
331    /// Default: 500; Maximum: 1000.
332    limit: Option<u64>,
333}
334
335impl GetAggregateTradesParams {
336    pub fn new(symbol: impl Into<String>) -> Self {
337        Self {
338            symbol: symbol.into(),
339            from_id: None,
340            start_time: None,
341            end_time: None,
342            limit: None,
343        }
344    }
345
346    pub fn from_id(mut self, from_id: i64) -> Self {
347        self.from_id = Some(from_id);
348        self
349    }
350
351    pub fn start_time(mut self, start_time: Timestamp) -> Self {
352        self.start_time = Some(start_time);
353        self
354    }
355
356    pub fn end_time(mut self, end_time: Timestamp) -> Self {
357        self.end_time = Some(end_time);
358        self
359    }
360
361    pub fn limit(mut self, limit: u64) -> Self {
362        self.limit = Some(limit);
363        self
364    }
365}
366
367#[derive(Debug, Deserialize, PartialEq)]
368pub struct AggregateTrade {
369    /// Aggregate tradeId
370    #[serde(rename = "a")]
371    pub id: i64,
372    /// Price
373    #[serde(rename = "p")]
374    pub price: Decimal,
375    /// Quantity
376    #[serde(rename = "q")]
377    pub qty: Decimal,
378    /// First tradeId
379    #[serde(rename = "f")]
380    pub first_trade_id: i64,
381    /// Last tradeId
382    #[serde(rename = "l")]
383    pub last_trade_id: i64,
384    /// Timestamp
385    #[serde(rename = "T")]
386    pub time: Timestamp,
387    /// Was the buyer the maker?
388    #[serde(rename = "m")]
389    pub is_buyer_maker: bool,
390    /// Was the trade the best price match?
391    #[serde(rename = "M")]
392    pub is_best_match: bool,
393}
394
395#[derive(Debug, Serialize, PartialEq)]
396#[serde(rename_all = "camelCase")]
397pub struct GetKlineListParams {
398    symbol: String,
399    interval: KlineInterval,
400    start_time: Option<Timestamp>,
401    end_time: Option<Timestamp>,
402    time_zone: Option<String>,
403    /// Default: 500; Maximum: 1000.
404    limit: Option<u64>,
405}
406
407impl GetKlineListParams {
408    pub fn new(symbol: impl Into<String>, interval: KlineInterval) -> Self {
409        Self {
410            symbol: symbol.into(),
411            interval,
412            start_time: None,
413            end_time: None,
414            time_zone: None,
415            limit: None,
416        }
417    }
418
419    pub fn start_time(mut self, start_time: Timestamp) -> Self {
420        self.start_time = Some(start_time);
421        self
422    }
423
424    pub fn end_time(mut self, end_time: Timestamp) -> Self {
425        self.end_time = Some(end_time);
426        self
427    }
428
429    pub fn time_zone(mut self, time_zone: impl Into<String>) -> Self {
430        self.time_zone = Some(time_zone.into());
431        self
432    }
433
434    pub fn limit(mut self, limit: u64) -> Self {
435        self.limit = Some(limit);
436        self
437    }
438}
439
440#[derive(Debug, Deserialize, PartialEq)]
441pub struct Kline(
442    Timestamp, // Kline open time
443    Decimal,   // Open price
444    Decimal,   // High price
445    Decimal,   // Low price
446    Decimal,   // Close price
447    Decimal,   // Volume
448    Timestamp, // Kline Close time
449    Decimal,   // Quote asset volume
450    u64,       // Number of trades
451    Decimal,   // Taker buy base asset volume
452    Decimal,   // Taker buy quote asset volume
453    String,    // DEPRECATED: Unused field, ignore.
454);
455
456impl Kline {
457    /// Kline open time
458    pub fn time_open(&self) -> Timestamp {
459        self.0
460    }
461    /// Open price
462    pub fn open(&self) -> Decimal {
463        self.1
464    }
465    /// High price
466    pub fn high(&self) -> Decimal {
467        self.2
468    }
469    /// Low price
470    pub fn low(&self) -> Decimal {
471        self.3
472    }
473    /// Close price
474    pub fn close(&self) -> Decimal {
475        self.4
476    }
477    /// Volume
478    pub fn volume(&self) -> Decimal {
479        self.5
480    }
481    /// Kline Close time
482    pub fn time_close(&self) -> Timestamp {
483        self.6
484    }
485    /// Quote asset volume
486    pub fn quote_asset_volume(&self) -> Decimal {
487        self.7
488    }
489    /// Number of trades
490    pub fn id(&self) -> u64 {
491        self.8
492    }
493    /// Taker buy base asset volume
494    pub fn taker_buy_base_asset_volume(&self) -> Decimal {
495        self.9
496    }
497    /// Taker buy quote asset volume
498    pub fn taker_buy_quote_asset_volume(&self) -> Decimal {
499        self.10
500    }
501}
502
503#[derive(Debug, Serialize, PartialEq)]
504#[serde(rename_all = "camelCase")]
505pub struct GetCurrentAveragePriceParams {
506    symbol: String,
507}
508
509impl GetCurrentAveragePriceParams {
510    pub fn new(symbol: impl Into<String>) -> Self {
511        Self {
512            symbol: symbol.into(),
513        }
514    }
515}
516
517#[derive(Debug, Deserialize, PartialEq)]
518#[serde(rename_all = "camelCase")]
519pub struct CurrentAveragePrice {
520    /// Average price interval (in minutes)
521    pub mins: u64,
522    /// Average price
523    pub price: Decimal,
524    /// Last trade time
525    pub close_time: Timestamp,
526}
527
528/// Supported values: FULL or MINI.
529/// If none provided, the default is FULL
530#[derive(Debug, Serialize, PartialEq)]
531#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
532pub enum GetTickerPriceChangeStatisticsParams {
533    Mini(SymbolOrSymbols),
534    Full(SymbolOrSymbols),
535}
536
537#[derive(Debug, Default, Serialize, PartialEq)]
538pub struct SymbolOrSymbols {
539    /// Parameter symbol and symbols cannot be used in combination.
540    /// If neither parameter is sent, tickers for all symbols will be returned in an array.
541    symbol: Option<String>,
542    /// Examples of accepted format for the symbols parameter: ["BTCUSDT","BNBUSDT"]
543    /// TODO: check serialization
544    /// or
545    /// %5B%22BTCUSDT%22,%22BNBUSDT%22%5D
546    symbols: Option<Vec<String>>,
547}
548
549impl SymbolOrSymbols {
550    pub fn new() -> Self {
551        Self::default()
552    }
553
554    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
555        self.symbol = Some(symbol.into());
556        self
557    }
558
559    pub fn symbols(mut self, symbols: Vec<String>) -> Self {
560        self.symbols = Some(symbols);
561        self
562    }
563}
564
565#[derive(Debug, Deserialize, PartialEq)]
566#[serde(untagged)]
567pub enum TickerPriceChangeStatistic {
568    MiniElement(TickerPriceChangeStatisticMini),
569    MiniList(Vec<TickerPriceChangeStatisticMini>),
570    FullElement(TickerPriceChangeStatisticFull),
571    FullList(Vec<TickerPriceChangeStatisticFull>),
572}
573
574#[derive(Debug, Deserialize, PartialEq)]
575#[serde(rename_all = "camelCase")]
576pub struct TickerPriceChangeStatisticFull {
577    pub symbol: String,
578    pub price_change: Decimal,
579    pub price_change_percent: Decimal,
580    pub weighted_avg_price: Decimal,
581    pub prev_close_price: Decimal,
582    pub last_price: Decimal,
583    pub last_qty: Decimal,
584    pub bid_price: Decimal,
585    pub bid_qty: Decimal,
586    pub ask_price: Decimal,
587    pub ask_qty: Decimal,
588    pub open_price: Decimal,
589    pub high_price: Decimal,
590    pub low_price: Decimal,
591    pub volume: Decimal,
592    pub quote_volume: Decimal,
593    pub open_time: Timestamp,
594    pub close_time: Timestamp,
595    /// First traded
596    pub first_id: i64,
597    /// Last traded
598    pub last_id: i64,
599    /// Trade count
600    pub count: u64,
601}
602
603#[derive(Debug, Deserialize, PartialEq)]
604#[serde(rename_all = "camelCase")]
605pub struct TickerPriceChangeStatisticMini {
606    /// Symbol Name
607    pub symbol: String,
608    /// Opening price of the Interval
609    pub open_price: Decimal,
610    /// Highest price in the interval
611    pub high_price: Decimal,
612    /// Lowest  price in the interval
613    pub low_price: Decimal,
614    /// Closing price of the interval
615    pub last_price: Decimal,
616    /// Total trade volume (in base asset)
617    pub volume: Decimal,
618    /// Total trade volume (in quote asset)
619    pub quote_volume: Decimal,
620    /// Start of the ticker interval
621    pub open_time: Timestamp,
622    /// End of the ticker interval
623    pub close_time: Timestamp,
624    /// First tradeId considered
625    pub first_id: i64,
626    /// Last tradeId considered
627    pub last_id: i64,
628    /// Total trade count
629    pub count: u64,
630}
631
632#[derive(Debug, Serialize, PartialEq)]
633#[serde(rename_all = "camelCase")]
634pub struct NewOrderRequest {
635    symbol: String,
636    side: OrderSide,
637    #[serde(rename = "type")]
638    order_type: OrderType,
639    time_in_force: Option<TimeInForce>,
640    quantity: Option<Decimal>,
641    quote_order_qty: Option<Decimal>,
642    price: Option<Decimal>,
643    /// A unique id among open orders. Automatically generated if not sent.
644    /// Orders with the same newClientOrderID can be accepted only when the previous one is filled, otherwise the order will be rejected.
645    new_client_order_id: Option<String>,
646    strategy_id: Option<i64>,
647    /// The value cannot be less than 1000000.
648    strategy_type: Option<i64>,
649    /// Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders.
650    stop_price: Option<Decimal>,
651    /// See Trailing Stop order FAQ.
652    trailing_delta: Option<i64>,
653    /// Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order.
654    iceberg_qty: Option<Decimal>,
655    /// Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to FULL, all other orders default to ACK.
656    /// Mandatory - because there is a problem with deserialization of untagged enum Order
657    new_order_resp_type: OrderResponseType,
658    /// The allowed enums is dependent on what is configured on the symbol. The possible supported values are: STP Modes.
659    self_trade_prevention_mode: Option<STPMode>,
660    /// The value cannot be greater than 60000
661    recv_window: Option<u64>,
662    /// Only for test endpoint to place a new order.
663    compute_commission_rates: Option<bool>,
664}
665
666impl NewOrderRequest {
667    pub fn new(
668        symbol: impl Into<String>,
669        side: OrderSide,
670        order_type: OrderType,
671        new_order_resp_type: OrderResponseType,
672    ) -> Self {
673        Self {
674            symbol: symbol.into(),
675            side,
676            order_type,
677            new_order_resp_type,
678            time_in_force: None,
679            quantity: None,
680            quote_order_qty: None,
681            price: None,
682            new_client_order_id: None,
683            strategy_id: None,
684            strategy_type: None,
685            stop_price: None,
686            trailing_delta: None,
687            iceberg_qty: None,
688            self_trade_prevention_mode: None,
689            recv_window: None,
690            compute_commission_rates: None,
691        }
692    }
693
694    pub fn time_in_force(mut self, value: TimeInForce) -> Self {
695        self.time_in_force = Some(value);
696        self
697    }
698
699    pub fn quantity(mut self, value: Decimal) -> Self {
700        self.quantity = Some(value);
701        self
702    }
703
704    pub fn quote_order_qty(mut self, value: Decimal) -> Self {
705        self.quote_order_qty = Some(value);
706        self
707    }
708
709    pub fn price(mut self, value: Decimal) -> Self {
710        self.price = Some(value);
711        self
712    }
713
714    pub fn new_client_order_id(mut self, value: impl Into<String>) -> Self {
715        self.new_client_order_id = Some(value.into());
716        self
717    }
718
719    pub fn strategy_id(mut self, value: i64) -> Self {
720        self.strategy_id = Some(value);
721        self
722    }
723
724    pub fn strategy_type(mut self, value: i64) -> Self {
725        self.strategy_type = Some(value);
726        self
727    }
728
729    pub fn stop_price(mut self, value: Decimal) -> Self {
730        self.stop_price = Some(value);
731        self
732    }
733
734    pub fn trailing_delta(mut self, value: i64) -> Self {
735        self.trailing_delta = Some(value);
736        self
737    }
738
739    pub fn iceberg_qty(mut self, value: Decimal) -> Self {
740        self.iceberg_qty = Some(value);
741        self
742    }
743
744    pub fn self_trade_prevention_mode(mut self, value: STPMode) -> Self {
745        self.self_trade_prevention_mode = Some(value);
746        self
747    }
748
749    pub fn recv_window(mut self, value: u64) -> Self {
750        self.recv_window = Some(value);
751        self
752    }
753
754    pub fn compute_commission_rates(mut self, value: bool) -> Self {
755        self.compute_commission_rates = Some(value);
756        self
757    }
758
759    pub fn is_valid(&self) -> bool {
760        match self.order_type {
761            OrderType::Limit => {
762                self.time_in_force.is_some() && self.quantity.is_some() && self.price.is_some()
763            }
764            OrderType::Market => {
765                // MARKET orders using the quantity field specifies the amount of the base asset the user wants to buy or sell at the market price.
766                // E.g. MARKET order on BTCUSDT will specify how much BTC the user is buying or selling.
767
768                // MARKET orders using quoteOrderQty specifies the amount the user wants to spend (when buying) or receive (when selling) the quote asset; the correct quantity will be determined based on the market liquidity and quoteOrderQty.
769                // E.g. Using the symbol BTCUSDT:
770                // BUY side, the order will buy as many BTC as quoteOrderQty USDT can.
771                // SELL side, the order will sell as much BTC needed to receive quoteOrderQty USDT.
772                self.quantity.is_some() || self.quote_order_qty.is_some()
773            }
774            OrderType::StopLoss => {
775                // This will execute a MARKET order when the conditions are met. (e.g. stopPrice is met or trailingDelta is activated)
776                self.quantity.is_some()
777                    && (self.stop_price.is_some() || self.trailing_delta.is_some())
778            }
779            OrderType::StopLossLimit => {
780                self.time_in_force.is_some()
781                    && self.quantity.is_some()
782                    && self.price.is_some()
783                    && (self.stop_price.is_some() || self.trailing_delta.is_some())
784            }
785            OrderType::TakeProfit => {
786                // This will execute a MARKET order when the conditions are met. (e.g. stopPrice is met or trailingDelta is activated)
787                self.quantity.is_some()
788                    && (self.stop_price.is_some() || self.trailing_delta.is_some())
789            }
790            OrderType::TakeProfitLimit => {
791                self.time_in_force.is_some()
792                    && self.quantity.is_some()
793                    && self.price.is_some()
794                    && (self.stop_price.is_some() || self.trailing_delta.is_some())
795            }
796            OrderType::LimitMaker => {
797                // This is a LIMIT order that will be rejected if the order immediately matches and trades as a taker.
798                // This is also known as a POST-ONLY order.
799                self.quantity.is_some() && self.price.is_some()
800            }
801        }
802    }
803}
804
805#[derive(Debug, Deserialize, PartialEq)]
806#[serde(untagged)]
807pub enum NewOrderResponse {
808    Full(NewOrderResponseFull),
809    Result(NewOrderResponseResult),
810    Ack(NewOrderResponseAck),
811}
812
813#[derive(Debug, Deserialize, PartialEq)]
814#[serde(rename_all = "camelCase")]
815pub struct NewOrderResponseAck {
816    pub symbol: String,
817    pub order_id: i64,
818    /// Unless it's part of an order list, value will be -1
819    pub order_list_id: i64,
820    pub client_order_id: String,
821    pub transact_time: Timestamp,
822    /// Quantity for the iceberg order
823    /// Appears only if the parameter icebergQty was sent in the request.
824    pub iceberg_qty: Option<Decimal>,
825    /// When used in combination with symbol, can be used to query a prevented match.
826    /// Appears only if the order expired due to STP.
827    pub prevented_match_id: Option<i64>,
828    /// Order quantity that expired due to STP
829    /// Appears only if the order expired due to STP.
830    pub prevented_quantity: Option<Decimal>,
831    /// Price when the algorithmic order will be triggered
832    /// Appears for STOP_LOSS. TAKE_PROFIT, STOP_LOSS_LIMIT and TAKE_PROFIT_LIMIT orders.
833    pub stop_price: Option<Decimal>,
834    /// Can be used to label an order that's part of an order strategy.
835    /// Appears if the parameter was populated in the request.
836    pub strategy_id: Option<i64>,
837    /// Can be used to label an order that is using an order strategy.
838    /// Appears if the parameter was populated in the request.
839    pub strategy_type: Option<i64>,
840    /// Delta price change required before order activation
841    /// Appears for Trailing Stop Orders.
842    pub trailing_delta: Option<i64>,
843    /// Time when the trailing order is now active and tracking price changes
844    /// Appears only for Trailing Stop Orders.
845    pub trailing_time: Option<i64>,
846    /// Field that determines whether order used SOR
847    /// Appears when placing orders using SOR
848    pub used_sor: Option<bool>,
849    /// Field that determines whether the order is being filled by the SOR or by the order book the order was submitted to.
850    /// Appears when placing orders using SOR
851    pub working_floor: Option<WorkingFloor>,
852}
853
854#[derive(Debug, Deserialize, PartialEq)]
855#[serde(rename_all = "camelCase")]
856pub struct NewOrderResponseResult {
857    pub symbol: String,
858    pub order_id: i64,
859    /// Unless it's part of an order list, value will be -1
860    pub order_list_id: i64,
861    pub client_order_id: String,
862    pub transact_time: Timestamp,
863    pub price: Decimal,
864    pub orig_qty: Decimal,
865    pub executed_qty: Decimal,
866    pub orig_quote_order_qty: Decimal,
867    pub cummulative_quote_qty: Decimal,
868    pub status: OrderStatus,
869    pub time_in_force: TimeInForce,
870    #[serde(rename = "type")]
871    pub order_type: OrderType,
872    pub side: OrderSide,
873    pub working_time: Timestamp,
874    pub self_trade_prevention_mode: STPMode,
875    /// Quantity for the iceberg order
876    /// Appears only if the parameter icebergQty was sent in the request.
877    pub iceberg_qty: Option<Decimal>,
878    /// When used in combination with symbol, can be used to query a prevented match.
879    /// Appears only if the order expired due to STP.
880    pub prevented_match_id: Option<i64>,
881    /// Order quantity that expired due to STP
882    /// Appears only if the order expired due to STP.
883    pub prevented_quantity: Option<Decimal>,
884    /// Price when the algorithmic order will be triggered
885    /// Appears for STOP_LOSS. TAKE_PROFIT, STOP_LOSS_LIMIT and TAKE_PROFIT_LIMIT orders.
886    pub stop_price: Option<Decimal>,
887    /// Can be used to label an order that's part of an order strategy.
888    /// Appears if the parameter was populated in the request.
889    pub strategy_id: Option<i64>,
890    /// Can be used to label an order that is using an order strategy.
891    /// Appears if the parameter was populated in the request.
892    pub strategy_type: Option<i64>,
893    /// Delta price change required before order activation
894    /// Appears for Trailing Stop Orders.
895    pub trailing_delta: Option<i64>,
896    /// Time when the trailing order is now active and tracking price changes
897    /// Appears only for Trailing Stop Orders.
898    pub trailing_time: Option<i64>,
899    /// Field that determines whether order used SOR
900    /// Appears when placing orders using SOR
901    pub used_sor: Option<bool>,
902    /// Field that determines whether the order is being filled by the SOR or by the order book the order was submitted to.
903    /// Appears when placing orders using SOR
904    pub working_floor: Option<WorkingFloor>,
905}
906
907#[derive(Debug, Deserialize, PartialEq)]
908#[serde(rename_all = "camelCase")]
909pub struct NewOrderResponseFull {
910    pub symbol: String,
911    pub order_id: i64,
912    /// Unless it's part of an order list, value will be -1
913    pub order_list_id: i64,
914    pub client_order_id: String,
915    pub transact_time: Timestamp,
916    pub price: Decimal,
917    pub orig_qty: Decimal,
918    pub executed_qty: Decimal,
919    pub orig_quote_order_qty: Decimal,
920    pub cummulative_quote_qty: Decimal,
921    pub status: OrderStatus,
922    pub time_in_force: TimeInForce,
923    #[serde(rename = "type")]
924    pub order_type: OrderType,
925    pub side: OrderSide,
926    pub working_time: Timestamp,
927    pub self_trade_prevention_mode: STPMode,
928    pub fills: Vec<OrderFill>,
929    /// Quantity for the iceberg order
930    /// Appears only if the parameter icebergQty was sent in the request.
931    pub iceberg_qty: Option<Decimal>,
932    /// When used in combination with symbol, can be used to query a prevented match.
933    /// Appears only if the order expired due to STP.
934    pub prevented_match_id: Option<i64>,
935    /// Order quantity that expired due to STP
936    /// Appears only if the order expired due to STP.
937    pub prevented_quantity: Option<Decimal>,
938    /// Price when the algorithmic order will be triggered
939    /// Appears for STOP_LOSS. TAKE_PROFIT, STOP_LOSS_LIMIT and TAKE_PROFIT_LIMIT orders.
940    pub stop_price: Option<Decimal>,
941    /// Can be used to label an order that's part of an order strategy.
942    /// Appears if the parameter was populated in the request.
943    pub strategy_id: Option<i64>,
944    /// Can be used to label an order that is using an order strategy.
945    /// Appears if the parameter was populated in the request.
946    pub strategy_type: Option<i64>,
947    /// Delta price change required before order activation
948    /// Appears for Trailing Stop Orders.
949    pub trailing_delta: Option<i64>,
950    /// Time when the trailing order is now active and tracking price changes
951    /// Appears only for Trailing Stop Orders.
952    pub trailing_time: Option<i64>,
953    /// Field that determines whether order used SOR
954    /// Appears when placing orders using SOR
955    pub used_sor: Option<bool>,
956    /// Field that determines whether the order is being filled by the SOR or by the order book the order was submitted to.
957    /// Appears when placing orders using SOR
958    pub working_floor: Option<WorkingFloor>,
959}
960
961#[derive(Debug, Deserialize, PartialEq)]
962#[serde(rename_all = "camelCase")]
963pub struct OrderFill {
964    pub price: Decimal,
965    pub qty: Decimal,
966    pub commission: Decimal,
967    pub commission_asset: String,
968    pub trade_id: i64,
969}
970
971#[derive(Debug, Deserialize, PartialEq)]
972#[serde(untagged)]
973pub enum TestCommissionRates {
974    Full(TestCommissionRatesFull),
975    Empty(TestCommissionRatesEmpty),
976}
977
978#[derive(Debug, Deserialize, PartialEq)]
979pub struct TestCommissionRatesEmpty {}
980
981#[derive(Debug, Deserialize, PartialEq)]
982#[serde(rename_all = "camelCase")]
983pub struct TestCommissionRatesFull {
984    /// Standard commission rates on trades from the order.
985    pub standard_commission_for_order: CommissionForOrder,
986    /// Tax commission rates for trades from the order.
987    pub tax_commission_for_order: CommissionForOrder,
988    /// Discount on standard commissions when paying in BNB.
989    pub discount: Discount,
990}
991#[derive(Debug, Deserialize, PartialEq)]
992#[serde(rename_all = "camelCase")]
993pub struct CommissionForOrder {
994    pub maker: Decimal,
995    pub taker: Decimal,
996}
997
998#[derive(Debug, Deserialize, PartialEq)]
999#[serde(rename_all = "camelCase")]
1000pub struct Discount {
1001    pub enabled_for_account: bool,
1002    pub enabled_for_symbol: bool,
1003    pub discount_asset: String,
1004    /// Standard commission is reduced by this rate when paying commission in BNB.
1005    pub discount: Decimal,
1006}
1007
1008#[derive(Debug, Default, Serialize, PartialEq)]
1009#[serde(rename_all = "camelCase")]
1010pub struct GetAccountInformationParams {
1011    /// When set to true, emits only the non-zero balances of an account.
1012    /// Default value: false
1013    omit_zero_balances: Option<bool>,
1014    /// The value cannot be greater than 60000
1015    recv_window: Option<u64>,
1016}
1017
1018impl GetAccountInformationParams {
1019    pub fn new() -> Self {
1020        Self::default()
1021    }
1022
1023    pub fn omit_zero_balances(mut self, value: bool) -> Self {
1024        self.omit_zero_balances = Some(value);
1025        self
1026    }
1027
1028    pub fn recv_window(mut self, value: u64) -> Self {
1029        self.recv_window = Some(value);
1030        self
1031    }
1032}
1033
1034#[derive(Debug, Deserialize, PartialEq)]
1035#[serde(rename_all = "camelCase")]
1036pub struct AccountInformation {
1037    pub maker_commission: f64,
1038    pub taker_commission: f64,
1039    pub buyer_commission: f64,
1040    pub seller_commission: f64,
1041    pub commission_rates: CommissionRates,
1042    pub can_trade: bool,
1043    pub can_withdraw: bool,
1044    pub can_deposit: bool,
1045    pub brokered: bool,
1046    pub require_self_trade_prevention: bool,
1047    pub prevent_sor: bool,
1048    pub update_time: Timestamp,
1049    pub account_type: AccountType,
1050    pub balances: Vec<Balance>,
1051    pub permissions: Option<Vec<String>>,
1052    pub permission_sets: Option<Vec<Vec<String>>>,
1053    pub uid: i64,
1054}
1055
1056#[derive(Debug, Deserialize, PartialEq)]
1057#[serde(rename_all = "camelCase")]
1058pub struct CommissionRates {
1059    pub maker: Decimal,
1060    pub taker: Decimal,
1061    pub buyer: Decimal,
1062    pub seller: Decimal,
1063}
1064
1065#[derive(Debug, Deserialize, PartialEq)]
1066#[serde(rename_all = "camelCase")]
1067pub struct Balance {
1068    pub asset: String,
1069    pub free: Decimal,
1070    pub locked: Decimal,
1071}
1072
1073#[derive(Debug, Serialize, PartialEq)]
1074#[serde(rename_all = "camelCase")]
1075pub struct QueryOrderParams {
1076    symbol: String,
1077    order_id: Option<i64>,
1078    orig_client_order_id: Option<String>,
1079    /// The value cannot be greater than 60000
1080    recv_window: Option<u64>,
1081}
1082
1083impl QueryOrderParams {
1084    pub fn new(symbol: impl Into<String>) -> Self {
1085        Self {
1086            symbol: symbol.into(),
1087            order_id: None,
1088            orig_client_order_id: None,
1089            recv_window: None,
1090        }
1091    }
1092
1093    pub fn order_id(mut self, value: i64) -> Self {
1094        self.order_id = Some(value);
1095        self
1096    }
1097
1098    pub fn orig_client_order_id(mut self, value: impl Into<String>) -> Self {
1099        self.orig_client_order_id = Some(value.into());
1100        self
1101    }
1102
1103    pub fn recv_window(mut self, value: u64) -> Self {
1104        self.recv_window = Some(value);
1105        self
1106    }
1107}
1108
1109#[derive(Debug, Deserialize, PartialEq)]
1110#[serde(rename_all = "camelCase")]
1111pub struct Order {
1112    pub symbol: String,
1113    pub order_id: i64,
1114    /// This field will always have a value of -1 if not an order list.
1115    pub order_list_id: i64,
1116    pub client_order_id: String,
1117    pub price: Decimal,
1118    pub orig_qty: Decimal,
1119    pub executed_qty: Decimal,
1120    pub cummulative_quote_qty: Decimal,
1121    pub status: OrderStatus,
1122    pub time_in_force: TimeInForce,
1123    #[serde(rename = "type")]
1124    pub order_type: OrderType,
1125    pub side: OrderSide,
1126    /// Price when the algorithmic order will be triggered
1127    /// Appears for STOP_LOSS. TAKE_PROFIT, STOP_LOSS_LIMIT and TAKE_PROFIT_LIMIT orders.
1128    pub stop_price: Option<Decimal>,
1129    /// Quantity for the iceberg order
1130    /// Appears only if the parameter icebergQty was sent in the request.
1131    pub iceberg_qty: Option<Decimal>,
1132    pub time: Timestamp,
1133    pub update_time: Timestamp,
1134    pub is_working: bool,
1135    pub working_time: Timestamp,
1136    pub orig_quote_order_qty: Decimal,
1137    pub self_trade_prevention_mode: STPMode,
1138}
1139
1140#[cfg(test)]
1141mod tests {
1142    use rust_decimal::dec;
1143
1144    use crate::serde::deserialize_json;
1145
1146    use super::*;
1147
1148    #[test]
1149    fn deserialize_response_server_time() {
1150        let json = r#"{
1151            "serverTime": 1499827319559
1152        }"#;
1153        let expected = ServerTime {
1154            server_time: 1499827319559,
1155        };
1156
1157        let current = deserialize_json(json).unwrap();
1158
1159        assert_eq!(expected, current);
1160    }
1161
1162    #[test]
1163    fn deserialize_response_exchange_info() {
1164        let json = r#"{
1165            "timezone": "UTC",
1166            "serverTime": 1565246363776,
1167            "rateLimits": [],
1168            "exchangeFilters": [],
1169            "symbols": [
1170                {
1171                    "symbol": "ETHBTC",
1172                    "status": "TRADING",
1173                    "baseAsset": "ETH",
1174                    "baseAssetPrecision": 8,
1175                    "quoteAsset": "BTC",
1176                    "quotePrecision": 8,
1177                    "quoteAssetPrecision": 8,
1178                    "baseCommissionPrecision": 8,
1179                    "quoteCommissionPrecision": 8,
1180                    "orderTypes": [
1181                        "LIMIT",
1182                        "LIMIT_MAKER",
1183                        "MARKET",
1184                        "STOP_LOSS",
1185                        "STOP_LOSS_LIMIT",
1186                        "TAKE_PROFIT",
1187                        "TAKE_PROFIT_LIMIT"
1188                    ],
1189                    "icebergAllowed": true,
1190                    "ocoAllowed": true,
1191                    "otoAllowed": true,
1192                    "quoteOrderQtyMarketAllowed": true,
1193                    "allowTrailingStop": false,
1194                    "cancelReplaceAllowed":false,
1195                    "amendAllowed":false,
1196                    "isSpotTradingAllowed": true,
1197                    "isMarginTradingAllowed": true,
1198                    "filters": [],
1199                    "permissions": [],
1200                    "permissionSets": [
1201                        [
1202                            "SPOT",
1203                            "MARGIN"
1204                        ]
1205                    ],
1206                    "defaultSelfTradePreventionMode": "NONE",
1207                    "allowedSelfTradePreventionModes": [
1208                        "NONE"
1209                    ]
1210                }
1211            ],
1212            "sors": [
1213                {
1214                    "baseAsset": "BTC",
1215                    "symbols": [
1216                        "BTCUSDT",
1217                        "BTCUSDC"
1218                    ]
1219                }
1220            ]
1221        }"#;
1222        let expected = ExchangeInfo {
1223            timezone: String::from("UTC"),
1224            server_time: 1565246363776,
1225            rate_limits: vec![],
1226            exchange_filters: vec![],
1227            symbols: vec![SymbolInfo {
1228                symbol: String::from("ETHBTC"),
1229                status: SymbolStatus::Trading,
1230                base_asset: String::from("ETH"),
1231                base_asset_precision: 8,
1232                quote_asset: String::from("BTC"),
1233                quote_asset_precision: 8,
1234                base_commission_precision: 8,
1235                quote_commission_precision: 8,
1236                order_types: vec![
1237                    OrderType::Limit,
1238                    OrderType::LimitMaker,
1239                    OrderType::Market,
1240                    OrderType::StopLoss,
1241                    OrderType::StopLossLimit,
1242                    OrderType::TakeProfit,
1243                    OrderType::TakeProfitLimit,
1244                ],
1245                iceberg_allowed: true,
1246                oco_allowed: true,
1247                oto_allowed: true,
1248                quote_order_qty_market_allowed: true,
1249                allow_trailing_stop: false,
1250                cancel_replace_allowed: false,
1251                amend_allowed: false,
1252                is_spot_trading_allowed: true,
1253                is_margin_trading_allowed: true,
1254                filters: vec![],
1255                permissions: vec![],
1256                permission_sets: vec![vec![String::from("SPOT"), String::from("MARGIN")]],
1257                default_self_trade_prevention_mode: STPMode::None,
1258                allowed_self_trade_prevention_modes: vec![STPMode::None],
1259            }],
1260            sors: Some(vec![SOR {
1261                base_asset: String::from("BTC"),
1262                symbols: vec![String::from("BTCUSDT"), String::from("BTCUSDC")],
1263            }]),
1264        };
1265
1266        let current = deserialize_json(json).unwrap();
1267
1268        assert_eq!(expected, current);
1269    }
1270
1271    #[test]
1272    fn deserialize_response_order_book() {
1273        let json = r#"{
1274            "lastUpdateId": 1027024,
1275            "bids": [
1276                [
1277                "4.00000000",
1278                "431.00000000"
1279                ]
1280            ],
1281            "asks": [
1282                [
1283                "4.00000200",
1284                "12.00000000"
1285                ]
1286            ]
1287        }"#;
1288        let expected = OrderBook {
1289            last_update_id: 1027024,
1290            bids: vec![OrderLevel(dec!(4.00000000), dec!(431.00000000))],
1291            asks: vec![OrderLevel(dec!(4.00000200), dec!(12.00000000))],
1292        };
1293
1294        let current = deserialize_json(json).unwrap();
1295
1296        assert_eq!(expected, current);
1297    }
1298
1299    #[test]
1300    fn deserialize_response_order_ack() {
1301        let json = r#"{
1302            "symbol": "BTCUSDT",
1303            "orderId": 28,
1304            "orderListId": -1,
1305            "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP",
1306            "transactTime": 1507725176595
1307        }"#;
1308        let response = NewOrderResponseAck {
1309            symbol: String::from("BTCUSDT"),
1310            order_id: 28,
1311            order_list_id: -1,
1312            client_order_id: String::from("6gCrw2kRUAF9CvJDGP16IP"),
1313            transact_time: 1507725176595,
1314            iceberg_qty: None,
1315            prevented_match_id: None,
1316            prevented_quantity: None,
1317            stop_price: None,
1318            strategy_id: None,
1319            strategy_type: None,
1320            trailing_delta: None,
1321            trailing_time: None,
1322            used_sor: None,
1323            working_floor: None,
1324        };
1325        let expected = NewOrderResponse::Ack(response);
1326
1327        let current = deserialize_json(json).unwrap();
1328
1329        assert_eq!(expected, current);
1330    }
1331
1332    #[test]
1333    fn deserialize_response_order_result() {
1334        let json = r#"{
1335            "symbol": "BTCUSDT",
1336            "orderId": 28,
1337            "orderListId": -1,
1338            "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP",
1339            "transactTime": 1507725176595,
1340            "price": "0.00000000",
1341            "origQty": "10.00000000",
1342            "executedQty": "10.00000000",
1343            "origQuoteOrderQty": "0.000000",
1344            "cummulativeQuoteQty": "10.00000000",
1345            "status": "FILLED",
1346            "timeInForce": "GTC",
1347            "type": "MARKET",
1348            "side": "SELL",
1349            "workingTime": 1507725176595,
1350            "selfTradePreventionMode": "NONE"
1351        }"#;
1352        let response = NewOrderResponseResult {
1353            symbol: String::from("BTCUSDT"),
1354            order_id: 28,
1355            order_list_id: -1,
1356            client_order_id: String::from("6gCrw2kRUAF9CvJDGP16IP"),
1357            transact_time: 1507725176595,
1358            price: dec!(0.00000000),
1359            orig_qty: dec!(10.00000000),
1360            executed_qty: dec!(10.00000000),
1361            orig_quote_order_qty: dec!(0.00000000),
1362            cummulative_quote_qty: dec!(10.00000000),
1363            status: OrderStatus::Filled,
1364            time_in_force: TimeInForce::GTC,
1365            order_type: OrderType::Market,
1366            side: OrderSide::SELL,
1367            working_time: 1507725176595,
1368            self_trade_prevention_mode: STPMode::None,
1369            iceberg_qty: None,
1370            prevented_match_id: None,
1371            prevented_quantity: None,
1372            stop_price: None,
1373            strategy_id: None,
1374            strategy_type: None,
1375            trailing_delta: None,
1376            trailing_time: None,
1377            used_sor: None,
1378            working_floor: None,
1379        };
1380        let expected = NewOrderResponse::Result(response);
1381
1382        let current = deserialize_json(json).unwrap();
1383
1384        assert_eq!(expected, current);
1385    }
1386
1387    #[test]
1388    fn deserialize_response_order_full() {
1389        let json = r#"{
1390            "symbol": "BTCUSDT",
1391            "orderId": 28,
1392            "orderListId": -1,
1393            "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP",
1394            "transactTime": 1507725176595,
1395            "price": "0.00000000",
1396            "origQty": "10.00000000",
1397            "executedQty": "10.00000000",
1398            "origQuoteOrderQty": "0.000000",
1399            "cummulativeQuoteQty": "10.00000000",
1400            "status": "FILLED",
1401            "timeInForce": "GTC",
1402            "type": "MARKET",
1403            "side": "SELL",
1404            "workingTime": 1507725176595,
1405            "selfTradePreventionMode": "NONE",
1406            "fills": [
1407                {
1408                    "price": "4000.00000000",
1409                    "qty": "1.00000000",
1410                    "commission": "4.00000000",
1411                    "commissionAsset": "USDT",
1412                    "tradeId": 56
1413                },
1414                {
1415                    "price": "3999.00000000",
1416                    "qty": "5.00000000",
1417                    "commission": "19.99500000",
1418                    "commissionAsset": "USDT",
1419                    "tradeId": 57
1420                },
1421                {
1422                    "price": "3998.00000000",
1423                    "qty": "2.00000000",
1424                    "commission": "7.99600000",
1425                    "commissionAsset": "USDT",
1426                    "tradeId": 58
1427                },
1428                {
1429                    "price": "3997.00000000",
1430                    "qty": "1.00000000",
1431                    "commission": "3.99700000",
1432                    "commissionAsset": "USDT",
1433                    "tradeId": 59
1434                },
1435                {
1436                    "price": "3995.00000000",
1437                    "qty": "1.00000000",
1438                    "commission": "3.99500000",
1439                    "commissionAsset": "USDT",
1440                    "tradeId": 60
1441                }
1442            ]
1443        }"#;
1444        let response = NewOrderResponseFull {
1445            symbol: String::from("BTCUSDT"),
1446            order_id: 28,
1447            order_list_id: -1,
1448            client_order_id: String::from("6gCrw2kRUAF9CvJDGP16IP"),
1449            transact_time: 1507725176595,
1450            price: dec!(0.00000000),
1451            orig_qty: dec!(10.00000000),
1452            executed_qty: dec!(10.00000000),
1453            orig_quote_order_qty: dec!(0.00000000),
1454            cummulative_quote_qty: dec!(10.00000000),
1455            status: OrderStatus::Filled,
1456            time_in_force: TimeInForce::GTC,
1457            order_type: OrderType::Market,
1458            side: OrderSide::SELL,
1459            working_time: 1507725176595,
1460            self_trade_prevention_mode: STPMode::None,
1461            fills: vec![
1462                OrderFill {
1463                    price: dec!(4000.00000000),
1464                    qty: dec!(1.00000000),
1465                    commission: dec!(4.00000000),
1466                    commission_asset: String::from("USDT"),
1467                    trade_id: 56,
1468                },
1469                OrderFill {
1470                    price: dec!(3999.00000000),
1471                    qty: dec!(5.00000000),
1472                    commission: dec!(19.99500000),
1473                    commission_asset: String::from("USDT"),
1474                    trade_id: 57,
1475                },
1476                OrderFill {
1477                    price: dec!(3998.00000000),
1478                    qty: dec!(2.00000000),
1479                    commission: dec!(7.99600000),
1480                    commission_asset: String::from("USDT"),
1481                    trade_id: 58,
1482                },
1483                OrderFill {
1484                    price: dec!(3997.00000000),
1485                    qty: dec!(1.00000000),
1486                    commission: dec!(3.99700000),
1487                    commission_asset: String::from("USDT"),
1488                    trade_id: 59,
1489                },
1490                OrderFill {
1491                    price: dec!(3995.00000000),
1492                    qty: dec!(1.00000000),
1493                    commission: dec!(3.99500000),
1494                    commission_asset: String::from("USDT"),
1495                    trade_id: 60,
1496                },
1497            ],
1498            iceberg_qty: None,
1499            prevented_match_id: None,
1500            prevented_quantity: None,
1501            stop_price: None,
1502            strategy_id: None,
1503            strategy_type: None,
1504            trailing_delta: None,
1505            trailing_time: None,
1506            used_sor: None,
1507            working_floor: None,
1508        };
1509        let expected = NewOrderResponse::Full(response);
1510
1511        let current = deserialize_json(json).unwrap();
1512
1513        assert_eq!(expected, current);
1514    }
1515
1516    #[test]
1517    fn deserialize_response_test_order_commission_rates_empty() {
1518        let json = r#"{}"#;
1519        let expected = TestCommissionRates::Empty(TestCommissionRatesEmpty {});
1520
1521        let current = deserialize_json(json).unwrap();
1522
1523        assert_eq!(expected, current);
1524    }
1525
1526    #[test]
1527    fn deserialize_response_test_order_commission_rates_full() {
1528        let json = r#"{
1529            "standardCommissionForOrder": {
1530                "maker": "0.00000112",
1531                "taker": "0.00000114"
1532            },
1533            "taxCommissionForOrder": {
1534                "maker": "0.00000112",
1535                "taker": "0.00000114"
1536            },
1537            "discount": {
1538                "enabledForAccount": true,
1539                "enabledForSymbol": true,
1540                "discountAsset": "BNB",
1541                "discount": "0.25000000"
1542            }
1543        }"#;
1544        let rates = TestCommissionRatesFull {
1545            standard_commission_for_order: CommissionForOrder {
1546                maker: dec!(0.00000112),
1547                taker: dec!(0.00000114),
1548            },
1549            tax_commission_for_order: CommissionForOrder {
1550                maker: dec!(0.00000112),
1551                taker: dec!(0.00000114),
1552            },
1553            discount: Discount {
1554                enabled_for_account: true,
1555                enabled_for_symbol: true,
1556                discount_asset: String::from("BNB"),
1557                discount: dec!(0.25000000),
1558            },
1559        };
1560        let expected = TestCommissionRates::Full(rates);
1561
1562        let current = deserialize_json(json).unwrap();
1563
1564        assert_eq!(expected, current);
1565    }
1566
1567    #[test]
1568    fn deserialize_response_account_information() {
1569        let json = r#"{
1570            "makerCommission": 15,
1571            "takerCommission": 15,
1572            "buyerCommission": 0,
1573            "sellerCommission": 0,
1574            "commissionRates": {
1575                "maker": "0.00150000",
1576                "taker": "0.00150000",
1577                "buyer": "0.00000000",
1578                "seller": "0.00000000"
1579            },
1580            "canTrade": true,
1581            "canWithdraw": true,
1582            "canDeposit": true,
1583            "brokered": false,
1584            "requireSelfTradePrevention": false,
1585            "preventSor": false,
1586            "updateTime": 123456789,
1587            "accountType": "SPOT",
1588            "balances": [
1589                {
1590                "asset": "BTC",
1591                "free": "4723846.89208129",
1592                "locked": "0.00000000"
1593                },
1594                {
1595                "asset": "LTC",
1596                "free": "4763368.68006011",
1597                "locked": "0.00000000"
1598                }
1599            ],
1600            "permissions": [
1601                "SPOT"
1602            ],
1603            "uid": 354937868
1604        }"#;
1605        let expected = AccountInformation {
1606            maker_commission: 15.0,
1607            taker_commission: 15.0,
1608            buyer_commission: 0.0,
1609            seller_commission: 0.0,
1610            commission_rates: CommissionRates {
1611                maker: dec!(0.00150000),
1612                taker: dec!(0.00150000),
1613                buyer: dec!(0.00000000),
1614                seller: dec!(0.00000000),
1615            },
1616            can_trade: true,
1617            can_withdraw: true,
1618            can_deposit: true,
1619            brokered: false,
1620            require_self_trade_prevention: false,
1621            prevent_sor: false,
1622            update_time: 123456789,
1623            account_type: AccountType::Spot,
1624            balances: vec![
1625                Balance {
1626                    asset: String::from("BTC"),
1627                    free: dec!(4723846.89208129),
1628                    locked: dec!(0.00000000),
1629                },
1630                Balance {
1631                    asset: String::from("LTC"),
1632                    free: dec!(4763368.68006011),
1633                    locked: dec!(0.00000000),
1634                },
1635            ],
1636            permissions: Some(vec![String::from("SPOT")]),
1637            permission_sets: None,
1638            uid: 354937868,
1639        };
1640
1641        let current = deserialize_json(json).unwrap();
1642
1643        assert_eq!(expected, current);
1644    }
1645
1646    #[test]
1647    fn deserialize_response_query_order() {
1648        let json = r#"{
1649            "symbol": "LTCBTC",
1650            "orderId": 1,
1651            "orderListId": -1,
1652            "clientOrderId": "myOrder1",
1653            "price": "0.1",
1654            "origQty": "1.0",
1655            "executedQty": "0.0",
1656            "cummulativeQuoteQty": "0.0",
1657            "status": "NEW",
1658            "timeInForce": "GTC",
1659            "type": "LIMIT",
1660            "side": "BUY",
1661            "stopPrice": "0.0",
1662            "icebergQty": "0.0",
1663            "time": 1499827319559,
1664            "updateTime": 1499827319559,
1665            "isWorking": true,
1666            "workingTime":1499827319559,
1667            "origQuoteOrderQty": "0.000000",
1668            "selfTradePreventionMode": "NONE"
1669        }"#;
1670        let expected = Order {
1671            symbol: String::from("LTCBTC"),
1672            order_id: 1,
1673            order_list_id: -1,
1674            client_order_id: String::from("myOrder1"),
1675            price: dec!(0.1),
1676            orig_qty: dec!(1.0),
1677            executed_qty: dec!(0.0),
1678            cummulative_quote_qty: dec!(0.0),
1679            status: OrderStatus::New,
1680            time_in_force: TimeInForce::GTC,
1681            order_type: OrderType::Limit,
1682            side: OrderSide::BUY,
1683            stop_price: Some(dec!(0.0)),
1684            iceberg_qty: Some(dec!(0.0)),
1685            time: 1499827319559,
1686            update_time: 1499827319559,
1687            is_working: true,
1688            working_time: 1499827319559,
1689            orig_quote_order_qty: dec!(0.000000),
1690            self_trade_prevention_mode: STPMode::None,
1691        };
1692
1693        let current = deserialize_json(json).unwrap();
1694
1695        assert_eq!(expected, current);
1696    }
1697}