1use std::fmt::Debug;
21
22use nautilus_core::{UUID4, nanos::UnixNanos, string::secret::SecretString};
23use nautilus_model::{
24 enums::AccountType,
25 events::AccountState,
26 identifiers::AccountId,
27 types::{AccountBalance, Currency, Money},
28};
29use rust_decimal::Decimal;
30use zeroize::{Zeroize, ZeroizeOnDrop};
31
32use crate::{
33 common::{
34 enums::{
35 BinanceOrderStatus, BinanceSelfTradePreventionMode, BinanceSide, BinanceTimeInForce,
36 },
37 parse::parse_micros_or_init,
38 },
39 spot::sbe::spot::{
40 contingency_type::ContingencyType, list_order_status::ListOrderStatus,
41 list_status_type::ListStatusType, order_side::OrderSide, order_status::OrderStatus,
42 order_type::OrderType, self_trade_prevention_mode::SelfTradePreventionMode,
43 time_in_force::TimeInForce,
44 },
45};
46
47#[derive(Debug, Clone, Copy, PartialEq)]
49pub struct BinancePriceLevel {
50 pub price_mantissa: i64,
52 pub qty_mantissa: i64,
54}
55
56#[derive(Debug, Clone, PartialEq)]
58pub struct BinanceDepth {
59 pub last_update_id: i64,
61 pub price_exponent: i8,
63 pub qty_exponent: i8,
65 pub bids: Vec<BinancePriceLevel>,
67 pub asks: Vec<BinancePriceLevel>,
69}
70
71#[derive(Debug, Clone, PartialEq)]
73pub struct BinanceTrade {
74 pub id: i64,
76 pub price_mantissa: i64,
78 pub qty_mantissa: i64,
80 pub quote_qty_mantissa: i64,
82 pub time: i64,
84 pub is_buyer_maker: bool,
86 pub is_best_match: bool,
88}
89
90#[derive(Debug, Clone, PartialEq)]
92pub struct BinanceTrades {
93 pub price_exponent: i8,
95 pub qty_exponent: i8,
97 pub trades: Vec<BinanceTrade>,
99}
100
101#[derive(Debug, Clone, PartialEq)]
103pub struct BinanceAggTrade {
104 pub id: i64,
106 pub price_mantissa: i64,
108 pub qty_mantissa: i64,
110 pub first_trade_id: i64,
112 pub last_trade_id: i64,
114 pub time: i64,
116 pub is_buyer_maker: bool,
118 pub is_best_match: bool,
120}
121
122#[derive(Debug, Clone, PartialEq)]
124pub struct BinanceAggTrades {
125 pub price_exponent: i8,
127 pub qty_exponent: i8,
129 pub trades: Vec<BinanceAggTrade>,
131}
132
133#[derive(Debug, Clone, PartialEq)]
135pub struct BinanceOrderFill {
136 pub price_mantissa: i64,
138 pub qty_mantissa: i64,
140 pub commission_mantissa: i64,
142 pub commission_exponent: i8,
144 pub commission_asset: String,
146 pub trade_id: Option<i64>,
148}
149
150#[derive(Debug, Clone, PartialEq)]
152pub struct BinanceNewOrderResponse {
153 pub price_exponent: i8,
155 pub qty_exponent: i8,
157 pub order_id: i64,
159 pub order_list_id: Option<i64>,
161 pub transact_time: i64,
163 pub price_mantissa: i64,
165 pub orig_qty_mantissa: i64,
167 pub executed_qty_mantissa: i64,
169 pub cummulative_quote_qty_mantissa: i64,
171 pub status: OrderStatus,
173 pub time_in_force: TimeInForce,
175 pub order_type: OrderType,
177 pub side: OrderSide,
179 pub stop_price_mantissa: Option<i64>,
181 pub working_time: Option<i64>,
183 pub self_trade_prevention_mode: SelfTradePreventionMode,
185 pub client_order_id: String,
187 pub symbol: String,
189 pub fills: Vec<BinanceOrderFill>,
191 pub expiry_reason: Option<u8>,
193}
194
195#[derive(Debug, Clone, PartialEq)]
197pub struct BinanceCancelOrderResponse {
198 pub price_exponent: i8,
200 pub qty_exponent: i8,
202 pub order_id: i64,
204 pub order_list_id: Option<i64>,
206 pub transact_time: i64,
208 pub price_mantissa: i64,
210 pub orig_qty_mantissa: i64,
212 pub executed_qty_mantissa: i64,
214 pub cummulative_quote_qty_mantissa: i64,
216 pub status: OrderStatus,
218 pub time_in_force: TimeInForce,
220 pub order_type: OrderType,
222 pub side: OrderSide,
224 pub self_trade_prevention_mode: SelfTradePreventionMode,
226 pub client_order_id: String,
228 pub orig_client_order_id: String,
230 pub symbol: String,
232}
233
234#[derive(Debug, Clone, PartialEq)]
236pub struct BinanceCancelOrderListOrder {
237 pub symbol: String,
239 pub order_id: i64,
241 pub client_order_id: String,
243}
244
245#[derive(Debug, Clone, PartialEq)]
247pub struct BinanceCancelOrderListResponse {
248 pub order_list_id: i64,
250 pub contingency_type: ContingencyType,
252 pub list_status_type: ListStatusType,
254 pub list_order_status: ListOrderStatus,
256 pub transaction_time: i64,
258 pub list_client_order_id: String,
260 pub symbol: String,
262 pub orders: Vec<BinanceCancelOrderListOrder>,
264 pub order_reports: Vec<BinanceCancelOrderResponse>,
266}
267
268#[derive(Debug, Clone, PartialEq)]
270pub enum BinanceCancelOpenOrdersResponse {
271 Order(BinanceCancelOrderResponse),
273 OrderList(BinanceCancelOrderListResponse),
275}
276
277#[derive(Debug, Clone, PartialEq)]
279pub struct BinanceOrderResponse {
280 pub price_exponent: i8,
282 pub qty_exponent: i8,
284 pub order_id: i64,
286 pub order_list_id: Option<i64>,
288 pub price_mantissa: i64,
290 pub orig_qty_mantissa: i64,
292 pub executed_qty_mantissa: i64,
294 pub cummulative_quote_qty_mantissa: i64,
296 pub status: OrderStatus,
298 pub time_in_force: TimeInForce,
300 pub order_type: OrderType,
302 pub side: OrderSide,
304 pub stop_price_mantissa: Option<i64>,
306 pub iceberg_qty_mantissa: Option<i64>,
308 pub time: i64,
310 pub update_time: i64,
312 pub is_working: bool,
314 pub working_time: Option<i64>,
316 pub orig_quote_order_qty_mantissa: i64,
318 pub self_trade_prevention_mode: SelfTradePreventionMode,
320 pub client_order_id: String,
322 pub symbol: String,
324 pub expiry_reason: Option<u8>,
326}
327
328#[derive(Debug, Clone, PartialEq)]
330pub struct BinanceBalance {
331 pub asset: String,
333 pub free_mantissa: i64,
335 pub locked_mantissa: i64,
337 pub exponent: i8,
339}
340
341#[derive(Debug, Clone, PartialEq)]
343pub struct BinanceAccountInfo {
344 pub commission_exponent: i8,
346 pub maker_commission_mantissa: i64,
348 pub taker_commission_mantissa: i64,
350 pub buyer_commission_mantissa: i64,
352 pub seller_commission_mantissa: i64,
354 pub can_trade: bool,
356 pub can_withdraw: bool,
358 pub can_deposit: bool,
360 pub require_self_trade_prevention: bool,
362 pub prevent_sor: bool,
364 pub update_time: i64,
366 pub account_type: String,
368 pub balances: Vec<BinanceBalance>,
370}
371
372impl BinanceAccountInfo {
373 #[must_use]
375 pub fn to_account_state(&self, account_id: AccountId, ts_init: UnixNanos) -> AccountState {
376 let mut balances = Vec::with_capacity(self.balances.len());
377
378 for asset in &self.balances {
379 let currency =
380 Currency::get_or_create_crypto_with_context(&asset.asset, Some("spot balance"));
381
382 let exponent = asset.exponent as i32;
383 let multiplier = Decimal::new(1, (-exponent) as u32);
384
385 let free = Decimal::new(asset.free_mantissa, 0) * multiplier;
386 let locked = Decimal::new(asset.locked_mantissa, 0) * multiplier;
387 let total = free + locked;
388
389 match AccountBalance::from_total_and_locked(total, locked, currency) {
390 Ok(balance) => balances.push(balance),
391 Err(e) => log::warn!("Skipping spot balance for {}: {e}", currency.code),
392 }
393 }
394
395 if balances.is_empty() {
397 let zero_currency = Currency::USDT();
398 let zero_money = Money::zero(zero_currency);
399 let zero_balance = AccountBalance::new(zero_money, zero_money, zero_money);
400 balances.push(zero_balance);
401 }
402
403 let ts_event =
404 parse_micros_or_init(self.update_time, "Spot SBE account update time", ts_init);
405
406 AccountState::new(
407 account_id,
408 AccountType::Cash,
409 balances,
410 vec![], true, UUID4::new(),
413 ts_event,
414 ts_init,
415 None, )
417 }
418}
419
420#[derive(Debug, Clone, PartialEq)]
422pub struct BinancePriceFilterSbe {
423 pub price_exponent: i8,
425 pub min_price: i64,
427 pub max_price: i64,
429 pub tick_size: i64,
431}
432
433#[derive(Debug, Clone, PartialEq)]
435pub struct BinanceLotSizeFilterSbe {
436 pub qty_exponent: i8,
438 pub min_qty: i64,
440 pub max_qty: i64,
442 pub step_size: i64,
444}
445
446#[derive(Debug, Clone, PartialEq)]
448pub struct BinanceNotionalFilter {
449 pub min: Decimal,
451 pub max: Option<Decimal>,
453 pub apply_min_to_market: bool,
455 pub apply_max_to_market: bool,
457 pub avg_price_mins: u32,
459}
460
461#[derive(Debug, Clone, Default, PartialEq)]
463pub struct BinanceSymbolFiltersSbe {
464 pub price_filter: Option<BinancePriceFilterSbe>,
466 pub lot_size_filter: Option<BinanceLotSizeFilterSbe>,
468 pub notional_filters: Vec<BinanceNotionalFilter>,
470}
471
472#[derive(Debug, Clone, PartialEq)]
474pub struct BinanceSymbolSbe {
475 pub symbol: String,
477 pub base_asset: String,
479 pub quote_asset: String,
481 pub base_asset_precision: u8,
483 pub quote_asset_precision: u8,
485 pub status: u8,
487 pub order_types: u16,
489 pub iceberg_allowed: bool,
491 pub oco_allowed: bool,
493 pub oto_allowed: bool,
495 pub quote_order_qty_market_allowed: bool,
497 pub allow_trailing_stop: bool,
499 pub cancel_replace_allowed: bool,
501 pub amend_allowed: bool,
503 pub is_spot_trading_allowed: bool,
505 pub is_margin_trading_allowed: bool,
507 pub filters: BinanceSymbolFiltersSbe,
509 pub permissions: Vec<Vec<String>>,
511}
512
513#[derive(Debug, Clone, PartialEq)]
515pub struct BinanceExchangeInfoSbe {
516 pub symbols: Vec<BinanceSymbolSbe>,
518}
519
520#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
522pub struct BinanceExchangeInfoJson {
523 pub symbols: Vec<BinanceSymbolJson>,
525}
526
527#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
529#[serde(rename_all = "camelCase")]
530pub struct BinanceSymbolJson {
531 pub symbol: String,
533 pub status: String,
535 pub base_asset: String,
537 pub quote_asset: String,
539 pub base_asset_precision: u8,
541 pub quote_asset_precision: u8,
543 pub filters: Vec<BinanceSymbolFilterJson>,
545}
546
547#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
549#[serde(rename_all = "camelCase")]
550pub struct BinanceSymbolFilterJson {
551 pub filter_type: String,
553 pub min_price: Option<String>,
555 pub max_price: Option<String>,
557 pub tick_size: Option<String>,
559 pub min_qty: Option<String>,
561 pub max_qty: Option<String>,
563 pub step_size: Option<String>,
565 pub min_notional: Option<String>,
567 pub max_notional: Option<String>,
569 pub apply_to_market: Option<bool>,
571 pub apply_min_to_market: Option<bool>,
573 pub apply_max_to_market: Option<bool>,
575 pub avg_price_mins: Option<u32>,
577}
578
579#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
581#[serde(rename_all = "camelCase")]
582pub struct BinanceAccountCommission {
583 pub symbol: String,
585 pub standard_commission: BinanceCommissionRates,
587}
588
589#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
591pub struct BinanceCommissionRates {
592 pub maker: String,
594 pub taker: String,
596}
597
598#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
600#[serde(rename_all = "camelCase")]
601pub struct BinanceAccountRatesJson {
602 pub commission_rates: BinanceCommissionRates,
604}
605
606#[derive(Debug, Clone, PartialEq)]
608pub struct BinanceAccountTrade {
609 pub price_exponent: i8,
611 pub qty_exponent: i8,
613 pub commission_exponent: i8,
615 pub id: i64,
617 pub order_id: i64,
619 pub order_list_id: Option<i64>,
621 pub price_mantissa: i64,
623 pub qty_mantissa: i64,
625 pub quote_qty_mantissa: i64,
627 pub commission_mantissa: i64,
629 pub time: i64,
631 pub is_buyer: bool,
633 pub is_maker: bool,
635 pub is_best_match: bool,
637 pub symbol: String,
639 pub commission_asset: String,
641}
642
643#[derive(Debug, Clone, PartialEq)]
645pub struct BinanceKlines {
646 pub price_exponent: i8,
648 pub qty_exponent: i8,
650 pub klines: Vec<BinanceKline>,
652}
653
654#[derive(Debug, Clone, PartialEq, serde::Deserialize, Zeroize, ZeroizeOnDrop)]
656#[serde(rename_all = "camelCase")]
657pub struct ListenKeyResponse {
658 pub listen_key: SecretString,
660}
661
662impl ListenKeyResponse {
663 #[must_use]
665 pub fn into_listen_key(mut self) -> SecretString {
666 std::mem::take(&mut self.listen_key)
667 }
668}
669
670#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
672#[serde(rename_all = "camelCase")]
673pub struct Ticker24hr {
674 pub symbol: String,
676 pub price_change: String,
678 pub price_change_percent: String,
680 pub weighted_avg_price: String,
682 pub prev_close_price: String,
684 pub last_price: String,
686 pub last_qty: String,
688 pub bid_price: String,
690 pub bid_qty: String,
692 pub ask_price: String,
694 pub ask_qty: String,
696 pub open_price: String,
698 pub high_price: String,
700 pub low_price: String,
702 pub volume: String,
704 pub quote_volume: String,
706 pub open_time: i64,
708 pub close_time: i64,
710 pub first_id: i64,
712 pub last_id: i64,
714 pub count: i64,
716}
717
718#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
720pub struct TickerPrice {
721 pub symbol: String,
723 pub price: String,
725}
726
727#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
729#[serde(rename_all = "camelCase")]
730pub struct BookTicker {
731 pub symbol: String,
733 pub bid_price: String,
735 pub bid_qty: String,
737 pub ask_price: String,
739 pub ask_qty: String,
741}
742
743#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
745pub struct AvgPrice {
746 pub mins: i64,
748 pub price: String,
750 #[serde(rename = "closeTime")]
752 pub close_time: i64,
753}
754
755#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
757#[serde(rename_all = "camelCase")]
758pub struct TradeFee {
759 pub symbol: String,
761 pub maker_commission: String,
763 pub taker_commission: String,
765}
766
767#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
769#[serde(rename_all = "camelCase")]
770pub struct NewOcoOrderListResponse {
771 pub order_list_id: i64,
773 pub contingency_type: String,
775 pub list_status_type: String,
777 pub list_order_status: String,
779 pub list_client_order_id: String,
781 pub transaction_time: i64,
783 pub symbol: String,
785 pub orders: Vec<OrderListOrder>,
787}
788
789#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
791#[serde(rename_all = "camelCase")]
792pub struct OrderListOrder {
793 pub symbol: String,
795 pub order_id: i64,
797 pub client_order_id: String,
799}
800
801#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
805#[serde(untagged)]
806pub enum BatchOrderResult {
807 Success(Box<BatchOrderSuccess>),
809 Error(BatchOrderError),
811}
812
813#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
815#[serde(rename_all = "camelCase")]
816pub struct BatchOrderSuccess {
817 pub symbol: String,
819 pub order_id: i64,
821 #[serde(default)]
823 pub order_list_id: Option<i64>,
824 pub client_order_id: String,
826 pub transact_time: i64,
828 pub price: String,
830 pub orig_qty: String,
832 pub executed_qty: String,
834 #[serde(rename = "cummulativeQuoteQty")]
836 pub cummulative_quote_qty: String,
837 pub status: BinanceOrderStatus,
839 pub time_in_force: BinanceTimeInForce,
841 #[serde(rename = "type")]
843 pub order_type: String,
844 pub side: BinanceSide,
846 #[serde(default)]
848 pub working_time: Option<i64>,
849 #[serde(default)]
851 pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
852}
853
854#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
856pub struct BatchOrderError {
857 pub code: i64,
859 pub msg: String,
861}
862
863#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
865#[serde(untagged)]
866pub enum BatchCancelResult {
867 Success(Box<BatchCancelSuccess>),
869 Error(BatchOrderError),
871}
872
873#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
875#[serde(rename_all = "camelCase")]
876pub struct BatchCancelSuccess {
877 pub symbol: String,
879 pub orig_client_order_id: String,
881 pub order_id: i64,
883 #[serde(default)]
885 pub order_list_id: Option<i64>,
886 pub client_order_id: String,
888 #[serde(default)]
890 pub transact_time: Option<i64>,
891 pub price: String,
893 pub orig_qty: String,
895 pub executed_qty: String,
897 #[serde(rename = "cummulativeQuoteQty")]
899 pub cummulative_quote_qty: String,
900 pub status: BinanceOrderStatus,
902 pub time_in_force: BinanceTimeInForce,
904 #[serde(rename = "type")]
906 pub order_type: String,
907 pub side: BinanceSide,
909 #[serde(default)]
911 pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
912}
913
914#[derive(Debug, Clone, PartialEq)]
916pub struct BinanceKline {
917 pub open_time: i64,
919 pub open_price: i64,
921 pub high_price: i64,
923 pub low_price: i64,
925 pub close_price: i64,
927 pub volume: [u8; 16],
929 pub close_time: i64,
931 pub quote_volume: [u8; 16],
933 pub num_trades: i64,
935 pub taker_buy_base_volume: [u8; 16],
937 pub taker_buy_quote_volume: [u8; 16],
939}
940
941#[cfg(test)]
942mod tests {
943 use rstest::rstest;
944 use zeroize::Zeroize;
945
946 use super::*;
947 use crate::common::testing::load_fixture_string;
948
949 fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
950
951 #[rstest]
952 fn test_listen_key_response_deserialize() {
953 assert_zeroize_on_drop::<ListenKeyResponse>();
954
955 let json = r#"{"listenKey": "abc123xyz"}"#;
956 let mut response: ListenKeyResponse = serde_json::from_str(json).unwrap();
957
958 let debug = format!("{response:?}");
959 assert_eq!(response.listen_key.expose_secret(), "abc123xyz");
960 assert_eq!(debug, "ListenKeyResponse { listen_key: <redacted> }");
961 assert!(!debug.contains(response.listen_key.expose_secret()));
962
963 response.zeroize();
964 assert!(response.listen_key.expose_secret().is_empty());
965 }
966
967 #[rstest]
968 fn test_ticker_price_deserialize() {
969 let json = load_fixture_string("spot/http_json/ticker_price_response.json");
970 let response: TickerPrice = serde_json::from_str(&json).unwrap();
971 assert_eq!(response.symbol, "LTCBTC");
972 assert_eq!(response.price, "4.00000200");
973 }
974
975 #[rstest]
976 fn test_book_ticker_deserialize() {
977 let json = load_fixture_string("spot/http_json/book_ticker_response.json");
978 let response: BookTicker = serde_json::from_str(&json).unwrap();
979 assert_eq!(response.symbol, "LTCBTC");
980 assert_eq!(response.bid_price, "4.00000000");
981 assert_eq!(response.ask_price, "4.00000200");
982 }
983
984 #[rstest]
985 fn test_avg_price_deserialize() {
986 let json = load_fixture_string("spot/http_json/avg_price_response.json");
987 let response: AvgPrice = serde_json::from_str(&json).unwrap();
988 assert_eq!(response.mins, 5);
989 assert_eq!(response.price, "9.35751834");
990 assert_eq!(response.close_time, 1694061154503);
991 }
992
993 #[rstest]
994 fn test_trade_fee_deserialize() {
995 let json = r#"{
996 "symbol": "BTCUSDT",
997 "makerCommission": "0.001",
998 "takerCommission": "0.001"
999 }"#;
1000 let response: TradeFee = serde_json::from_str(json).unwrap();
1001 assert_eq!(response.symbol, "BTCUSDT");
1002 assert_eq!(response.maker_commission, "0.001");
1003 assert_eq!(response.taker_commission, "0.001");
1004 }
1005
1006 #[rstest]
1007 fn test_batch_order_result_success() {
1008 let json = load_fixture_string("spot/http_json/new_order_full_response.json");
1009 let result: BatchOrderResult = serde_json::from_str(&json).unwrap();
1010 match result {
1011 BatchOrderResult::Success(order) => {
1012 assert_eq!(order.symbol, "BTCUSDT");
1013 assert_eq!(order.order_id, 28);
1014 assert_eq!(order.status, BinanceOrderStatus::Filled);
1015 assert_eq!(order.time_in_force, BinanceTimeInForce::Gtc);
1016 assert_eq!(order.order_type, "MARKET");
1017 assert_eq!(order.side, BinanceSide::Sell);
1018 assert_eq!(
1019 order.self_trade_prevention_mode,
1020 Some(BinanceSelfTradePreventionMode::None)
1021 );
1022 }
1023 BatchOrderResult::Error(_) => panic!("Expected Success"),
1024 }
1025 }
1026
1027 #[rstest]
1028 fn test_batch_order_result_error() {
1029 let json = r#"{"code": -1013, "msg": "Invalid quantity."}"#;
1030 let result: BatchOrderResult = serde_json::from_str(json).unwrap();
1031 match result {
1032 BatchOrderResult::Success(_) => panic!("Expected Error"),
1033 BatchOrderResult::Error(error) => {
1034 assert_eq!(error.code, -1013);
1035 assert_eq!(error.msg, "Invalid quantity.");
1036 }
1037 }
1038 }
1039
1040 #[rstest]
1041 fn test_batch_cancel_result_success() {
1042 let json = load_fixture_string("spot/http_json/cancel_order_response.json");
1043 let result: BatchCancelResult = serde_json::from_str(&json).unwrap();
1044 match result {
1045 BatchCancelResult::Success(cancel) => {
1046 assert_eq!(cancel.symbol, "LTCBTC");
1047 assert_eq!(cancel.order_id, 4);
1048 assert_eq!(cancel.status, BinanceOrderStatus::Canceled);
1049 assert_eq!(cancel.time_in_force, BinanceTimeInForce::Gtc);
1050 assert_eq!(cancel.order_type, "LIMIT");
1051 assert_eq!(cancel.side, BinanceSide::Buy);
1052 assert_eq!(
1053 cancel.self_trade_prevention_mode,
1054 Some(BinanceSelfTradePreventionMode::None)
1055 );
1056 }
1057 BatchCancelResult::Error(_) => panic!("Expected Success"),
1058 }
1059 }
1060
1061 #[rstest]
1062 fn test_batch_cancel_result_error() {
1063 let json = r#"{"code": -2011, "msg": "Unknown order sent."}"#;
1064 let result: BatchCancelResult = serde_json::from_str(json).unwrap();
1065 match result {
1066 BatchCancelResult::Success(_) => panic!("Expected Error"),
1067 BatchCancelResult::Error(error) => {
1068 assert_eq!(error.code, -2011);
1069 assert_eq!(error.msg, "Unknown order sent.");
1070 }
1071 }
1072 }
1073
1074 #[rstest]
1075 fn test_ticker_24hr_deserialize() {
1076 let json = load_fixture_string("spot/http_json/ticker_24hr_response.json");
1077 let response: Ticker24hr = serde_json::from_str(&json).unwrap();
1078 assert_eq!(response.symbol, "BNBBTC");
1079 assert_eq!(response.last_price, "4.00000200");
1080 assert_eq!(response.count, 76);
1081 }
1082}