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