// use crates use crate::coex::assets_managing_team::*; use crate::coex::exchange_team::*; use crate::coin_health_check_team::request_others::TradeFee; use crate::database_control::*; use crate::decimal_funcs::*; use crate::signal_association::signal_decision::*; use crate::strategy_team::{AllData, TimeData}; use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; use crate::value_estimation_team::indicators::ema::EmaData; use crate::value_estimation_team::indicators::sma::SmaData; use crate::value_estimation_team::indicators::stoch_rsi::StochRsiData; use crate::value_estimation_team::indicators::supertrend::{supertrend, SupertrendData}; use futures::future::try_join_all; use log; use reqwest::{Client, ClientBuilder}; use rust_decimal::{prelude::FromPrimitive, prelude::ToPrimitive, Decimal, RoundingStrategy}; use rust_decimal_macros::dec; use serde_json::Value; use sqlx::FromRow; use std::collections::HashMap; use tokio::time::*; use super::{hmac_signature, REAL, SIMUL, TEST, RUNNING_MODE, FUTURES_URL, FUTURES_URL_TEST, API_KEY, API_KEY_TESTNET, FuturesExchangeInfo, PositionCoinList, FuturesTradeFee, EntryCoinInfo, select_listuped_positions}; use crate::strategy_team::future_strategy; pub enum TimeInForce { Gtc, Ioc, Fok, } #[derive(PartialEq)] pub enum MarginType { Isolated, Crossed } pub async fn entry_position( price_map: &HashMap, futures_exchange_info_map: &HashMap, future_trade_fee: &FuturesTradeFee, ) -> Result<(), Box> { let mut available_usdt = get_future_available_usdt().await?; let mut unit_trade_usdt = get_unit_trade_usdt().await; if !available_usdt.is_zero() && !unit_trade_usdt.is_zero() && available_usdt >= unit_trade_usdt && future_trade_fee.user_level.is_some() { let listup_positions = select_listuped_positions().await?; let server_epoch = get_server_epoch().await; let client = ClientBuilder::new() .timeout(tokio::time::Duration::from_millis(3000)) .build() .unwrap(); for element in &listup_positions { available_usdt = get_future_available_usdt().await?; unit_trade_usdt = get_unit_trade_usdt().await; if !available_usdt.is_zero() && !unit_trade_usdt.is_zero() && available_usdt >= unit_trade_usdt && futures_exchange_info_map.contains_key(&element.symbol) && price_map.get(&element.symbol).is_some() && server_epoch - element.registered_server_epoch < 10_000 { let futures_exchange_info = futures_exchange_info_map.get(&element.symbol).unwrap(); let lot_step_size = futures_exchange_info.stepsize; let tick_size = futures_exchange_info.ticksize; let base_asset_precision = futures_exchange_info.base_asset_precision; let trade_fee = future_trade_fee.maker_fee_percent; let entry_price = rust_decimal::prelude::FromPrimitive::from_f64(*price_map.get(&element.symbol).unwrap()).unwrap(); // calculate minimum order usdt >= unit_trade_usdt let notional = futures_exchange_info.notional; let mut minimum_quantity = decimal_div(notional, entry_price) .round_dp_with_strategy(lot_step_size.normalize().scale(), RoundingStrategy::ToPositiveInfinity); let mut minimum_order_usdt = decimal_mul(minimum_quantity, entry_price) .round_dp_with_strategy(tick_size.normalize().scale(), RoundingStrategy::ToPositiveInfinity); if minimum_order_usdt <= unit_trade_usdt { let mut base_qty_ordered = Decimal::new(0, 8); base_qty_ordered = decimal_div(unit_trade_usdt, entry_price) .round_dp_with_strategy( lot_step_size.normalize().scale(), RoundingStrategy::ToZero, ); let mut used_usdt = Decimal::new(0, 8); used_usdt = decimal_mul(base_qty_ordered, entry_price) .round_dp_with_strategy( tick_size.normalize().scale(), RoundingStrategy::ToZero, ); // order the symbol based on base_qty_ordered and current_price limit_order_entry( &element, futures_exchange_info_map, trade_fee, TimeInForce::Gtc, entry_price, base_qty_ordered, used_usdt, &client, ) .await; } } else { break; } } } Ok(()) } pub async fn limit_order_entry( entry_coin_info: &PositionCoinList, exchange_info_map: &HashMap, trade_fee: Decimal, tif: TimeInForce, order_price: Decimal, order_quantity: Decimal, used_usdt: Decimal, client: &Client, ) -> Result<(), Box> { let update_table_name = String::from("future_ordered_coin_list"); let server_epoch = get_server_epoch().await; unsafe { if RUNNING_MODE == SIMUL { let mut update_values = vec![ (String::from("status"), String::from("SIMUL")), (String::from("transact_time"), server_epoch.to_string()), (String::from("close_time"), entry_coin_info.close_time.to_string()), (String::from("used_usdt"), used_usdt.to_string()), (String::from("entry_price"), order_price.to_string()), (String::from("current_price"), order_price.to_string()), (String::from("base_qty_ordered"), order_quantity.to_string()), ]; let update_condition = vec![(String::from("id"), entry_coin_info.id.to_string())]; update_record2(&update_table_name, &update_values, &update_condition) .await.unwrap(); sub_future_available_usdt(used_usdt).await; println!("SIMUL {} {}", entry_coin_info.position, entry_coin_info.symbol); } else { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); 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/v1/order?"; url.push_str(endpoint_url); let mut url_build = String::new(); // add parameters into URL url_build.push_str("&symbol="); url_build.push_str(&entry_coin_info.symbol); if entry_coin_info.position.contains("Long") { url_build.push_str("&side=BUY"); } else { url_build.push_str("&side=SELL"); } url_build.push_str("&type=LIMIT"); url_build.push_str("&quantity="); url_build.push_str(order_quantity.to_string().as_str()); url_build.push_str("&price="); url_build.push_str(order_price.to_string().as_str()); match tif { TimeInForce::Gtc => { url_build.push_str("&timeInForce=GTC"); } TimeInForce::Ioc => { url_build.push_str("&timeInForce=IOC"); } TimeInForce::Fok => { url_build.push_str("&timeInForce=FOK"); } } hmac_signature(&mut url_build).await; url.push_str(&url_build); let res = client .post(&url) .header("X-MBX-APIKEY", api_key) .send() .await .unwrap(); let body = res.text_with_charset("utf-8").await.unwrap(); // deserialize JSON and then insert record into table let v = serde_json::from_str::(body.as_str()); match v { Ok(T) => { if T.get("code").is_some() { // when request failed } else { // when request succeed let mut update_values = vec![]; update_values.push((String::from("order_type"), entry_coin_info.order_type.clone())); update_values.push((String::from("order_id"), T.get("orderId").unwrap().as_u64().unwrap().to_string())); update_values.push((String::from("transact_time"), server_epoch.to_string())); update_values.push((String::from("close_time"), entry_coin_info.close_time.to_string())); // status if T.get("status").unwrap().as_str().unwrap() == "NEW" { update_values.push((String::from("status"), String::from("NEW"))); } else if T.get("status").unwrap().as_str().unwrap() == "FILLED" { update_values.push((String::from("status"), String::from("FILLED"))); println!("{} {}", entry_coin_info.position, entry_coin_info.symbol); } else if T.get("status").unwrap().as_str().unwrap() == "PARTIALLY_FILLED" { update_values.push((String::from("status"), String::from("PARTIALLY_FILLED"))); println!("Partially filled {} {}", entry_coin_info.position, entry_coin_info.symbol); } let base_qty_ordered = rust_decimal::prelude::FromStr::from_str( T.get("origQty").unwrap().as_str().unwrap(), ) .unwrap(); let cummulative_quote_qty = rust_decimal::prelude::FromStr::from_str( T.get("cumQuote").unwrap().as_str().unwrap(), ) .unwrap(); // let base_asset_precision = exchange_info_vec // .iter() // .find(|exchange_info| exchange_info.symbol == element.symbol) // .unwrap() // .base_asset_precision; let entry_price = decimal_div(cummulative_quote_qty, base_qty_ordered) .round_dp_with_strategy(8, RoundingStrategy::ToZero); update_values.push((String::from("used_usdt"), cummulative_quote_qty.to_string())); update_values.push((String::from("entry_price"), entry_price.to_string())); update_values.push((String::from("base_qty_ordered"), base_qty_ordered.to_string())); // reflect available_usdt in [asset_manage_announcement] // sub_available_usdt(cummulative_quote_qty).await; sub_future_available_usdt(cummulative_quote_qty).await; let update_condition = vec![(String::from("id"), entry_coin_info.id.to_string())]; update_record2(&update_table_name, &update_values, &update_condition) .await.unwrap(); } }, Err(e) => { log::warn!("order failed!: {}", body); } } } } Ok(()) } pub async fn monitoring_unfilled_order( client: &Client, future_trade_fee: &FuturesTradeFee, ) -> Result<(), Box> { let open_positioning_orders = select_open_positioning_orders().await; if !open_positioning_orders.is_empty() { let server_epoch = get_server_epoch().await; let orders_outdated = open_positioning_orders .iter() .filter(|&element| server_epoch - element.registered_server_epoch >= 60_000) .collect::>(); // wait up to 60 secs let orders_to_be_queried = open_positioning_orders .iter() .filter(|&element| server_epoch - element.registered_server_epoch < 60_000) .collect::>(); // cancel orders outdated over 3mins and delete the records in [buy_ordered_coin_list] if !orders_outdated.is_empty() { for element in orders_outdated { cancel_open_positioning_order(element, &client, future_trade_fee).await; sleep(Duration::from_millis(200)).await; } } if !orders_to_be_queried.is_empty() { for element in orders_to_be_queried { query_open_positioning_order(element, &client, future_trade_fee).await; sleep(Duration::from_millis(200)).await; } } } let open_closing_orders = select_open_closing_orders().await; if !open_closing_orders.is_empty() { } Ok(()) } // Cancel an LISTUP or NEW or PARTIALLY FILLED order. (/api, Weight(IP) 1) pub async fn cancel_open_positioning_order( order: &PositionCoinList, client: &Client, future_trade_fee: &FuturesTradeFee, ) -> Result<(), Box> { if order.status.contains("LISTUP") { let table_name = String::from("future_ordered_coin_list"); let mut condition_build = String::from("WHERE id = "); condition_build.push_str(order.id.to_string().as_str()); condition_build.push_str(" AND symbol = \'"); condition_build.push_str(&order.symbol); condition_build.push('\''); delete_record(&table_name, &condition_build).await; return Ok(()); } // 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 if RUNNING_MODE == REAL { url.push_str(FUTURES_URL); api_key = API_KEY.to_string(); } } let endpoint_url = "/fapi/v1/order?"; url.push_str(endpoint_url); let mut url_build = String::new(); // add parameters into URL url_build.push_str("&symbol="); url_build.push_str(&order.symbol); url_build.push_str("&orderId="); url_build.push_str(order.order_id.to_string().as_str()); hmac_signature(&mut url_build).await; url.push_str(&url_build); let res = client .delete(&url) .header("X-MBX-APIKEY", api_key) .send() .await?; let body = res.text_with_charset("utf-8").await.unwrap(); // println!("cancel_buy_order실행 body: {}", body); // deserialize JSON and then insert record into table let v = serde_json::from_str::(body.as_str()); match v { Ok(T) => { if T.get("status").is_some() { // case that the order is canceled successfully if T.get("status").unwrap().as_str().unwrap() == "CANCELED" { let table_name = String::from("future_ordered_coin_list"); let cummulative_quote_qty: Decimal = rust_decimal::prelude::FromStr::from_str( T.get("cumQuote").unwrap().as_str().unwrap(), ) .unwrap(); if cummulative_quote_qty.is_zero() { // NOT Filled case // delete record in [future_ordered_coin_list] let mut condition_build = String::from("WHERE order_id = "); condition_build.push_str(order.order_id.to_string().as_str()); condition_build.push_str(" AND symbol = \'"); condition_build.push_str(&order.symbol); condition_build.push('\''); delete_record(&table_name, &condition_build).await; } else { // Patially Filled case // update values in [future_ordered_coin_list] // calculate values to be updated if future_trade_fee.user_level.is_some() { let trade_fee = future_trade_fee.maker_fee_percent; let base_qty_ordered = rust_decimal::prelude::FromStr::from_str( T.get("executedQty").unwrap().as_str().unwrap(), ) .unwrap(); let entry_price = decimal_div(cummulative_quote_qty, base_qty_ordered) .round_dp_with_strategy(8, RoundingStrategy::ToZero); let update_values = vec![ (String::from("status"), String::from("FILLED")), // status (String::from("used_usdt"), cummulative_quote_qty.to_string()), // used_usdt (String::from("entry_price"), entry_price.to_string()), // entry_price (String::from("base_qty_ordered"),base_qty_ordered.to_string()), // base_qty_ordered ]; let update_condition = vec![ (String::from("order_id"), order.order_id.to_string()), (String::from("symbol"), order.symbol.clone()), ]; update_record2(&table_name, &update_values, &update_condition) .await .unwrap(); println!("partially Positioning {} {}", order.position, order.symbol); } } } } else if T.get("code").is_some() { // case that the order isn't canceled because the order completes while canceling // update record in ordered_coin_list query_open_positioning_order(order, &client, future_trade_fee).await; } } Err(e) => { query_open_positioning_order(order, &client, future_trade_fee).await; log::warn!("cancel order buy failed!: {}", body); } } Ok(()) } // query order and update record in buy_ordered_coin_list (/api, Weight(IP) 1) pub async fn query_open_positioning_order( order: &PositionCoinList, client: &Client, future_trade_fee: &FuturesTradeFee, ) -> 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 if RUNNING_MODE == REAL { url.push_str(FUTURES_URL); api_key = API_KEY.to_string(); } } let endpoint_url = "/fapi/v1/order?"; url.push_str(endpoint_url); let mut url_build = String::new(); // add parameters into URL url_build.push_str("&symbol="); url_build.push_str(&order.symbol); url_build.push_str("&orderId="); url_build.push_str(order.order_id.to_string().as_str()); 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.unwrap(); // println!("query_buy_order: {}", body); // deserialize JSON and then update record in table let v = serde_json::from_str::(body.as_str()); match v { Ok(T) => { if T.get("status").is_some() { if T.get("status").unwrap().as_str().unwrap() == "FILLED" || T.get("status").unwrap().as_str().unwrap() == "PARTIALLY_FILLED" { // update values in [future_ordered_coin_list] let table_name = String::from("future_ordered_coin_list"); let mut value_build = String::new(); value_build.push_str(T.get("status").unwrap().as_str().unwrap()); // calculate values to be updated if future_trade_fee.user_level.is_some() { let trade_fee = future_trade_fee.maker_fee_percent; let base_qty_ordered = rust_decimal::prelude::FromStr::from_str( T.get("executedQty").unwrap().as_str().unwrap(), ) .unwrap(); let cummulative_quote_qty = rust_decimal::prelude::FromStr::from_str( T.get("cumQuote").unwrap().as_str().unwrap(), ) .unwrap(); let entry_price = decimal_div(cummulative_quote_qty, base_qty_ordered) .round_dp_with_strategy(8, RoundingStrategy::ToZero); let update_values = vec![ (String::from("status"), value_build), // status (String::from("used_usdt"), cummulative_quote_qty.to_string()), // used_usdt (String::from("entry_price"), entry_price.to_string()), // entry_price (String::from("base_qty_ordered"), base_qty_ordered.to_string()), // base_qty_ordered ]; let update_condition = vec![ (String::from("order_id"), order.order_id.to_string()), (String::from("symbol"), order.symbol.clone()), ]; update_record2(&table_name, &update_values, &update_condition) .await .unwrap(); if T.get("status").unwrap().as_str().unwrap() == "FILLED" { println!("Positioning {} {}", order.position, order.symbol); // update available_usdt if order.used_usdt > cummulative_quote_qty { add_available_usdt(decimal_sub( order.used_usdt, cummulative_quote_qty, )); } else { sub_available_usdt(decimal_sub( cummulative_quote_qty, order.used_usdt, )); } } } } else if T.get("status").unwrap().as_str().unwrap() == "CANCELED" { let update_table_name = String::from("future_ordered_coin_list"); let update_values = vec![(String::from("status"), String::from("CANCELED"))]; let update_condition = vec![ (String::from("order_id"), order.order_id.to_string()), (String::from("symbol"), order.symbol.clone()), ]; update_record2(&update_table_name, &update_values, &update_condition) .await .unwrap(); } } } Err(e) => { log::warn!("query order failed!: {}", body); } } Ok(()) } // pub async fn set_initial_leverage(symbol: &String, leverage_ratio: i32, 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/v1/leverage?"; url.push_str(endpoint_url); let mut url_build = String::new(); url_build.push_str("&symbol="); url_build.push_str(symbol); url_build.push_str("&leverage="); url_build.push_str(leverage_ratio.to_string().as_str()); hmac_signature(&mut url_build).await; url.push_str(&url_build); let res = client .post(&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(leverage), Some(symbol), Some(max_notional_value)) = (T.get("leverage"), T.get("symbol"), T.get("maxNotionalValue")) { log::info!("symbol: {:?}. leverage: {:?}, max_notional_value: {:?}", symbol, leverage, max_notional_value); } 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 set_margin_type(symbol: &String, margin_type: MarginType, 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/v1/marginType?"; url.push_str(endpoint_url); let mut url_build = String::new(); url_build.push_str("&symbol="); url_build.push_str(symbol); url_build.push_str("&marginType="); if margin_type == MarginType::Isolated { url_build.push_str("ISOLATED"); } else { url_build.push_str("CROSSED"); } hmac_signature(&mut url_build).await; url.push_str(&url_build); let res = client .post(&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(code), Some(msg)) = (T.get("code"), T.get("msg")) { log::info!("code: {:?}. msg: {:?}", code, msg); } else { log::error!("Endpoint(/fapi/v1/marginType?) output changed!"); return Err("Err")?; } } Err(e) => { log::warn!("account information failed!: {}, {}", body, e); } } Ok(()) } // Basic: One-way Mode pub async fn set_position_mode(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/v1/positionSide/dual?"; url.push_str(endpoint_url); let mut url_build = String::new(); url_build.push_str("&dualSidePosition="); url_build.push_str("false"); // true: Hedge Mode, false: One-way Mode hmac_signature(&mut url_build).await; url.push_str(&url_build); let res = client .post(&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(code), Some(msg)) = (T.get("code"), T.get("msg")) { log::info!("code: {:?}. msg: {:?}", code, msg); } else { log::error!("Endpoint(/fapi/v1/marginType?) output changed!"); return Err("Err")?; } } Err(e) => { log::warn!("account information failed!: {}, {}", body, e); } } Ok(()) } // Basic: Single-Asset Mode pub async fn set_asset_mode(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/v1/multiAssetsMargin?"; url.push_str(endpoint_url); let mut url_build = String::new(); url_build.push_str("&multiAssetsMargin="); url_build.push_str("false"); // true: Multi-Assets Mode, false: Single-Asset Mode hmac_signature(&mut url_build).await; url.push_str(&url_build); let res = client .post(&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(code), Some(msg)) = (T.get("code"), T.get("msg")) { log::info!("code: {:?}. msg: {:?}", code, msg); } else { log::error!("Endpoint(/fapi/v1/marginType?) output changed!"); return Err("Err")?; } } Err(e) => { log::warn!("account information failed!: {}, {}", body, e); } } Ok(()) } pub async fn get_last_price(client: &Client, price_map: &mut HashMap,) -> 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/ticker/price"; url.push_str(endpoint_url); 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: 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::().unwrap(), "time" => {}, _ => { log::error!("Elements in body msg are changed. Please update both your coinprices table and vectors."); } } price_map.insert(symbol.clone(), price); } } Ok(()) } // request exchange information. (/api, Weight(IP) 1) pub async fn request_future_exchange_infomation( client: &Client, exchange_info_map: &mut HashMap, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); url.push_str("https://fapi.binance.com"); let endpoint_url = "/fapi/v1/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 = FuturesExchangeInfo::new(); let mut data_map_temp: HashMap = 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") { if element.get("contractType").is_some_and(|a| a.as_str().is_some_and(|b| b == "PERPETUAL")) { 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.quote_precision = (element.get("quotePrecision").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(); } else if element .as_object() .unwrap() .get("filterType") .unwrap() .as_str() .unwrap() .starts_with("MIN_NOTIONAL") { exchange_info.notional = rust_decimal::prelude::FromStr::from_str( element.get("notional").unwrap().as_str().unwrap(), ) .unwrap(); } } data_map_temp.insert(symbol.clone(), exchange_info.clone()); } } } } } } *exchange_info_map = data_map_temp; Ok(()) } // subtract available USDT pub async fn sub_future_available_usdt(sub_usdt: Decimal) { let update_table_name = String::from("future_available_balance"); let mut value_build = String::from("available_usdt - "); value_build.push_str(sub_usdt.to_string().as_str()); let update_values = vec![(String::from("available_usdt"), value_build)]; let update_condition = vec![(String::from("id"), String::from("1"))]; update_record3(&update_table_name, &update_values, &update_condition) .await .unwrap(); } // get available USDT pub async fn get_future_available_usdt() -> Result> { let select_table_name = String::from("future_available_balance"); let select_columns = String::from("*"); let select_condition = None; #[derive(FromRow)] struct AvailableUsdt { available_usdt: Decimal, } let data_struct = AvailableUsdt{ available_usdt: Decimal::new(0, 8) }; 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().first().unwrap().available_usdt) } else { eprint!("get_future_available_usdt() error!"); Err("error")? } } async fn select_open_positioning_orders() -> Vec { let select_table_name = String::from("future_ordered_coin_list"); let select_columns = String::from("*"); let select_condition = Some(String::from( "WHERE order_type = 'POSITIONING' AND (status = 'LISTUP' OR status = 'NEW' OR status = 'PARTIALLY_FILLED')", )); let data_struct = PositionCoinList::new(); let select_result = try_select_record( &select_table_name, &select_columns, &select_condition, &data_struct, ) .await .unwrap(); select_result } async fn select_open_closing_orders() -> Vec { let select_table_name = String::from("future_ordered_coin_list"); let select_columns = String::from("*"); let select_condition = Some(String::from( "WHERE order_type = 'CLOSING' AND (status = 'NEW' OR status = 'PARTIALLY_FILLED')", )); let data_struct = PositionCoinList::new(); let select_result = try_select_record( &select_table_name, &select_columns, &select_condition, &data_struct, ) .await .unwrap(); select_result }