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 pub symbol: Option<String>,
35 pub symbols: Option<Vec<String>>,
40 pub permissions: Option<Vec<String>>,
47 pub show_permission_sets: Option<bool>,
49 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 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, pub quote_asset: String,
84 pub quote_asset_precision: u8, pub base_commission_precision: u8, pub quote_commission_precision: u8, 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 }
110
111#[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 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 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 pub limit: Option<u64>,
174 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 pub from_id: Option<i64>,
184 pub start_time: Option<Timestamp>,
186 pub end_time: Option<Timestamp>,
188 pub limit: Option<u64>,
190}
191
192#[derive(Debug, Deserialize, PartialEq)]
193pub struct AggregateTrade {
194 #[serde(rename = "a")]
196 pub id: i64,
197 #[serde(rename = "p")]
199 pub price: Decimal,
200 #[serde(rename = "q")]
202 pub qty: Decimal,
203 #[serde(rename = "f")]
205 pub first_trade_id: i64,
206 #[serde(rename = "l")]
208 pub last_trade_id: i64,
209 #[serde(rename = "T")]
211 pub time: Timestamp,
212 #[serde(rename = "m")]
214 pub is_buyer_maker: bool,
215 #[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 pub limit: Option<u64>,
230}
231
232#[derive(Debug, Deserialize, PartialEq)]
233pub struct Kline(
234 Timestamp, Decimal, Decimal, Decimal, Decimal, Decimal, Timestamp, Decimal, u64, Decimal, Decimal, String, );
247
248impl Kline {
249 pub fn time_open(&self) -> Timestamp {
251 self.0
252 }
253 pub fn open(&self) -> Decimal {
255 self.1
256 }
257 pub fn high(&self) -> Decimal {
259 self.2
260 }
261 pub fn low(&self) -> Decimal {
263 self.3
264 }
265 pub fn close(&self) -> Decimal {
267 self.4
268 }
269 pub fn volume(&self) -> Decimal {
271 self.5
272 }
273 pub fn time_close(&self) -> Timestamp {
275 self.6
276 }
277 pub fn quote_asset_volume(&self) -> Decimal {
279 self.7
280 }
281 pub fn id(&self) -> u64 {
283 self.8
284 }
285 pub fn taker_buy_base_asset_volume(&self) -> Decimal {
287 self.9
288 }
289 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 pub mins: u64,
306 pub price: Decimal,
308 pub close_time: Timestamp,
310}
311
312#[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 pub symbol: Option<String>,
326 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 pub first_id: i64,
365 pub last_id: i64,
367 pub count: u64,
369}
370
371#[derive(Debug, Deserialize, PartialEq)]
372#[serde(rename_all = "camelCase")]
373pub struct TickerPriceChangeStatisticMini {
374 pub symbol: String,
376 pub open_price: Decimal,
378 pub high_price: Decimal,
380 pub low_price: Decimal,
382 pub last_price: Decimal,
384 pub volume: Decimal,
386 pub quote_volume: Decimal,
388 pub open_time: Timestamp,
390 pub close_time: Timestamp,
392 pub first_id: i64,
394 pub last_id: i64,
396 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}