315 lines
13 KiB
Rust
315 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 = futures_trade_fee.maker_fee_percent;
|
|
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 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_precision,
|
|
RoundingStrategy::ToZero,
|
|
),
|
|
decimal_sub(dec!(1), trade_fee),
|
|
);
|
|
|
|
// TODO: sell_count >=1 이면 expected_get_usdt 는 한번만 tradefee만 적용하여 업데이트 할 것. 현재는 수수료를 2번 (매수,매도)를 계산함. 아래 변수에 든 값으로 업데이트 하면 됨
|
|
// let expected_get_usdt =
|
|
// decimal_mul(base_qty_to_be_ordered, price).round_dp_with_strategy(
|
|
// quote_commission_precision,
|
|
// RoundingStrategy::ToZero,
|
|
// );
|
|
let mut pure_profit_percent;
|
|
if element.position.contains("Long") {
|
|
pure_profit_percent = ((expected_get_usdt.to_f64().unwrap()
|
|
/ element.used_usdt.to_f64().unwrap())
|
|
- 1.0)
|
|
* 100.0;
|
|
} else {
|
|
pure_profit_percent = ((expected_get_usdt.to_f64().unwrap()
|
|
/ element.used_usdt.to_f64().unwrap())
|
|
- 1.0)
|
|
* -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(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
|
|
}
|
|
|
|
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")?
|
|
}
|
|
}
|