use std::collections::{BTreeSet, HashSet};
use super::utils::{http_get, normalize_pair_with_quotes};
use crypto_market_type::MarketType;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
static SPOT_QUOTES: Lazy<HashSet<String>> = Lazy::new(|| {
let mut set: HashSet<String> = vec![
"ARS", "AUD", "BIDR", "BKRW", "BNB", "BRL", "BTC", "BUSD", "BVND", "DAI", "DOGE", "DOT",
"ETH", "EUR", "GBP", "GYEN", "IDRT", "NGN", "PAX", "PLN", "RON", "RUB", "TRX", "TRY",
"TUSD", "UAH", "USDC", "USDP", "USDS", "USDT", "UST", "VAI", "XRP", "ZAR",
]
.into_iter()
.map(|x| x.to_string())
.collect();
let from_online = fetch_spot_quotes();
set.extend(from_online);
set
});
#[derive(Serialize, Deserialize)]
struct BinanceResponse {
symbols: Vec<SpotMarket>,
}
#[derive(Serialize, Deserialize)]
#[allow(non_snake_case)]
struct SpotMarket {
quoteAsset: String,
#[serde(flatten)]
extra: HashMap<String, Value>,
}
fn fetch_spot_quotes() -> BTreeSet<String> {
if let Ok(txt) = http_get("https://api.binance.com/api/v3/exchangeInfo") {
let resp = serde_json::from_str::<BinanceResponse>(&txt).unwrap();
resp.symbols.into_iter().map(|m| m.quoteAsset).collect::<BTreeSet<String>>()
} else {
BTreeSet::new()
}
}
pub(crate) fn normalize_pair(symbol: &str) -> Option<String> {
if let Some(base) = symbol.strip_suffix("USD_PERP") {
Some(format!("{base}/USD"))
} else if symbol.ends_with("-P") || symbol.ends_with("-C") {
let pos = symbol.find('-').unwrap();
let base = &symbol[..pos];
Some(format!("{base}/USDT"))
} else if symbol.len() > 7 && (symbol[(symbol.len() - 6)..]).parse::<i64>().is_ok() {
let remove_date = &symbol[..symbol.len() - 7];
if remove_date.ends_with("USDT") {
let base = remove_date.strip_suffix("USDT").unwrap();
Some(format!("{base}/USDT"))
} else if remove_date.ends_with("USD") {
let base = remove_date.strip_suffix("USD").unwrap();
Some(format!("{base}/USD"))
} else {
panic!("Unsupported symbol {symbol}");
}
} else {
let quotes = &(*SPOT_QUOTES);
normalize_pair_with_quotes(symbol, quotes)
}
}
pub(crate) fn get_market_type(symbol: &str, is_spot: Option<bool>) -> MarketType {
if symbol.ends_with("USD_PERP") {
MarketType::InverseSwap
} else if symbol.ends_with("-P") || symbol.ends_with("-C") {
MarketType::EuropeanOption
} else if symbol.len() > 7 && (symbol[(symbol.len() - 6)..]).parse::<i64>().is_ok() {
let remove_date = &symbol[..symbol.len() - 7];
if remove_date.ends_with("USDT") {
MarketType::LinearFuture
} else if remove_date.ends_with("USD") {
MarketType::InverseFuture
} else {
MarketType::Unknown
}
} else if let Some(is_spot) = is_spot {
if is_spot { MarketType::Spot } else { MarketType::LinearSwap }
} else {
MarketType::LinearSwap
}
}
#[cfg(test)]
mod tests {
use super::fetch_spot_quotes;
#[test]
fn spot_quotes() {
let mut map = fetch_spot_quotes();
for coin in super::SPOT_QUOTES.iter() {
map.insert(coin.clone());
}
for quote in map.iter() {
println!("\"{quote}\",");
}
}
#[test]
fn normalize_pair() {
assert_eq!("BDOT/DOT", super::normalize_pair("BDOTDOT").unwrap());
assert_eq!("ETH/PLN", super::normalize_pair("ETHPLN").unwrap());
assert_eq!("USDT/ARS", super::normalize_pair("USDTARS").unwrap());
}
}