use super::{parse_decimal, parse_decimal_multi, value_to_hashmap};
use ccxt_core::{
Result,
error::{Error, ParseError},
types::{
Market, Ticker,
financial::{Amount, Price},
},
};
use serde_json::Value;
pub fn parse_ticker(data: &Value, market: Option<&Market>) -> Result<Ticker> {
let symbol = if let Some(m) = market {
m.symbol.clone()
} else {
data["symbol"]
.as_str()
.ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
.to_string()
};
let timestamp = data["closeTime"].as_i64();
Ok(Ticker {
symbol,
timestamp: timestamp.unwrap_or(0),
datetime: timestamp.map(|t| {
chrono::DateTime::from_timestamp(t / 1000, 0)
.map(|dt| dt.to_rfc3339())
.unwrap_or_default()
}),
high: parse_decimal(data, "highPrice").map(Price::new),
low: parse_decimal(data, "lowPrice").map(Price::new),
bid: parse_decimal(data, "bidPrice").map(Price::new),
bid_volume: parse_decimal(data, "bidQty").map(Amount::new),
ask: parse_decimal(data, "askPrice").map(Price::new),
ask_volume: parse_decimal(data, "askQty").map(Amount::new),
vwap: parse_decimal(data, "weightedAvgPrice").map(Price::new),
open: parse_decimal(data, "openPrice").map(Price::new),
close: parse_decimal(data, "lastPrice").map(Price::new),
last: parse_decimal(data, "lastPrice").map(Price::new),
previous_close: parse_decimal(data, "prevClosePrice").map(Price::new),
change: parse_decimal(data, "priceChange").map(Price::new),
percentage: parse_decimal(data, "priceChangePercent"),
average: None,
base_volume: parse_decimal(data, "volume").map(Amount::new),
quote_volume: parse_decimal(data, "quoteVolume").map(Amount::new),
funding_rate: None,
open_interest: None,
index_price: None,
mark_price: None,
info: value_to_hashmap(data),
})
}
pub fn parse_ws_ticker(data: &Value, market: Option<&Market>) -> Result<Ticker> {
let market_id = data["s"]
.as_str()
.or_else(|| data["symbol"].as_str())
.ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?;
let symbol = if let Some(m) = market {
m.symbol.clone()
} else {
market_id.to_string()
};
let event = data["e"].as_str().unwrap_or("bookTicker");
if event == "markPriceUpdate" {
let timestamp = data["E"].as_i64().unwrap_or(0);
return Ok(Ticker {
symbol,
timestamp,
datetime: Some(
chrono::DateTime::from_timestamp_millis(timestamp)
.map(|dt| dt.to_rfc3339())
.unwrap_or_default(),
),
high: None,
low: None,
bid: None,
bid_volume: None,
ask: None,
ask_volume: None,
vwap: None,
open: None,
close: parse_decimal(data, "p").map(Price::from),
last: parse_decimal(data, "p").map(Price::from),
previous_close: None,
change: None,
percentage: None,
average: None,
base_volume: None,
quote_volume: None,
funding_rate: parse_decimal(data, "r"),
open_interest: None,
index_price: parse_decimal(data, "i").map(Price::from),
mark_price: parse_decimal(data, "p").map(Price::from),
info: value_to_hashmap(data),
});
}
let timestamp = if event == "bookTicker" {
data["E"]
.as_i64()
.or_else(|| data["time"].as_i64())
.unwrap_or_else(|| chrono::Utc::now().timestamp_millis())
} else {
data["C"]
.as_i64()
.or_else(|| data["E"].as_i64())
.or_else(|| data["time"].as_i64())
.unwrap_or_else(|| chrono::Utc::now().timestamp_millis())
};
let last = parse_decimal_multi(data, &["c", "price"]);
Ok(Ticker {
symbol,
timestamp,
datetime: Some(
chrono::DateTime::from_timestamp_millis(timestamp)
.map(|dt| dt.to_rfc3339())
.unwrap_or_default(),
),
high: parse_decimal(data, "h").map(Price::from),
low: parse_decimal(data, "l").map(Price::from),
bid: parse_decimal_multi(data, &["b", "bidPrice"]).map(Price::from),
bid_volume: parse_decimal_multi(data, &["B", "bidQty"]).map(Amount::from),
ask: parse_decimal_multi(data, &["a", "askPrice"]).map(Price::from),
ask_volume: parse_decimal_multi(data, &["A", "askQty"]).map(Amount::from),
vwap: parse_decimal(data, "w").map(Price::from),
open: parse_decimal(data, "o").map(Price::from),
close: last.map(Price::from),
last: last.map(Price::from),
previous_close: parse_decimal(data, "x").map(Price::from),
change: parse_decimal(data, "p").map(Price::from),
percentage: parse_decimal(data, "P"),
average: None,
base_volume: parse_decimal(data, "v").map(Amount::from),
quote_volume: parse_decimal(data, "q").map(Amount::from),
funding_rate: None,
open_interest: None,
index_price: None,
mark_price: None,
info: value_to_hashmap(data),
})
}
pub fn parse_bid_ask(data: &Value) -> Result<ccxt_core::types::BidAsk> {
use ccxt_core::types::BidAsk;
use rust_decimal::Decimal;
let symbol = data["symbol"]
.as_str()
.ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
.to_string();
let formatted_symbol = if symbol.len() >= 6 {
let quote_currencies = ["USDT", "BUSD", "USDC", "BTC", "ETH", "BNB"];
let mut found = false;
let mut formatted = symbol.clone();
for quote in "e_currencies {
if symbol.ends_with(quote) {
let base = &symbol[..symbol.len() - quote.len()];
formatted = format!("{}/{}", base, quote);
found = true;
break;
}
}
if found { formatted } else { symbol.clone() }
} else {
symbol.clone()
};
let bid_price = parse_decimal(data, "bidPrice").unwrap_or(Decimal::ZERO);
let bid_quantity = parse_decimal(data, "bidQty").unwrap_or(Decimal::ZERO);
let ask_price = parse_decimal(data, "askPrice").unwrap_or(Decimal::ZERO);
let ask_quantity = parse_decimal(data, "askQty").unwrap_or(Decimal::ZERO);
let timestamp = data["time"]
.as_i64()
.unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
Ok(BidAsk {
symbol: formatted_symbol,
bid_price,
bid_quantity,
ask_price,
ask_quantity,
timestamp,
})
}
pub fn parse_bids_asks(data: &Value) -> Result<Vec<ccxt_core::types::BidAsk>> {
if let Some(array) = data.as_array() {
array.iter().map(|item| parse_bid_ask(item)).collect()
} else {
Ok(vec![parse_bid_ask(data)?])
}
}
pub fn parse_last_price(data: &Value) -> Result<ccxt_core::types::LastPrice> {
use ccxt_core::types::LastPrice;
use rust_decimal::Decimal;
let symbol = data["symbol"]
.as_str()
.ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
.to_string();
let formatted_symbol = if symbol.len() >= 6 {
let quote_currencies = ["USDT", "BUSD", "USDC", "BTC", "ETH", "BNB"];
let mut found = false;
let mut formatted = symbol.clone();
for quote in "e_currencies {
if symbol.ends_with(quote) {
let base = &symbol[..symbol.len() - quote.len()];
formatted = format!("{}/{}", base, quote);
found = true;
break;
}
}
if found { formatted } else { symbol.clone() }
} else {
symbol.clone()
};
let price = parse_decimal(data, "price").unwrap_or(Decimal::ZERO);
let timestamp = data["time"]
.as_i64()
.unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
let datetime = chrono::DateTime::from_timestamp_millis(timestamp)
.map(|dt| dt.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string())
.unwrap_or_default();
Ok(LastPrice {
symbol: formatted_symbol,
price,
timestamp,
datetime,
})
}
pub fn parse_last_prices(data: &Value) -> Result<Vec<ccxt_core::types::LastPrice>> {
if let Some(array) = data.as_array() {
array.iter().map(|item| parse_last_price(item)).collect()
} else {
Ok(vec![parse_last_price(data)?])
}
}
pub fn parse_ws_bid_ask(data: &Value) -> Result<ccxt_core::types::BidAsk> {
use ccxt_core::types::BidAsk;
use rust_decimal::Decimal;
use rust_decimal::prelude::FromStr;
let symbol = data["s"]
.as_str()
.ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
.to_string();
let bid_price = data["b"]
.as_str()
.and_then(|s| Decimal::from_str(s).ok())
.ok_or_else(|| Error::from(ParseError::missing_field("bid_price")))?;
let bid_quantity = data["B"]
.as_str()
.and_then(|s| Decimal::from_str(s).ok())
.ok_or_else(|| Error::from(ParseError::missing_field("bid_quantity")))?;
let ask_price = data["a"]
.as_str()
.and_then(|s| Decimal::from_str(s).ok())
.ok_or_else(|| Error::from(ParseError::missing_field("ask_price")))?;
let ask_quantity = data["A"]
.as_str()
.and_then(|s| Decimal::from_str(s).ok())
.ok_or_else(|| Error::from(ParseError::missing_field("ask_quantity")))?;
let timestamp = data["E"].as_i64().unwrap_or(0);
Ok(BidAsk {
symbol,
bid_price,
bid_quantity,
ask_price,
ask_quantity,
timestamp,
})
}