use super::{hmac_signature, REAL, SIMUL, TEST, RUNNING_MODE, FUTURES_URL, FUTURES_URL_TEST, API_KEY, API_KEY_TESTNET, FuturesExchangeInfo, PositionCoinList, FuturesTradeFee}; use std::collections::HashMap; use rust_decimal::{Decimal, RoundingStrategy}; use crate::database_control::*; use crate::decimal_funcs::*; use rust_decimal_macros::dec; use rust_decimal::prelude::ToPrimitive; use serde_json::Value; use reqwest::{Client, ClientBuilder}; pub async fn get_tradefee_balance(future_trade_fee: &mut FuturesTradeFee, client: &Client) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); unsafe { if RUNNING_MODE == TEST { url.push_str(FUTURES_URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else { url.push_str(FUTURES_URL); api_key = API_KEY.to_string(); } } let endpoint_url = "/fapi/v2/account?"; url.push_str(endpoint_url); let mut url_build = String::new(); hmac_signature(&mut url_build).await; url.push_str(&url_build); let res = client .get(&url) .header("X-MBX-APIKEY", api_key) .send() .await?; let body = res.text_with_charset("utf-8").await?; // deserialize JSON let v = serde_json::from_str::(body.as_str()); match v { Ok(T) => { if let (Some(fee_tier), Some(available_balance)) = (T.get("feeTier"), T.get("availableBalance")) { let update_table_name = String::from("future_available_balance"); let update_values = vec![ (String::from("available_usdt"), available_balance.as_str().unwrap().to_string()), ]; let update_condition = vec![(String::from("id"), String::from("1"))]; update_record2(&update_table_name, &update_values, &update_condition) .await .unwrap(); match fee_tier.as_number().unwrap().as_i64() { Some(0) => { // Regular User future_trade_fee.user_level = Some(0); future_trade_fee.maker_fee_percent = dec!(0.0200); future_trade_fee.taker_fee_percent = dec!(0.0500); }, Some(1) => { // VIP1 future_trade_fee.user_level = Some(1); future_trade_fee.maker_fee_percent = dec!(0.0160); future_trade_fee.taker_fee_percent = dec!(0.0400); }, Some(2) => {// VIP 2 future_trade_fee.user_level = Some(2); future_trade_fee.maker_fee_percent = dec!(0.0140); future_trade_fee.taker_fee_percent = dec!(0.0350); }, Some(3) => {// VIP 3 future_trade_fee.user_level = Some(3); future_trade_fee.maker_fee_percent = dec!(0.0120); future_trade_fee.taker_fee_percent = dec!(0.0320); }, Some(4) => {// VIP 4 future_trade_fee.user_level = Some(4); future_trade_fee.maker_fee_percent = dec!(0.0100); future_trade_fee.taker_fee_percent = dec!(0.0300); }, Some(5) => {// VIP 5 future_trade_fee.user_level = Some(5); future_trade_fee.maker_fee_percent = dec!(0.0080); future_trade_fee.taker_fee_percent = dec!(0.0270); }, Some(6) => {// VIP 6 future_trade_fee.user_level = Some(6); future_trade_fee.maker_fee_percent = dec!(0.0060); future_trade_fee.taker_fee_percent = dec!(0.0250); }, Some(7) => {// VIP 7 future_trade_fee.user_level = Some(7); future_trade_fee.maker_fee_percent = dec!(0.0040); future_trade_fee.taker_fee_percent = dec!(0.0220); }, Some(8) => {// VIP 8 future_trade_fee.user_level = Some(8); future_trade_fee.maker_fee_percent = dec!(0.0020); future_trade_fee.taker_fee_percent = dec!(0.0200); }, Some(9) => {// VIP 9 future_trade_fee.user_level = Some(9); future_trade_fee.maker_fee_percent = dec!(0.0000); future_trade_fee.taker_fee_percent = dec!(0.0170); }, Some(_) => {}, None => {} } log::info!("fee_tier: {:?}. available_balance: {:?}", fee_tier, available_balance); } else { log::error!("Endpoint(/fapi/v1/leverage?) output changed!"); return Err("Err")?; } } Err(e) => { log::warn!("account information failed!: {}, {}", body, e); } } Ok(()) } pub async fn update_price_of_filled_positions( coin_price_map: &HashMap, exchange_info_map: &HashMap, futures_trade_fee: &FuturesTradeFee, ) -> Result<(), Box> { let filled_buy_orders = select_filled_positions().await?; if !filled_buy_orders.is_empty() { // 심볼들을 5개씩 청스로 나누어 테스크를 생성하고 각 태스크 별로 병렬로 처리한다. // update real-time current price to each record through chunks let chunks: std::slice::Chunks<'_, PositionCoinList> = filled_buy_orders.chunks(5); let mut task_vec = Vec::new(); for chunk in chunks { let chunk_vec = chunk.to_vec(); let coin_price_vec_c = coin_price_map.clone(); let exchange_info_vec_c = exchange_info_map.clone(); let futures_trade_fee_map_c = futures_trade_fee.clone(); task_vec.push(tokio::spawn(async move { update_repeat_task( chunk_vec, &coin_price_vec_c, &exchange_info_vec_c, &futures_trade_fee_map_c, ) .await; })); } } Ok(()) } async fn update_repeat_task( buy_ordered_coin_vec: Vec, coin_price_map: &HashMap, exchange_info_map: &HashMap, futures_trade_fee: &FuturesTradeFee, ) -> Result<(), Box> { let update_table_name = String::from("future_ordered_coin_list"); let mut update_values: Vec<(String, String)> = Vec::new(); let mut update_condition: Vec<(String, String)> = Vec::new(); let mut price = Decimal::new(0, 8); let mut profit_percent = 0.0; let update_colums = vec![ "current_price", "expected_get_usdt", "expected_usdt_profit", "pure_profit_percent", "minimum_profit_percent", "maximum_profit_percent", ]; let mut update_record_build: Vec = Vec::new(); let mut update_record: Vec> = Vec::new(); // update current_price, expected__get_usdt, expected_usdt_profit, pure_profit_percent, minimum_profit_percent, maximum_profit_percent for element in buy_ordered_coin_vec { // build update values update_record_build.clear(); if coin_price_map.contains_key(&element.symbol) && exchange_info_map.contains_key(&element.symbol) && futures_trade_fee.user_level.is_some() { price = rust_decimal::prelude::FromPrimitive::from_f64( *coin_price_map.get(&element.symbol).unwrap(), ) .unwrap(); if !price.is_zero() { // to get quote_commission_precision let trade_fee = decimal_div(futures_trade_fee.maker_fee_percent, dec!(100)); let lot_step_size = exchange_info_map.get(&element.symbol).unwrap().stepsize; let quote_precision = exchange_info_map .get(&element.symbol) .unwrap() .quote_precision; let position_size = element.base_qty_ordered.round_dp_with_strategy( lot_step_size.normalize().scale(), RoundingStrategy::ToZero, ); let entry_trade_fee = decimal_mul(decimal_mul(position_size, element.entry_price), trade_fee); let exit_trade_fee = decimal_mul(decimal_mul(position_size, element.current_price), trade_fee); let fee_total = decimal_add(entry_trade_fee, exit_trade_fee); let initial_margin = decimal_add(decimal_mul(position_size, element.entry_price), entry_trade_fee); let mut profit = Decimal::new(0, 8); if element.position.contains("Long") { profit = decimal_sub(decimal_sub(element.current_price, element.entry_price),fee_total); } else { profit = decimal_sub(decimal_sub(element.entry_price, element.current_price),fee_total); } let mut pure_profit_percent = (profit.to_f64().unwrap() / initial_margin.to_f64().unwrap()) * 100.0; pure_profit_percent = (pure_profit_percent * 100.0).round() / 100.0; // Rounding update_record_build.push(element.id.to_string()); // id update_record_build.push(price.to_string()); // current_price update_record_build.push(decimal_add(profit, element.used_usdt).to_string()); //expected_get_usdt update_record_build.push(profit.to_string()); // expected_usdt_profit update_record_build.push(pure_profit_percent.to_string()); // pure_profit_percent if element.minimum_profit_percent > pure_profit_percent { update_record_build.push(pure_profit_percent.to_string()); // minimum_profit_percent } else if pure_profit_percent >= 0.0 { update_record_build.push(0.0.to_string()); // minimum_profit_percent } else { update_record_build.push(element.minimum_profit_percent.to_string()); // minimum_profit_percent } if element.maximum_profit_percent < pure_profit_percent { update_record_build.push(pure_profit_percent.to_string()); // maximum_profit_percent } else if pure_profit_percent <= 0.0 { update_record_build.push(0.0.to_string()); // maximum_profit_percent } else { update_record_build.push(element.maximum_profit_percent.to_string()); // maximum_profit_percent } update_record.push(update_record_build.clone()); } } } update_records(&update_table_name, &update_record, &update_colums).await; Ok(()) } pub async fn select_filled_positions() -> Result, Box> { let select_table_name = String::from("future_ordered_coin_list"); let select_columns = String::from("*"); let mut select_condition_build = String::from("WHERE (status = 'FILLED' or status = 'SIMUL')"); let select_condition = Some(select_condition_build); let data_struct = PositionCoinList::new(); let select_result = try_select_record( &select_table_name, &select_columns, &select_condition, &data_struct, ) .await; if select_result.is_ok() { Ok(select_result.unwrap()) } else { eprint!("select_filled_positions() error!"); Err("error")? } } pub async fn select_listuped_positions() -> Result, Box> { let select_table_name = String::from("future_ordered_coin_list"); let select_columns = String::from("*"); let mut select_condition_build = String::from("WHERE status = 'LISTUP'"); let select_condition = Some(select_condition_build); let data_struct = PositionCoinList::new(); let select_result = try_select_record( &select_table_name, &select_columns, &select_condition, &data_struct, ) .await; if select_result.is_ok() { Ok(select_result.unwrap()) } else { eprint!("select_listup_positions() error!"); Err("error")? } }