use serde_json::Value;
use crate::Result;
use crate::client::Client;
use crate::models::{
AggTrade, AveragePrice, BookTicker, ExchangeInfo, Kline, OrderBook, RollingWindowTicker,
RollingWindowTickerMini, ServerTime, Ticker24h, TickerPrice, Trade, TradingDayTicker,
TradingDayTickerMini,
};
use crate::types::{KlineInterval, SymbolStatus, TickerType};
const API_V3_PING: &str = "/api/v3/ping";
const API_V3_TIME: &str = "/api/v3/time";
const API_V3_EXCHANGE_INFO: &str = "/api/v3/exchangeInfo";
const API_V3_DEPTH: &str = "/api/v3/depth";
const API_V3_TRADES: &str = "/api/v3/trades";
const API_V3_HISTORICAL_TRADES: &str = "/api/v3/historicalTrades";
const API_V3_AGG_TRADES: &str = "/api/v3/aggTrades";
const API_V3_KLINES: &str = "/api/v3/klines";
const API_V3_UI_KLINES: &str = "/api/v3/uiKlines";
const API_V3_AVG_PRICE: &str = "/api/v3/avgPrice";
const API_V3_TICKER_24HR: &str = "/api/v3/ticker/24hr";
const API_V3_TICKER_TRADING_DAY: &str = "/api/v3/ticker/tradingDay";
const API_V3_TICKER_PRICE: &str = "/api/v3/ticker/price";
const API_V3_TICKER_BOOK_TICKER: &str = "/api/v3/ticker/bookTicker";
const API_V3_TICKER: &str = "/api/v3/ticker";
#[derive(Clone)]
pub struct Market {
client: Client,
}
impl Market {
pub(crate) fn new(client: Client) -> Self {
Self { client }
}
pub async fn ping(&self) -> Result<()> {
let _: Value = self.client.get(API_V3_PING, None).await?;
Ok(())
}
pub async fn server_time(&self) -> Result<ServerTime> {
self.client.get(API_V3_TIME, None).await
}
pub async fn exchange_info(&self) -> Result<ExchangeInfo> {
self.client.get(API_V3_EXCHANGE_INFO, None).await
}
pub async fn exchange_info_for_symbols(&self, symbols: &[&str]) -> Result<ExchangeInfo> {
let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
let query = format!("symbols={}", urlencoding::encode(&symbols_json));
self.client.get(API_V3_EXCHANGE_INFO, Some(&query)).await
}
pub async fn depth(&self, symbol: &str, limit: Option<u16>) -> Result<OrderBook> {
let mut query = format!("symbol={}", symbol);
if let Some(l) = limit {
query.push_str(&format!("&limit={}", l));
}
self.client.get(API_V3_DEPTH, Some(&query)).await
}
pub async fn trades(&self, symbol: &str, limit: Option<u16>) -> Result<Vec<Trade>> {
let mut query = format!("symbol={}", symbol);
if let Some(l) = limit {
query.push_str(&format!("&limit={}", l));
}
self.client.get(API_V3_TRADES, Some(&query)).await
}
pub async fn historical_trades(
&self,
symbol: &str,
from_id: Option<u64>,
limit: Option<u16>,
) -> Result<Vec<Trade>> {
let mut query = format!("symbol={}", symbol);
if let Some(id) = from_id {
query.push_str(&format!("&fromId={}", id));
}
if let Some(l) = limit {
query.push_str(&format!("&limit={}", l));
}
self.client
.get_with_api_key(API_V3_HISTORICAL_TRADES, Some(&query))
.await
}
pub async fn agg_trades(
&self,
symbol: &str,
from_id: Option<u64>,
start_time: Option<u64>,
end_time: Option<u64>,
limit: Option<u16>,
) -> Result<Vec<AggTrade>> {
let mut query = format!("symbol={}", symbol);
if let Some(id) = from_id {
query.push_str(&format!("&fromId={}", id));
}
if let Some(start) = start_time {
query.push_str(&format!("&startTime={}", start));
}
if let Some(end) = end_time {
query.push_str(&format!("&endTime={}", end));
}
if let Some(l) = limit {
query.push_str(&format!("&limit={}", l));
}
self.client.get(API_V3_AGG_TRADES, Some(&query)).await
}
pub async fn klines(
&self,
symbol: &str,
interval: KlineInterval,
start_time: Option<u64>,
end_time: Option<u64>,
limit: Option<u16>,
) -> Result<Vec<Kline>> {
let mut query = format!("symbol={}&interval={}", symbol, interval);
if let Some(start) = start_time {
query.push_str(&format!("&startTime={}", start));
}
if let Some(end) = end_time {
query.push_str(&format!("&endTime={}", end));
}
if let Some(l) = limit {
query.push_str(&format!("&limit={}", l));
}
let raw: Vec<Vec<Value>> = self.client.get(API_V3_KLINES, Some(&query)).await?;
Ok(parse_klines(raw))
}
pub async fn ui_klines(
&self,
symbol: &str,
interval: KlineInterval,
start_time: Option<u64>,
end_time: Option<u64>,
limit: Option<u16>,
) -> Result<Vec<Kline>> {
let mut query = format!("symbol={}&interval={}", symbol, interval);
if let Some(start) = start_time {
query.push_str(&format!("&startTime={}", start));
}
if let Some(end) = end_time {
query.push_str(&format!("&endTime={}", end));
}
if let Some(l) = limit {
query.push_str(&format!("&limit={}", l));
}
let raw: Vec<Vec<Value>> = self.client.get(API_V3_UI_KLINES, Some(&query)).await?;
Ok(parse_klines(raw))
}
pub async fn avg_price(&self, symbol: &str) -> Result<AveragePrice> {
let query = format!("symbol={}", symbol);
self.client.get(API_V3_AVG_PRICE, Some(&query)).await
}
pub async fn ticker_24h(&self, symbol: &str) -> Result<Ticker24h> {
let query = format!("symbol={}", symbol);
self.client.get(API_V3_TICKER_24HR, Some(&query)).await
}
pub async fn ticker_24h_all(&self) -> Result<Vec<Ticker24h>> {
self.client.get(API_V3_TICKER_24HR, None).await
}
pub async fn trading_day_ticker(
&self,
symbol: &str,
time_zone: Option<&str>,
symbol_status: Option<SymbolStatus>,
) -> Result<TradingDayTicker> {
let mut params: Vec<(&str, String)> = vec![("symbol", symbol.to_string())];
if let Some(tz) = time_zone {
params.push(("timeZone", tz.to_string()));
}
if let Some(status) = symbol_status {
params.push(("symbolStatus", status.to_string()));
}
let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
self.client
.get_with_params(API_V3_TICKER_TRADING_DAY, ¶ms_ref)
.await
}
pub async fn trading_day_ticker_mini(
&self,
symbol: &str,
time_zone: Option<&str>,
symbol_status: Option<SymbolStatus>,
) -> Result<TradingDayTickerMini> {
let mut params: Vec<(&str, String)> = vec![("symbol", symbol.to_string())];
params.push(("type", TickerType::Mini.to_string()));
if let Some(tz) = time_zone {
params.push(("timeZone", tz.to_string()));
}
if let Some(status) = symbol_status {
params.push(("symbolStatus", status.to_string()));
}
let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
self.client
.get_with_params(API_V3_TICKER_TRADING_DAY, ¶ms_ref)
.await
}
pub async fn trading_day_tickers(
&self,
symbols: &[&str],
time_zone: Option<&str>,
symbol_status: Option<SymbolStatus>,
) -> Result<Vec<TradingDayTicker>> {
let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
let mut params: Vec<(&str, String)> =
vec![("symbols", urlencoding::encode(&symbols_json).into_owned())];
if let Some(tz) = time_zone {
params.push(("timeZone", tz.to_string()));
}
if let Some(status) = symbol_status {
params.push(("symbolStatus", status.to_string()));
}
let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
self.client
.get_with_params(API_V3_TICKER_TRADING_DAY, ¶ms_ref)
.await
}
pub async fn trading_day_tickers_mini(
&self,
symbols: &[&str],
time_zone: Option<&str>,
symbol_status: Option<SymbolStatus>,
) -> Result<Vec<TradingDayTickerMini>> {
let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
let mut params: Vec<(&str, String)> =
vec![("symbols", urlencoding::encode(&symbols_json).into_owned())];
params.push(("type", TickerType::Mini.to_string()));
if let Some(tz) = time_zone {
params.push(("timeZone", tz.to_string()));
}
if let Some(status) = symbol_status {
params.push(("symbolStatus", status.to_string()));
}
let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
self.client
.get_with_params(API_V3_TICKER_TRADING_DAY, ¶ms_ref)
.await
}
pub async fn rolling_window_ticker(
&self,
symbol: &str,
window_size: Option<&str>,
symbol_status: Option<SymbolStatus>,
) -> Result<RollingWindowTicker> {
let mut params: Vec<(&str, String)> = vec![("symbol", symbol.to_string())];
if let Some(window) = window_size {
params.push(("windowSize", window.to_string()));
}
if let Some(status) = symbol_status {
params.push(("symbolStatus", status.to_string()));
}
let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
self.client
.get_with_params(API_V3_TICKER, ¶ms_ref)
.await
}
pub async fn rolling_window_ticker_mini(
&self,
symbol: &str,
window_size: Option<&str>,
symbol_status: Option<SymbolStatus>,
) -> Result<RollingWindowTickerMini> {
let mut params: Vec<(&str, String)> = vec![("symbol", symbol.to_string())];
params.push(("type", TickerType::Mini.to_string()));
if let Some(window) = window_size {
params.push(("windowSize", window.to_string()));
}
if let Some(status) = symbol_status {
params.push(("symbolStatus", status.to_string()));
}
let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
self.client
.get_with_params(API_V3_TICKER, ¶ms_ref)
.await
}
pub async fn rolling_window_tickers(
&self,
symbols: &[&str],
window_size: Option<&str>,
symbol_status: Option<SymbolStatus>,
) -> Result<Vec<RollingWindowTicker>> {
let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
let mut params: Vec<(&str, String)> =
vec![("symbols", urlencoding::encode(&symbols_json).into_owned())];
if let Some(window) = window_size {
params.push(("windowSize", window.to_string()));
}
if let Some(status) = symbol_status {
params.push(("symbolStatus", status.to_string()));
}
let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
self.client
.get_with_params(API_V3_TICKER, ¶ms_ref)
.await
}
pub async fn rolling_window_tickers_mini(
&self,
symbols: &[&str],
window_size: Option<&str>,
symbol_status: Option<SymbolStatus>,
) -> Result<Vec<RollingWindowTickerMini>> {
let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
let mut params: Vec<(&str, String)> =
vec![("symbols", urlencoding::encode(&symbols_json).into_owned())];
params.push(("type", TickerType::Mini.to_string()));
if let Some(window) = window_size {
params.push(("windowSize", window.to_string()));
}
if let Some(status) = symbol_status {
params.push(("symbolStatus", status.to_string()));
}
let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
self.client
.get_with_params(API_V3_TICKER, ¶ms_ref)
.await
}
pub async fn price(&self, symbol: &str) -> Result<TickerPrice> {
let query = format!("symbol={}", symbol);
self.client.get(API_V3_TICKER_PRICE, Some(&query)).await
}
pub async fn prices(&self) -> Result<Vec<TickerPrice>> {
self.client.get(API_V3_TICKER_PRICE, None).await
}
pub async fn prices_for(&self, symbols: &[&str]) -> Result<Vec<TickerPrice>> {
let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
let query = format!("symbols={}", urlencoding::encode(&symbols_json));
self.client.get(API_V3_TICKER_PRICE, Some(&query)).await
}
pub async fn book_ticker(&self, symbol: &str) -> Result<BookTicker> {
let query = format!("symbol={}", symbol);
self.client
.get(API_V3_TICKER_BOOK_TICKER, Some(&query))
.await
}
pub async fn book_tickers(&self) -> Result<Vec<BookTicker>> {
self.client.get(API_V3_TICKER_BOOK_TICKER, None).await
}
pub async fn book_tickers_for(&self, symbols: &[&str]) -> Result<Vec<BookTicker>> {
let symbols_json = serde_json::to_string(symbols).unwrap_or_default();
let query = format!("symbols={}", urlencoding::encode(&symbols_json));
self.client
.get(API_V3_TICKER_BOOK_TICKER, Some(&query))
.await
}
}
fn parse_value_as_f64(value: &Value) -> f64 {
match value {
Value::String(s) => s.parse().unwrap_or_default(),
Value::Number(n) => n.as_f64().unwrap_or_default(),
_ => 0.0,
}
}
fn parse_klines(raw: Vec<Vec<Value>>) -> Vec<Kline> {
raw.into_iter()
.map(|row| Kline {
open_time: row[0].as_i64().unwrap_or_default(),
open: parse_value_as_f64(&row[1]),
high: parse_value_as_f64(&row[2]),
low: parse_value_as_f64(&row[3]),
close: parse_value_as_f64(&row[4]),
volume: parse_value_as_f64(&row[5]),
close_time: row[6].as_i64().unwrap_or_default(),
quote_asset_volume: parse_value_as_f64(&row[7]),
number_of_trades: row[8].as_i64().unwrap_or_default(),
taker_buy_base_asset_volume: parse_value_as_f64(&row[9]),
taker_buy_quote_asset_volume: parse_value_as_f64(&row[10]),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_value_as_f64_string() {
let value = Value::String("123.456".to_string());
assert_eq!(parse_value_as_f64(&value), 123.456);
}
#[test]
fn test_parse_value_as_f64_number() {
let value = serde_json::json!(123.456);
assert_eq!(parse_value_as_f64(&value), 123.456);
}
#[test]
fn test_parse_value_as_f64_invalid() {
let value = Value::Null;
assert_eq!(parse_value_as_f64(&value), 0.0);
}
}