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