Skip to main content

binance_api_client/models/
market.rs

1//! Market data models.
2//!
3//! These models represent responses from public market data endpoints.
4
5use serde::{Deserialize, Serialize};
6
7use crate::types::{OrderType, RateLimitInterval, RateLimitType, SymbolPermission, SymbolStatus};
8
9/// Server time response.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct ServerTime {
13    /// Server timestamp in milliseconds.
14    pub server_time: u64,
15}
16
17/// Exchange information response.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct ExchangeInfo {
21    /// Exchange timezone.
22    pub timezone: String,
23    /// Server timestamp.
24    pub server_time: u64,
25    /// Rate limits.
26    pub rate_limits: Vec<RateLimit>,
27    /// Trading symbols.
28    pub symbols: Vec<Symbol>,
29    /// Exchange-level filters.
30    #[serde(default)]
31    pub exchange_filters: Vec<SymbolFilter>,
32}
33
34/// Rate limit information.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct RateLimit {
38    /// Rate limit type.
39    pub rate_limit_type: RateLimitType,
40    /// Interval.
41    pub interval: RateLimitInterval,
42    /// Interval number.
43    pub interval_num: i32,
44    /// Limit value.
45    pub limit: i32,
46}
47
48/// Trading symbol information.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct Symbol {
52    /// Symbol name (e.g., "BTCUSDT").
53    pub symbol: String,
54    /// Symbol status.
55    pub status: SymbolStatus,
56    /// Base asset (e.g., "BTC").
57    pub base_asset: String,
58    /// Base asset precision.
59    pub base_asset_precision: u8,
60    /// Quote asset (e.g., "USDT").
61    pub quote_asset: String,
62    /// Quote asset precision.
63    pub quote_precision: u8,
64    /// Quote asset precision (duplicate field in API).
65    pub quote_asset_precision: u8,
66    /// Base commission precision.
67    #[serde(default)]
68    pub base_commission_precision: u8,
69    /// Quote commission precision.
70    #[serde(default)]
71    pub quote_commission_precision: u8,
72    /// Allowed order types.
73    pub order_types: Vec<OrderType>,
74    /// Whether iceberg orders are allowed.
75    pub iceberg_allowed: bool,
76    /// Whether OCO orders are allowed.
77    pub oco_allowed: bool,
78    /// Whether quote order quantity is allowed for market orders.
79    #[serde(default)]
80    pub quote_order_qty_market_allowed: bool,
81    /// Whether spot trading is allowed.
82    #[serde(default = "default_true")]
83    pub is_spot_trading_allowed: bool,
84    /// Whether margin trading is allowed.
85    #[serde(default)]
86    pub is_margin_trading_allowed: bool,
87    /// Symbol filters.
88    pub filters: Vec<SymbolFilter>,
89    /// Symbol permissions.
90    #[serde(default)]
91    pub permissions: Vec<SymbolPermission>,
92}
93
94fn default_true() -> bool {
95    true
96}
97
98impl Symbol {
99    /// Get the LOT_SIZE filter for this symbol.
100    pub fn lot_size(&self) -> Option<&SymbolFilter> {
101        self.filters
102            .iter()
103            .find(|f| matches!(f, SymbolFilter::LotSize { .. }))
104    }
105
106    /// Get the PRICE_FILTER filter for this symbol.
107    pub fn price_filter(&self) -> Option<&SymbolFilter> {
108        self.filters
109            .iter()
110            .find(|f| matches!(f, SymbolFilter::PriceFilter { .. }))
111    }
112
113    /// Get the MIN_NOTIONAL filter for this symbol.
114    pub fn min_notional(&self) -> Option<&SymbolFilter> {
115        self.filters
116            .iter()
117            .find(|f| matches!(f, SymbolFilter::MinNotional { .. }))
118    }
119}
120
121/// Symbol filter types.
122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
123#[serde(tag = "filterType")]
124pub enum SymbolFilter {
125    /// Price filter - valid price range and tick size.
126    #[serde(rename = "PRICE_FILTER")]
127    #[serde(rename_all = "camelCase")]
128    PriceFilter {
129        /// Minimum price.
130        #[serde(with = "string_or_float")]
131        min_price: f64,
132        /// Maximum price.
133        #[serde(with = "string_or_float")]
134        max_price: f64,
135        /// Tick size (price must be a multiple of this).
136        #[serde(with = "string_or_float")]
137        tick_size: f64,
138    },
139    /// Percent price filter - price relative to average.
140    #[serde(rename = "PERCENT_PRICE")]
141    #[serde(rename_all = "camelCase")]
142    PercentPrice {
143        /// Upper multiplier.
144        #[serde(with = "string_or_float")]
145        multiplier_up: f64,
146        /// Lower multiplier.
147        #[serde(with = "string_or_float")]
148        multiplier_down: f64,
149        /// Average price minutes.
150        avg_price_mins: u64,
151    },
152    /// Lot size filter - valid quantity range and step size.
153    #[serde(rename = "LOT_SIZE")]
154    #[serde(rename_all = "camelCase")]
155    LotSize {
156        /// Minimum quantity.
157        #[serde(with = "string_or_float")]
158        min_qty: f64,
159        /// Maximum quantity.
160        #[serde(with = "string_or_float")]
161        max_qty: f64,
162        /// Step size (quantity must be a multiple of this).
163        #[serde(with = "string_or_float")]
164        step_size: f64,
165    },
166    /// Market lot size filter.
167    #[serde(rename = "MARKET_LOT_SIZE")]
168    #[serde(rename_all = "camelCase")]
169    MarketLotSize {
170        /// Minimum quantity.
171        #[serde(with = "string_or_float")]
172        min_qty: f64,
173        /// Maximum quantity.
174        #[serde(with = "string_or_float")]
175        max_qty: f64,
176        /// Step size.
177        #[serde(with = "string_or_float")]
178        step_size: f64,
179    },
180    /// Minimum notional filter.
181    #[serde(rename = "MIN_NOTIONAL")]
182    #[serde(rename_all = "camelCase")]
183    MinNotional {
184        /// Minimum notional value (price * quantity).
185        #[serde(with = "string_or_float")]
186        min_notional: f64,
187        /// Apply to market orders.
188        apply_to_market: bool,
189        /// Average price minutes.
190        avg_price_mins: u64,
191    },
192    /// Notional filter (newer version).
193    #[serde(rename = "NOTIONAL")]
194    #[serde(rename_all = "camelCase")]
195    Notional {
196        /// Minimum notional value.
197        #[serde(with = "string_or_float")]
198        min_notional: f64,
199        /// Apply minimum to market orders.
200        apply_min_to_market: bool,
201        /// Maximum notional value.
202        #[serde(with = "string_or_float")]
203        max_notional: f64,
204        /// Apply maximum to market orders.
205        apply_max_to_market: bool,
206        /// Average price minutes.
207        avg_price_mins: u64,
208    },
209    /// Iceberg parts filter.
210    #[serde(rename = "ICEBERG_PARTS")]
211    #[serde(rename_all = "camelCase")]
212    IcebergParts {
213        /// Maximum iceberg parts.
214        limit: u16,
215    },
216    /// Max orders filter.
217    #[serde(rename = "MAX_NUM_ORDERS")]
218    #[serde(rename_all = "camelCase")]
219    MaxNumOrders {
220        /// Maximum number of orders.
221        max_num_orders: u16,
222    },
223    /// Max algo orders filter.
224    #[serde(rename = "MAX_NUM_ALGO_ORDERS")]
225    #[serde(rename_all = "camelCase")]
226    MaxNumAlgoOrders {
227        /// Maximum number of algo orders.
228        max_num_algo_orders: u16,
229    },
230    /// Max iceberg orders filter.
231    #[serde(rename = "MAX_NUM_ICEBERG_ORDERS")]
232    #[serde(rename_all = "camelCase")]
233    MaxNumIcebergOrders {
234        /// Maximum number of iceberg orders.
235        max_num_iceberg_orders: u16,
236    },
237    /// Max position filter.
238    #[serde(rename = "MAX_POSITION")]
239    #[serde(rename_all = "camelCase")]
240    MaxPosition {
241        /// Maximum position value.
242        #[serde(with = "string_or_float")]
243        max_position: f64,
244    },
245    /// Exchange max orders filter.
246    #[serde(rename = "EXCHANGE_MAX_NUM_ORDERS")]
247    #[serde(rename_all = "camelCase")]
248    ExchangeMaxNumOrders {
249        /// Maximum number of orders.
250        max_num_orders: u16,
251    },
252    /// Exchange max algo orders filter.
253    #[serde(rename = "EXCHANGE_MAX_NUM_ALGO_ORDERS")]
254    #[serde(rename_all = "camelCase")]
255    ExchangeMaxNumAlgoOrders {
256        /// Maximum number of algo orders.
257        max_num_algo_orders: u16,
258    },
259    /// Trailing delta filter.
260    #[serde(rename = "TRAILING_DELTA")]
261    #[serde(rename_all = "camelCase")]
262    TrailingDelta {
263        /// Minimum trailing delta.
264        min_trailing_above_delta: u32,
265        /// Maximum trailing delta.
266        max_trailing_above_delta: u32,
267        /// Minimum trailing delta below.
268        min_trailing_below_delta: u32,
269        /// Maximum trailing delta below.
270        max_trailing_below_delta: u32,
271    },
272    /// Unknown filter type.
273    #[serde(other)]
274    Other,
275}
276
277/// Order book response.
278#[derive(Debug, Clone, Serialize, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct OrderBook {
281    /// Last update ID.
282    pub last_update_id: u64,
283    /// Bid entries (price, quantity).
284    pub bids: Vec<OrderBookEntry>,
285    /// Ask entries (price, quantity).
286    pub asks: Vec<OrderBookEntry>,
287}
288
289/// Order book entry (price level).
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct OrderBookEntry {
292    /// Price level.
293    #[serde(with = "string_or_float")]
294    pub price: f64,
295    /// Quantity at this price level.
296    #[serde(with = "string_or_float")]
297    pub quantity: f64,
298}
299
300/// Recent trade.
301#[derive(Debug, Clone, Serialize, Deserialize)]
302#[serde(rename_all = "camelCase")]
303pub struct Trade {
304    /// Trade ID.
305    pub id: u64,
306    /// Price.
307    #[serde(with = "string_or_float")]
308    pub price: f64,
309    /// Quantity.
310    #[serde(rename = "qty", with = "string_or_float")]
311    pub quantity: f64,
312    /// Quote quantity.
313    #[serde(rename = "quoteQty", with = "string_or_float")]
314    pub quote_quantity: f64,
315    /// Trade time in milliseconds.
316    pub time: u64,
317    /// Was the buyer the maker.
318    pub is_buyer_maker: bool,
319    /// Was this the best price match.
320    pub is_best_match: bool,
321}
322
323/// Aggregate trade.
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct AggTrade {
326    /// Aggregate trade ID.
327    #[serde(rename = "a")]
328    pub agg_trade_id: u64,
329    /// Price.
330    #[serde(rename = "p", with = "string_or_float")]
331    pub price: f64,
332    /// Quantity.
333    #[serde(rename = "q", with = "string_or_float")]
334    pub quantity: f64,
335    /// First trade ID.
336    #[serde(rename = "f")]
337    pub first_trade_id: u64,
338    /// Last trade ID.
339    #[serde(rename = "l")]
340    pub last_trade_id: u64,
341    /// Timestamp.
342    #[serde(rename = "T")]
343    pub timestamp: u64,
344    /// Was the buyer the maker.
345    #[serde(rename = "m")]
346    pub is_buyer_maker: bool,
347    /// Was this the best price match.
348    #[serde(rename = "M")]
349    pub is_best_match: bool,
350}
351
352/// Kline/candlestick data.
353#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
354pub struct Kline {
355    /// Kline open time.
356    pub open_time: i64,
357    /// Open price.
358    pub open: f64,
359    /// High price.
360    pub high: f64,
361    /// Low price.
362    pub low: f64,
363    /// Close price.
364    pub close: f64,
365    /// Volume.
366    pub volume: f64,
367    /// Kline close time.
368    pub close_time: i64,
369    /// Quote asset volume.
370    pub quote_asset_volume: f64,
371    /// Number of trades.
372    pub number_of_trades: i64,
373    /// Taker buy base asset volume.
374    pub taker_buy_base_asset_volume: f64,
375    /// Taker buy quote asset volume.
376    pub taker_buy_quote_asset_volume: f64,
377}
378
379/// 24hr ticker price change statistics.
380#[derive(Debug, Clone, Serialize, Deserialize)]
381#[serde(rename_all = "camelCase")]
382pub struct Ticker24h {
383    /// Symbol.
384    pub symbol: String,
385    /// Price change.
386    #[serde(with = "string_or_float")]
387    pub price_change: f64,
388    /// Price change percent.
389    #[serde(with = "string_or_float")]
390    pub price_change_percent: f64,
391    /// Weighted average price.
392    #[serde(with = "string_or_float")]
393    pub weighted_avg_price: f64,
394    /// Previous close price.
395    #[serde(with = "string_or_float")]
396    pub prev_close_price: f64,
397    /// Last price.
398    #[serde(with = "string_or_float")]
399    pub last_price: f64,
400    /// Last quantity.
401    #[serde(with = "string_or_float")]
402    pub last_qty: f64,
403    /// Bid price.
404    #[serde(with = "string_or_float")]
405    pub bid_price: f64,
406    /// Bid quantity.
407    #[serde(with = "string_or_float")]
408    pub bid_qty: f64,
409    /// Ask price.
410    #[serde(with = "string_or_float")]
411    pub ask_price: f64,
412    /// Ask quantity.
413    #[serde(with = "string_or_float")]
414    pub ask_qty: f64,
415    /// Open price.
416    #[serde(with = "string_or_float")]
417    pub open_price: f64,
418    /// High price.
419    #[serde(with = "string_or_float")]
420    pub high_price: f64,
421    /// Low price.
422    #[serde(with = "string_or_float")]
423    pub low_price: f64,
424    /// Total volume.
425    #[serde(with = "string_or_float")]
426    pub volume: f64,
427    /// Quote volume.
428    #[serde(with = "string_or_float")]
429    pub quote_volume: f64,
430    /// Open time.
431    pub open_time: u64,
432    /// Close time.
433    pub close_time: u64,
434    /// First trade ID.
435    pub first_id: i64,
436    /// Last trade ID.
437    pub last_id: i64,
438    /// Trade count.
439    pub count: u64,
440}
441
442/// Trading day ticker statistics (FULL).
443#[derive(Debug, Clone, Serialize, Deserialize)]
444#[serde(rename_all = "camelCase")]
445pub struct TradingDayTicker {
446    /// Symbol.
447    pub symbol: String,
448    /// Price change.
449    #[serde(with = "string_or_float")]
450    pub price_change: f64,
451    /// Price change percent.
452    #[serde(with = "string_or_float")]
453    pub price_change_percent: f64,
454    /// Weighted average price.
455    #[serde(with = "string_or_float")]
456    pub weighted_avg_price: f64,
457    /// Open price.
458    #[serde(with = "string_or_float")]
459    pub open_price: f64,
460    /// High price.
461    #[serde(with = "string_or_float")]
462    pub high_price: f64,
463    /// Low price.
464    #[serde(with = "string_or_float")]
465    pub low_price: f64,
466    /// Last price.
467    #[serde(with = "string_or_float")]
468    pub last_price: f64,
469    /// Total volume.
470    #[serde(with = "string_or_float")]
471    pub volume: f64,
472    /// Quote volume.
473    #[serde(with = "string_or_float")]
474    pub quote_volume: f64,
475    /// Open time.
476    pub open_time: u64,
477    /// Close time.
478    pub close_time: u64,
479    /// First trade ID.
480    pub first_id: i64,
481    /// Last trade ID.
482    pub last_id: i64,
483    /// Trade count.
484    pub count: u64,
485}
486
487/// Trading day ticker statistics (MINI).
488#[derive(Debug, Clone, Serialize, Deserialize)]
489#[serde(rename_all = "camelCase")]
490pub struct TradingDayTickerMini {
491    /// Symbol.
492    pub symbol: String,
493    /// Open price.
494    #[serde(with = "string_or_float")]
495    pub open_price: f64,
496    /// High price.
497    #[serde(with = "string_or_float")]
498    pub high_price: f64,
499    /// Low price.
500    #[serde(with = "string_or_float")]
501    pub low_price: f64,
502    /// Last price.
503    #[serde(with = "string_or_float")]
504    pub last_price: f64,
505    /// Total volume.
506    #[serde(with = "string_or_float")]
507    pub volume: f64,
508    /// Quote volume.
509    #[serde(with = "string_or_float")]
510    pub quote_volume: f64,
511    /// Open time.
512    pub open_time: u64,
513    /// Close time.
514    pub close_time: u64,
515    /// First trade ID.
516    pub first_id: i64,
517    /// Last trade ID.
518    pub last_id: i64,
519    /// Trade count.
520    pub count: u64,
521}
522
523/// Rolling window ticker statistics (FULL).
524#[derive(Debug, Clone, Serialize, Deserialize)]
525#[serde(rename_all = "camelCase")]
526pub struct RollingWindowTicker {
527    /// Symbol.
528    pub symbol: String,
529    /// Price change.
530    #[serde(with = "string_or_float")]
531    pub price_change: f64,
532    /// Price change percent.
533    #[serde(with = "string_or_float")]
534    pub price_change_percent: f64,
535    /// Weighted average price.
536    #[serde(with = "string_or_float")]
537    pub weighted_avg_price: f64,
538    /// Open price.
539    #[serde(with = "string_or_float")]
540    pub open_price: f64,
541    /// High price.
542    #[serde(with = "string_or_float")]
543    pub high_price: f64,
544    /// Low price.
545    #[serde(with = "string_or_float")]
546    pub low_price: f64,
547    /// Last price.
548    #[serde(with = "string_or_float")]
549    pub last_price: f64,
550    /// Total volume.
551    #[serde(with = "string_or_float")]
552    pub volume: f64,
553    /// Quote volume.
554    #[serde(with = "string_or_float")]
555    pub quote_volume: f64,
556    /// Open time.
557    pub open_time: u64,
558    /// Close time.
559    pub close_time: u64,
560    /// First trade ID.
561    pub first_id: i64,
562    /// Last trade ID.
563    pub last_id: i64,
564    /// Trade count.
565    pub count: u64,
566}
567
568/// Rolling window ticker statistics (MINI).
569#[derive(Debug, Clone, Serialize, Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct RollingWindowTickerMini {
572    /// Symbol.
573    pub symbol: String,
574    /// Open price.
575    #[serde(with = "string_or_float")]
576    pub open_price: f64,
577    /// High price.
578    #[serde(with = "string_or_float")]
579    pub high_price: f64,
580    /// Low price.
581    #[serde(with = "string_or_float")]
582    pub low_price: f64,
583    /// Last price.
584    #[serde(with = "string_or_float")]
585    pub last_price: f64,
586    /// Total volume.
587    #[serde(with = "string_or_float")]
588    pub volume: f64,
589    /// Quote volume.
590    #[serde(with = "string_or_float")]
591    pub quote_volume: f64,
592    /// Open time.
593    pub open_time: u64,
594    /// Close time.
595    pub close_time: u64,
596    /// First trade ID.
597    pub first_id: i64,
598    /// Last trade ID.
599    pub last_id: i64,
600    /// Trade count.
601    pub count: u64,
602}
603
604/// Symbol price ticker.
605#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
606pub struct TickerPrice {
607    /// Symbol.
608    pub symbol: String,
609    /// Current price.
610    #[serde(with = "string_or_float")]
611    pub price: f64,
612}
613
614/// Symbol order book ticker (best bid/ask).
615#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
616#[serde(rename_all = "camelCase")]
617pub struct BookTicker {
618    /// Symbol.
619    pub symbol: String,
620    /// Best bid price.
621    #[serde(with = "string_or_float")]
622    pub bid_price: f64,
623    /// Best bid quantity.
624    #[serde(with = "string_or_float")]
625    pub bid_qty: f64,
626    /// Best ask price.
627    #[serde(with = "string_or_float")]
628    pub ask_price: f64,
629    /// Best ask quantity.
630    #[serde(with = "string_or_float")]
631    pub ask_qty: f64,
632}
633
634/// Average price response.
635#[derive(Debug, Clone, Serialize, Deserialize)]
636pub struct AveragePrice {
637    /// Number of minutes the average is calculated over.
638    pub mins: u64,
639    /// Average price.
640    #[serde(with = "string_or_float")]
641    pub price: f64,
642}
643
644/// Helper module for deserializing string or float values.
645///
646/// Binance API sometimes returns numbers as strings and sometimes as numbers.
647pub mod string_or_float {
648    use serde::{Deserialize, Deserializer, Serializer, de};
649    use std::fmt;
650
651    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
652    where
653        T: fmt::Display,
654        S: Serializer,
655    {
656        serializer.collect_str(value)
657    }
658
659    pub fn deserialize<'de, D>(deserializer: D) -> Result<f64, D::Error>
660    where
661        D: Deserializer<'de>,
662    {
663        #[derive(Deserialize)]
664        #[serde(untagged)]
665        enum StringOrFloat {
666            String(String),
667            Float(f64),
668        }
669
670        match StringOrFloat::deserialize(deserializer)? {
671            StringOrFloat::String(s) => s.parse().map_err(de::Error::custom),
672            StringOrFloat::Float(f) => Ok(f),
673        }
674    }
675}
676
677/// Helper module for deserializing optional string or float values.
678pub mod string_or_float_opt {
679    use serde::{Deserialize, Deserializer, Serializer};
680    use std::fmt;
681
682    pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
683    where
684        T: fmt::Display,
685        S: Serializer,
686    {
687        match value {
688            Some(v) => super::string_or_float::serialize(v, serializer),
689            None => serializer.serialize_none(),
690        }
691    }
692
693    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
694    where
695        D: Deserializer<'de>,
696    {
697        #[derive(Deserialize)]
698        #[serde(untagged)]
699        enum StringOrFloat {
700            String(String),
701            Float(f64),
702            Null,
703        }
704
705        match StringOrFloat::deserialize(deserializer)? {
706            StringOrFloat::String(s) if s.is_empty() => Ok(None),
707            StringOrFloat::String(s) => s.parse().map(Some).map_err(serde::de::Error::custom),
708            StringOrFloat::Float(f) => Ok(Some(f)),
709            StringOrFloat::Null => Ok(None),
710        }
711    }
712}
713
714#[cfg(test)]
715mod tests {
716    use super::*;
717
718    #[test]
719    fn test_server_time_deserialize() {
720        let json = r#"{"serverTime": 1234567890123}"#;
721        let time: ServerTime = serde_json::from_str(json).unwrap();
722        assert_eq!(time.server_time, 1234567890123);
723    }
724
725    #[test]
726    fn test_ticker_price_deserialize() {
727        let json = r#"{"symbol": "BTCUSDT", "price": "50000.00"}"#;
728        let ticker: TickerPrice = serde_json::from_str(json).unwrap();
729        assert_eq!(ticker.symbol, "BTCUSDT");
730        assert_eq!(ticker.price, 50000.0);
731    }
732
733    #[test]
734    fn test_book_ticker_deserialize() {
735        let json = r#"{
736            "symbol": "BTCUSDT",
737            "bidPrice": "50000.00",
738            "bidQty": "1.5",
739            "askPrice": "50001.00",
740            "askQty": "2.0"
741        }"#;
742        let ticker: BookTicker = serde_json::from_str(json).unwrap();
743        assert_eq!(ticker.symbol, "BTCUSDT");
744        assert_eq!(ticker.bid_price, 50000.0);
745        assert_eq!(ticker.bid_qty, 1.5);
746        assert_eq!(ticker.ask_price, 50001.0);
747        assert_eq!(ticker.ask_qty, 2.0);
748    }
749
750    #[test]
751    fn test_order_book_entry_deserialize() {
752        // Order book entries come as arrays: [price, quantity]
753        let json = r#"["50000.00", "1.5"]"#;
754        let entry: OrderBookEntry = serde_json::from_str(json).unwrap();
755        assert_eq!(entry.price, 50000.0);
756        assert_eq!(entry.quantity, 1.5);
757    }
758
759    #[test]
760    fn test_agg_trade_deserialize() {
761        let json = r#"{
762            "a": 12345,
763            "p": "50000.00",
764            "q": "1.5",
765            "f": 100,
766            "l": 105,
767            "T": 1234567890123,
768            "m": true,
769            "M": true
770        }"#;
771        let trade: AggTrade = serde_json::from_str(json).unwrap();
772        assert_eq!(trade.agg_trade_id, 12345);
773        assert_eq!(trade.price, 50000.0);
774        assert_eq!(trade.quantity, 1.5);
775        assert_eq!(trade.first_trade_id, 100);
776        assert_eq!(trade.last_trade_id, 105);
777        assert_eq!(trade.timestamp, 1234567890123);
778        assert!(trade.is_buyer_maker);
779        assert!(trade.is_best_match);
780    }
781
782    #[test]
783    fn test_average_price_deserialize() {
784        let json = r#"{"mins": 5, "price": "50000.00"}"#;
785        let avg: AveragePrice = serde_json::from_str(json).unwrap();
786        assert_eq!(avg.mins, 5);
787        assert_eq!(avg.price, 50000.0);
788    }
789
790    #[test]
791    fn test_symbol_filter_price_filter() {
792        let json = r#"{
793            "filterType": "PRICE_FILTER",
794            "minPrice": "0.00001000",
795            "maxPrice": "1000000.00000000",
796            "tickSize": "0.00001000"
797        }"#;
798        let filter: SymbolFilter = serde_json::from_str(json).unwrap();
799        match filter {
800            SymbolFilter::PriceFilter {
801                min_price,
802                max_price,
803                tick_size,
804            } => {
805                assert_eq!(min_price, 0.00001);
806                assert_eq!(max_price, 1000000.0);
807                assert_eq!(tick_size, 0.00001);
808            }
809            _ => panic!("Expected PriceFilter"),
810        }
811    }
812
813    #[test]
814    fn test_symbol_filter_lot_size() {
815        let json = r#"{
816            "filterType": "LOT_SIZE",
817            "minQty": "0.00100000",
818            "maxQty": "100000.00000000",
819            "stepSize": "0.00100000"
820        }"#;
821        let filter: SymbolFilter = serde_json::from_str(json).unwrap();
822        match filter {
823            SymbolFilter::LotSize {
824                min_qty,
825                max_qty,
826                step_size,
827            } => {
828                assert_eq!(min_qty, 0.001);
829                assert_eq!(max_qty, 100000.0);
830                assert_eq!(step_size, 0.001);
831            }
832            _ => panic!("Expected LotSize"),
833        }
834    }
835
836    #[test]
837    fn test_unknown_filter_type() {
838        let json = r#"{"filterType": "UNKNOWN_FILTER_TYPE"}"#;
839        let filter: SymbolFilter = serde_json::from_str(json).unwrap();
840        assert_eq!(filter, SymbolFilter::Other);
841    }
842}