tradingbot/src/future/table_mgmt.rs

304 lines
13 KiB
Rust

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