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 symbol: Option<String>,
39 #[serde(serialize_with = "serialize_option_as_json")]
42 symbols: Option<Vec<String>>,
43 #[serde(serialize_with = "serialize_option_as_json")]
47 permissions: Option<Vec<String>>,
48 show_permission_sets: Option<bool>,
50 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 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, pub quote_asset: String,
116 pub quote_asset_precision: u8, pub base_commission_precision: u8, pub quote_commission_precision: u8, 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#[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 #[serde(other)]
203 Unknown,
204}
205
206#[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 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 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 limit: Option<u64>,
297 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 from_id: Option<i64>,
327 start_time: Option<Timestamp>,
329 end_time: Option<Timestamp>,
331 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 #[serde(rename = "a")]
371 pub id: i64,
372 #[serde(rename = "p")]
374 pub price: Decimal,
375 #[serde(rename = "q")]
377 pub qty: Decimal,
378 #[serde(rename = "f")]
380 pub first_trade_id: i64,
381 #[serde(rename = "l")]
383 pub last_trade_id: i64,
384 #[serde(rename = "T")]
386 pub time: Timestamp,
387 #[serde(rename = "m")]
389 pub is_buyer_maker: bool,
390 #[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 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, Decimal, Decimal, Decimal, Decimal, Decimal, Timestamp, Decimal, u64, Decimal, Decimal, String, );
455
456impl Kline {
457 pub fn time_open(&self) -> Timestamp {
459 self.0
460 }
461 pub fn open(&self) -> Decimal {
463 self.1
464 }
465 pub fn high(&self) -> Decimal {
467 self.2
468 }
469 pub fn low(&self) -> Decimal {
471 self.3
472 }
473 pub fn close(&self) -> Decimal {
475 self.4
476 }
477 pub fn volume(&self) -> Decimal {
479 self.5
480 }
481 pub fn time_close(&self) -> Timestamp {
483 self.6
484 }
485 pub fn quote_asset_volume(&self) -> Decimal {
487 self.7
488 }
489 pub fn id(&self) -> u64 {
491 self.8
492 }
493 pub fn taker_buy_base_asset_volume(&self) -> Decimal {
495 self.9
496 }
497 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 pub mins: u64,
522 pub price: Decimal,
524 pub close_time: Timestamp,
526}
527
528#[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 symbol: Option<String>,
542 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 pub first_id: i64,
597 pub last_id: i64,
599 pub count: u64,
601}
602
603#[derive(Debug, Deserialize, PartialEq)]
604#[serde(rename_all = "camelCase")]
605pub struct TickerPriceChangeStatisticMini {
606 pub symbol: String,
608 pub open_price: Decimal,
610 pub high_price: Decimal,
612 pub low_price: Decimal,
614 pub last_price: Decimal,
616 pub volume: Decimal,
618 pub quote_volume: Decimal,
620 pub open_time: Timestamp,
622 pub close_time: Timestamp,
624 pub first_id: i64,
626 pub last_id: i64,
628 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 new_client_order_id: Option<String>,
646 strategy_id: Option<i64>,
647 strategy_type: Option<i64>,
649 stop_price: Option<Decimal>,
651 trailing_delta: Option<i64>,
653 iceberg_qty: Option<Decimal>,
655 new_order_resp_type: OrderResponseType,
658 self_trade_prevention_mode: Option<STPMode>,
660 recv_window: Option<u64>,
662 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 self.quantity.is_some() || self.quote_order_qty.is_some()
773 }
774 OrderType::StopLoss => {
775 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 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 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 pub order_list_id: i64,
820 pub client_order_id: String,
821 pub transact_time: Timestamp,
822 pub iceberg_qty: Option<Decimal>,
825 pub prevented_match_id: Option<i64>,
828 pub prevented_quantity: Option<Decimal>,
831 pub stop_price: Option<Decimal>,
834 pub strategy_id: Option<i64>,
837 pub strategy_type: Option<i64>,
840 pub trailing_delta: Option<i64>,
843 pub trailing_time: Option<i64>,
846 pub used_sor: Option<bool>,
849 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 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 pub iceberg_qty: Option<Decimal>,
878 pub prevented_match_id: Option<i64>,
881 pub prevented_quantity: Option<Decimal>,
884 pub stop_price: Option<Decimal>,
887 pub strategy_id: Option<i64>,
890 pub strategy_type: Option<i64>,
893 pub trailing_delta: Option<i64>,
896 pub trailing_time: Option<i64>,
899 pub used_sor: Option<bool>,
902 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 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 pub iceberg_qty: Option<Decimal>,
932 pub prevented_match_id: Option<i64>,
935 pub prevented_quantity: Option<Decimal>,
938 pub stop_price: Option<Decimal>,
941 pub strategy_id: Option<i64>,
944 pub strategy_type: Option<i64>,
947 pub trailing_delta: Option<i64>,
950 pub trailing_time: Option<i64>,
953 pub used_sor: Option<bool>,
956 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 pub standard_commission_for_order: CommissionForOrder,
986 pub tax_commission_for_order: CommissionForOrder,
988 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 pub discount: Decimal,
1006}
1007
1008#[derive(Debug, Default, Serialize, PartialEq)]
1009#[serde(rename_all = "camelCase")]
1010pub struct GetAccountInformationParams {
1011 omit_zero_balances: Option<bool>,
1014 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 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 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 pub stop_price: Option<Decimal>,
1129 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}