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