2187 lines
91 KiB
Rust
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("×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<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("×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::<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
|
|
}
|