Skip to main content

nautilus_binance/spot/http/
models.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Binance Spot HTTP response models.
17//!
18//! These models represent Binance venue-specific response types decoded from SBE.
19
20use nautilus_core::{UUID4, nanos::UnixNanos};
21use nautilus_model::{
22    enums::AccountType,
23    events::AccountState,
24    identifiers::AccountId,
25    types::{AccountBalance, Currency, Money},
26};
27use rust_decimal::Decimal;
28
29use crate::{
30    common::enums::{
31        BinanceOrderStatus, BinanceSelfTradePreventionMode, BinanceSide, BinanceTimeInForce,
32    },
33    spot::sbe::spot::{
34        order_side::OrderSide, order_status::OrderStatus, order_type::OrderType,
35        self_trade_prevention_mode::SelfTradePreventionMode, time_in_force::TimeInForce,
36    },
37};
38
39/// Price/quantity level in an order book.
40#[derive(Debug, Clone, Copy, PartialEq)]
41pub struct BinancePriceLevel {
42    /// Price mantissa (multiply by 10^exponent to get actual price).
43    pub price_mantissa: i64,
44    /// Quantity mantissa (multiply by 10^exponent to get actual quantity).
45    pub qty_mantissa: i64,
46}
47
48/// Binance order book depth response.
49#[derive(Debug, Clone, PartialEq)]
50pub struct BinanceDepth {
51    /// Last update ID for this depth snapshot.
52    pub last_update_id: i64,
53    /// Price exponent for all price levels.
54    pub price_exponent: i8,
55    /// Quantity exponent for all quantity values.
56    pub qty_exponent: i8,
57    /// Bid price levels (best bid first).
58    pub bids: Vec<BinancePriceLevel>,
59    /// Ask price levels (best ask first).
60    pub asks: Vec<BinancePriceLevel>,
61}
62
63/// A single trade from Binance.
64#[derive(Debug, Clone, PartialEq)]
65pub struct BinanceTrade {
66    /// Trade ID.
67    pub id: i64,
68    /// Price mantissa.
69    pub price_mantissa: i64,
70    /// Quantity mantissa.
71    pub qty_mantissa: i64,
72    /// Quote quantity mantissa (price * qty).
73    pub quote_qty_mantissa: i64,
74    /// Trade timestamp in microseconds (SBE precision).
75    pub time: i64,
76    /// Whether the buyer is the maker.
77    pub is_buyer_maker: bool,
78    /// Whether this trade is the best price match.
79    pub is_best_match: bool,
80}
81
82/// Binance trades response.
83#[derive(Debug, Clone, PartialEq)]
84pub struct BinanceTrades {
85    /// Price exponent for all trades.
86    pub price_exponent: i8,
87    /// Quantity exponent for all trades.
88    pub qty_exponent: i8,
89    /// List of trades.
90    pub trades: Vec<BinanceTrade>,
91}
92
93/// A fill from an order execution.
94#[derive(Debug, Clone, PartialEq)]
95pub struct BinanceOrderFill {
96    /// Fill price mantissa.
97    pub price_mantissa: i64,
98    /// Fill quantity mantissa.
99    pub qty_mantissa: i64,
100    /// Commission mantissa.
101    pub commission_mantissa: i64,
102    /// Commission exponent.
103    pub commission_exponent: i8,
104    /// Commission asset.
105    pub commission_asset: String,
106    /// Trade ID (if available).
107    pub trade_id: Option<i64>,
108}
109
110/// New order response (FULL response type).
111#[derive(Debug, Clone, PartialEq)]
112pub struct BinanceNewOrderResponse {
113    /// Price exponent for this response.
114    pub price_exponent: i8,
115    /// Quantity exponent for this response.
116    pub qty_exponent: i8,
117    /// Exchange order ID.
118    pub order_id: i64,
119    /// Order list ID (for OCO orders).
120    pub order_list_id: Option<i64>,
121    /// Transaction time in microseconds.
122    pub transact_time: i64,
123    /// Order price mantissa.
124    pub price_mantissa: i64,
125    /// Original order quantity mantissa.
126    pub orig_qty_mantissa: i64,
127    /// Executed quantity mantissa.
128    pub executed_qty_mantissa: i64,
129    /// Cumulative quote quantity mantissa.
130    pub cummulative_quote_qty_mantissa: i64,
131    /// Order status.
132    pub status: OrderStatus,
133    /// Time in force.
134    pub time_in_force: TimeInForce,
135    /// Order type.
136    pub order_type: OrderType,
137    /// Order side.
138    pub side: OrderSide,
139    /// Stop price mantissa (for stop orders).
140    pub stop_price_mantissa: Option<i64>,
141    /// Working time in microseconds.
142    pub working_time: Option<i64>,
143    /// Self-trade prevention mode.
144    pub self_trade_prevention_mode: SelfTradePreventionMode,
145    /// Client order ID.
146    pub client_order_id: String,
147    /// Symbol.
148    pub symbol: String,
149    /// Order fills.
150    pub fills: Vec<BinanceOrderFill>,
151}
152
153/// Cancel order response.
154#[derive(Debug, Clone, PartialEq)]
155pub struct BinanceCancelOrderResponse {
156    /// Price exponent for this response.
157    pub price_exponent: i8,
158    /// Quantity exponent for this response.
159    pub qty_exponent: i8,
160    /// Exchange order ID.
161    pub order_id: i64,
162    /// Order list ID (for OCO orders).
163    pub order_list_id: Option<i64>,
164    /// Transaction time in microseconds.
165    pub transact_time: i64,
166    /// Order price mantissa.
167    pub price_mantissa: i64,
168    /// Original order quantity mantissa.
169    pub orig_qty_mantissa: i64,
170    /// Executed quantity mantissa.
171    pub executed_qty_mantissa: i64,
172    /// Cumulative quote quantity mantissa.
173    pub cummulative_quote_qty_mantissa: i64,
174    /// Order status.
175    pub status: OrderStatus,
176    /// Time in force.
177    pub time_in_force: TimeInForce,
178    /// Order type.
179    pub order_type: OrderType,
180    /// Order side.
181    pub side: OrderSide,
182    /// Self-trade prevention mode.
183    pub self_trade_prevention_mode: SelfTradePreventionMode,
184    /// Client order ID.
185    pub client_order_id: String,
186    /// Original client order ID.
187    pub orig_client_order_id: String,
188    /// Symbol.
189    pub symbol: String,
190}
191
192/// Query order response.
193#[derive(Debug, Clone, PartialEq)]
194pub struct BinanceOrderResponse {
195    /// Price exponent for this response.
196    pub price_exponent: i8,
197    /// Quantity exponent for this response.
198    pub qty_exponent: i8,
199    /// Exchange order ID.
200    pub order_id: i64,
201    /// Order list ID (for OCO orders).
202    pub order_list_id: Option<i64>,
203    /// Order price mantissa.
204    pub price_mantissa: i64,
205    /// Original order quantity mantissa.
206    pub orig_qty_mantissa: i64,
207    /// Executed quantity mantissa.
208    pub executed_qty_mantissa: i64,
209    /// Cumulative quote quantity mantissa.
210    pub cummulative_quote_qty_mantissa: i64,
211    /// Order status.
212    pub status: OrderStatus,
213    /// Time in force.
214    pub time_in_force: TimeInForce,
215    /// Order type.
216    pub order_type: OrderType,
217    /// Order side.
218    pub side: OrderSide,
219    /// Stop price mantissa (for stop orders).
220    pub stop_price_mantissa: Option<i64>,
221    /// Iceberg quantity mantissa.
222    pub iceberg_qty_mantissa: Option<i64>,
223    /// Order creation time in microseconds.
224    pub time: i64,
225    /// Last update time in microseconds.
226    pub update_time: i64,
227    /// Whether the order is working.
228    pub is_working: bool,
229    /// Working time in microseconds.
230    pub working_time: Option<i64>,
231    /// Original quote order quantity mantissa.
232    pub orig_quote_order_qty_mantissa: i64,
233    /// Self-trade prevention mode.
234    pub self_trade_prevention_mode: SelfTradePreventionMode,
235    /// Client order ID.
236    pub client_order_id: String,
237    /// Symbol.
238    pub symbol: String,
239}
240
241/// Account balance for a single asset.
242#[derive(Debug, Clone, PartialEq)]
243pub struct BinanceBalance {
244    /// Asset symbol.
245    pub asset: String,
246    /// Free (available) balance mantissa.
247    pub free_mantissa: i64,
248    /// Locked balance mantissa.
249    pub locked_mantissa: i64,
250    /// Balance exponent.
251    pub exponent: i8,
252}
253
254/// Account information response.
255#[derive(Debug, Clone, PartialEq)]
256pub struct BinanceAccountInfo {
257    /// Commission exponent.
258    pub commission_exponent: i8,
259    /// Maker commission rate mantissa.
260    pub maker_commission_mantissa: i64,
261    /// Taker commission rate mantissa.
262    pub taker_commission_mantissa: i64,
263    /// Buyer commission rate mantissa.
264    pub buyer_commission_mantissa: i64,
265    /// Seller commission rate mantissa.
266    pub seller_commission_mantissa: i64,
267    /// Whether trading is enabled.
268    pub can_trade: bool,
269    /// Whether withdrawals are enabled.
270    pub can_withdraw: bool,
271    /// Whether deposits are enabled.
272    pub can_deposit: bool,
273    /// Whether the account requires self-trade prevention.
274    pub require_self_trade_prevention: bool,
275    /// Whether to prevent self-trade by quote order ID.
276    pub prevent_sor: bool,
277    /// Account update time in microseconds.
278    pub update_time: i64,
279    /// Account type.
280    pub account_type: String,
281    /// Account balances.
282    pub balances: Vec<BinanceBalance>,
283}
284
285impl BinanceAccountInfo {
286    /// Converts this Binance account info to a Nautilus [`AccountState`].
287    #[must_use]
288    pub fn to_account_state(&self, account_id: AccountId, ts_init: UnixNanos) -> AccountState {
289        let mut balances = Vec::with_capacity(self.balances.len());
290
291        for asset in &self.balances {
292            let currency =
293                Currency::get_or_create_crypto_with_context(&asset.asset, Some("spot balance"));
294
295            let exponent = asset.exponent as i32;
296            let multiplier = Decimal::new(1, (-exponent) as u32);
297
298            let free = Decimal::new(asset.free_mantissa, 0) * multiplier;
299            let locked = Decimal::new(asset.locked_mantissa, 0) * multiplier;
300            let total = free + locked;
301
302            match AccountBalance::from_total_and_locked(total, locked, currency) {
303                Ok(balance) => balances.push(balance),
304                Err(e) => log::warn!("Skipping spot balance for {}: {e}", currency.code.as_str()),
305            }
306        }
307
308        // Ensure at least one balance exists
309        if balances.is_empty() {
310            let zero_currency = Currency::USDT();
311            let zero_money = Money::new(0.0, zero_currency);
312            let zero_balance = AccountBalance::new(zero_money, zero_money, zero_money);
313            balances.push(zero_balance);
314        }
315
316        let ts_event = UnixNanos::from_micros(self.update_time as u64);
317
318        AccountState::new(
319            account_id,
320            AccountType::Cash,
321            balances,
322            vec![], // No margins for spot
323            true,   // is_reported
324            UUID4::new(),
325            ts_event,
326            ts_init,
327            None, // No base currency for spot
328        )
329    }
330}
331
332/// Price filter from SBE response.
333#[derive(Debug, Clone, PartialEq)]
334pub struct BinancePriceFilterSbe {
335    /// Price exponent for mantissa conversion.
336    pub price_exponent: i8,
337    /// Minimum price mantissa.
338    pub min_price: i64,
339    /// Maximum price mantissa.
340    pub max_price: i64,
341    /// Tick size mantissa.
342    pub tick_size: i64,
343}
344
345/// Lot size filter from SBE response.
346#[derive(Debug, Clone, PartialEq)]
347pub struct BinanceLotSizeFilterSbe {
348    /// Quantity exponent for mantissa conversion.
349    pub qty_exponent: i8,
350    /// Minimum quantity mantissa.
351    pub min_qty: i64,
352    /// Maximum quantity mantissa.
353    pub max_qty: i64,
354    /// Step size mantissa.
355    pub step_size: i64,
356}
357
358/// Symbol filters from SBE response.
359#[derive(Debug, Clone, Default, PartialEq)]
360pub struct BinanceSymbolFiltersSbe {
361    /// Price filter (required for trading).
362    pub price_filter: Option<BinancePriceFilterSbe>,
363    /// Lot size filter (required for trading).
364    pub lot_size_filter: Option<BinanceLotSizeFilterSbe>,
365}
366
367/// Symbol information from SBE exchange info response.
368#[derive(Debug, Clone, PartialEq)]
369pub struct BinanceSymbolSbe {
370    /// Symbol name (e.g., "BTCUSDT").
371    pub symbol: String,
372    /// Base asset (e.g., "BTC").
373    pub base_asset: String,
374    /// Quote asset (e.g., "USDT").
375    pub quote_asset: String,
376    /// Base asset precision.
377    pub base_asset_precision: u8,
378    /// Quote asset precision.
379    pub quote_asset_precision: u8,
380    /// Symbol status.
381    pub status: u8,
382    /// Order types bitset.
383    pub order_types: u16,
384    /// Whether iceberg orders are allowed.
385    pub iceberg_allowed: bool,
386    /// Whether OCO orders are allowed.
387    pub oco_allowed: bool,
388    /// Whether OTO orders are allowed.
389    pub oto_allowed: bool,
390    /// Whether quote order quantity market orders are allowed.
391    pub quote_order_qty_market_allowed: bool,
392    /// Whether trailing stop is allowed.
393    pub allow_trailing_stop: bool,
394    /// Whether cancel-replace is allowed.
395    pub cancel_replace_allowed: bool,
396    /// Whether amend is allowed.
397    pub amend_allowed: bool,
398    /// Whether spot trading is allowed.
399    pub is_spot_trading_allowed: bool,
400    /// Whether margin trading is allowed.
401    pub is_margin_trading_allowed: bool,
402    /// Symbol filters decoded from SBE.
403    pub filters: BinanceSymbolFiltersSbe,
404    /// Permission sets.
405    pub permissions: Vec<Vec<String>>,
406}
407
408/// Exchange information from SBE response.
409#[derive(Debug, Clone, PartialEq)]
410pub struct BinanceExchangeInfoSbe {
411    /// List of symbols.
412    pub symbols: Vec<BinanceSymbolSbe>,
413}
414
415/// Account trade history entry.
416#[derive(Debug, Clone, PartialEq)]
417pub struct BinanceAccountTrade {
418    /// Price exponent.
419    pub price_exponent: i8,
420    /// Quantity exponent.
421    pub qty_exponent: i8,
422    /// Commission exponent.
423    pub commission_exponent: i8,
424    /// Trade ID.
425    pub id: i64,
426    /// Order ID.
427    pub order_id: i64,
428    /// Order list ID (for OCO).
429    pub order_list_id: Option<i64>,
430    /// Trade price mantissa.
431    pub price_mantissa: i64,
432    /// Trade quantity mantissa.
433    pub qty_mantissa: i64,
434    /// Quote quantity mantissa.
435    pub quote_qty_mantissa: i64,
436    /// Commission mantissa.
437    pub commission_mantissa: i64,
438    /// Trade time in microseconds.
439    pub time: i64,
440    /// Whether the trade was as buyer.
441    pub is_buyer: bool,
442    /// Whether the trade was as maker.
443    pub is_maker: bool,
444    /// Whether this is the best price match.
445    pub is_best_match: bool,
446    /// Symbol.
447    pub symbol: String,
448    /// Commission asset.
449    pub commission_asset: String,
450}
451
452/// Kline (candlestick) data response.
453#[derive(Debug, Clone, PartialEq)]
454pub struct BinanceKlines {
455    /// Price exponent for all klines.
456    pub price_exponent: i8,
457    /// Quantity exponent for all klines.
458    pub qty_exponent: i8,
459    /// List of klines.
460    pub klines: Vec<BinanceKline>,
461}
462
463/// Listen key response for user data stream.
464#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
465#[serde(rename_all = "camelCase")]
466pub struct ListenKeyResponse {
467    /// The listen key for WebSocket user data stream.
468    pub listen_key: String,
469}
470
471/// 24-hour ticker statistics response.
472#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
473#[serde(rename_all = "camelCase")]
474pub struct Ticker24hr {
475    /// Trading pair symbol.
476    pub symbol: String,
477    /// Price change in the last 24 hours.
478    pub price_change: String,
479    /// Price change percentage in the last 24 hours.
480    pub price_change_percent: String,
481    /// Weighted average price.
482    pub weighted_avg_price: String,
483    /// Previous close price.
484    pub prev_close_price: String,
485    /// Last price.
486    pub last_price: String,
487    /// Last quantity.
488    pub last_qty: String,
489    /// Best bid price.
490    pub bid_price: String,
491    /// Best bid quantity.
492    pub bid_qty: String,
493    /// Best ask price.
494    pub ask_price: String,
495    /// Best ask quantity.
496    pub ask_qty: String,
497    /// Open price.
498    pub open_price: String,
499    /// High price.
500    pub high_price: String,
501    /// Low price.
502    pub low_price: String,
503    /// Total traded base asset volume.
504    pub volume: String,
505    /// Total traded quote asset volume.
506    pub quote_volume: String,
507    /// Statistics open time in milliseconds.
508    pub open_time: i64,
509    /// Statistics close time in milliseconds.
510    pub close_time: i64,
511    /// First trade ID.
512    pub first_id: i64,
513    /// Last trade ID.
514    pub last_id: i64,
515    /// Number of trades.
516    pub count: i64,
517}
518
519/// Symbol price ticker response.
520#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
521pub struct TickerPrice {
522    /// Trading pair symbol.
523    pub symbol: String,
524    /// Latest price.
525    pub price: String,
526}
527
528/// Book ticker response (best bid/ask).
529#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
530#[serde(rename_all = "camelCase")]
531pub struct BookTicker {
532    /// Trading pair symbol.
533    pub symbol: String,
534    /// Best bid price.
535    pub bid_price: String,
536    /// Best bid quantity.
537    pub bid_qty: String,
538    /// Best ask price.
539    pub ask_price: String,
540    /// Best ask quantity.
541    pub ask_qty: String,
542}
543
544/// Average price response.
545#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
546pub struct AvgPrice {
547    /// Average price interval in minutes.
548    pub mins: i64,
549    /// Average price.
550    pub price: String,
551    /// Close time in milliseconds.
552    #[serde(rename = "closeTime")]
553    pub close_time: i64,
554}
555
556/// Trade fee information for a symbol.
557#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
558#[serde(rename_all = "camelCase")]
559pub struct TradeFee {
560    /// Trading pair symbol.
561    pub symbol: String,
562    /// Maker commission rate.
563    pub maker_commission: String,
564    /// Taker commission rate.
565    pub taker_commission: String,
566}
567
568/// Result of a single order in a batch operation.
569///
570/// Each item in a batch response can be either a success or an error.
571#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
572#[serde(untagged)]
573pub enum BatchOrderResult {
574    /// Successful order placement.
575    Success(Box<BatchOrderSuccess>),
576    /// Failed order placement.
577    Error(BatchOrderError),
578}
579
580/// Successful order in a batch response.
581#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
582#[serde(rename_all = "camelCase")]
583pub struct BatchOrderSuccess {
584    /// Trading pair symbol.
585    pub symbol: String,
586    /// Exchange order ID.
587    pub order_id: i64,
588    /// Order list ID (for OCO orders).
589    #[serde(default)]
590    pub order_list_id: Option<i64>,
591    /// Client order ID.
592    pub client_order_id: String,
593    /// Transaction time in milliseconds.
594    pub transact_time: i64,
595    /// Order price.
596    pub price: String,
597    /// Original order quantity.
598    pub orig_qty: String,
599    /// Executed quantity.
600    pub executed_qty: String,
601    /// Cumulative quote quantity.
602    #[serde(rename = "cummulativeQuoteQty")]
603    pub cummulative_quote_qty: String,
604    /// Order status.
605    pub status: BinanceOrderStatus,
606    /// Time in force.
607    pub time_in_force: BinanceTimeInForce,
608    /// Order type.
609    #[serde(rename = "type")]
610    pub order_type: String,
611    /// Order side.
612    pub side: BinanceSide,
613    /// Working time in milliseconds.
614    #[serde(default)]
615    pub working_time: Option<i64>,
616    /// Self-trade prevention mode.
617    #[serde(default)]
618    pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
619}
620
621/// Error in a batch order response.
622#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
623pub struct BatchOrderError {
624    /// Error code from Binance.
625    pub code: i64,
626    /// Error message.
627    pub msg: String,
628}
629
630/// Result of a single cancel in a batch cancel operation.
631#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
632#[serde(untagged)]
633pub enum BatchCancelResult {
634    /// Successful order cancellation.
635    Success(Box<BatchCancelSuccess>),
636    /// Failed order cancellation.
637    Error(BatchOrderError),
638}
639
640/// Successful cancel in a batch response.
641#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
642#[serde(rename_all = "camelCase")]
643pub struct BatchCancelSuccess {
644    /// Trading pair symbol.
645    pub symbol: String,
646    /// Original client order ID.
647    pub orig_client_order_id: String,
648    /// Exchange order ID.
649    pub order_id: i64,
650    /// Order list ID (for OCO orders).
651    #[serde(default)]
652    pub order_list_id: Option<i64>,
653    /// Client order ID.
654    pub client_order_id: String,
655    /// Transaction time in milliseconds.
656    #[serde(default)]
657    pub transact_time: Option<i64>,
658    /// Order price.
659    pub price: String,
660    /// Original order quantity.
661    pub orig_qty: String,
662    /// Executed quantity.
663    pub executed_qty: String,
664    /// Cumulative quote quantity.
665    #[serde(rename = "cummulativeQuoteQty")]
666    pub cummulative_quote_qty: String,
667    /// Order status.
668    pub status: BinanceOrderStatus,
669    /// Time in force.
670    pub time_in_force: BinanceTimeInForce,
671    /// Order type.
672    #[serde(rename = "type")]
673    pub order_type: String,
674    /// Order side.
675    pub side: BinanceSide,
676    /// Self-trade prevention mode.
677    #[serde(default)]
678    pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
679}
680
681/// A single kline (candlestick) from Binance.
682#[derive(Debug, Clone, PartialEq)]
683pub struct BinanceKline {
684    /// Kline open time in microseconds.
685    pub open_time: i64,
686    /// Open price mantissa.
687    pub open_price: i64,
688    /// High price mantissa.
689    pub high_price: i64,
690    /// Low price mantissa.
691    pub low_price: i64,
692    /// Close price mantissa.
693    pub close_price: i64,
694    /// Volume (base asset) as 128-bit bytes.
695    pub volume: [u8; 16],
696    /// Kline close time in microseconds.
697    pub close_time: i64,
698    /// Quote volume as 128-bit bytes.
699    pub quote_volume: [u8; 16],
700    /// Number of trades.
701    pub num_trades: i64,
702    /// Taker buy base volume as 128-bit bytes.
703    pub taker_buy_base_volume: [u8; 16],
704    /// Taker buy quote volume as 128-bit bytes.
705    pub taker_buy_quote_volume: [u8; 16],
706}
707
708#[cfg(test)]
709mod tests {
710    use rstest::rstest;
711
712    use super::*;
713    use crate::common::testing::load_fixture_string;
714
715    #[rstest]
716    fn test_listen_key_response_deserialize() {
717        let json = r#"{"listenKey": "abc123xyz"}"#;
718        let response: ListenKeyResponse = serde_json::from_str(json).unwrap();
719        assert_eq!(response.listen_key, "abc123xyz");
720    }
721
722    #[rstest]
723    fn test_ticker_price_deserialize() {
724        let json = load_fixture_string("spot/http_json/ticker_price_response.json");
725        let response: TickerPrice = serde_json::from_str(&json).unwrap();
726        assert_eq!(response.symbol, "LTCBTC");
727        assert_eq!(response.price, "4.00000200");
728    }
729
730    #[rstest]
731    fn test_book_ticker_deserialize() {
732        let json = load_fixture_string("spot/http_json/book_ticker_response.json");
733        let response: BookTicker = serde_json::from_str(&json).unwrap();
734        assert_eq!(response.symbol, "LTCBTC");
735        assert_eq!(response.bid_price, "4.00000000");
736        assert_eq!(response.ask_price, "4.00000200");
737    }
738
739    #[rstest]
740    fn test_avg_price_deserialize() {
741        let json = load_fixture_string("spot/http_json/avg_price_response.json");
742        let response: AvgPrice = serde_json::from_str(&json).unwrap();
743        assert_eq!(response.mins, 5);
744        assert_eq!(response.price, "9.35751834");
745        assert_eq!(response.close_time, 1694061154503);
746    }
747
748    #[rstest]
749    fn test_trade_fee_deserialize() {
750        let json = r#"{
751            "symbol": "BTCUSDT",
752            "makerCommission": "0.001",
753            "takerCommission": "0.001"
754        }"#;
755        let response: TradeFee = serde_json::from_str(json).unwrap();
756        assert_eq!(response.symbol, "BTCUSDT");
757        assert_eq!(response.maker_commission, "0.001");
758        assert_eq!(response.taker_commission, "0.001");
759    }
760
761    #[rstest]
762    fn test_batch_order_result_success() {
763        let json = load_fixture_string("spot/http_json/new_order_full_response.json");
764        let result: BatchOrderResult = serde_json::from_str(&json).unwrap();
765        match result {
766            BatchOrderResult::Success(order) => {
767                assert_eq!(order.symbol, "BTCUSDT");
768                assert_eq!(order.order_id, 28);
769                assert_eq!(order.status, BinanceOrderStatus::Filled);
770                assert_eq!(order.time_in_force, BinanceTimeInForce::Gtc);
771                assert_eq!(order.order_type, "MARKET");
772                assert_eq!(order.side, BinanceSide::Sell);
773                assert_eq!(
774                    order.self_trade_prevention_mode,
775                    Some(BinanceSelfTradePreventionMode::None)
776                );
777            }
778            BatchOrderResult::Error(_) => panic!("Expected Success"),
779        }
780    }
781
782    #[rstest]
783    fn test_batch_order_result_error() {
784        let json = r#"{"code": -1013, "msg": "Invalid quantity."}"#;
785        let result: BatchOrderResult = serde_json::from_str(json).unwrap();
786        match result {
787            BatchOrderResult::Success(_) => panic!("Expected Error"),
788            BatchOrderResult::Error(error) => {
789                assert_eq!(error.code, -1013);
790                assert_eq!(error.msg, "Invalid quantity.");
791            }
792        }
793    }
794
795    #[rstest]
796    fn test_batch_cancel_result_success() {
797        let json = load_fixture_string("spot/http_json/cancel_order_response.json");
798        let result: BatchCancelResult = serde_json::from_str(&json).unwrap();
799        match result {
800            BatchCancelResult::Success(cancel) => {
801                assert_eq!(cancel.symbol, "LTCBTC");
802                assert_eq!(cancel.order_id, 4);
803                assert_eq!(cancel.status, BinanceOrderStatus::Canceled);
804                assert_eq!(cancel.time_in_force, BinanceTimeInForce::Gtc);
805                assert_eq!(cancel.order_type, "LIMIT");
806                assert_eq!(cancel.side, BinanceSide::Buy);
807                assert_eq!(
808                    cancel.self_trade_prevention_mode,
809                    Some(BinanceSelfTradePreventionMode::None)
810                );
811            }
812            BatchCancelResult::Error(_) => panic!("Expected Success"),
813        }
814    }
815
816    #[rstest]
817    fn test_batch_cancel_result_error() {
818        let json = r#"{"code": -2011, "msg": "Unknown order sent."}"#;
819        let result: BatchCancelResult = serde_json::from_str(json).unwrap();
820        match result {
821            BatchCancelResult::Success(_) => panic!("Expected Error"),
822            BatchCancelResult::Error(error) => {
823                assert_eq!(error.code, -2011);
824                assert_eq!(error.msg, "Unknown order sent.");
825            }
826        }
827    }
828
829    #[rstest]
830    fn test_ticker_24hr_deserialize() {
831        let json = load_fixture_string("spot/http_json/ticker_24hr_response.json");
832        let response: Ticker24hr = serde_json::from_str(&json).unwrap();
833        assert_eq!(response.symbol, "BNBBTC");
834        assert_eq!(response.last_price, "4.00000200");
835        assert_eq!(response.count, 76);
836    }
837}