use crate::{config::HttpsConfig, prelude::*, Currency, Error, ErrorKind, Price, TradingPair};
use iqhttp::{HttpsClient, Query};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::{
convert::TryFrom,
fmt::{self, Display},
str::FromStr,
};
use tokio::try_join;
pub const API_HOST: &str = "api.binance.com";
pub struct BinanceSource {
https_client: HttpsClient,
}
impl BinanceSource {
pub fn new(config: &HttpsConfig) -> Result<Self, Error> {
let https_client = config.new_client(API_HOST)?;
Ok(Self { https_client })
}
pub async fn approx_price_for_pair(&self, pair: &TradingPair) -> Result<Price, Error> {
if let Ok(symbol_name) = SymbolName::try_from(pair) {
return self.avg_price_for_symbol(symbol_name).await;
}
match pair {
TradingPair(Currency::Luna, Currency::Krw) => {
let (luna_btc, btc_bkrw) = try_join!(
self.avg_price_for_symbol(SymbolName::LunaBtc),
self.avg_price_for_symbol(SymbolName::BtcBkrw)
)?;
Ok(luna_btc * btc_bkrw)
}
TradingPair(Currency::Luna, Currency::Usd) => {
let (luna_busd, luna_usdt) = try_join!(
self.avg_price_for_symbol(SymbolName::LunaBusd),
self.avg_price_for_symbol(SymbolName::LunaUsdt)
)?;
Ok((luna_busd + luna_usdt) / 2)
}
_ => fail!(ErrorKind::Currency, "unsupported Binance pair: {}", pair),
}
}
pub async fn avg_price_for_symbol(&self, symbol_name: SymbolName) -> Result<Price, Error> {
let mut query = Query::new();
query.add("symbol".to_owned(), symbol_name.to_string());
let api_response: AvgPriceResponse = self
.https_client
.get_json("/api/v3/avgPrice", &query)
.await?;
Price::new(api_response.price)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SymbolName {
BtcBkrw,
BtcBusd,
BtcGbp,
BtcEur,
BtcUsdc,
BtcUsdt,
EthBtc,
EthBusd,
EthEur,
EthGbp,
EthUsdc,
EthUsdt,
LunaBnb,
LunaBtc,
LunaBusd,
LunaUsdt,
}
impl Display for SymbolName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
SymbolName::BtcBkrw => "BTCBKRW",
SymbolName::BtcBusd => "BTCBUSD",
SymbolName::BtcGbp => "BTCGBP",
SymbolName::BtcEur => "BTCEUR",
SymbolName::BtcUsdc => "BTCUSDC",
SymbolName::BtcUsdt => "BTCUSDT",
SymbolName::EthBtc => "ETHBTC",
SymbolName::EthBusd => "ETHBUSD",
SymbolName::EthEur => "ETHEUR",
SymbolName::EthGbp => "ETHGBP",
SymbolName::EthUsdc => "ETHUSDC",
SymbolName::EthUsdt => "ETHUSDT",
SymbolName::LunaBnb => "LUNABNB",
SymbolName::LunaBtc => "LUNABTC",
SymbolName::LunaBusd => "LUNABUSD",
SymbolName::LunaUsdt => "LUNAUSDT",
})
}
}
impl FromStr for SymbolName {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_ascii_uppercase().as_ref() {
"BTCBKRW" => Ok(SymbolName::BtcBkrw),
"BTCBUSD" => Ok(SymbolName::BtcBusd),
"BTCGBP" => Ok(SymbolName::BtcGbp),
"BTCEUR" => Ok(SymbolName::BtcEur),
"BTCUSDC" => Ok(SymbolName::BtcUsdc),
"BTCUSDT" => Ok(SymbolName::BtcUsdt),
"ETHBTC" => Ok(SymbolName::EthBtc),
"ETHBUSD" => Ok(SymbolName::EthBusd),
"ETHEUR" => Ok(SymbolName::EthEur),
"ETHGBP" => Ok(SymbolName::EthGbp),
"ETHUSDC" => Ok(SymbolName::EthUsdc),
"ETHUSDT" => Ok(SymbolName::EthUsdt),
"LUNABNB" => Ok(SymbolName::LunaBnb),
"LUNABTC" => Ok(SymbolName::LunaBtc),
"LUNABUSD" => Ok(SymbolName::LunaBusd),
"LUNAUSDT" => Ok(SymbolName::LunaUsdt),
_ => fail!(ErrorKind::Currency, "unknown Binance symbol name: {}", s),
}
}
}
impl TryFrom<&TradingPair> for SymbolName {
type Error = Error;
fn try_from(pair: &TradingPair) -> Result<Self, Error> {
pair.to_string()
.chars()
.filter(|&c| c != '/')
.collect::<String>()
.parse()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AvgPriceResponse {
pub mins: u32,
pub price: Decimal,
}
#[cfg(test)]
mod tests {
use super::BinanceSource;
#[ignore]
#[tokio::test]
async fn avg_price_for_symbol() {
let binance = BinanceSource::new(&Default::default()).unwrap();
let luna_bnb = binance
.avg_price_for_symbol("LUNABNB".parse().unwrap())
.await
.unwrap();
dbg!(luna_bnb);
let luna_btc = binance
.avg_price_for_symbol("LUNABTC".parse().unwrap())
.await
.unwrap();
dbg!(luna_btc);
let luna_busd = binance
.avg_price_for_symbol("LUNABUSD".parse().unwrap())
.await
.unwrap();
dbg!(luna_busd);
let luna_usdt = binance
.avg_price_for_symbol("LUNAUSDT".parse().unwrap())
.await
.unwrap();
dbg!(luna_usdt);
}
#[ignore]
#[tokio::test]
async fn approx_price_for_pair() {
let binance = BinanceSource::new(&Default::default()).unwrap();
let luna_btc = binance
.approx_price_for_pair(&"LUNA/BTC".parse().unwrap())
.await
.unwrap();
dbg!(luna_btc);
let luna_krw = binance
.approx_price_for_pair(&"LUNA/KRW".parse().unwrap())
.await
.unwrap();
dbg!(luna_krw);
let luna_usd = binance
.approx_price_for_pair(&"LUNA/USD".parse().unwrap())
.await
.unwrap();
dbg!(luna_usd);
}
}