binance/spot/
api.rs

1use rust_decimal::Decimal;
2use serde::{Deserialize, Serialize};
3
4use crate::spot::{
5    ExchangeFilter, KlineInterval, OrderType, RateLimitInterval, RateLimiter, STPMode, SymbolStatus,
6};
7
8pub type Timestamp = u64;
9
10#[derive(Debug, PartialEq)]
11pub struct Response<T> {
12    pub result: T,
13    pub headers: Headers,
14}
15
16#[derive(Debug, PartialEq)]
17pub struct Headers {
18    pub retry_after: Option<Timestamp>,
19}
20
21#[derive(Debug, Deserialize, PartialEq)]
22pub struct TestConnectivity {}
23
24#[derive(Debug, Deserialize, PartialEq)]
25#[serde(rename_all = "camelCase")]
26pub struct ServerTime {
27    pub server_time: Timestamp,
28}
29
30#[derive(Debug, Serialize, PartialEq)]
31#[serde(rename_all = "camelCase")]
32pub struct GetExchangeInfoParams {
33    /// Example: curl -X GET "https://api.binance.com/api/v3/exchangeInfo?symbol=BNBBTC"
34    pub symbol: Option<String>,
35    /// Examples: curl -X GET "https://api.binance.com/api/v3/exchangeInfo?symbols=%5B%22BNBBTC%22,%22BTCUSDT%22%5D"
36    /// or
37    /// curl -g -X GET 'https://api.binance.com/api/v3/exchangeInfo?symbols=["BTCUSDT","BNBBTC"]'
38    /// TODO: Check serialization.
39    pub symbols: Option<Vec<String>>,
40    /// Examples: curl -X GET "https://api.binance.com/api/v3/exchangeInfo?permissions=SPOT"
41    /// or
42    /// curl -X GET "https://api.binance.com/api/v3/exchangeInfo?permissions=%5B%22MARGIN%22%2C%22LEVERAGED%22%5D"
43    /// or
44    /// curl -g -X GET 'https://api.binance.com/api/v3/exchangeInfo?permissions=["MARGIN","LEVERAGED"]'
45    /// TODO: Check serialization.
46    pub permissions: Option<Vec<String>>,
47    /// Controls whether the content of the permissionSets field is populated or not. Defaults to true
48    pub show_permission_sets: Option<bool>,
49    /// Filters symbols that have this tradingStatus. Valid values: TRADING, HALT, BREAK
50    /// Cannot be used in combination with symbols or symbol.
51    pub symbol_status: Option<SymbolStatus>,
52}
53
54#[derive(Debug, Deserialize, PartialEq)]
55#[serde(rename_all = "camelCase")]
56pub struct ExchangeInfo {
57    pub timezone: String,
58    pub server_time: Timestamp,
59    pub rate_limits: Vec<RateLimit>,
60    pub exchange_filters: Vec<ExchangeFilter>,
61    pub symbols: Vec<SymbolInfo>,
62    /// Optional field. Present only when SOR is available.
63    /// LINK: https://github.com/binance/binance-spot-api-docs/blob/master/faqs/sor_faq.md
64    pub sors: Option<Vec<SOR>>,
65}
66
67#[derive(Debug, Deserialize, PartialEq)]
68#[serde(rename_all = "camelCase")]
69pub struct RateLimit {
70    pub rate_limit_type: RateLimiter,
71    pub interval: RateLimitInterval,
72    pub interval_num: u64,
73    pub limit: u64,
74}
75
76#[derive(Debug, Deserialize, PartialEq)]
77#[serde(rename_all = "camelCase")]
78pub struct SymbolInfo {
79    pub symbol: String,
80    pub status: SymbolStatus,
81    pub base_asset: String,
82    pub base_asset_precision: u8, // value range: [0:8]
83    pub quote_asset: String,
84    // INFO: 'quote_precision' will be removed in future api versions (v4+)
85    pub quote_asset_precision: u8,      // value range: [0:8]
86    pub base_commission_precision: u8,  // value range: [0:8]
87    pub quote_commission_precision: u8, // value range: [0:8]
88    pub order_types: Vec<OrderType>,
89    pub iceberg_allowed: bool,
90    pub oco_allowed: bool,
91    pub oto_allowed: bool,
92    pub quote_order_qty_market_allowed: bool,
93    pub allow_trailing_stop: bool,
94    pub cancel_replace_allowed: bool,
95    pub amend_allowed: bool,
96    pub is_spot_trading_allowed: bool,
97    pub is_margin_trading_allowed: bool,
98    pub filters: Vec<Filter>,
99    pub permissions: Vec<String>,
100    pub permission_sets: Vec<Vec<String>>,
101    pub default_self_trade_prevention_mode: STPMode,
102    pub allowed_self_trade_prevention_modes: Vec<STPMode>,
103}
104
105#[derive(Debug, Deserialize, PartialEq)]
106#[serde(rename_all = "camelCase")]
107pub struct Filter {
108    // TODO:
109}
110
111/// Smart Order Routing (SOR).
112#[derive(Debug, Deserialize, PartialEq)]
113#[serde(rename_all = "camelCase")]
114pub struct SOR {
115    pub base_asset: String,
116    pub symbols: Vec<String>,
117}
118
119#[derive(Debug, Serialize, PartialEq)]
120#[serde(rename_all = "camelCase")]
121pub struct GetOrderBookParams {
122    pub symbol: String,
123    /// Default: 100; Maximum: 5000.
124    /// If limit > 5000, only 5000 entries will be returned.
125    pub limit: Option<u64>,
126}
127
128#[derive(Debug, Deserialize, PartialEq)]
129#[serde(rename_all = "camelCase")]
130pub struct OrderBook {
131    pub last_update_id: i64,
132    pub bids: Vec<OrderLevel>,
133    pub asks: Vec<OrderLevel>,
134}
135
136#[derive(Debug, Deserialize, PartialEq)]
137pub struct OrderLevel(Decimal, Decimal);
138
139impl OrderLevel {
140    pub fn price(&self) -> Decimal {
141        self.0
142    }
143    pub fn qty(&self) -> Decimal {
144        self.0
145    }
146}
147
148#[derive(Debug, Serialize, PartialEq)]
149#[serde(rename_all = "camelCase")]
150pub struct GetRecentTradesParams {
151    pub symbol: String,
152    /// Default: 500; Maximum: 1000.
153    pub limit: Option<u64>,
154}
155
156#[derive(Debug, Deserialize, PartialEq)]
157#[serde(rename_all = "camelCase")]
158pub struct RecentTrade {
159    pub id: i64,
160    pub price: Decimal,
161    pub qty: Decimal,
162    pub quote_qty: Decimal,
163    pub time: Timestamp,
164    pub is_buyer_maker: bool,
165    pub is_best_match: bool,
166}
167
168#[derive(Debug, Serialize, PartialEq)]
169#[serde(rename_all = "camelCase")]
170pub struct GetOlderTradesParams {
171    pub symbol: String,
172    /// Default: 500; Maximum: 1000.
173    pub limit: Option<u64>,
174    /// TradeId to fetch from. Default gets most recent trades.
175    pub from_id: Option<i64>,
176}
177
178#[derive(Debug, Serialize, PartialEq)]
179#[serde(rename_all = "camelCase")]
180pub struct GetAggregateTradesParams {
181    pub symbol: String,
182    /// ID to get aggregate trades from INCLUSIVE.
183    pub from_id: Option<i64>,
184    /// Timestamp in ms to get aggregate trades from INCLUSIVE.
185    pub start_time: Option<Timestamp>,
186    /// Timestamp in ms to get aggregate trades until INCLUSIVE.
187    pub end_time: Option<Timestamp>,
188    /// Default: 500; Maximum: 1000.
189    pub limit: Option<u64>,
190}
191
192#[derive(Debug, Deserialize, PartialEq)]
193pub struct AggregateTrade {
194    /// Aggregate tradeId
195    #[serde(rename = "a")]
196    pub id: i64,
197    /// Price
198    #[serde(rename = "p")]
199    pub price: Decimal,
200    /// Quantity
201    #[serde(rename = "q")]
202    pub qty: Decimal,
203    /// First tradeId
204    #[serde(rename = "f")]
205    pub first_trade_id: i64,
206    /// Last tradeId
207    #[serde(rename = "l")]
208    pub last_trade_id: i64,
209    /// Timestamp
210    #[serde(rename = "T")]
211    pub time: Timestamp,
212    /// Was the buyer the maker?
213    #[serde(rename = "m")]
214    pub is_buyer_maker: bool,
215    /// Was the trade the best price match?
216    #[serde(rename = "M")]
217    pub is_best_match: bool,
218}
219
220#[derive(Debug, Serialize, PartialEq)]
221#[serde(rename_all = "camelCase")]
222pub struct GetKlineListParams {
223    pub symbol: String,
224    pub interval: KlineInterval,
225    pub start_time: Option<Timestamp>,
226    pub end_time: Option<Timestamp>,
227    pub time_zone: Option<String>,
228    /// Default: 500; Maximum: 1000.
229    pub limit: Option<u64>,
230}
231
232#[derive(Debug, Deserialize, PartialEq)]
233pub struct Kline(
234    Timestamp, // Kline open time
235    Decimal,   // Open price
236    Decimal,   // High price
237    Decimal,   // Low price
238    Decimal,   // Close price
239    Decimal,   // Volume
240    Timestamp, // Kline Close time
241    Decimal,   // Quote asset volume
242    u64,       // Number of trades
243    Decimal,   // Taker buy base asset volume
244    Decimal,   // Taker buy quote asset volume
245    String,    // DEPRECATED: Unused field, ignore.
246);
247
248impl Kline {
249    /// Kline open time
250    pub fn time_open(&self) -> Timestamp {
251        self.0
252    }
253    /// Open price
254    pub fn open(&self) -> Decimal {
255        self.1
256    }
257    /// High price
258    pub fn high(&self) -> Decimal {
259        self.2
260    }
261    /// Low price
262    pub fn low(&self) -> Decimal {
263        self.3
264    }
265    /// Close price
266    pub fn close(&self) -> Decimal {
267        self.4
268    }
269    /// Volume
270    pub fn volume(&self) -> Decimal {
271        self.5
272    }
273    /// Kline Close time
274    pub fn time_close(&self) -> Timestamp {
275        self.6
276    }
277    /// Quote asset volume
278    pub fn quote_asset_volume(&self) -> Decimal {
279        self.7
280    }
281    /// Number of trades
282    pub fn id(&self) -> u64 {
283        self.8
284    }
285    /// Taker buy base asset volume
286    pub fn taker_buy_base_asset_volume(&self) -> Decimal {
287        self.9
288    }
289    /// Taker buy quote asset volume
290    pub fn taker_buy_quote_asset_volume(&self) -> Decimal {
291        self.10
292    }
293}
294
295#[derive(Debug, Serialize, PartialEq)]
296#[serde(rename_all = "camelCase")]
297pub struct GetCurrentAveragePriceParams {
298    pub symbol: String,
299}
300
301#[derive(Debug, Deserialize, PartialEq)]
302#[serde(rename_all = "camelCase")]
303pub struct CurrentAveragePrice {
304    /// Average price interval (in minutes)
305    pub mins: u64,
306    /// Average price
307    pub price: Decimal,
308    /// Last trade time
309    pub close_time: Timestamp,
310}
311
312/// Supported values: FULL or MINI.
313/// If none provided, the default is FULL
314#[derive(Debug, Serialize, PartialEq)]
315#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
316pub enum GetTickerPriceChangeStatisticsParams {
317    Mini(SymbolOrSymbols),
318    Full(SymbolOrSymbols),
319}
320
321#[derive(Debug, Serialize, PartialEq)]
322pub struct SymbolOrSymbols {
323    /// Parameter symbol and symbols cannot be used in combination.
324    /// If neither parameter is sent, tickers for all symbols will be returned in an array.
325    pub symbol: Option<String>,
326    /// Examples of accepted format for the symbols parameter: ["BTCUSDT","BNBUSDT"]
327    /// TODO: check serialization
328    /// or
329    /// %5B%22BTCUSDT%22,%22BNBUSDT%22%5D
330    pub symbols: Option<Vec<String>>,
331}
332
333#[derive(Debug, Deserialize, PartialEq)]
334#[serde(untagged)]
335pub enum TickerPriceChangeStatistic {
336    MiniElement(TickerPriceChangeStatisticMini),
337    MiniList(Vec<TickerPriceChangeStatisticMini>),
338    FullElement(TickerPriceChangeStatisticFull),
339    FullList(Vec<TickerPriceChangeStatisticFull>),
340}
341
342#[derive(Debug, Deserialize, PartialEq)]
343#[serde(rename_all = "camelCase")]
344pub struct TickerPriceChangeStatisticFull {
345    pub symbol: String,
346    pub price_change: Decimal,
347    pub price_change_percent: Decimal,
348    pub weighted_avg_price: Decimal,
349    pub prev_close_price: Decimal,
350    pub last_price: Decimal,
351    pub last_qty: Decimal,
352    pub bid_price: Decimal,
353    pub bid_qty: Decimal,
354    pub ask_price: Decimal,
355    pub ask_qty: Decimal,
356    pub open_price: Decimal,
357    pub high_price: Decimal,
358    pub low_price: Decimal,
359    pub volume: Decimal,
360    pub quote_volume: Decimal,
361    pub open_time: Timestamp,
362    pub close_time: Timestamp,
363    /// First traded
364    pub first_id: i64,
365    /// Last traded
366    pub last_id: i64,
367    /// Trade count
368    pub count: u64,
369}
370
371#[derive(Debug, Deserialize, PartialEq)]
372#[serde(rename_all = "camelCase")]
373pub struct TickerPriceChangeStatisticMini {
374    /// Symbol Name
375    pub symbol: String,
376    /// Opening price of the Interval
377    pub open_price: Decimal,
378    /// Highest price in the interval
379    pub high_price: Decimal,
380    /// Lowest  price in the interval
381    pub low_price: Decimal,
382    /// Closing price of the interval
383    pub last_price: Decimal,
384    /// Total trade volume (in base asset)
385    pub volume: Decimal,
386    /// Total trade volume (in quote asset)
387    pub quote_volume: Decimal,
388    /// Start of the ticker interval
389    pub open_time: Timestamp,
390    /// End of the ticker interval
391    pub close_time: Timestamp,
392    /// First tradeId considered
393    pub first_id: i64,
394    /// Last tradeId considered
395    pub last_id: i64,
396    /// Total trade count
397    pub count: u64,
398}
399
400#[cfg(test)]
401mod tests {
402    use rust_decimal::dec;
403
404    use crate::spot::serde::deserialize_str;
405
406    use super::*;
407
408    #[test]
409    fn deserialize_response_exchange_info() {
410        let json = r#"{
411            "timezone": "UTC",
412            "serverTime": 1565246363776,
413            "rateLimits": [],
414            "exchangeFilters": [],
415            "symbols": [
416                {
417                    "symbol": "ETHBTC",
418                    "status": "TRADING",
419                    "baseAsset": "ETH",
420                    "baseAssetPrecision": 8,
421                    "quoteAsset": "BTC",
422                    "quotePrecision": 8,
423                    "quoteAssetPrecision": 8,
424                    "baseCommissionPrecision": 8,
425                    "quoteCommissionPrecision": 8,
426                    "orderTypes": [
427                        "LIMIT",
428                        "LIMIT_MAKER",
429                        "MARKET",
430                        "STOP_LOSS",
431                        "STOP_LOSS_LIMIT",
432                        "TAKE_PROFIT",
433                        "TAKE_PROFIT_LIMIT"
434                    ],
435                    "icebergAllowed": true,
436                    "ocoAllowed": true,
437                    "otoAllowed": true,
438                    "quoteOrderQtyMarketAllowed": true,
439                    "allowTrailingStop": false,
440                    "cancelReplaceAllowed":false,
441                    "amendAllowed":false,
442                    "isSpotTradingAllowed": true,
443                    "isMarginTradingAllowed": true,
444                    "filters": [],
445                    "permissions": [],
446                    "permissionSets": [
447                        [
448                            "SPOT",
449                            "MARGIN"
450                        ]
451                    ],
452                    "defaultSelfTradePreventionMode": "NONE",
453                    "allowedSelfTradePreventionModes": [
454                        "NONE"
455                    ]
456                }
457            ],
458            "sors": [
459                {
460                    "baseAsset": "BTC",
461                    "symbols": [
462                        "BTCUSDT",
463                        "BTCUSDC"
464                    ]
465                }
466            ]
467        }"#;
468        let expected = ExchangeInfo {
469            timezone: String::from("UTC"),
470            server_time: 1565246363776,
471            rate_limits: vec![],
472            exchange_filters: vec![],
473            symbols: vec![SymbolInfo {
474                symbol: String::from("ETHBTC"),
475                status: SymbolStatus::Trading,
476                base_asset: String::from("ETH"),
477                base_asset_precision: 8,
478                quote_asset: String::from("BTC"),
479                quote_asset_precision: 8,
480                base_commission_precision: 8,
481                quote_commission_precision: 8,
482                order_types: vec![
483                    OrderType::Limit,
484                    OrderType::LimitMaker,
485                    OrderType::Market,
486                    OrderType::StopLoss,
487                    OrderType::StopLossLimit,
488                    OrderType::TakeProfit,
489                    OrderType::TakeProfitLimit,
490                ],
491                iceberg_allowed: true,
492                oco_allowed: true,
493                oto_allowed: true,
494                quote_order_qty_market_allowed: true,
495                allow_trailing_stop: false,
496                cancel_replace_allowed: false,
497                amend_allowed: false,
498                is_spot_trading_allowed: true,
499                is_margin_trading_allowed: true,
500                filters: vec![],
501                permissions: vec![],
502                permission_sets: vec![vec![String::from("SPOT"), String::from("MARGIN")]],
503                default_self_trade_prevention_mode: STPMode::None,
504                allowed_self_trade_prevention_modes: vec![STPMode::None],
505            }],
506            sors: Some(vec![SOR {
507                base_asset: String::from("BTC"),
508                symbols: vec![String::from("BTCUSDT"), String::from("BTCUSDC")],
509            }]),
510        };
511
512        let current = deserialize_str(json).unwrap();
513
514        assert_eq!(expected, current);
515    }
516
517    #[test]
518    fn deserialize_response_order_book() {
519        let json = r#"{
520            "lastUpdateId": 1027024,
521            "bids": [
522                [
523                "4.00000000",
524                "431.00000000"
525                ]
526            ],
527            "asks": [
528                [
529                "4.00000200",
530                "12.00000000"
531                ]
532            ]
533        }"#;
534        let expected = OrderBook {
535            last_update_id: 1027024,
536            bids: vec![OrderLevel(dec!(4.00000000), dec!(431.00000000))],
537            asks: vec![OrderLevel(dec!(4.00000200), dec!(12.00000000))],
538        };
539
540        let current = deserialize_str(json).unwrap();
541
542        assert_eq!(expected, current);
543    }
544}