1use serde_json::Value;
7
8use crate::Result;
9use crate::client::Client;
10use crate::models::{
11 AggTrade, AveragePrice, BookTicker, ExchangeInfo, Kline, OrderBook, RollingWindowTicker,
12 RollingWindowTickerMini, ServerTime, Ticker24h, TickerPrice, Trade, TradingDayTicker,
13 TradingDayTickerMini,
14};
15use crate::types::{KlineInterval, SymbolStatus, TickerType};
16
17const API_V3_PING: &str = "/api/v3/ping";
19const API_V3_TIME: &str = "/api/v3/time";
20const API_V3_EXCHANGE_INFO: &str = "/api/v3/exchangeInfo";
21const API_V3_DEPTH: &str = "/api/v3/depth";
22const API_V3_TRADES: &str = "/api/v3/trades";
23const API_V3_HISTORICAL_TRADES: &str = "/api/v3/historicalTrades";
24const API_V3_AGG_TRADES: &str = "/api/v3/aggTrades";
25const API_V3_KLINES: &str = "/api/v3/klines";
26const API_V3_UI_KLINES: &str = "/api/v3/uiKlines";
27const API_V3_AVG_PRICE: &str = "/api/v3/avgPrice";
28const API_V3_TICKER_24HR: &str = "/api/v3/ticker/24hr";
29const API_V3_TICKER_TRADING_DAY: &str = "/api/v3/ticker/tradingDay";
30const API_V3_TICKER_PRICE: &str = "/api/v3/ticker/price";
31const API_V3_TICKER_BOOK_TICKER: &str = "/api/v3/ticker/bookTicker";
32const API_V3_TICKER: &str = "/api/v3/ticker";
33
34#[derive(Clone)]
38pub struct Market {
39 client: Client,
40}
41
42impl Market {
43 pub(crate) fn new(client: Client) -> Self {
45 Self { client }
46 }
47
48 pub async fn ping(&self) -> Result<()> {
57 let _: Value = self.client.get(API_V3_PING, None).await?;
58 Ok(())
59 }
60
61 pub async fn server_time(&self) -> Result<ServerTime> {
71 self.client.get(API_V3_TIME, None).await
72 }
73
74 pub async fn exchange_info(&self) -> Result<ExchangeInfo> {
86 self.client.get(API_V3_EXCHANGE_INFO, None).await
87 }
88
89 pub async fn exchange_info_for_symbols(&self, symbols: &[&str]) -> Result<ExchangeInfo> {
102 let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
103 let query = format!("symbols={}", urlencoding::encode(&symbols_json));
104 self.client.get(API_V3_EXCHANGE_INFO, Some(&query)).await
105 }
106
107 pub async fn depth(&self, symbol: &str, limit: Option<u16>) -> Result<OrderBook> {
125 let mut query = format!("symbol={}", symbol);
126 if let Some(l) = limit {
127 query.push_str(&format!("&limit={}", l));
128 }
129 self.client.get(API_V3_DEPTH, Some(&query)).await
130 }
131
132 pub async fn trades(&self, symbol: &str, limit: Option<u16>) -> Result<Vec<Trade>> {
146 let mut query = format!("symbol={}", symbol);
147 if let Some(l) = limit {
148 query.push_str(&format!("&limit={}", l));
149 }
150 self.client.get(API_V3_TRADES, Some(&query)).await
151 }
152
153 pub async fn historical_trades(
170 &self,
171 symbol: &str,
172 from_id: Option<u64>,
173 limit: Option<u16>,
174 ) -> Result<Vec<Trade>> {
175 let mut query = format!("symbol={}", symbol);
176 if let Some(id) = from_id {
177 query.push_str(&format!("&fromId={}", id));
178 }
179 if let Some(l) = limit {
180 query.push_str(&format!("&limit={}", l));
181 }
182 self.client
184 .get_with_api_key(API_V3_HISTORICAL_TRADES, Some(&query))
185 .await
186 }
187
188 pub async fn agg_trades(
208 &self,
209 symbol: &str,
210 from_id: Option<u64>,
211 start_time: Option<u64>,
212 end_time: Option<u64>,
213 limit: Option<u16>,
214 ) -> Result<Vec<AggTrade>> {
215 let mut query = format!("symbol={}", symbol);
216 if let Some(id) = from_id {
217 query.push_str(&format!("&fromId={}", id));
218 }
219 if let Some(start) = start_time {
220 query.push_str(&format!("&startTime={}", start));
221 }
222 if let Some(end) = end_time {
223 query.push_str(&format!("&endTime={}", end));
224 }
225 if let Some(l) = limit {
226 query.push_str(&format!("&limit={}", l));
227 }
228 self.client.get(API_V3_AGG_TRADES, Some(&query)).await
229 }
230
231 pub async fn klines(
253 &self,
254 symbol: &str,
255 interval: KlineInterval,
256 start_time: Option<u64>,
257 end_time: Option<u64>,
258 limit: Option<u16>,
259 ) -> Result<Vec<Kline>> {
260 let mut query = format!("symbol={}&interval={}", symbol, interval);
261 if let Some(start) = start_time {
262 query.push_str(&format!("&startTime={}", start));
263 }
264 if let Some(end) = end_time {
265 query.push_str(&format!("&endTime={}", end));
266 }
267 if let Some(l) = limit {
268 query.push_str(&format!("&limit={}", l));
269 }
270
271 let raw: Vec<Vec<Value>> = self.client.get(API_V3_KLINES, Some(&query)).await?;
273
274 Ok(parse_klines(raw))
275 }
276
277 pub async fn ui_klines(
301 &self,
302 symbol: &str,
303 interval: KlineInterval,
304 start_time: Option<u64>,
305 end_time: Option<u64>,
306 limit: Option<u16>,
307 ) -> Result<Vec<Kline>> {
308 let mut query = format!("symbol={}&interval={}", symbol, interval);
309 if let Some(start) = start_time {
310 query.push_str(&format!("&startTime={}", start));
311 }
312 if let Some(end) = end_time {
313 query.push_str(&format!("&endTime={}", end));
314 }
315 if let Some(l) = limit {
316 query.push_str(&format!("&limit={}", l));
317 }
318
319 let raw: Vec<Vec<Value>> = self.client.get(API_V3_UI_KLINES, Some(&query)).await?;
320
321 Ok(parse_klines(raw))
322 }
323
324 pub async fn avg_price(&self, symbol: &str) -> Result<AveragePrice> {
338 let query = format!("symbol={}", symbol);
339 self.client.get(API_V3_AVG_PRICE, Some(&query)).await
340 }
341
342 pub async fn ticker_24h(&self, symbol: &str) -> Result<Ticker24h> {
356 let query = format!("symbol={}", symbol);
357 self.client.get(API_V3_TICKER_24HR, Some(&query)).await
358 }
359
360 pub async fn ticker_24h_all(&self) -> Result<Vec<Ticker24h>> {
369 self.client.get(API_V3_TICKER_24HR, None).await
370 }
371
372 pub async fn trading_day_ticker(
380 &self,
381 symbol: &str,
382 time_zone: Option<&str>,
383 symbol_status: Option<SymbolStatus>,
384 ) -> Result<TradingDayTicker> {
385 let mut params: Vec<(&str, String)> = vec![("symbol", symbol.to_string())];
386
387 if let Some(tz) = time_zone {
388 params.push(("timeZone", tz.to_string()));
389 }
390 if let Some(status) = symbol_status {
391 params.push(("symbolStatus", status.to_string()));
392 }
393
394 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
395 self.client
396 .get_with_params(API_V3_TICKER_TRADING_DAY, ¶ms_ref)
397 .await
398 }
399
400 pub async fn trading_day_ticker_mini(
402 &self,
403 symbol: &str,
404 time_zone: Option<&str>,
405 symbol_status: Option<SymbolStatus>,
406 ) -> Result<TradingDayTickerMini> {
407 let mut params: Vec<(&str, String)> = vec![("symbol", symbol.to_string())];
408
409 params.push(("type", TickerType::Mini.to_string()));
410
411 if let Some(tz) = time_zone {
412 params.push(("timeZone", tz.to_string()));
413 }
414 if let Some(status) = symbol_status {
415 params.push(("symbolStatus", status.to_string()));
416 }
417
418 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
419 self.client
420 .get_with_params(API_V3_TICKER_TRADING_DAY, ¶ms_ref)
421 .await
422 }
423
424 pub async fn trading_day_tickers(
426 &self,
427 symbols: &[&str],
428 time_zone: Option<&str>,
429 symbol_status: Option<SymbolStatus>,
430 ) -> Result<Vec<TradingDayTicker>> {
431 let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
432 let mut params: Vec<(&str, String)> =
433 vec![("symbols", urlencoding::encode(&symbols_json).into_owned())];
434
435 if let Some(tz) = time_zone {
436 params.push(("timeZone", tz.to_string()));
437 }
438 if let Some(status) = symbol_status {
439 params.push(("symbolStatus", status.to_string()));
440 }
441
442 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
443 self.client
444 .get_with_params(API_V3_TICKER_TRADING_DAY, ¶ms_ref)
445 .await
446 }
447
448 pub async fn trading_day_tickers_mini(
450 &self,
451 symbols: &[&str],
452 time_zone: Option<&str>,
453 symbol_status: Option<SymbolStatus>,
454 ) -> Result<Vec<TradingDayTickerMini>> {
455 let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
456 let mut params: Vec<(&str, String)> =
457 vec![("symbols", urlencoding::encode(&symbols_json).into_owned())];
458
459 params.push(("type", TickerType::Mini.to_string()));
460
461 if let Some(tz) = time_zone {
462 params.push(("timeZone", tz.to_string()));
463 }
464 if let Some(status) = symbol_status {
465 params.push(("symbolStatus", status.to_string()));
466 }
467
468 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
469 self.client
470 .get_with_params(API_V3_TICKER_TRADING_DAY, ¶ms_ref)
471 .await
472 }
473
474 pub async fn rolling_window_ticker(
482 &self,
483 symbol: &str,
484 window_size: Option<&str>,
485 symbol_status: Option<SymbolStatus>,
486 ) -> Result<RollingWindowTicker> {
487 let mut params: Vec<(&str, String)> = vec![("symbol", symbol.to_string())];
488
489 if let Some(window) = window_size {
490 params.push(("windowSize", window.to_string()));
491 }
492 if let Some(status) = symbol_status {
493 params.push(("symbolStatus", status.to_string()));
494 }
495
496 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
497 self.client
498 .get_with_params(API_V3_TICKER, ¶ms_ref)
499 .await
500 }
501
502 pub async fn rolling_window_ticker_mini(
504 &self,
505 symbol: &str,
506 window_size: Option<&str>,
507 symbol_status: Option<SymbolStatus>,
508 ) -> Result<RollingWindowTickerMini> {
509 let mut params: Vec<(&str, String)> = vec![("symbol", symbol.to_string())];
510
511 params.push(("type", TickerType::Mini.to_string()));
512
513 if let Some(window) = window_size {
514 params.push(("windowSize", window.to_string()));
515 }
516 if let Some(status) = symbol_status {
517 params.push(("symbolStatus", status.to_string()));
518 }
519
520 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
521 self.client
522 .get_with_params(API_V3_TICKER, ¶ms_ref)
523 .await
524 }
525
526 pub async fn rolling_window_tickers(
528 &self,
529 symbols: &[&str],
530 window_size: Option<&str>,
531 symbol_status: Option<SymbolStatus>,
532 ) -> Result<Vec<RollingWindowTicker>> {
533 let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
534 let mut params: Vec<(&str, String)> =
535 vec![("symbols", urlencoding::encode(&symbols_json).into_owned())];
536
537 if let Some(window) = window_size {
538 params.push(("windowSize", window.to_string()));
539 }
540 if let Some(status) = symbol_status {
541 params.push(("symbolStatus", status.to_string()));
542 }
543
544 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
545 self.client
546 .get_with_params(API_V3_TICKER, ¶ms_ref)
547 .await
548 }
549
550 pub async fn rolling_window_tickers_mini(
552 &self,
553 symbols: &[&str],
554 window_size: Option<&str>,
555 symbol_status: Option<SymbolStatus>,
556 ) -> Result<Vec<RollingWindowTickerMini>> {
557 let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
558 let mut params: Vec<(&str, String)> =
559 vec![("symbols", urlencoding::encode(&symbols_json).into_owned())];
560
561 params.push(("type", TickerType::Mini.to_string()));
562
563 if let Some(window) = window_size {
564 params.push(("windowSize", window.to_string()));
565 }
566 if let Some(status) = symbol_status {
567 params.push(("symbolStatus", status.to_string()));
568 }
569
570 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
571 self.client
572 .get_with_params(API_V3_TICKER, ¶ms_ref)
573 .await
574 }
575
576 pub async fn price(&self, symbol: &str) -> Result<TickerPrice> {
590 let query = format!("symbol={}", symbol);
591 self.client.get(API_V3_TICKER_PRICE, Some(&query)).await
592 }
593
594 pub async fn prices(&self) -> Result<Vec<TickerPrice>> {
606 self.client.get(API_V3_TICKER_PRICE, None).await
607 }
608
609 pub async fn prices_for(&self, symbols: &[&str]) -> Result<Vec<TickerPrice>> {
622 let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
623 let query = format!("symbols={}", urlencoding::encode(&symbols_json));
624 self.client.get(API_V3_TICKER_PRICE, Some(&query)).await
625 }
626
627 pub async fn book_ticker(&self, symbol: &str) -> Result<BookTicker> {
642 let query = format!("symbol={}", symbol);
643 self.client
644 .get(API_V3_TICKER_BOOK_TICKER, Some(&query))
645 .await
646 }
647
648 pub async fn book_tickers(&self) -> Result<Vec<BookTicker>> {
657 self.client.get(API_V3_TICKER_BOOK_TICKER, None).await
658 }
659
660 pub async fn book_tickers_for(&self, symbols: &[&str]) -> Result<Vec<BookTicker>> {
673 let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
674 let query = format!("symbols={}", urlencoding::encode(&symbols_json));
675 self.client
676 .get(API_V3_TICKER_BOOK_TICKER, Some(&query))
677 .await
678 }
679}
680
681fn parse_value_as_f64(value: &Value) -> f64 {
683 match value {
684 Value::String(s) => s.parse().unwrap_or_default(),
685 Value::Number(n) => n.as_f64().unwrap_or_default(),
686 _ => 0.0,
687 }
688}
689
690fn parse_klines(raw: Vec<Vec<Value>>) -> Vec<Kline> {
691 raw.into_iter()
692 .map(|row| Kline {
693 open_time: row[0].as_i64().unwrap_or_default(),
694 open: parse_value_as_f64(&row[1]),
695 high: parse_value_as_f64(&row[2]),
696 low: parse_value_as_f64(&row[3]),
697 close: parse_value_as_f64(&row[4]),
698 volume: parse_value_as_f64(&row[5]),
699 close_time: row[6].as_i64().unwrap_or_default(),
700 quote_asset_volume: parse_value_as_f64(&row[7]),
701 number_of_trades: row[8].as_i64().unwrap_or_default(),
702 taker_buy_base_asset_volume: parse_value_as_f64(&row[9]),
703 taker_buy_quote_asset_volume: parse_value_as_f64(&row[10]),
704 })
705 .collect()
706}
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711
712 #[test]
713 fn test_parse_value_as_f64_string() {
714 let value = Value::String("123.456".to_string());
715 assert_eq!(parse_value_as_f64(&value), 123.456);
716 }
717
718 #[test]
719 fn test_parse_value_as_f64_number() {
720 let value = serde_json::json!(123.456);
721 assert_eq!(parse_value_as_f64(&value), 123.456);
722 }
723
724 #[test]
725 fn test_parse_value_as_f64_invalid() {
726 let value = Value::Null;
727 assert_eq!(parse_value_as_f64(&value), 0.0);
728 }
729}