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 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 timezone: String,
58 server_time: Timestamp,
59 rate_limits: Vec<RateLimit>,
60 exchange_filters: Vec<ExchangeFilter>,
61 symbols: Vec<SymbolInfo>,
62 sors: Option<Vec<SOR>>,
65}
66
67#[derive(Debug, Deserialize, PartialEq)]
68#[serde(rename_all = "camelCase")]
69pub struct RateLimit {
70 rate_limit_type: RateLimiter,
71 interval: RateLimitInterval,
72 interval_num: u64,
73 limit: u64,
74}
75
76#[derive(Debug, Deserialize, PartialEq)]
77#[serde(rename_all = "camelCase")]
78pub struct SymbolInfo {
79 symbol: String,
80 status: SymbolStatus,
81 base_asset: String,
82 base_asset_precision: u8, quote_asset: String,
84 quote_asset_precision: u8, base_commission_precision: u8, quote_commission_precision: u8, order_types: Vec<OrderType>,
89 iceberg_allowed: bool,
90 oco_allowed: bool,
91 oto_allowed: bool,
92 quote_order_qty_market_allowed: bool,
93 allow_trailing_stop: bool,
94 cancel_replace_allowed: bool,
95 amend_allowed: bool,
96 is_spot_trading_allowed: bool,
97 is_margin_trading_allowed: bool,
98 filters: Vec<Filter>,
99 permissions: Vec<String>,
100 permission_sets: Vec<Vec<String>>,
101 default_self_trade_prevention_mode: STPMode,
102 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 base_asset: String,
116 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 last_update_id: u64,
132 bids: Vec<OrderLevel>,
133 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 id: u64,
160 price: Decimal,
161 qty: Decimal,
162 quote_qty: Decimal,
163 time: Timestamp,
164 is_buyer_maker: bool,
165 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<u64>,
176}
177
178#[derive(Debug, Serialize, PartialEq)]
179#[serde(rename_all = "camelCase")]
180pub struct GetAggregateTradesParams {
181 pub symbol: String,
182 pub from_id: Option<u64>,
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 id: u64,
197 #[serde(rename = "p")]
199 price: Decimal,
200 #[serde(rename = "q")]
202 qty: Decimal,
203 #[serde(rename = "f")]
205 first_trade_id: u64,
206 #[serde(rename = "l")]
208 last_trade_id: u64,
209 #[serde(rename = "T")]
211 time: Timestamp,
212 #[serde(rename = "m")]
214 is_buyer_maker: bool,
215 #[serde(rename = "M")]
217 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#[cfg(test)]
313mod tests {
314 use rust_decimal::dec;
315
316 use crate::spot::serde::deserialize_str;
317
318 use super::*;
319
320 #[test]
321 fn deserialize_response_exchange_info() {
322 let json = r#"{
323 "timezone": "UTC",
324 "serverTime": 1565246363776,
325 "rateLimits": [],
326 "exchangeFilters": [],
327 "symbols": [
328 {
329 "symbol": "ETHBTC",
330 "status": "TRADING",
331 "baseAsset": "ETH",
332 "baseAssetPrecision": 8,
333 "quoteAsset": "BTC",
334 "quotePrecision": 8,
335 "quoteAssetPrecision": 8,
336 "baseCommissionPrecision": 8,
337 "quoteCommissionPrecision": 8,
338 "orderTypes": [
339 "LIMIT",
340 "LIMIT_MAKER",
341 "MARKET",
342 "STOP_LOSS",
343 "STOP_LOSS_LIMIT",
344 "TAKE_PROFIT",
345 "TAKE_PROFIT_LIMIT"
346 ],
347 "icebergAllowed": true,
348 "ocoAllowed": true,
349 "otoAllowed": true,
350 "quoteOrderQtyMarketAllowed": true,
351 "allowTrailingStop": false,
352 "cancelReplaceAllowed":false,
353 "amendAllowed":false,
354 "isSpotTradingAllowed": true,
355 "isMarginTradingAllowed": true,
356 "filters": [],
357 "permissions": [],
358 "permissionSets": [
359 [
360 "SPOT",
361 "MARGIN"
362 ]
363 ],
364 "defaultSelfTradePreventionMode": "NONE",
365 "allowedSelfTradePreventionModes": [
366 "NONE"
367 ]
368 }
369 ],
370 "sors": [
371 {
372 "baseAsset": "BTC",
373 "symbols": [
374 "BTCUSDT",
375 "BTCUSDC"
376 ]
377 }
378 ]
379 }"#;
380 let expected = ExchangeInfo {
381 timezone: String::from("UTC"),
382 server_time: 1565246363776,
383 rate_limits: vec![],
384 exchange_filters: vec![],
385 symbols: vec![SymbolInfo {
386 symbol: String::from("ETHBTC"),
387 status: SymbolStatus::Trading,
388 base_asset: String::from("ETH"),
389 base_asset_precision: 8,
390 quote_asset: String::from("BTC"),
391 quote_asset_precision: 8,
392 base_commission_precision: 8,
393 quote_commission_precision: 8,
394 order_types: vec![
395 OrderType::Limit,
396 OrderType::LimitMaker,
397 OrderType::Market,
398 OrderType::StopLoss,
399 OrderType::StopLossLimit,
400 OrderType::TakeProfit,
401 OrderType::TakeProfitLimit,
402 ],
403 iceberg_allowed: true,
404 oco_allowed: true,
405 oto_allowed: true,
406 quote_order_qty_market_allowed: true,
407 allow_trailing_stop: false,
408 cancel_replace_allowed: false,
409 amend_allowed: false,
410 is_spot_trading_allowed: true,
411 is_margin_trading_allowed: true,
412 filters: vec![],
413 permissions: vec![],
414 permission_sets: vec![vec![String::from("SPOT"), String::from("MARGIN")]],
415 default_self_trade_prevention_mode: STPMode::None,
416 allowed_self_trade_prevention_modes: vec![STPMode::None],
417 }],
418 sors: Some(vec![SOR {
419 base_asset: String::from("BTC"),
420 symbols: vec![String::from("BTCUSDT"), String::from("BTCUSDC")],
421 }]),
422 };
423
424 let current = deserialize_str(json).unwrap();
425
426 assert_eq!(expected, current);
427 }
428
429 #[test]
430 fn deserialize_response_order_book() {
431 let json = r#"{
432 "lastUpdateId": 1027024,
433 "bids": [
434 [
435 "4.00000000",
436 "431.00000000"
437 ]
438 ],
439 "asks": [
440 [
441 "4.00000200",
442 "12.00000000"
443 ]
444 ]
445 }"#;
446 let expected = OrderBook {
447 last_update_id: 1027024,
448 bids: vec![OrderLevel(dec!(4.00000000), dec!(431.00000000))],
449 asks: vec![OrderLevel(dec!(4.00000200), dec!(12.00000000))],
450 };
451
452 let current = deserialize_str(json).unwrap();
453
454 assert_eq!(expected, current);
455 }
456}