tradingbot/src/coin_health_check_team/request_others.rs
Sik 4c4d947b99 Add filtering delist trades (#5)
Co-authored-by: Sik Yoon <younxxxx@gmail.com>
Reviewed-on: http://192.168.1.100:3000/Sik/tradingbot/pulls/5
2024-03-30 19:10:20 +00:00

573 lines
20 KiB
Rust

use crate::database_control::*;
use hex::ToHex;
use hmac_sha256::HMAC;
use reqwest::{Client, ClientBuilder, Response};
use rust_decimal::{prelude::FromPrimitive, prelude::ToPrimitive, Decimal, RoundingStrategy};
use serde::Deserialize;
use serde_json::Value;
use sqlx::{Error, FromRow};
use std::borrow::{Borrow, BorrowMut};
use std::collections::HashSet;
use std::sync::Arc;
use std::collections::HashMap;
use tokio::{join, sync::Mutex, time::*};
use log;
#[derive(Debug, Clone)]
pub struct TradeFee {
pub makercommission: Decimal,
pub takercommission: Decimal,
}
impl TradeFee {
fn new() -> TradeFee {
let a = TradeFee {
makercommission: Decimal::new(0, 8),
takercommission: Decimal::new(0, 8),
};
a
}
}
#[derive(Debug, FromRow, Clone)]
pub struct ExchangeInfo {
pub stepsize: Decimal,
pub ticksize: Decimal,
pub base_asset_precision: u32,
pub base_commission_precision: u32,
pub quote_asset_precision: u32,
pub quote_commission_precision: u32,
}
impl ExchangeInfo {
fn new() -> ExchangeInfo {
let a = ExchangeInfo {
stepsize: Decimal::new(0, 8),
ticksize: Decimal::new(0, 8),
base_asset_precision: 0,
base_commission_precision: 0,
quote_asset_precision: 0,
quote_commission_precision: 0,
};
a
}
}
// request all coin price (/api, Weight(IP) 2)
// request_all_coin_price -> de_all_coin_price_json -> store_coin_price_db
// pub async fn request_all_coin_price(client: &Client, price_vec: &Arc<Mutex<Vec<(String, f64)>>>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
pub async fn request_all_coin_price(
client: &Client,
price_vec: &mut HashMap<String, f64>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let url = "https://api.binance.com/api/v3/ticker/price";
let mut response = client.get(url).send().await?;
let mut body = response.text_with_charset("utf-8").await?;
de_all_coin_price_json(&body, price_vec).await?;
Ok(())
}
async fn de_all_coin_price_json(
body: &String,
price_vec: &mut HashMap<String, f64>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let v: Value = serde_json::from_str(body.as_str())?;
let into_vec = v.as_array();
if into_vec == None {
return Err("Err")?;
}
let mut object_map = &serde_json::map::Map::new();
let mut symbol = String::new();
let mut price = 0.0;
for element in into_vec.unwrap() {
object_map = element.as_object().unwrap();
let mut object_map_iter = object_map.iter();
for element in object_map_iter {
match element.0.as_str() {
"symbol" => symbol = element.1.as_str().unwrap().to_string(),
"price" => price = element.1.as_str().unwrap().parse::<f64>().unwrap(),
_ => {
log::error!("Elements in body msg are changed. Please update both your coinprices table and vectors.");
}
}
price_vec.insert(symbol.clone(), price);
}
}
Ok(())
}
// request trade fee (/sapi, Weight(IP) 1)
// request_trade_fee -> deserialization_trade_fee_json -> save_db_trade_fee
pub async fn request_trade_fee(
api_key: &str,
secret_key: &str,
local_epoch: u128,
difference_epoch: i64,
client: &Client,
tradefee_vec: &mut HashMap<String, TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut base_url = String::from("https://api.binance.com/sapi/v1/asset/tradeFee?");
// local 시간이 server 시간보다 너무 앞서거나 뒤쳐지면 USER_DATA를 요청할 수 없으므로 local과 server 의 시간 차이만큼을 local에서 빼어 보정한 뒤
// 이를 timestamp로 사용한다.
let mut timestamp;
if difference_epoch >= 0 {
timestamp = (local_epoch - difference_epoch as u128).to_string();
} else if difference_epoch < 0 {
timestamp = (local_epoch - (difference_epoch * -1) as u128).to_string();
} else {
timestamp = local_epoch.to_string();
}
let recv_window_size = "20000".to_string(); // default: 5,000ms, Max: 60,000ms
let mut query_string = String::from("&timestamp=");
query_string.push_str(&timestamp);
query_string.push_str("&recvWindow=");
query_string.push_str(&recv_window_size);
let signature = HMAC::mac(&query_string.as_bytes(), secret_key.as_bytes());
query_string.push_str("&signature=");
base_url.push_str(&query_string);
base_url.push_str(signature.encode_hex::<String>().as_str());
let mut response = client
.get(&base_url)
.header("X-MBX-APIKEY", api_key)
.send()
.await?;
while !response.status().is_success() {
sleep(Duration::from_secs(5)).await;
response = client
.get(&base_url)
.header("X-MBX-APIKEY", api_key)
.send()
.await?;
}
let mut body = response.text_with_charset("utf-8").await;
match body {
Ok(T) => {
de_trade_fee_json(&T, tradefee_vec).await?;
// println!("tradefee 완료");
}
Err(E) => {
log::warn!("request_trade_fee body failed!: {:?}", E);
}
}
Ok(())
}
async fn de_trade_fee_json(
body: &String,
tradefee_map: &mut HashMap<String, TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let v: Value = serde_json::from_str(body.as_str())?;
let into_vec = v.as_array();
if into_vec == None {
return Err("Err")?;
}
let mut object_map = &serde_json::map::Map::new();
let mut tradefee_map_build: HashMap<String, TradeFee> = HashMap::new();
let mut symbol = String::new();
let mut tradefee_data = TradeFee::new();
for element in into_vec.unwrap() {
object_map = element.as_object().unwrap();
let mut object_map_iter = object_map.iter();
for element in object_map_iter {
match element.0.as_str() {
"symbol" => symbol = element.1.as_str().unwrap().to_string(),
"makerCommission" => {
tradefee_data.makercommission =
rust_decimal::prelude::FromStr::from_str(element.1.as_str().unwrap())
.unwrap()
}
"takerCommission" => {
tradefee_data.takercommission =
rust_decimal::prelude::FromStr::from_str(element.1.as_str().unwrap())
.unwrap()
}
_ => {
log::error!("Elements in body msg are changed. Please update both your tradefees table and vectors.");
}
}
}
tradefee_map_build.insert(symbol.clone(), tradefee_data.clone());
}
*tradefee_map = tradefee_map_build;
Ok(())
}
// request 24hr Ticker Price Change Statistics (Weight(IP) 1 for a single symbol, 40 when the symbol parameter is omitted)
// request_24hr_ticker_price_change_statistics -> deserialization_24hr_ticker_price_change_statistics_json ->
pub async fn request_24hr_ticker_price_change_statistics(
client: &Client,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut response = client
.get("https://api.binance.com/api/v3/ticker/24hr")
.send()
.await;
match response {
Ok(T) => {
let mut body = T.text_with_charset("utf-8").await;
match body {
Ok(T) => {
de_24h_change_json(&T).await?;
// println!("24h change 완료");
}
Err(E) => {
log::warn!(
"request_24hr_ticker_price_change_statistics body failed!: {:?}",
E
);
}
}
}
Err(E) => {
log::warn!(
"request_24hr_ticker_price_change_statistics response failed!: {:?}",
E
);
}
}
Ok(())
}
async fn de_24h_change_json(body: &String) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let v: Value = serde_json::from_str(body.as_str())?;
let mut into_vec = v.as_array();
if into_vec == None {
return Err("Err")?;
}
let mut object_map = &serde_json::map::Map::new();
let columns = vec![
"symbol",
"priceChange",
"priceChangePercent",
"weightedAvgPrice",
"prevClosePrice",
"lastPrice",
"lastQty",
"bidPrice",
"bidQty",
"askPrice",
"askQty",
"openPrice",
"highPrice",
"lowPrice",
"volume",
"quoteVolume",
"openTime",
"closeTime",
"firstId",
"lastId",
"count",
];
let mut value_wrapper: Vec<Vec<String>> = Vec::new();
for element in into_vec.unwrap() {
object_map = element.as_object().unwrap();
let mut value_vec = vec![String::new(); 21];
let mut object_map_iter = object_map.iter();
for element in object_map_iter {
match element.0.as_str() {
"symbol" => value_vec[0] = element.1.as_str().unwrap().to_string(),
"priceChange" => value_vec[1] = element.1.as_str().unwrap().to_string(),
"priceChangePercent" => value_vec[2] = element.1.as_str().unwrap().to_string(),
"weightedAvgPrice" => value_vec[3] = element.1.as_str().unwrap().to_string(),
"prevClosePrice" => value_vec[4] = element.1.as_str().unwrap().to_string(),
"lastPrice" => value_vec[5] = element.1.as_str().unwrap().to_string(),
"lastQty" => value_vec[6] = element.1.as_str().unwrap().to_string(),
"bidPrice" => value_vec[7] = element.1.as_str().unwrap().to_string(),
"bidQty" => value_vec[8] = element.1.as_str().unwrap().to_string(),
"askPrice" => value_vec[9] = element.1.as_str().unwrap().to_string(),
"askQty" => value_vec[10] = element.1.as_str().unwrap().to_string(),
"openPrice" => value_vec[11] = element.1.as_str().unwrap().to_string(),
"highPrice" => value_vec[12] = element.1.as_str().unwrap().to_string(),
"lowPrice" => value_vec[13] = element.1.as_str().unwrap().to_string(),
"volume" => value_vec[14] = element.1.as_str().unwrap().to_string(),
"quoteVolume" => value_vec[15] = element.1.as_str().unwrap().to_string(),
"openTime" => value_vec[16] = element.1.as_i64().unwrap().to_string(),
"closeTime" => value_vec[17] = element.1.as_i64().unwrap().to_string(),
"firstId" => value_vec[18] = element.1.as_i64().unwrap().to_string(),
"lastId" => value_vec[19] = element.1.as_i64().unwrap().to_string(),
"count" => value_vec[20] = element.1.as_i64().unwrap().to_string(),
_ => {
log::error!("Elements in body msg are changed. Please update both your 24h_change table and vectors.");
}
}
}
value_wrapper.push(value_vec);
}
store_24h_change_db(columns, value_wrapper).await?;
Ok(())
}
async fn store_24h_change_db(
columns: Vec<&str>,
value_wrapper: Vec<Vec<String>>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let table_name = String::from("all_24h_change");
delete_all_rows(&table_name).await?;
insert_records(&table_name, &columns, &value_wrapper).await?;
Ok(())
}
// request exchange information. (/api, Weight(IP) 10)
pub async fn request_exchange_infomation(
client: &Client,
exchange_info_map: &mut HashMap<String, ExchangeInfo>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// building URL and API-keys
let mut url = String::new();
url.push_str("https://api.binance.com");
let endpoint_url = "/api/v3/exchangeInfo";
url.push_str(endpoint_url);
let mut res = client.get(&url).send().await?;
while !res.status().is_success() {
sleep(Duration::from_secs(5)).await;
res = client.get(&url).send().await?;
}
let body = res.text_with_charset("utf-8").await?;
// deserialize JSON
let v: Value = serde_json::from_str(body.as_str())?;
let mut into_vec = v.as_object();
if into_vec == None {
return Err("Err")?;
}
let mut symbol = String::new();
let mut exchange_info = ExchangeInfo::new();
let mut data_map_temp: HashMap<String, ExchangeInfo> = HashMap::new();
for element in into_vec.unwrap() {
if element.0.contains("symbols") {
for element in element.1.as_array().unwrap() {
if element.is_object() {
if element
.get("symbol")
.unwrap()
.as_str()
.unwrap()
.ends_with("USDT")
{
symbol =
(element.get("symbol").unwrap().as_str().unwrap().to_string());
exchange_info.base_asset_precision =
(element.get("baseAssetPrecision").unwrap().as_u64().unwrap()) as u32;
exchange_info.base_commission_precision = (element
.get("baseCommissionPrecision")
.unwrap()
.as_u64()
.unwrap())
as u32;
exchange_info.quote_asset_precision = (element
.get("quoteAssetPrecision")
.unwrap()
.as_u64()
.unwrap())
as u32;
exchange_info.quote_commission_precision = (element
.get("quoteCommissionPrecision")
.unwrap()
.as_u64()
.unwrap())
as u32;
for element in element.get("filters").unwrap().as_array().unwrap() {
if element
.as_object()
.unwrap()
.get("filterType")
.unwrap()
.as_str()
.unwrap()
.starts_with("LOT_SIZE")
{
exchange_info.stepsize = rust_decimal::prelude::FromStr::from_str(
element.get("stepSize").unwrap().as_str().unwrap(),
)
.unwrap();
} else if element
.as_object()
.unwrap()
.get("filterType")
.unwrap()
.as_str()
.unwrap()
.starts_with("PRICE_FILTER")
{
exchange_info.ticksize = rust_decimal::prelude::FromStr::from_str(
element.get("tickSize").unwrap().as_str().unwrap(),
)
.unwrap();
}
}
data_map_temp.insert(symbol.clone(), exchange_info.clone());
}
}
}
}
}
*exchange_info_map = data_map_temp;
Ok(())
}
pub async fn request_delist_symbols(
api_key: &str,
secret_key: &str,
local_epoch: u128,
difference_epoch: i64,
client: &Client
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut base_url = String::from("https://api.binance.com/sapi/v1/spot/delist-schedule?");
// local 시간이 server 시간보다 너무 앞서거나 뒤쳐지면 USER_DATA를 요청할 수 없으므로 local과 server 의 시간 차이만큼을 local에서 빼어 보정한 뒤
// 이를 timestamp로 사용한다.
let mut timestamp;
if difference_epoch >= 0 {
timestamp = (local_epoch - difference_epoch as u128).to_string();
} else if difference_epoch < 0 {
timestamp = (local_epoch - (difference_epoch * -1) as u128).to_string();
} else {
timestamp = local_epoch.to_string();
}
let recv_window_size = "20000".to_string(); // default: 5,000ms, Max: 60,000ms
let mut query_string = String::from("&timestamp=");
query_string.push_str(&timestamp);
query_string.push_str("&recvWindow=");
query_string.push_str(&recv_window_size);
let signature = HMAC::mac(&query_string.as_bytes(), secret_key.as_bytes());
query_string.push_str("&signature=");
base_url.push_str(&query_string);
base_url.push_str(signature.encode_hex::<String>().as_str());
let mut response = client
.get(&base_url)
.header("X-MBX-APIKEY", api_key)
.send()
.await?;
while !response.status().is_success() {
sleep(Duration::from_secs(5)).await;
response = client
.get(&base_url)
.header("X-MBX-APIKEY", api_key)
.send()
.await?;
}
let mut body = response.text_with_charset("utf-8").await;
match body {
Ok(T) => {
de_deilst_symbol_json(&T).await?;
}
Err(E) => {
log::warn!("request_delist_symbols body failed!: {:?}", E);
}
}
Ok(())
}
async fn de_deilst_symbol_json(
body: &String,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let v: Value = serde_json::from_str(body.as_str())?;
let into_vec = v.as_array();
if into_vec == None {
return Err("Err")?;
}
let mut object_map = &serde_json::map::Map::new();
let mut delist_hashset: HashSet<String> = HashSet::new();
let mut symbol = String::new();
for element in into_vec.unwrap() {
object_map = element.as_object().unwrap();
let mut object_map_iter = object_map.iter();
for element in object_map_iter {
match element.0.as_str() {
"delistTime" => {
},
"symbols" => {
if let Some(array) = element.1.as_array() {
for delist_symbol in array {
if let Some(symbol) = delist_symbol.as_str() {
if symbol.ends_with("USDT") && !delist_hashset.contains(symbol) {
delist_hashset.insert(symbol.to_string());
}
}
}
}
},
_ => {
log::error!("Elements in body msg are changed. Please update both your delist table and vectors.");
}
}
}
}
if delist_hashset.len() != 0 {
#[derive(Debug, FromRow)]
struct Symbols {
symbol: String,
}
let table_name = String::from("banned_usdt_trades");
let column_name = String::from("symbol");
let condition = None;
let mut symbols = Symbols {
symbol: String::new(),
};
let mut select_result = try_select_record(&table_name, &column_name, &condition, &symbols)
.await
.unwrap();
let mut banned_usdt_trades_set: HashSet<String> = HashSet::new();
for element in select_result {
banned_usdt_trades_set.insert(element.symbol.clone());
}
let insert_column = vec!["symbol"];
for element in &delist_hashset {
if !banned_usdt_trades_set.contains(element) {
let insert_values = vec![element.clone()];
insert_one_record(&table_name, &insert_column, &insert_values)
.await
.unwrap();
}
}
}
Ok(())
}