use futures_util::StreamExt;
use hex;
use hmac::{Hmac, Mac};
use reqwest::Client;
use sha2::Sha256;
use std::collections::HashMap;
use std::time::Duration;
use tokio_tungstenite::connect_async;
use tungstenite::Message;
use crate::tool::time::get_timestamp;
use crate::types::account::AccountInfo;
use crate::types::info::BinanceInfo;
use crate::types::kline::KlineData;
use crate::types::order::{OrderBook, OrderResponse, OrderType};
use crate::types::price::PriceData;
use crate::types::time::{KlineInterval, TimeInForce};
use crate::types::trade::{Position, TradeSide};
type HmacSha256 = Hmac<Sha256>;
const BASE_URL: &str = "https://fapi.binance.com";
const WS_URL: &str = "wss://fstream.binance.com";
const TEST_BASE_URL: &str = "https://testnet.binancefuture.com";
const TEST_WS_URL: &str = "wss://stream.binancefuture.com";
const TIME_OUT: u64 = 10;
pub struct BinanceClient {
http_client: Client,
base_url: String,
ws_url: String,
api_key: Option<String>,
secret_key: Option<String>,
}
impl BinanceClient {
pub fn new() -> Result<Self, String> {
match Client::builder()
.timeout(Duration::from_secs(TIME_OUT))
.build()
{
Ok(client) => {
return Ok(Self {
http_client: client,
base_url: BASE_URL.to_string(),
ws_url: WS_URL.to_string(),
api_key: None,
secret_key: None,
});
}
Err(e) => {
return Err(format!("{}", e));
}
}
}
pub fn auth(mut self, api_key: String, secret_key: String) -> Self {
self.api_key = Some(api_key);
self.secret_key = Some(secret_key);
self
}
pub fn testnet(mut self) -> Self {
self.base_url = TEST_BASE_URL.to_string();
self.ws_url = TEST_WS_URL.to_string();
self
}
fn create_signature(&self, query_string: &str) -> Result<String, ()> {
let secret_key = self.secret_key.as_ref().ok_or_else(|| {}).unwrap();
let mut mac = HmacSha256::new_from_slice(secret_key.as_bytes())
.map_err(|e| {})
.unwrap();
mac.update(query_string.as_bytes());
let result = mac.finalize();
let signature = hex::encode(result.into_bytes());
Ok(signature)
}
async fn send_public_request<T: serde::de::DeserializeOwned>(
&self,
method: &str,
endpoint: &str,
params: Option<HashMap<&str, String>>,
) -> Result<T, String> {
let url = format!("{}{}", self.base_url, endpoint);
let mut request = self.http_client.request(method.parse().unwrap(), &url);
if let Some(params) = params {
request = request.query(¶ms);
}
let response = request.send().await.unwrap();
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap();
return Err(format!("http error {}: {}", status, error_text));
}
let result: T = response.json().await.unwrap();
Ok(result)
}
async fn send_private_request<T: serde::de::DeserializeOwned>(
&self,
method: &str,
endpoint: &str,
mut params: HashMap<&str, String>,
) -> Result<T, String> {
let api_key = self.api_key.as_ref().ok_or_else(|| {}).unwrap();
params.insert("timestamp", get_timestamp().unwrap().to_string());
let query_string = params
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<String>>()
.join("&");
let signature = self.create_signature(&query_string).unwrap();
let url = format!(
"{}{}?{}&signature={}",
self.base_url, endpoint, query_string, signature
);
let response = self
.http_client
.request(method.parse().unwrap(), &url)
.header("X-MBX-APIKEY", api_key)
.send()
.await
.unwrap();
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap();
return Err(format!("http error {:?}: {:?}", status, error_text));
}
let result: T = response.json().await.unwrap();
Ok(result)
}
pub async fn get_price_by_symbol(&self, symbol: &str) -> Result<f64, ()> {
let endpoint = "/fapi/v1/ticker/price";
let mut params = HashMap::new();
params.insert("symbol", symbol.to_string());
let price_data: PriceData = self
.send_public_request("GET", endpoint, Some(params))
.await
.unwrap();
price_data.price.parse().map_err(|e| {})
}
pub async fn place_limit_order(
&self,
symbol: &str,
trade_side: TradeSide,
quantity: f64,
price: f64,
time_in_force: TimeInForce,
) -> Result<OrderResponse, ()> {
let endpoint = "/fapi/v1/order";
let mut params = HashMap::new();
params.insert("symbol", symbol.to_string());
params.insert("side", format!("{:?}", trade_side));
params.insert("type", "LIMIT".to_string());
params.insert("quantity", quantity.to_string());
params.insert("price", price.to_string());
params.insert("timeInForce", format!("{:?}", time_in_force));
let order_response: OrderResponse = self
.send_private_request("POST", endpoint, params)
.await
.unwrap();
Ok(order_response)
}
pub async fn place_current_market_price_order(
&self,
symbol: &str,
trade_side: TradeSide,
quantity: f64,
) -> Result<OrderResponse, ()> {
let endpoint = "/fapi/v1/order";
let mut params = HashMap::new();
params.insert("symbol", symbol.to_string());
params.insert("side", format!("{:?}", trade_side));
params.insert("type", "MARKET".to_string());
params.insert("quantity", quantity.to_string());
let order_response: OrderResponse = self
.send_private_request("POST", endpoint, params)
.await
.unwrap();
Ok(order_response)
}
pub async fn open_long_position(
&self,
symbol: &str,
quantity: f64,
price: f64,
order_type: OrderType,
) -> Result<OrderResponse, ()> {
let endpoint = "/fapi/v1/order";
let mut params = HashMap::new();
params.insert("symbol", symbol.to_string());
params.insert("side", "BUY".to_string());
params.insert("type", format!("{:?}", order_type));
params.insert("quantity", quantity.to_string());
if matches!(order_type, OrderType::LIMIT) {
params.insert("price", price.to_string());
params.insert("timeInForce", "GTC".to_string());
}
let order_response: OrderResponse = self
.send_private_request("POST", endpoint, params)
.await
.unwrap();
Ok(order_response)
}
pub async fn open_short_position(
&self,
symbol: &str,
quantity: f64,
price: f64,
order_type: OrderType,
) -> Result<OrderResponse, ()> {
let endpoint = "/fapi/v1/order";
let mut params = HashMap::new();
params.insert("symbol", symbol.to_string());
params.insert("side", "SELL".to_string());
params.insert("type", format!("{:?}", order_type));
params.insert("quantity", quantity.to_string());
if matches!(order_type, OrderType::LIMIT) {
params.insert("price", price.to_string());
params.insert("timeInForce", "GTC".to_string());
}
let order_response: OrderResponse = self
.send_private_request("POST", endpoint, params)
.await
.unwrap();
Ok(order_response)
}
pub async fn get_order_book_depth(
&self,
symbol: &str,
limit: Option<u32>,
) -> Result<OrderBook, ()> {
let endpoint = "/fapi/v1/depth";
let mut params = HashMap::new();
params.insert("symbol", symbol.to_string());
params.insert("limit", limit.unwrap_or(100).to_string());
let order_book: OrderBook = self
.send_public_request("GET", endpoint, Some(params))
.await
.unwrap();
Ok(order_book)
}
pub async fn get_account_info(&self) -> Result<AccountInfo, ()> {
let endpoint = "/fapi/v2/account";
let params = HashMap::new();
let account_info: AccountInfo = self
.send_private_request("GET", endpoint, params)
.await
.unwrap();
Ok(account_info)
}
pub async fn get_current_positions(&self, symbol: Option<&str>) -> Result<Vec<Position>, ()> {
let account_info = self.get_account_info().await?;
if let Some(sym) = symbol {
Ok(account_info
.positions
.into_iter()
.filter(|p| p.symbol == sym && p.position_amt != "0")
.collect())
} else {
Ok(account_info
.positions
.into_iter()
.filter(|p| p.position_amt != "0")
.collect())
}
}
pub async fn get_open_orders(&self, symbol: Option<&str>) -> Result<Vec<OrderResponse>, ()> {
let endpoint = "/fapi/v1/openOrders";
let mut params = HashMap::new();
if let Some(sym) = symbol {
params.insert("symbol", sym.to_string());
}
let orders: Vec<OrderResponse> = self
.send_private_request("GET", endpoint, params)
.await
.unwrap();
Ok(orders)
}
pub async fn cancel_order(&self, symbol: &str, order_id: u64) -> Result<OrderResponse, ()> {
let endpoint = "/fapi/v1/order";
let mut params = HashMap::new();
params.insert("symbol", symbol.to_string());
params.insert("orderId", order_id.to_string());
let order_response: OrderResponse = self
.send_private_request("DELETE", endpoint, params)
.await
.unwrap();
Ok(order_response)
}
pub async fn set_leverage(&self, symbol: &str, leverage: u32) -> Result<serde_json::Value, ()> {
let endpoint = "/fapi/v1/leverage";
let mut params = HashMap::new();
params.insert("symbol", symbol.to_string());
params.insert("leverage", leverage.to_string());
let response: serde_json::Value = self
.send_private_request("POST", endpoint, params)
.await
.unwrap();
Ok(response)
}
pub async fn get_klines_data(
&self,
symbol: &str,
interval: KlineInterval,
limit: Option<u32>,
) -> Result<Vec<KlineData>, ()> {
let endpoint = "/fapi/v1/klines";
let mut params = HashMap::new();
params.insert("symbol", symbol.to_string());
params.insert("interval", format!("{:?}", interval));
params.insert("limit", limit.unwrap_or(500).to_string());
let klines: Vec<Vec<serde_json::Value>> = self
.send_public_request("GET", endpoint, Some(params))
.await
.unwrap();
let result: Vec<KlineData> = klines
.into_iter()
.filter_map(|k| {
Some(KlineData {
open_time: k.get(0)?.as_u64()?,
open: k.get(1)?.as_str()?.to_string(),
high: k.get(2)?.as_str()?.to_string(),
low: k.get(3)?.as_str()?.to_string(),
close: k.get(4)?.as_str()?.to_string(),
volume: k.get(5)?.as_str()?.to_string(),
close_time: k.get(6)?.as_u64()?,
quote_asset_volume: k.get(7)?.as_str()?.to_string(),
number_of_trades: k.get(8)?.as_u64()?,
taker_buy_base_asset_volume: k.get(9)?.as_str()?.to_string(),
taker_buy_quote_asset_volume: k.get(10)?.as_str()?.to_string(),
ignore: k.get(11)?.as_str()?.to_string(),
})
})
.collect();
Ok(result)
}
pub async fn ws_subscription_price(&self, symbols: Vec<String>) -> Result<(), ()> {
let stream_names: Vec<String> = symbols
.into_iter()
.map(|symbol| format!("{}@ticker", symbol.to_lowercase()))
.collect();
let stream_url = format!("{}/stream?streams={}", self.ws_url, stream_names.join("/"));
let (ws_stream, _) = connect_async(&stream_url).await.unwrap();
let (mut write, mut read) = ws_stream.split();
tokio::spawn(async move {
while let Some(message) = read.next().await {
match message {
Ok(Message::Text(text)) => {
if let Ok(data) = serde_json::from_str::<serde_json::Value>(&text) {
if let Some(stream) = data.get("stream") {
if stream.as_str().unwrap().contains("ticker") {
if let Some(ticker_data) = data.get("data") {
let _symbol = ticker_data
.get("s")
.and_then(|s| s.as_str())
.unwrap_or("Unknown");
let _price = ticker_data
.get("c")
.and_then(|c| c.as_str())
.unwrap_or("0");
}
}
}
}
}
Ok(Message::Ping(_)) => {}
Ok(Message::Close(_)) => {
break;
}
Err(_e) => {
break;
}
_ => {}
}
}
});
Ok(())
}
pub async fn get_binance_info(&self) -> Result<BinanceInfo, ()> {
let endpoint = "/fapi/v1/exchangeInfo";
let exchange_info: BinanceInfo = self
.send_public_request("GET", endpoint, None)
.await
.unwrap();
Ok(exchange_info)
}
}