tradingbot/src/coex/order_team.rs
2023-11-19 01:58:09 +09:00

2187 lines
91 KiB
Rust

// 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::{CoinPriceData, 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::*;
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 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 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),
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),
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<dyn std::error::Error + Send + Sync>> {
// 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::<Value>(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_vec: &Vec<ExchangeInfo>,
trade_fee: Decimal,
tif: TimeInForce,
order_price: Decimal,
order_quantity: Decimal,
used_usdt: Decimal,
simul_base_qty_fee_adjusted: &String,
client: &Client,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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",
"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<String>> = Vec::new();
let mut insert_value_container: Vec<String> = Vec::new();
let server_epoch = 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(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::<Value>(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(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(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(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,
exchange_info_vec: &Vec<ExchangeInfo>,
trade_fee_vec: &Vec<TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let open_buy_orders = select_open_buy_orders().await;
if !open_buy_orders.is_empty() {
let server_epoch = server_epoch().await;
let orders_outdated = open_buy_orders
.iter()
.filter(|&element| server_epoch - element.transact_time >= 600_000)
.collect::<Vec<&BuyOrderedCoinList>>(); // wait up to 600 secs
let orders_to_be_queried = open_buy_orders
.iter()
.filter(|&element| server_epoch - element.transact_time < 600_000)
.collect::<Vec<&BuyOrderedCoinList>>();
// 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, exchange_info_vec, trade_fee_vec).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, exchange_info_vec, trade_fee_vec).await;
sleep(Duration::from_millis(500)).await; // Use max 30 LIMIT/min
}
}
}
Ok(())
}
pub async fn update_price_of_filled_buy_order(
coin_price_vec: &Vec<CoinPriceData>,
exchange_info_vec: &Vec<ExchangeInfo>,
trade_fee_vec: &Vec<TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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_vec.clone();
let exchange_info_vec_c = exchange_info_vec.clone();
let trade_fee_vec_c = trade_fee_vec.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<BuyOrderedCoinList>,
coin_price_vec: &Vec<CoinPriceData>,
exchange_info_vec: &Vec<ExchangeInfo>,
trade_fee_vec: &Vec<TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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 = 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<String> = Vec::new();
let mut update_record: Vec<Vec<String>> = 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();
let price_index_option = coin_price_vec
.iter()
.position(|x| *x.symbol == element.symbol);
let lot_step_size_result = exchange_info_vec
.iter()
.position(|ExchangeInfo| ExchangeInfo.symbol == element.symbol);
let quote_commission_precision_result = exchange_info_vec
.iter()
.position(|ExchangeInfo| ExchangeInfo.symbol == element.symbol);
let trade_fee_result = trade_fee_vec
.iter()
.position(|TradeFee| TradeFee.symbol == element.symbol);
if price_index_option.is_some()
&& lot_step_size_result.is_some()
&& quote_commission_precision_result.is_some()
&& trade_fee_result.is_some()
{
let price_result: Option<Decimal> = rust_decimal::prelude::FromPrimitive::from_f64(
coin_price_vec[price_index_option.unwrap()].current_price,
);
if price_result.is_some() {
price = price_result.unwrap();
if !price.is_zero() {
// to get quote_commission_precision
let trade_fee = trade_fee_vec[trade_fee_result.unwrap()].takercommission;
let lot_step_size = exchange_info_vec[lot_step_size_result.unwrap()].stepsize;
let quote_commission_precision = exchange_info_vec
[quote_commission_precision_result.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),
);
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_vec: &Vec<ExchangeInfo>,
trade_fee_vec: &Vec<TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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",
"base_qty_ordered",
"pure_profit_percent",
"maximum_profit_percent",
"registerer",
"is_long",
];
let mut insert_values: Vec<Vec<String>> = Vec::new();
let mut insert_value_container: Vec<String> = Vec::new();
let server_epoch = server_epoch().await;
if RUNNING_MODE == SIMUL && buy_ordered_coin.status == "SIMUL" {
let quote_asset_precision = exchange_info_vec
.iter()
// FIXME: find() should be position()
.find(|ExchangeInfo| ExchangeInfo.symbol == buy_ordered_coin.symbol)
.unwrap()
.quote_asset_precision;
let trade_fee = trade_fee_vec
.iter()
// FIXME: find() should be position()
.find(|TradeFee| TradeFee.symbol == 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(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::<Value>(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
let quote_asset_precision = exchange_info_vec
.iter()
// FIXME: find() should be position()
.find(|ExchangeInfo| ExchangeInfo.symbol == buy_ordered_coin.symbol)
.unwrap()
.quote_asset_precision;
let trade_fee = trade_fee_vec
.iter()
// FIXME: find() should be position()
.find(|TradeFee| TradeFee.symbol == 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(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_vec: &Vec<ExchangeInfo>,
trade_fee_vec: &Vec<TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let open_sell_orders = select_open_sell_orders().await;
if !open_sell_orders.is_empty() {
let server_epoch = server_epoch().await;
let orders_outdated = open_sell_orders
.iter()
.filter(|&element| server_epoch - element.transact_time >= 5_000)
.collect::<Vec<&SellOrderedCoinList>>();
let orders_to_be_queried = open_sell_orders
.iter()
.filter(|&element| server_epoch - element.transact_time < 5_000)
.collect::<Vec<&SellOrderedCoinList>>();
// 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_vec, trade_fee_vec).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_vec, trade_fee_vec).await;
sleep(Duration::from_millis(300)).await; // Use max 30 LIMIT/min
}
}
}
Ok(())
}
pub async fn monitoring_filled_sell_order(
client: &Client,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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<String>> = Vec::new();
let mut insert_value_build: Vec<String> = Vec::new();
let update_table_name = String::from("achievement_evaluation");
let server_epoch = 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);
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(
decimal_mul(decimal_div(pure_profit_usdt, element.used_usdt), dec!(100))
.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 {}", element.symbol);
}
// 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<dyn std::error::Error + Send + Sync>> {
// 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,
exchange_info_vec: &Vec<ExchangeInfo>,
trade_fee_vec: &Vec<TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// 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::<Value>(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
let trade_fee = trade_fee_vec
.iter()
// FIXME: find() should be position()
.find(|TradeFee| TradeFee.symbol == 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 base_asset_precision = exchange_info_vec
.iter()
// FIXME: find() should be position()
.find(|ExchangeInfo| ExchangeInfo.symbol == order.symbol)
.unwrap()
.base_asset_precision;
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, exchange_info_vec, trade_fee_vec).await;
}
}
Err(e) => {
query_buy_order(order, &client, exchange_info_vec, trade_fee_vec).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_vec: &Vec<ExchangeInfo>,
trade_fee_vec: &Vec<TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// 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::<Value>(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",
"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<String>> = Vec::new();
let mut insert_value_container: Vec<String> = Vec::new();
let server_epoch = 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.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 base_qty_executed == base_qty_ordered {
// FILLED case
// update status FILLED
let quote_asset_precision = exchange_info_vec
.iter()
// FIXME: find() should be position()
.find(|ExchangeInfo| ExchangeInfo.symbol == order.symbol)
.unwrap()
.quote_asset_precision;
let trade_fee = trade_fee_vec
.iter()
// FIXME: find() should be position()
.find(|TradeFee| TradeFee.symbol == 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 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 quote_asset_precision = exchange_info_vec
.iter()
// FIXME: find() should be position()
.find(|ExchangeInfo| ExchangeInfo.symbol == order.symbol)
.unwrap()
.quote_asset_precision;
let trade_fee = trade_fee_vec
.iter()
// FIXME: find() should be position()
.find(|TradeFee| TradeFee.symbol == order.symbol)
.unwrap()
.takercommission;
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<String>> = Vec::new();
let mut insert_value_container: Vec<String> = Vec::new();
let server_epoch = 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(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_vec, trade_fee_vec).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_vec, trade_fee_vec).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<dyn std::error::Error + Send + Sync>> {
// 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("&timestamp=");
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<dyn std::error::Error + Send + Sync>> {
// 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,
exchange_info_vec: &Vec<ExchangeInfo>,
trade_fee_vec: &Vec<TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// 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::<Value>(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
let trade_fee = trade_fee_vec
.iter()
// FIXME: find() should be position()
.find(|TradeFee| TradeFee.symbol == 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 base_asset_precision = exchange_info_vec
.iter()
// FIXME: find() should be position()
.find(|ExchangeInfo| ExchangeInfo.symbol == order.symbol)
.unwrap()
.base_asset_precision;
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_vec: &Vec<ExchangeInfo>,
trade_fee_vec: &Vec<TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// 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::<Value>(body.as_str());
match v {
Ok(T) => {
if T.get("status").unwrap().as_str().unwrap() == "FILLED"
|| T.get("status").unwrap().as_str().unwrap() == "PARTIALLY_FILLED"
{
let quote_asset_precision = exchange_info_vec
.iter()
// FIXME: find() should be position()
.find(|ExchangeInfo| ExchangeInfo.symbol == order.symbol)
.unwrap()
.quote_asset_precision;
let trade_fee = trade_fee_vec
.iter()
// FIXME: find() should be position()
.find(|TradeFee| TradeFee.symbol == 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<dyn std::error::Error + Send + Sync>> {
// 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<dyn std::error::Error + Send + Sync>> {
// 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::<Value>(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<String>> = Vec::new();
let mut insert_value_container: Vec<String> = 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<dyn std::error::Error + Send + Sync>> {
// 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::<Value>(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<String>> = Vec::new();
let mut insert_value_container: Vec<String> = 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("&timestamp=");
query_build.push_str(&timestamp);
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::<String>().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<Vec<BuyOrderedCoinList>, Box<dyn std::error::Error + Send + Sync>> {
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<BuyOrderedCoinList> {
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<SellOrderedCoinList> {
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<SellOrderedCoinList> {
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
}