// use consts use crate::RunningMode::*; use crate::API_KEY; use crate::API_KEY_TESTNET; use crate::RUNNING_MODE; use crate::SECRET_KEY; use crate::SECRET_KEY_TESTNET; use crate::URL; use crate::URL_TEST; // use crates use crate::coex::assets_managing_team::*; use crate::coex::exchange_team::*; use crate::coin_health_check_team::request_others::{ExchangeInfo, 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 hex::ToHex; use hmac_sha256::HMAC; 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 tokio::time::*; use std::collections::HashMap; pub enum OrderSide { Buy, Sell, } pub enum OrderType { Limit, Market, LimitMaker, } pub enum TimeInForce { Gtc, Ioc, Fok, } #[derive(Debug, FromRow, Clone)] pub struct BuyOrderedCoinList { pub id: u64, pub symbol: String, pub order_id: u64, pub transact_time: i64, pub close_time: i64, pub status: String, pub used_usdt: Decimal, pub expected_get_usdt: f64, pub expected_usdt_profit: f64, pub buy_price: Decimal, pub current_price: Decimal, pub stoploss: Decimal, pub target_price: Decimal, pub base_qty_ordered: Decimal, pub base_qty_fee_adjusted: Decimal, pub pure_profit_percent: f64, pub minimum_profit_percent: f64, pub maximum_profit_percent: f64, pub registerer: u16, pub is_long: u8, } #[derive(Debug, FromRow)] pub struct SellOrderedCoinList { pub id: u64, pub symbol: String, pub buy_order_id: u64, pub sell_order_id: u64, pub transact_time: i64, pub close_time: i64, pub status: String, pub used_usdt: Decimal, pub get_usdt: Decimal, pub get_usdt_fee_adjusted: Decimal, pub buy_price: Decimal, pub sell_price: Decimal, pub stoploss: Decimal, pub target_price: Decimal, pub base_qty_ordered: Decimal, pub pure_profit_percent: Decimal, pub maximum_profit_percent: f64, pub registerer: u16, pub is_long: u8, } #[derive(Debug, FromRow, Clone)] pub struct SellHistoryList { pub id: u64, pub symbol: String, pub soldtime: i64, pub used_usdt: Decimal, pub get_usdt: Decimal, pub buy_price: Decimal, pub sell_price: Decimal, pub base_qty: Decimal, pub pure_profit_percent: f64, pub pure_profit_usdt: Decimal, pub registerer: u16, } pub trait DBlist { fn new() -> Self; } impl DBlist for SellOrderedCoinList { fn new() -> SellOrderedCoinList { let a = SellOrderedCoinList { id: 0, symbol: String::new(), buy_order_id: 0, sell_order_id: 0, transact_time: 0, close_time: 0, status: String::new(), used_usdt: Decimal::new(0, 8), get_usdt: Decimal::new(0, 8), get_usdt_fee_adjusted: Decimal::new(0, 8), buy_price: Decimal::new(0, 8), sell_price: Decimal::new(0, 8), stoploss: Decimal::new(0, 8), target_price: Decimal::new(0, 8), base_qty_ordered: Decimal::new(0, 8), pure_profit_percent: Decimal::new(0, 8), maximum_profit_percent: 0.0, registerer: 0, is_long: 0, }; a } } impl DBlist for BuyOrderedCoinList { fn new() -> BuyOrderedCoinList { let a = BuyOrderedCoinList { id: 0, symbol: String::new(), order_id: 0, transact_time: 0, close_time: 0, status: String::new(), used_usdt: Decimal::new(0, 8), expected_get_usdt: 0.0, expected_usdt_profit: 0.0, buy_price: Decimal::new(0, 8), current_price: Decimal::new(0, 8), stoploss: Decimal::new(0, 8), target_price: Decimal::new(0, 8), base_qty_ordered: Decimal::new(0, 8), base_qty_fee_adjusted: Decimal::new(0, 8), pure_profit_percent: 0.0, minimum_profit_percent: 0.0, maximum_profit_percent: 0.0, registerer: 0, is_long: 0, }; a } } impl DBlist for SellHistoryList { fn new() -> SellHistoryList { let a = SellHistoryList { id: 0, symbol: String::new(), soldtime: 0, used_usdt: Decimal::new(0, 8), get_usdt: Decimal::new(0, 8), buy_price: Decimal::new(0, 8), sell_price: Decimal::new(0, 8), base_qty: Decimal::new(0, 8), pure_profit_percent: 0.0, pure_profit_usdt: Decimal::new(0, 8), registerer: 0, }; a } } #[derive(Debug, FromRow)] pub struct SignalDecisionInfo { pub decision: String, } pub async fn limit_order_buy_test( client: &Client, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/order?"; url.push_str(endpoint_url); let mut url_build = String::new(); // add parameters into URL url_build.push_str("&symbol=BTCUSDT"); url_build.push_str("&side=BUY"); url_build.push_str("&timeInForce=GTC"); url_build.push_str("&type=LIMIT"); url_build.push_str("&quantity=0.3"); url_build.push_str("&price=20451.37"); hmac_signature(&mut url_build).await; url.push_str(&url_build); println!("url: {}", url); let res = client .post(&url) .header("X-MBX-APIKEY", api_key) .send() .await .unwrap(); println!("response: {:?}", res); let body = res.text_with_charset("utf-8").await.unwrap(); println!("limit_order_buy실행 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("code").is_some() { // when request failed // } else { // when request succeed // insert_value_container.push(T.get("symbol").unwrap().as_str().unwrap().to_string()); // symbol // insert_value_container.push(T.get("orderId").unwrap().as_u64().unwrap().to_string()); // order_id // insert_value_container.push(server_epoch.to_string()); // transact_time // insert_value_container.push(element.close_time.to_string()); // close_time // insert_value_container.push(T.get("status").unwrap().as_str().unwrap().to_string()); // status // if T.get("status").unwrap().as_str().unwrap() == "FILLED" { // let base_qty_ordered = rust_decimal::prelude::FromStr::from_str(T.get("origQty").unwrap().as_str().unwrap()).unwrap(); // let base_qty_fee_adjusted = decimal_mul(base_qty_ordered, decimal_sub(dec!(1), trade_fee)); // let cummulative_quote_qty = rust_decimal::prelude::FromStr::from_str(T.get("cummulativeQuoteQty").unwrap().as_str().unwrap()).unwrap(); // let base_asset_precision = exchange_info_vec.iter().find(|ExchangeInfo| ExchangeInfo.symbol == element.symbol).unwrap().base_asset_precision; // let buy_price = decimal_div(cummulative_quote_qty, base_qty_ordered).round_dp_with_strategy(base_asset_precision, RoundingStrategy::ToZero); // insert_value_container.push(cummulative_quote_qty.to_string()); // used_usdt // insert_value_container.push(0.0.to_string()); // expected_get_usdt // insert_value_container.push(0.0.to_string()); // expected_usdt_profit // insert_value_container.push(buy_price.to_string()); // buy_price // insert_value_container.push(0.0.to_string()); // current_price // insert_value_container.push(base_qty_ordered.to_string()); // base_qty_ordered // insert_value_container.push(base_qty_fee_adjusted.to_string()); // base_qty_fee_adjusted // // reflect available_usdt in [asset_manage_announcement] // // sub_available_usdt(cummulative_quote_qty).await; // sub_available_usdt(simul_used_usdt).await; // println!(" buy coin 완료"); // } else { // insert_value_container.push(simul_used_usdt.to_string()); // used_usdt // insert_value_container.push(0.0.to_string()); // expected_get_usdt // insert_value_container.push(0.0.to_string()); // expected_usdt_profit // insert_value_container.push(0.0.to_string()); // buy_price // insert_value_container.push(0.0.to_string()); // current_price // insert_value_container.push(T.get("origQty").unwrap().as_str().unwrap().to_string()); // base_qty_ordered // insert_value_container.push(0.0.to_string()); // base_qty_fee_adjusted // // reflect available_usdt in [asset_manage_announcement] // sub_available_usdt(simul_used_usdt).await; // } // insert_value_container.push(0.0.to_string()); // pure_profit_percent // insert_value_container.push(0.0.to_string()); // minimum_profit_percent // insert_value_container.push(0.0.to_string()); // maximum_profit_percent // insert_value_container.push(element.registerer.to_string());// registerer // insert_value_container.push(0.to_string()); // is_long // insert_values.push(insert_value_container.clone()); // insert_records(&insert_table_name, &insert_columns, &insert_values).await; // } // }, // Err(e) => { // println!("order failed!: {}", body); // } // } Ok(()) } pub async fn limit_order_buy( element: &SuggestedCoin, exchange_info_map: &HashMap, trade_fee: Decimal, tif: TimeInForce, order_price: Decimal, order_quantity: Decimal, used_usdt: Decimal, simul_base_qty_fee_adjusted: &String, client: &Client, ) -> Result<(), Box> { let insert_table_name = String::from("buy_ordered_coin_list"); let insert_columns = vec![ "symbol", "order_id", "transact_time", "close_time", "status", "used_usdt", "expected_get_usdt", "expected_usdt_profit", "buy_price", "current_price", "stoploss", "target_price", "base_qty_ordered", "base_qty_fee_adjusted", "pure_profit_percent", "minimum_profit_percent", "maximum_profit_percent", "registerer", "is_long", ]; let mut insert_values: Vec> = Vec::new(); let mut insert_value_container: Vec = Vec::new(); let server_epoch = get_server_epoch().await; if RUNNING_MODE == SIMUL { insert_value_container.push(element.symbol.clone()); // symbol insert_value_container.push(0.to_string()); // order_id insert_value_container.push(server_epoch.to_string()); // transact_time insert_value_container.push(element.close_time.to_string()); // close_time insert_value_container.push(String::from("SIMUL")); // status insert_value_container.push(used_usdt.to_string()); // used_usdt insert_value_container.push(0.0.to_string()); // expected_get_usdt insert_value_container.push(0.0.to_string()); // expected_usdt_profit insert_value_container.push(order_price.to_string()); // buy_price insert_value_container.push(0.0.to_string()); // current_price insert_value_container.push(element.stoploss.to_string()); // stoploss insert_value_container.push(element.target_price.to_string()); // target_price insert_value_container.push(order_quantity.to_string()); // base_qty_ordered insert_value_container.push(simul_base_qty_fee_adjusted.to_string()); // base_qty_fee_adjusted insert_value_container.push(0.0.to_string()); // pure_profit_percent insert_value_container.push(0.0.to_string()); // minimum_profit_percent insert_value_container.push(0.0.to_string()); // maximum_profit_percent insert_value_container.push(element.registerer.to_string()); // registerer insert_value_container.push(0.to_string()); // is_long insert_values.push(insert_value_container.clone()); insert_records(&insert_table_name, &insert_columns, &insert_values).await; // reflect available_usdt in [asset_manage_announcement] sub_available_usdt(used_usdt).await; println!("SIMUL buy coin: {}", element.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(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/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(&element.symbol); url_build.push_str("&side=BUY"); 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"); } } 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()); 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 insert_value_container .push(T.get("symbol").unwrap().as_str().unwrap().to_string()); // symbol insert_value_container .push(T.get("orderId").unwrap().as_u64().unwrap().to_string()); // order_id insert_value_container.push(server_epoch.to_string()); // transact_time insert_value_container.push(element.close_time.to_string()); // close_time insert_value_container .push(T.get("status").unwrap().as_str().unwrap().to_string()); // status if T.get("status").unwrap().as_str().unwrap() == "FILLED" { let base_qty_ordered = rust_decimal::prelude::FromStr::from_str( T.get("origQty").unwrap().as_str().unwrap(), ) .unwrap(); let base_qty_fee_adjusted = decimal_mul(base_qty_ordered, decimal_sub(dec!(1), trade_fee)); let cummulative_quote_qty = rust_decimal::prelude::FromStr::from_str( T.get("cummulativeQuoteQty").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 buy_price = decimal_div(cummulative_quote_qty, base_qty_ordered) .round_dp_with_strategy(8, RoundingStrategy::ToZero); insert_value_container.push(cummulative_quote_qty.to_string()); // used_usdt insert_value_container.push(0.0.to_string()); // expected_get_usdt insert_value_container.push(0.0.to_string()); // expected_usdt_profit insert_value_container.push(buy_price.to_string()); // buy_price insert_value_container.push(0.0.to_string()); // current_price insert_value_container.push(element.stoploss.to_string()); // stoploss insert_value_container.push(element.target_price.to_string()); // target_price insert_value_container.push(base_qty_ordered.to_string()); // base_qty_ordered insert_value_container.push(base_qty_fee_adjusted.to_string()); // base_qty_fee_adjusted // reflect available_usdt in [asset_manage_announcement] // sub_available_usdt(cummulative_quote_qty).await; sub_available_usdt(used_usdt).await; println!("buy {}", element.symbol); } else { insert_value_container.push(used_usdt.to_string()); // used_usdt insert_value_container.push(0.0.to_string()); // expected_get_usdt insert_value_container.push(0.0.to_string()); // expected_usdt_profit insert_value_container.push(0.0.to_string()); // buy_price insert_value_container.push(0.0.to_string()); // current_price insert_value_container.push(element.stoploss.to_string()); // stoploss insert_value_container.push(element.target_price.to_string()); // target_price insert_value_container .push(T.get("origQty").unwrap().as_str().unwrap().to_string()); // base_qty_ordered insert_value_container.push(0.0.to_string()); // base_qty_fee_adjusted // reflect available_usdt in [asset_manage_announcement] sub_available_usdt(used_usdt).await; } insert_value_container.push(0.0.to_string()); // pure_profit_percent insert_value_container.push(0.0.to_string()); // minimum_profit_percent insert_value_container.push(0.0.to_string()); // maximum_profit_percent insert_value_container.push(element.registerer.to_string()); // registerer insert_value_container.push(0.to_string()); // is_long insert_values.push(insert_value_container.clone()); insert_records(&insert_table_name, &insert_columns, &insert_values).await; } } Err(e) => { println!("order failed!: {}", body); } } } Ok(()) } pub async fn monitoring_open_buy_order( client: &Client, trade_fee_map: &HashMap, ) -> Result<(), Box> { let open_buy_orders = select_open_buy_orders().await; if !open_buy_orders.is_empty() { let server_epoch = get_server_epoch().await; let orders_outdated = open_buy_orders .iter() .filter(|&element| server_epoch - element.transact_time >= 600_000) .collect::>(); // wait up to 600 secs let orders_to_be_queried = open_buy_orders .iter() .filter(|&element| server_epoch - element.transact_time < 600_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_buy_order(element, &client, trade_fee_map).await; sleep(Duration::from_millis(500)).await; // Use max 30 LIMIT/min } } if !orders_to_be_queried.is_empty() { for element in orders_to_be_queried { query_buy_order(element, &client, trade_fee_map).await; sleep(Duration::from_millis(500)).await; // Use max 30 LIMIT/min } } } Ok(()) } pub async fn update_price_of_filled_buy_order( coin_price_map: &HashMap, exchange_info_map: &HashMap, trade_fee_map: &HashMap, ) -> Result<(), Box> { let filled_buy_orders = select_filled_buy_orders(0).await?; if !filled_buy_orders.is_empty() { // 심볼들을 2개씩 청스로 나누어 테스크를 생성하고 각 태스크 별로 병렬로 처리한다. // update real-time current price to each record through chunks let chunks: std::slice::Chunks<'_, BuyOrderedCoinList> = filled_buy_orders.chunks(3); 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 trade_fee_vec_c = trade_fee_map.clone(); task_vec.push(tokio::spawn(async move { update_repeat_task( chunk_vec, &coin_price_vec_c, &exchange_info_vec_c, &trade_fee_vec_c, ) .await; })); } } Ok(()) } async fn update_repeat_task( buy_ordered_coin_vec: Vec, coin_price_map: &HashMap, exchange_info_map: &HashMap, trade_fee_map: &HashMap, ) -> Result<(), Box> { let update_table_name = String::from("buy_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 server_epoch = get_server_epoch().await; let update_colums = vec![ "current_price", "expected_get_usdt", "expected_usdt_profit", "pure_profit_percent", "minimum_profit_percent", "maximum_profit_percent", "is_long", ]; 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) && trade_fee_map.contains_key(&element.symbol) { 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 = trade_fee_map.get(&element.symbol).unwrap().takercommission; let lot_step_size = exchange_info_map.get(&element.symbol).unwrap().stepsize; let quote_commission_precision = exchange_info_map.get(&element.symbol).unwrap().quote_commission_precision; let base_qty_to_be_ordered = element.base_qty_fee_adjusted.round_dp_with_strategy( lot_step_size.normalize().scale(), RoundingStrategy::ToZero, ); let expected_get_usdt = decimal_mul( decimal_mul(base_qty_to_be_ordered, price).round_dp_with_strategy( quote_commission_precision, RoundingStrategy::ToZero, ), decimal_sub(dec!(1), trade_fee), ); // TODO: sell_count >=1 이면 expected_get_usdt 는 한번만 tradefee만 적용하여 업데이트 할 것. 현재는 수수료를 2번 (매수,매도)를 계산함. 아래 변수에 든 값으로 업데이트 하면 됨 // let expected_get_usdt = // decimal_mul(base_qty_to_be_ordered, price).round_dp_with_strategy( // quote_commission_precision, // RoundingStrategy::ToZero, // ); let pure_profit_percent = ((expected_get_usdt.to_f64().unwrap() / element.used_usdt.to_f64().unwrap()) - 1.0) * 100.0; update_record_build.push(element.id.to_string()); // id update_record_build.push(price.to_string()); // current_price update_record_build.push(expected_get_usdt.to_string()); //expected_get_usdt update_record_build .push(decimal_sub(expected_get_usdt, element.used_usdt).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 } if server_epoch - element.transact_time >= 86_400_000 { // turn is_long from 0 to 1 for orders whose transact time is over than a day (86,400,000 millis = a day) update_record_build.push(1.to_string()); } else { update_record_build.push(0.to_string()); } update_record.push(update_record_build.clone()); } } } update_records(&update_table_name, &update_record, &update_colums).await; Ok(()) } pub async fn limit_order_sell( buy_ordered_coin: &BuyOrderedCoinList, sell_base_price: Decimal, sell_base_quantity: Decimal, client: &Client, exchange_info_map: &HashMap, trade_fee_map: &HashMap, ) -> Result<(), Box> { let insert_table_name = String::from("sell_ordered_coin_list"); let insert_columns = vec![ "symbol", "buy_order_id", "sell_order_id", "transact_time", "close_time", "status", "used_usdt", "get_usdt", "get_usdt_fee_adjusted", "buy_price", "sell_price", "stoploss", "target_price", "base_qty_ordered", "pure_profit_percent", "maximum_profit_percent", "registerer", "is_long", ]; let mut insert_values: Vec> = Vec::new(); let mut insert_value_container: Vec = Vec::new(); let server_epoch = get_server_epoch().await; if RUNNING_MODE == SIMUL && buy_ordered_coin.status == "SIMUL" { if exchange_info_map.contains_key(&buy_ordered_coin.symbol) && trade_fee_map.contains_key(&buy_ordered_coin.symbol) { let quote_asset_precision = exchange_info_map.get(&buy_ordered_coin.symbol).unwrap().quote_asset_precision; let trade_fee = trade_fee_map.get(&buy_ordered_coin.symbol).unwrap().takercommission; let get_usdt = decimal_mul(sell_base_quantity, sell_base_price) .round_dp_with_strategy(quote_asset_precision, RoundingStrategy::ToZero); let get_usdt_fee_adjusted = decimal_mul(get_usdt, decimal_sub(dec!(1), trade_fee)) .round_dp_with_strategy(quote_asset_precision, RoundingStrategy::ToZero); insert_value_container.push(buy_ordered_coin.symbol.to_string()); // symbol insert_value_container.push(0.to_string()); // buy_order_id insert_value_container.push(0.to_string()); // sell_order_id insert_value_container.push(server_epoch.to_string()); // transact_time insert_value_container.push(buy_ordered_coin.close_time.to_string()); // close_time insert_value_container.push(String::from("SIMUL")); // status insert_value_container.push(buy_ordered_coin.used_usdt.to_string()); // used_usdt insert_value_container.push(get_usdt.to_string()); // get_usdt insert_value_container.push(get_usdt_fee_adjusted.to_string()); // get_usdt_fee_adjusted insert_value_container.push(buy_ordered_coin.buy_price.to_string()); // buy_price insert_value_container.push(sell_base_price.to_string()); // sell_price insert_value_container.push(buy_ordered_coin.stoploss.to_string()); // stoploss insert_value_container.push(buy_ordered_coin.target_price.to_string()); // target_price insert_value_container.push(sell_base_quantity.to_string()); // base_qty_ordered let pure_profit_percent = decimal_mul( decimal_sub( decimal_div(get_usdt_fee_adjusted, buy_ordered_coin.used_usdt), dec!(1), ), dec!(100), ); insert_value_container.push(pure_profit_percent.to_string()); // pure_profit_percent insert_value_container.push(buy_ordered_coin.maximum_profit_percent.to_string()); // maximum_profit_percent insert_value_container.push(buy_ordered_coin.registerer.to_string()); // registerer insert_value_container.push(buy_ordered_coin.is_long.to_string()); // is_long insert_values.push(insert_value_container.clone()); insert_records(&insert_table_name, &insert_columns, &insert_values).await; // delete record in buy_ordered_coin_list let delete_table_name = String::from("buy_ordered_coin_list"); let mut delete_condition = String::from("WHERE id = "); delete_condition.push_str(buy_ordered_coin.id.to_string().as_str()); delete_record(&delete_table_name, &delete_condition).await; } } else if RUNNING_MODE == REAL || RUNNING_MODE == TEST { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/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(&buy_ordered_coin.symbol); url_build.push_str("&side=SELL"); url_build.push_str("&timeInForce=GTC"); url_build.push_str("&type=LIMIT"); url_build.push_str("&quantity="); url_build.push_str(sell_base_quantity.to_string().as_str()); url_build.push_str("&price="); url_build.push_str(sell_base_price.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.unwrap(); // println!("limit_order_sell실행 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("code").is_some() { // when request failed sleep(Duration::from_secs(1)).await; } else { // when request succeed insert_value_container .push(T.get("symbol").unwrap().as_str().unwrap().to_string()); // symbol insert_value_container.push(buy_ordered_coin.order_id.to_string()); // buy_order_id insert_value_container .push(T.get("orderId").unwrap().as_u64().unwrap().to_string()); // sell_order_id insert_value_container.push(server_epoch.to_string()); // transact_time insert_value_container.push(buy_ordered_coin.close_time.to_string()); // close_time insert_value_container .push(T.get("status").unwrap().as_str().unwrap().to_string()); // status insert_value_container.push(buy_ordered_coin.used_usdt.to_string()); // used_usdt if exchange_info_map.contains_key(&buy_ordered_coin.symbol) && trade_fee_map.contains_key(&buy_ordered_coin.symbol) { let quote_asset_precision = exchange_info_map.get(&buy_ordered_coin.symbol).unwrap().quote_asset_precision; let trade_fee = trade_fee_map.get(&buy_ordered_coin.symbol).unwrap().takercommission; let get_usdt = rust_decimal::prelude::FromStr::from_str( T.get("cummulativeQuoteQty").unwrap().as_str().unwrap(), ) .unwrap(); let get_usdt_fee_adjusted = decimal_mul(get_usdt, decimal_sub(dec!(1), trade_fee)) .round_dp_with_strategy( quote_asset_precision, RoundingStrategy::ToZero, ); let ordered_base_qty = rust_decimal::prelude::FromStr::from_str( T.get("origQty").unwrap().as_str().unwrap(), ) .unwrap(); if T.get("status").unwrap().as_str().unwrap() == "FILLED" { insert_value_container.push(get_usdt.to_string()); // get_usdt insert_value_container.push(get_usdt_fee_adjusted.to_string()); // get_usdt_fee_adjusted } else { insert_value_container.push(0.0.to_string()); // get_usdt insert_value_container.push(0.0.to_string()); // get_usdt_fee_adjusted } insert_value_container.push(buy_ordered_coin.buy_price.to_string()); // buy_price if T.get("status").unwrap().as_str().unwrap() == "FILLED" { let sell_price = decimal_div(get_usdt, ordered_base_qty) .round_dp_with_strategy( quote_asset_precision, RoundingStrategy::ToZero, ); insert_value_container.push(sell_price.to_string()); // sell_price } else { insert_value_container.push(0.0.to_string()); // sell_price } insert_value_container.push(buy_ordered_coin.stoploss.to_string()); // stoploss insert_value_container.push(buy_ordered_coin.target_price.to_string()); // target_price insert_value_container.push(ordered_base_qty.to_string()); // base_qty_ordered if T.get("status").unwrap().as_str().unwrap() == "FILLED" { let pure_profit_percent = decimal_mul( decimal_sub( decimal_div(get_usdt_fee_adjusted, buy_ordered_coin.used_usdt), dec!(1), ), dec!(100), ); insert_value_container .push(buy_ordered_coin.pure_profit_percent.to_string()); // pure_profit_percent } else { insert_value_container.push(0.0.to_string()); // pure_profit_percent } insert_value_container .push(buy_ordered_coin.maximum_profit_percent.to_string()); // maximum_profit_percent insert_value_container.push(buy_ordered_coin.registerer.to_string()); // registerer insert_value_container.push(buy_ordered_coin.is_long.to_string()); // is_long insert_values.push(insert_value_container.clone()); insert_records(&insert_table_name, &insert_columns, &insert_values).await; // delete record in buy_ordered_coin_list let delete_table_name = String::from("buy_ordered_coin_list"); let mut delete_condition = String::from("WHERE id = "); delete_condition.push_str(buy_ordered_coin.id.to_string().as_str()); delete_record(&delete_table_name, &delete_condition).await; } } } Err(e) => { println!("sell order failed!: {}", body); } } } Ok(()) } pub async fn monitoring_open_sell_order( client: &Client, exchange_info_map: &HashMap, trade_fee_map: &HashMap, ) -> Result<(), Box> { let open_sell_orders = select_open_sell_orders().await; if !open_sell_orders.is_empty() { let server_epoch = get_server_epoch().await; let orders_outdated = open_sell_orders .iter() .filter(|&element| server_epoch - element.transact_time >= 5_000) .collect::>(); let orders_to_be_queried = open_sell_orders .iter() .filter(|&element| server_epoch - element.transact_time < 5_000) .collect::>(); // cancel orders outdated over 30secs, delete its records in [sell_ordered_coin_list], and move them into [buy_ordered_coin_list] if !orders_outdated.is_empty() { for element in orders_outdated { cancel_sell_order(element, &client, exchange_info_map, trade_fee_map).await; sleep(Duration::from_millis(200)).await; // Use max 30 LIMIT/min } } if !orders_to_be_queried.is_empty() { for element in orders_to_be_queried { query_sell_order(element, &client, exchange_info_map, trade_fee_map).await; sleep(Duration::from_millis(300)).await; // Use max 30 LIMIT/min } } } Ok(()) } pub async fn monitoring_filled_sell_order( client: &Client, ) -> Result<(), Box> { let filled_sell_orders = select_filled_sell_orders().await; // Move record whose status is "FILLED" or "SIMUL" into [sell_history] and [achievement_evaluation] if !filled_sell_orders.is_empty() { let insert_table_name = String::from("sell_history"); let insert_columns = vec![ "symbol", "soldtime", "used_usdt", "get_usdt", "buy_price", "sell_price", "base_qty", "pure_profit_percent", "pure_profit_usdt", "registerer", ]; let mut insert_value_container: Vec> = Vec::new(); let mut insert_value_build: Vec = Vec::new(); let update_table_name = String::from("achievement_evaluation"); let server_epoch = get_server_epoch().await; let mut total_get_usdt = Decimal::new(0, 8); for element in filled_sell_orders { // build insert value let pure_profit_usdt = decimal_sub(element.get_usdt_fee_adjusted, element.used_usdt); let pure_profit_percent = decimal_mul(decimal_div(pure_profit_usdt, element.used_usdt), dec!(100)); insert_value_build.clear(); insert_value_build.push(element.symbol.clone()); // symbol insert_value_build.push(server_epoch.to_string()); // soldtime insert_value_build.push(element.used_usdt.to_string()); // used_usdt insert_value_build.push(element.get_usdt_fee_adjusted.to_string()); // get_usdt insert_value_build.push(element.buy_price.to_string()); // buy_price insert_value_build.push(element.sell_price.to_string()); // sell_price insert_value_build.push(element.base_qty_ordered.to_string()); // base_qty insert_value_build.push(pure_profit_percent.to_string()); // pure_profit_percent insert_value_build.push(pure_profit_usdt.to_string()); // pure_profit_usdt insert_value_build.push(element.registerer.to_string()); // registerer insert_value_container.push(insert_value_build.clone()); // update [achievement_evaluation] let mut value_build1 = String::from("invested_usdt + "); let mut invested_usdt = Decimal::new(0, 8); invested_usdt = decimal_add(invested_usdt, element.used_usdt); value_build1.push_str(invested_usdt.to_string().as_str()); let update_value1 = vec![(String::from("invested_usdt"), value_build1)]; let update_condition = vec![(String::from("strategist"), element.registerer.to_string())]; update_record3(&update_table_name, &update_value1, &update_condition) .await .unwrap(); if element.get_usdt_fee_adjusted.is_sign_negative() { let mut value_build2 = String::from("usdt_profit - "); value_build2.push_str(element.get_usdt_fee_adjusted.abs().to_string().as_str()); let update_value2 = vec![(String::from("usdt_profit"), value_build2)]; update_record3(&update_table_name, &update_value2, &update_condition) .await .unwrap(); } else { let mut value_build2 = String::from("usdt_profit + "); value_build2.push_str(element.get_usdt_fee_adjusted.to_string().as_str()); let update_value2 = vec![(String::from("usdt_profit"), value_build2)]; update_record3(&update_table_name, &update_value2, &update_condition) .await .unwrap(); } // add available_usdt add_available_usdt(element.get_usdt_fee_adjusted).await; // delete in [sell_ordered_coin_list] let delete_table_name = String::from("sell_ordered_coin_list"); let mut delete_condition = String::from("WHERE id = "); delete_condition.push_str(element.id.to_string().as_str()); delete_record(&delete_table_name, &delete_condition).await; // total_get_usdt = decimal_add(total_get_usdt, element.get_usdt_fee_adjusted); println!("sell {}: {:.2}%", element.symbol, pure_profit_percent); } // update profit_percent in [achievement_evaluation] update_profit_percent().await; // insert in [sell_history] insert_records(&insert_table_name, &insert_columns, &insert_value_container).await; // add available_usdt // if total_get_usdt.is_sign_positive() { // add_available_usdt(total_get_usdt).await; // } else { // sub_available_usdt(total_get_usdt.abs()).await; // } } Ok(()) } pub async fn market_order( symbol: &String, side: OrderSide, quantity: u64, client: &Client, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/order?"; url.push_str(endpoint_url); hmac_signature(&mut url).await; // add parameters into URL url.push_str("&symbol="); url.push_str(symbol.as_str()); match side { OrderSide::Buy => { url.push_str("&side=BUY"); } OrderSide::Sell => { url.push_str("&side=SELL"); } } url.push_str("&type=MARKET"); url.push_str("&quantity="); url.push_str(quantity.to_string().as_str()); let res = client .post(&url) .header("X-MBX-APIKEY", api_key) .send() .await?; Ok(()) } // Cancel an NEW or PARTIALLY FILLED order. (/api, Weight(IP) 1) pub async fn cancel_buy_order( order: &BuyOrderedCoinList, client: &Client, trade_fee_map: &HashMap, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else if RUNNING_MODE == REAL { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/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("buy_ordered_coin_list"); let cummulative_quote_qty: Decimal = rust_decimal::prelude::FromStr::from_str( T.get("cummulativeQuoteQty").unwrap().as_str().unwrap(), ) .unwrap(); if cummulative_quote_qty.is_zero() { // NOT Filled case // delete record in [buy_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; add_available_usdt(order.used_usdt).await; let update_table_name = String::from("suggested_coin_list"); let update_condition = vec![ (String::from("symbol"), order.symbol.clone()), (String::from("close_time"), order.close_time.to_string()), ]; let update_values: Vec<(String, String)> = vec![(String::from("already_buy"), 0.to_string())]; update_record2(&update_table_name, &update_values, &update_condition).await; } else { // Patially Filled case // update values in [buy_ordered_coin_list] let mut status_value_build = String::from("\'"); status_value_build.push_str("FILLED"); status_value_build.push('\''); // calculate values to be updated if trade_fee_map.contains_key(&order.symbol) { let trade_fee = trade_fee_map.get(&order.symbol).unwrap().takercommission; let base_qty_ordered = rust_decimal::prelude::FromStr::from_str( T.get("executedQty").unwrap().as_str().unwrap(), ) .unwrap(); let base_qty_fee_adjusted = decimal_mul(base_qty_ordered, decimal_sub(dec!(1), trade_fee)); let buy_price = decimal_div(cummulative_quote_qty, base_qty_ordered) .round_dp_with_strategy(8, RoundingStrategy::ToZero); let update_values = vec![ (String::from("status"), status_value_build), // status (String::from("used_usdt"), cummulative_quote_qty.to_string()), // used_usdt (String::from("buy_price"), buy_price.to_string()), // buy_price ( String::from("base_qty_ordered"), base_qty_ordered.to_string(), ), // base_qty_ordered ( String::from("base_qty_fee_adjusted"), base_qty_fee_adjusted.to_string(), ), // base_qty_fee_adjusted ]; let update_condition = vec![ (String::from("order_id"), order.order_id.to_string()), (String::from("symbol"), order.symbol.clone()), ]; update_record3(&table_name, &update_values, &update_condition) .await .unwrap(); // 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, )); } println!("partially buy {}", 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_buy_order(order, &client, trade_fee_map).await; } } Err(e) => { query_buy_order(order, &client, trade_fee_map).await; // println!("cancel order buy failed!: {}", body); } } Ok(()) } // Cancel an NEW or PARTIALLY FILLED order. (/api, Weight(IP) 1) pub async fn cancel_sell_order( order: &SellOrderedCoinList, client: &Client, exchange_info_map: &HashMap, trade_fee_map: &HashMap, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else if RUNNING_MODE == REAL { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/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.sell_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_sell_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 insert_table_name = String::from("buy_ordered_coin_list"); let insert_columns = vec![ "symbol", "order_id", "transact_time", "close_time", "status", "used_usdt", "expected_get_usdt", "expected_usdt_profit", "buy_price", "current_price", "stoploss", "target_price", "base_qty_ordered", "base_qty_fee_adjusted", "pure_profit_percent", "minimum_profit_percent", "maximum_profit_percent", "registerer", "is_long", ]; let base_qty_executed: Decimal = rust_decimal::prelude::FromStr::from_str( T.get("executedQty").unwrap().as_str().unwrap(), ) .unwrap(); let base_qty_ordered: Decimal = rust_decimal::prelude::FromStr::from_str( T.get("origQty").unwrap().as_str().unwrap(), ) .unwrap(); if base_qty_executed.is_zero() { // Not FILLED // delete record in [sell_ordered_coin_list] let table_name = String::from("sell_ordered_coin_list"); let mut condition_build = String::from("WHERE sell_order_id = "); condition_build.push_str(order.sell_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; // insert record in [buy_ordered_coin_list] let mut insert_values: Vec> = Vec::new(); let mut insert_value_container: Vec = Vec::new(); let server_epoch = get_server_epoch().await; insert_value_container.push(order.symbol.clone()); // symbol insert_value_container.push(order.buy_order_id.to_string()); // order_id insert_value_container.push(order.transact_time.to_string()); // transact_time insert_value_container.push(order.close_time.to_string()); // close_time insert_value_container.push(String::from("FILLED")); // status insert_value_container.push(order.used_usdt.to_string()); // used_usdt insert_value_container.push(0.0.to_string()); // expected_get_usdt insert_value_container.push(0.0.to_string()); // expected_usdt_profit insert_value_container.push(order.buy_price.to_string()); // buy_price insert_value_container.push(0.0.to_string()); // current_price insert_value_container.push(order.stoploss.to_string()); // stoploss insert_value_container.push(order.target_price.to_string()); // target_price insert_value_container.push(order.base_qty_ordered.to_string()); // base_qty_ordered insert_value_container.push(order.base_qty_ordered.to_string()); // base_qty_fee_adjusted insert_value_container.push(0.0.to_string()); // pure_profit_percent insert_value_container.push(0.0.to_string()); // minimum_profit_percent insert_value_container.push(order.maximum_profit_percent.to_string()); // maximum_profit_percent insert_value_container.push(order.registerer.to_string()); // registerer insert_value_container.push(order.is_long.to_string()); // is_long insert_values.push(insert_value_container.clone()); insert_records(&insert_table_name, &insert_columns, &insert_values).await; } else { if exchange_info_map.contains_key(&order.symbol) && trade_fee_map.contains_key(&order.symbol) { let quote_asset_precision = exchange_info_map.get(&order.symbol).unwrap().quote_asset_precision; let trade_fee = trade_fee_map.get(&order.symbol).unwrap().takercommission; if base_qty_executed == base_qty_ordered { // FILLED case // update status FILLED let get_usdt = rust_decimal::prelude::FromStr::from_str( T.get("cummulativeQuoteQty").unwrap().as_str().unwrap(), ) .unwrap(); let get_usdt_fee_adjusted = decimal_mul(get_usdt, decimal_sub(dec!(1), trade_fee)) .round_dp_with_strategy( quote_asset_precision, RoundingStrategy::ToZero, ); let sell_price = decimal_div(get_usdt, base_qty_executed) .round_dp_with_strategy( quote_asset_precision, RoundingStrategy::ToZero, ); let pure_profit_percent = decimal_mul( decimal_sub( decimal_div(get_usdt_fee_adjusted, order.used_usdt), dec!(1), ), dec!(100), ); let table_name = String::from("sell_ordered_coin_list"); let mut value_build = String::from("\'"); value_build.push_str("FILLED"); value_build.push('\''); let update_values = vec![ (String::from("status"), value_build), (String::from("get_usdt"), get_usdt.to_string()), ( String::from("get_usdt_fee_adjusted"), get_usdt_fee_adjusted.to_string(), ), (String::from("sell_price"), sell_price.to_string()), ( String::from("pure_profit_percent"), pure_profit_percent.to_string(), ), ]; let update_condition = vec![(String::from("id"), order.id.to_string())]; update_record3(&table_name, &update_values, &update_condition) .await .unwrap(); } else { // PARTIALLY FILLED case // reflect partially filled information and update status with FILLED let used_usdt = decimal_mul(base_qty_executed, order.buy_price); let get_usdt = rust_decimal::prelude::FromStr::from_str( T.get("cummulativeQuoteQty").unwrap().as_str().unwrap(), ) .unwrap(); let get_usdt_fee_adjusted = decimal_mul(get_usdt, decimal_sub(dec!(1), trade_fee)) .round_dp_with_strategy( quote_asset_precision, RoundingStrategy::ToZero, ); let rest_used_usdt = decimal_sub(order.used_usdt, used_usdt); let sell_price = decimal_div(get_usdt, base_qty_executed) .round_dp_with_strategy( quote_asset_precision, RoundingStrategy::ToZero, ); let pure_profit_percent = decimal_mul( decimal_sub( decimal_div(get_usdt_fee_adjusted, get_usdt), dec!(1), ), dec!(100), ); let table_name = String::from("sell_ordered_coin_list"); let mut value_build = String::from("\'"); value_build.push_str("FILLED"); value_build.push('\''); let update_values = vec![ (String::from("status"), value_build), (String::from("used_usdt"), used_usdt.to_string()), (String::from("get_usdt"), get_usdt.to_string()), ( String::from("get_usdt_fee_adjusted"), get_usdt_fee_adjusted.to_string(), ), ( String::from("base_qty_ordered"), base_qty_executed.to_string(), ), (String::from("sell_price"), sell_price.to_string()), ( String::from("pure_profit_percent"), pure_profit_percent.to_string(), ), ]; let update_condition = vec![(String::from("id"), order.id.to_string())]; update_record3(&table_name, &update_values, &update_condition) .await .unwrap(); // insert record in [buy_ordered_coin_list] let rest_base_qty = decimal_sub(base_qty_ordered, base_qty_executed); let rest_base_qty_fee_adjusted = decimal_mul(rest_base_qty, decimal_sub(dec!(1), trade_fee)); let mut insert_values: Vec> = Vec::new(); let mut insert_value_container: Vec = Vec::new(); let server_epoch = get_server_epoch().await; insert_value_container.push(order.symbol.clone()); // symbol insert_value_container.push(order.buy_order_id.to_string()); // order_id insert_value_container.push(server_epoch.to_string()); // transact_time insert_value_container.push(order.close_time.to_string()); // close_time insert_value_container.push(String::from("FILLED")); // status insert_value_container.push(rest_used_usdt.to_string()); // used_usdt insert_value_container.push(0.0.to_string()); // expected_get_usdt insert_value_container.push(0.0.to_string()); // expected_usdt_profit insert_value_container.push(order.buy_price.to_string()); // buy_price insert_value_container.push(0.0.to_string()); // current_price insert_value_container.push(order.stoploss.to_string()); // stoploss insert_value_container.push(order.target_price.to_string()); // target_price insert_value_container.push(rest_base_qty.to_string()); // base_qty_ordered insert_value_container.push(rest_base_qty_fee_adjusted.to_string()); // base_qty_fee_adjusted insert_value_container.push(0.0.to_string()); // pure_profit_percent insert_value_container.push(0.0.to_string()); // minimum_profit_percent insert_value_container.push(0.0.to_string()); // maximum_profit_percent insert_value_container.push(order.registerer.to_string()); // registerer insert_value_container.push(order.is_long.to_string()); // is_long insert_values.push(insert_value_container.clone()); insert_records(&insert_table_name, &insert_columns, &insert_values) .await; } } } } else { query_sell_order(&order, &client, exchange_info_map, trade_fee_map).await; } } 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_sell_order(&order, &client, exchange_info_map, trade_fee_map).await; } } Err(e) => { println!("cancel sell order failed!: {}", body); } } Ok(()) } // cancel all open orders on a symbol (/api, Weight(IP) 1) pub async fn cancel_all_order( symbol: &String, client: &Client, ) -> Result<(), Box> { // building URL let mut url = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); } else if RUNNING_MODE == REAL { url.push_str(URL); } let endpoint_url = "/api/v3/openOrders?"; url.push_str(endpoint_url); // add parameters into URL url.push_str("&symbol="); url.push_str(symbol.as_str()); url.push_str("×tamp="); url.push_str(get_timestamp().await.as_str()); let res = client.delete(&url).send().await?; Ok(()) } // Get all account orders; active, canceled, or filled. on a symbol (/api, Weight(IP) 10) pub async fn all_orders( symbol: &String, client: &Client, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else if RUNNING_MODE == REAL { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/allOrders?"; url.push_str(endpoint_url); let mut url_build = String::new(); // add parameters into URL url_build.push_str("&symbol="); url_build.push_str(&symbol); 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!("{}", body); Ok(()) } // query order and update record in buy_ordered_coin_list (/api, Weight(IP) 2) pub async fn query_buy_order( order: &BuyOrderedCoinList, client: &Client, trade_fee_map: &HashMap, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else if RUNNING_MODE == REAL { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/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" { let update_table_name = String::from("suggested_coin_list"); let update_condition = vec![ (String::from("symbol"), order.symbol.clone()), (String::from("close_time"), order.close_time.to_string()), ]; let update_values: Vec<(String, String)> = vec![(String::from("already_buy"), 1.to_string())]; update_record2(&update_table_name, &update_values, &update_condition).await; // update values in [buy_ordered_coin_list] let table_name = String::from("buy_ordered_coin_list"); let mut value_build = String::from("\'"); value_build.push_str(T.get("status").unwrap().as_str().unwrap()); value_build.push('\''); // calculate values to be updated if trade_fee_map.contains_key(&order.symbol) { let trade_fee = trade_fee_map.get(&order.symbol).unwrap().takercommission; let base_qty_ordered = rust_decimal::prelude::FromStr::from_str( T.get("executedQty").unwrap().as_str().unwrap(), ) .unwrap(); let base_qty_fee_adjusted = decimal_mul(base_qty_ordered, decimal_sub(dec!(1), trade_fee)); let cummulative_quote_qty = rust_decimal::prelude::FromStr::from_str( T.get("cummulativeQuoteQty").unwrap().as_str().unwrap(), ) .unwrap(); let buy_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("buy_price"), buy_price.to_string()), // buy_price ( String::from("base_qty_ordered"), base_qty_ordered.to_string(), ), // base_qty_ordered ( String::from("base_qty_fee_adjusted"), base_qty_fee_adjusted.to_string(), ), // base_qty_fee_adjusted ]; let update_condition = vec![ (String::from("order_id"), order.order_id.to_string()), (String::from("symbol"), order.symbol.clone()), ]; update_record3(&table_name, &update_values, &update_condition) .await .unwrap(); if T.get("status").unwrap().as_str().unwrap() == "FILLED" { println!("buy {}", 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("suggested_coin_list"); let update_condition = vec![ (String::from("symbol"), order.symbol.clone()), (String::from("close_time"), order.close_time.to_string()), ]; let update_values: Vec<(String, String)> = vec![(String::from("already_buy"), 0.to_string())]; } } } Err(e) => { println!("query order failed!: {}", body); } } Ok(()) } // query order and update record in sell_ordered_coin_list (/api, Weight(IP) 2) pub async fn query_sell_order( order: &SellOrderedCoinList, client: &Client, exchange_info_map: &HashMap, trade_fee_map: &HashMap, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else if RUNNING_MODE == REAL { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/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.sell_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_sell_order실행 body: {}", 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_and(|a| a.as_str().unwrap() == "FILLED") || T.get("status").is_some_and(|a| a.as_str().unwrap() == "PARTIALLY_FILLED") { if exchange_info_map.contains_key(&order.symbol) && trade_fee_map.contains_key(&order.symbol) { let quote_asset_precision = exchange_info_map.get(&order.symbol).unwrap().quote_asset_precision; let trade_fee = trade_fee_map.get(&order.symbol).unwrap().takercommission; let get_usdt = rust_decimal::prelude::FromStr::from_str( T.get("cummulativeQuoteQty").unwrap().as_str().unwrap(), ) .unwrap(); let get_usdt_fee_adjusted = decimal_mul(get_usdt, decimal_sub(dec!(1), trade_fee)) .round_dp_with_strategy( quote_asset_precision, RoundingStrategy::ToZero, ); let ordered_base_qty = rust_decimal::prelude::FromStr::from_str( T.get("executedQty").unwrap().as_str().unwrap(), ) .unwrap(); let sell_price = decimal_div(get_usdt, ordered_base_qty) .round_dp_with_strategy(quote_asset_precision, RoundingStrategy::ToZero); let pure_profit_percent = decimal_mul( decimal_sub(decimal_div(get_usdt_fee_adjusted, order.used_usdt), dec!(1)), dec!(100), ); let table_name = String::from("sell_ordered_coin_list"); let mut value_build = String::from("\'"); value_build.push_str(T.get("status").unwrap().as_str().unwrap()); value_build.push('\''); let update_values = vec![ (String::from("status"), value_build), (String::from("get_usdt"), get_usdt.to_string()), ( String::from("get_usdt_fee_adjusted"), get_usdt_fee_adjusted.to_string(), ), (String::from("sell_price"), sell_price.to_string()), ( String::from("pure_profit_percent"), pure_profit_percent.to_string(), ), ]; let update_condition = vec![(String::from("id"), order.id.to_string())]; update_record3(&table_name, &update_values, &update_condition) .await .unwrap(); } } } Err(e) => { println!("query sell order failed!: {}", body); } } Ok(()) } // request current open orders (/api, Weight(IP) 40) pub async fn current_open_orders( client: &Client, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else if RUNNING_MODE == REAL { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/openOrders?"; 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.unwrap(); // println!("msg: {}", body); Ok(()) } // request all information and status of current wallet. (/sapi, Weight(IP) 10) // NOT FOR TESTNET pub async fn all_coins_information( client: &Client, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); url.push_str(URL); api_key = API_KEY.to_string(); let endpoint_url = "/sapi/v1/capital/config/getall?"; 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) => { let mut into_vec = T.as_array(); if into_vec == None { return Err("Err")?; } let insert_table_name = String::from("wallet"); let insert_columns = vec!["symbol", "free", "locked"]; let mut insert_values: Vec> = Vec::new(); let mut insert_value_container: Vec = Vec::new(); for element in into_vec.unwrap() { if element.is_object() { insert_value_container.clear(); insert_value_container .push(element.get("coin").unwrap().as_str().unwrap().to_string()); insert_value_container .push(element.get("free").unwrap().as_str().unwrap().to_string()); insert_value_container .push(element.get("locked").unwrap().as_str().unwrap().to_string()); insert_values.push(insert_value_container.clone()); } } delete_all_rows(&insert_table_name).await; insert_records(&insert_table_name, &insert_columns, &insert_values).await; } Err(e) => { println!("all coins information failed!: {}", body); } } Ok(()) } // request balances of all rest coins in the account. (/api, Weight(IP) 10) pub async fn account_information( client: &Client, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); let mut api_key = String::new(); if RUNNING_MODE == TEST { url.push_str(URL_TEST); api_key = API_KEY_TESTNET.to_string(); } else if RUNNING_MODE == REAL { url.push_str(URL); api_key = API_KEY.to_string(); } let endpoint_url = "/api/v3/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.unwrap(); // deserialize JSON let v = serde_json::from_str::(body.as_str()); match v { Ok(T) => { let option_result = T.get("balances"); if option_result.is_none() { return Err("Err")?; } let mut into_vec = option_result.unwrap().as_array(); if into_vec == None { return Err("Err")?; } // insert contents into table let mut insert_table_name = String::new(); if RUNNING_MODE == TEST { insert_table_name = String::from("wallet_testnet"); } else if RUNNING_MODE == REAL { insert_table_name = String::from("wallet"); } let insert_columns = vec!["asset", "free", "locked"]; let mut insert_values: Vec> = Vec::new(); let mut insert_value_container: Vec = Vec::new(); for element in into_vec.unwrap() { if element.is_object() { insert_value_container.clear(); insert_value_container .push(element.get("asset").unwrap().as_str().unwrap().to_string()); insert_value_container .push(element.get("free").unwrap().as_str().unwrap().to_string()); insert_value_container .push(element.get("locked").unwrap().as_str().unwrap().to_string()); insert_values.push(insert_value_container.clone()); } } delete_all_rows(&insert_table_name).await; insert_records(&insert_table_name, &insert_columns, &insert_values).await; } Err(e) => { println!("account information failed!: {}", body); } } Ok(()) } async fn hmac_signature(query: &mut String) { // fetch time information from [time] table let table_name = String::from("time"); let columns = String::from("*"); let condition = None; let mut time_info = TimeData { server_epoch: 0, local_epoch: 0, epoch_difference: 0, server_ymdhs: String::new(), local_ymdhs: String::new(), last_server_epoch: 0, last_server_ymdhs: String::new(), }; let select_result = select_record(&table_name, &columns, &condition, &time_info) .await .unwrap(); let difference_epoch = select_result.first().unwrap().epoch_difference; let server_epoch = select_result.first().unwrap().server_epoch; // build query message // local 시간이 server 시간보다 너무 앞서거나 뒤쳐지면 USER_DATA를 요청할 수 없으므로 local과 server 의 시간 차이만큼을 local에서 빼어 보정한 뒤 // 이를 timestamp로 사용한다. let mut timestamp; if difference_epoch >= 0 { timestamp = (server_epoch as u128 + difference_epoch as u128).to_string(); } else if difference_epoch < 0 { timestamp = (server_epoch as u128 + (difference_epoch * -1) as u128).to_string(); } else { timestamp = server_epoch.to_string(); } let recv_window_size = "30000".to_string(); // default: 5,000ms, Max: 60,000ms let mut query_build = String::from("×tamp="); query_build.push_str(×tamp); query_build.push_str("&recvWindow="); query_build.push_str(&recv_window_size); let mut secret_key = String::new(); if RUNNING_MODE == TEST { secret_key.push_str(SECRET_KEY_TESTNET); } else if RUNNING_MODE == REAL { secret_key.push_str(SECRET_KEY); } query.push_str(&query_build); let signature = HMAC::mac(&query.as_bytes(), secret_key.as_bytes()); query.push_str("&signature="); query.push_str(signature.encode_hex::().as_str()); } async fn get_timestamp() -> String { // fetch time information from [time] table let table_name = String::from("time"); let columns = String::from("*"); let condition = None; let mut time_info = TimeData { server_epoch: 0, local_epoch: 0, epoch_difference: 0, server_ymdhs: String::new(), local_ymdhs: String::new(), last_server_epoch: 0, last_server_ymdhs: String::new(), }; let select_result = select_record(&table_name, &columns, &condition, &time_info) .await .unwrap(); let difference_epoch = select_result.first().unwrap().epoch_difference; let local_epoch = select_result.first().unwrap().local_epoch; // build query message // local 시간이 server 시간보다 너무 앞서거나 뒤쳐지면 USER_DATA를 요청할 수 없으므로 local과 server 의 시간 차이만큼을 local에서 빼어 보정한 뒤 // 이를 timestamp로 사용한다. let mut timestamp; if difference_epoch >= 0 { timestamp = (local_epoch as u128 - difference_epoch as u128).to_string(); } else if difference_epoch < 0 { timestamp = (local_epoch as u128 - (difference_epoch * -1) as u128).to_string(); } else { timestamp = local_epoch.to_string(); } timestamp } // parameter 0: select all registerers pub async fn select_filled_buy_orders( registerer: u32, ) -> Result, Box> { let select_table_name = String::from("buy_ordered_coin_list"); let select_columns = String::from("*"); let mut select_condition_build = String::from("WHERE (status = 'FILLED' or status = 'SIMUL')"); if registerer > 0 { select_condition_build.push_str(" and registerer = "); select_condition_build.push_str(registerer.to_string().as_str()); } let select_condition = Some(select_condition_build); let data_struct = BuyOrderedCoinList::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_buy_order() error!"); Err("error")? } } // select open buy orders (NEW, Partially Filled) async fn select_open_buy_orders() -> Vec { let select_table_name = String::from("buy_ordered_coin_list"); let select_columns = String::from("*"); let select_condition = Some(String::from( "WHERE status = 'NEW' or status = 'PARTIALLY_FILLED'", )); let data_struct = BuyOrderedCoinList::new(); let select_result = try_select_record( &select_table_name, &select_columns, &select_condition, &data_struct, ) .await .unwrap(); select_result } pub async fn select_filled_sell_orders() -> Vec { let select_table_name = String::from("sell_ordered_coin_list"); let select_columns = String::from("*"); let select_condition = Some(String::from("WHERE status = 'FILLED' or status = 'SIMUL'")); let data_struct = SellOrderedCoinList::new(); let select_result = try_select_record( &select_table_name, &select_columns, &select_condition, &data_struct, ) .await .unwrap(); select_result } // select open sell orders (NEW, Partially Filled) async fn select_open_sell_orders() -> Vec { let select_table_name = String::from("sell_ordered_coin_list"); let select_columns = String::from("*"); let select_condition = Some(String::from( "WHERE status = 'NEW' or status = 'PARTIALLY_FILLED'", )); let data_struct = SellOrderedCoinList::new(); let select_result = try_select_record( &select_table_name, &select_columns, &select_condition, &data_struct, ) .await .unwrap(); select_result }