binance/spot/
api.rs

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    /// Example: curl -X GET "https://api.binance.com/api/v3/exchangeInfo?symbol=BNBBTC"
42    pub symbol: Option<String>,
43    /// Examples: curl -X GET "https://api.binance.com/api/v3/exchangeInfo?symbols=%5B%22BNBBTC%22,%22BTCUSDT%22%5D"
44    /// or
45    /// curl -g -X GET 'https://api.binance.com/api/v3/exchangeInfo?symbols=["BTCUSDT","BNBBTC"]'
46    /// TODO: Check serialization.
47    pub symbols: Option<Vec<String>>,
48    /// Examples: curl -X GET "https://api.binance.com/api/v3/exchangeInfo?permissions=SPOT"
49    /// or
50    /// curl -X GET "https://api.binance.com/api/v3/exchangeInfo?permissions=%5B%22MARGIN%22%2C%22LEVERAGED%22%5D"
51    /// or
52    /// curl -g -X GET 'https://api.binance.com/api/v3/exchangeInfo?permissions=["MARGIN","LEVERAGED"]'
53    /// TODO: Check serialization.
54    pub permissions: Option<Vec<String>>,
55    /// Controls whether the content of the permissionSets field is populated or not. Defaults to true
56    pub show_permission_sets: Option<bool>,
57    /// Filters symbols that have this tradingStatus. Valid values: TRADING, HALT, BREAK
58    /// Cannot be used in combination with symbols or symbol.
59    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    /// Optional field. Present only when SOR is available.
71    /// LINK: https://github.com/binance/binance-spot-api-docs/blob/master/faqs/sor_faq.md
72    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, // value range: [0:8]
91    pub quote_asset: String,
92    // INFO: 'quote_precision' will be removed in future api versions (v4+)
93    pub quote_asset_precision: u8,      // value range: [0:8]
94    pub base_commission_precision: u8,  // value range: [0:8]
95    pub quote_commission_precision: u8, // value range: [0:8]
96    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    // TODO:
117}
118
119/// Smart Order Routing (SOR).
120#[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    /// Default: 100; Maximum: 5000.
132    /// If limit > 5000, only 5000 entries will be returned.
133    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    /// Default: 500; Maximum: 1000.
161    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    /// Default: 500; Maximum: 1000.
181    pub limit: Option<u64>,
182    /// TradeId to fetch from. Default gets most recent trades.
183    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    /// ID to get aggregate trades from INCLUSIVE.
191    pub from_id: Option<i64>,
192    /// Timestamp in ms to get aggregate trades from INCLUSIVE.
193    pub start_time: Option<Timestamp>,
194    /// Timestamp in ms to get aggregate trades until INCLUSIVE.
195    pub end_time: Option<Timestamp>,
196    /// Default: 500; Maximum: 1000.
197    pub limit: Option<u64>,
198}
199
200#[derive(Debug, Deserialize, PartialEq)]
201pub struct AggregateTrade {
202    /// Aggregate tradeId
203    #[serde(rename = "a")]
204    pub id: i64,
205    /// Price
206    #[serde(rename = "p")]
207    pub price: Decimal,
208    /// Quantity
209    #[serde(rename = "q")]
210    pub qty: Decimal,
211    /// First tradeId
212    #[serde(rename = "f")]
213    pub first_trade_id: i64,
214    /// Last tradeId
215    #[serde(rename = "l")]
216    pub last_trade_id: i64,
217    /// Timestamp
218    #[serde(rename = "T")]
219    pub time: Timestamp,
220    /// Was the buyer the maker?
221    #[serde(rename = "m")]
222    pub is_buyer_maker: bool,
223    /// Was the trade the best price match?
224    #[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    /// Default: 500; Maximum: 1000.
237    pub limit: Option<u64>,
238}
239
240#[derive(Debug, Deserialize, PartialEq)]
241pub struct Kline(
242    Timestamp, // Kline open time
243    Decimal,   // Open price
244    Decimal,   // High price
245    Decimal,   // Low price
246    Decimal,   // Close price
247    Decimal,   // Volume
248    Timestamp, // Kline Close time
249    Decimal,   // Quote asset volume
250    u64,       // Number of trades
251    Decimal,   // Taker buy base asset volume
252    Decimal,   // Taker buy quote asset volume
253    String,    // DEPRECATED: Unused field, ignore.
254);
255
256impl Kline {
257    /// Kline open time
258    pub fn time_open(&self) -> Timestamp {
259        self.0
260    }
261    /// Open price
262    pub fn open(&self) -> Decimal {
263        self.1
264    }
265    /// High price
266    pub fn high(&self) -> Decimal {
267        self.2
268    }
269    /// Low price
270    pub fn low(&self) -> Decimal {
271        self.3
272    }
273    /// Close price
274    pub fn close(&self) -> Decimal {
275        self.4
276    }
277    /// Volume
278    pub fn volume(&self) -> Decimal {
279        self.5
280    }
281    /// Kline Close time
282    pub fn time_close(&self) -> Timestamp {
283        self.6
284    }
285    /// Quote asset volume
286    pub fn quote_asset_volume(&self) -> Decimal {
287        self.7
288    }
289    /// Number of trades
290    pub fn id(&self) -> u64 {
291        self.8
292    }
293    /// Taker buy base asset volume
294    pub fn taker_buy_base_asset_volume(&self) -> Decimal {
295        self.9
296    }
297    /// Taker buy quote asset volume
298    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    /// Average price interval (in minutes)
313    pub mins: u64,
314    /// Average price
315    pub price: Decimal,
316    /// Last trade time
317    pub close_time: Timestamp,
318}
319
320/// Supported values: FULL or MINI.
321/// If none provided, the default is FULL
322#[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    /// Parameter symbol and symbols cannot be used in combination.
332    /// If neither parameter is sent, tickers for all symbols will be returned in an array.
333    pub symbol: Option<String>,
334    /// Examples of accepted format for the symbols parameter: ["BTCUSDT","BNBUSDT"]
335    /// TODO: check serialization
336    /// or
337    /// %5B%22BTCUSDT%22,%22BNBUSDT%22%5D
338    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    /// First traded
372    pub first_id: i64,
373    /// Last traded
374    pub last_id: i64,
375    /// Trade count
376    pub count: u64,
377}
378
379#[derive(Debug, Deserialize, PartialEq)]
380#[serde(rename_all = "camelCase")]
381pub struct TickerPriceChangeStatisticMini {
382    /// Symbol Name
383    pub symbol: String,
384    /// Opening price of the Interval
385    pub open_price: Decimal,
386    /// Highest price in the interval
387    pub high_price: Decimal,
388    /// Lowest  price in the interval
389    pub low_price: Decimal,
390    /// Closing price of the interval
391    pub last_price: Decimal,
392    /// Total trade volume (in base asset)
393    pub volume: Decimal,
394    /// Total trade volume (in quote asset)
395    pub quote_volume: Decimal,
396    /// Start of the ticker interval
397    pub open_time: Timestamp,
398    /// End of the ticker interval
399    pub close_time: Timestamp,
400    /// First tradeId considered
401    pub first_id: i64,
402    /// Last tradeId considered
403    pub last_id: i64,
404    /// Total trade count
405    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    /// A unique id among open orders. Automatically generated if not sent.
420    /// Orders with the same newClientOrderID can be accepted only when the previous one is filled, otherwise the order will be rejected.
421    pub new_client_order_id: Option<String>,
422    pub strategy_id: Option<i64>,
423    /// The value cannot be less than 1000000.
424    pub strategy_type: Option<i64>,
425    /// Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders.
426    pub stop_price: Option<Decimal>,
427    /// See Trailing Stop order FAQ.
428    pub trailing_delta: Option<i64>,
429    /// Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order.
430    pub iceberg_qty: Option<Decimal>,
431    /// Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to FULL, all other orders default to ACK.
432    /// Mandatory - because there is a problem with deserialization of untagged enum Order
433    pub new_order_resp_type: OrderResponseType,
434    /// The allowed enums is dependent on what is configured on the symbol. The possible supported values are: STP Modes.
435    pub self_trade_prevention_mode: Option<STPMode>,
436    /// The value cannot be greater than 60000
437    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                // MARKET orders using the quantity field specifies the amount of the base asset the user wants to buy or sell at the market price.
449                // E.g. MARKET order on BTCUSDT will specify how much BTC the user is buying or selling.
450
451                // 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.
452                // E.g. Using the symbol BTCUSDT:
453                // BUY side, the order will buy as many BTC as quoteOrderQty USDT can.
454                // SELL side, the order will sell as much BTC needed to receive quoteOrderQty USDT.
455                self.quantity.is_some() || self.quote_order_qty.is_some()
456            }
457            OrderType::StopLoss => {
458                // This will execute a MARKET order when the conditions are met. (e.g. stopPrice is met or trailingDelta is activated)
459                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                // This will execute a MARKET order when the conditions are met. (e.g. stopPrice is met or trailingDelta is activated)
470                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                // This is a LIMIT order that will be rejected if the order immediately matches and trades as a taker.
481                // This is also known as a POST-ONLY order.
482                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    /// Unless it's part of an order list, value will be -1
502    pub order_list_id: i64,
503    pub client_order_id: String,
504    pub transact_time: Timestamp,
505    /// Quantity for the iceberg order
506    /// Appears only if the parameter icebergQty was sent in the request.
507    pub iceberg_qty: Option<Decimal>,
508    /// When used in combination with symbol, can be used to query a prevented match.
509    /// Appears only if the order expired due to STP.
510    pub prevented_match_id: Option<i64>,
511    /// Order quantity that expired due to STP
512    /// Appears only if the order expired due to STP.
513    pub prevented_quantity: Option<Decimal>,
514    /// Price when the algorithmic order will be triggered
515    /// Appears for STOP_LOSS. TAKE_PROFIT, STOP_LOSS_LIMIT and TAKE_PROFIT_LIMIT orders.
516    pub stop_price: Option<Decimal>,
517    /// Can be used to label an order that's part of an order strategy.
518    /// Appears if the parameter was populated in the request.
519    pub strategy_id: Option<i64>,
520    /// Can be used to label an order that is using an order strategy.
521    /// Appears if the parameter was populated in the request.
522    pub strategy_type: Option<i64>,
523    /// Delta price change required before order activation
524    /// Appears for Trailing Stop Orders.
525    pub trailing_delta: Option<i64>,
526    /// Time when the trailing order is now active and tracking price changes
527    /// Appears only for Trailing Stop Orders.
528    pub trailing_time: Option<i64>,
529    /// Field that determines whether order used SOR
530    /// Appears when placing orders using SOR
531    pub used_sor: Option<bool>,
532    /// Field that determines whether the order is being filled by the SOR or by the order book the order was submitted to.
533    /// Appears when placing orders using SOR
534    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    /// Unless it's part of an order list, value will be -1
543    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    /// Quantity for the iceberg order
559    /// Appears only if the parameter icebergQty was sent in the request.
560    pub iceberg_qty: Option<Decimal>,
561    /// When used in combination with symbol, can be used to query a prevented match.
562    /// Appears only if the order expired due to STP.
563    pub prevented_match_id: Option<i64>,
564    /// Order quantity that expired due to STP
565    /// Appears only if the order expired due to STP.
566    pub prevented_quantity: Option<Decimal>,
567    /// Price when the algorithmic order will be triggered
568    /// Appears for STOP_LOSS. TAKE_PROFIT, STOP_LOSS_LIMIT and TAKE_PROFIT_LIMIT orders.
569    pub stop_price: Option<Decimal>,
570    /// Can be used to label an order that's part of an order strategy.
571    /// Appears if the parameter was populated in the request.
572    pub strategy_id: Option<i64>,
573    /// Can be used to label an order that is using an order strategy.
574    /// Appears if the parameter was populated in the request.
575    pub strategy_type: Option<i64>,
576    /// Delta price change required before order activation
577    /// Appears for Trailing Stop Orders.
578    pub trailing_delta: Option<i64>,
579    /// Time when the trailing order is now active and tracking price changes
580    /// Appears only for Trailing Stop Orders.
581    pub trailing_time: Option<i64>,
582    /// Field that determines whether order used SOR
583    /// Appears when placing orders using SOR
584    pub used_sor: Option<bool>,
585    /// Field that determines whether the order is being filled by the SOR or by the order book the order was submitted to.
586    /// Appears when placing orders using SOR
587    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    /// Unless it's part of an order list, value will be -1
596    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    /// Quantity for the iceberg order
613    /// Appears only if the parameter icebergQty was sent in the request.
614    pub iceberg_qty: Option<Decimal>,
615    /// When used in combination with symbol, can be used to query a prevented match.
616    /// Appears only if the order expired due to STP.
617    pub prevented_match_id: Option<i64>,
618    /// Order quantity that expired due to STP
619    /// Appears only if the order expired due to STP.
620    pub prevented_quantity: Option<Decimal>,
621    /// Price when the algorithmic order will be triggered
622    /// Appears for STOP_LOSS. TAKE_PROFIT, STOP_LOSS_LIMIT and TAKE_PROFIT_LIMIT orders.
623    pub stop_price: Option<Decimal>,
624    /// Can be used to label an order that's part of an order strategy.
625    /// Appears if the parameter was populated in the request.
626    pub strategy_id: Option<i64>,
627    /// Can be used to label an order that is using an order strategy.
628    /// Appears if the parameter was populated in the request.
629    pub strategy_type: Option<i64>,
630    /// Delta price change required before order activation
631    /// Appears for Trailing Stop Orders.
632    pub trailing_delta: Option<i64>,
633    /// Time when the trailing order is now active and tracking price changes
634    /// Appears only for Trailing Stop Orders.
635    pub trailing_time: Option<i64>,
636    /// Field that determines whether order used SOR
637    /// Appears when placing orders using SOR
638    pub used_sor: Option<bool>,
639    /// Field that determines whether the order is being filled by the SOR or by the order book the order was submitted to.
640    /// Appears when placing orders using SOR
641    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    /// Standard commission rates on trades from the order.
668    pub standard_commission_for_order: CommissionForOrder,
669    /// Tax commission rates for trades from the order.
670    pub tax_commission_for_order: CommissionForOrder,
671    /// Discount on standard commissions when paying in BNB.
672    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    /// Standard commission is reduced by this rate when paying commission in BNB.
688    pub discount: Decimal,
689}
690
691#[derive(Debug, Serialize, PartialEq)]
692#[serde(rename_all = "camelCase")]
693pub struct GetAccountInformationParams {
694    /// When set to true, emits only the non-zero balances of an account.
695    /// Default value: false
696    pub omit_zero_balances: Option<bool>,
697    /// The value cannot be greater than 60000
698    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    /// The value cannot be greater than 60000
748    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    /// This field will always have a value of -1 if not an order list.
758    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    /// Price when the algorithmic order will be triggered
770    /// Appears for STOP_LOSS. TAKE_PROFIT, STOP_LOSS_LIMIT and TAKE_PROFIT_LIMIT orders.
771    pub stop_price: Option<Decimal>,
772    /// Quantity for the iceberg order
773    /// Appears only if the parameter icebergQty was sent in the request.
774    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}