983 lines
38 KiB
Rust
983 lines
38 KiB
Rust
// use crates
|
|
use crate::coex::assets_managing_team::*;
|
|
use crate::coex::exchange_team::*;
|
|
use crate::coin_health_check_team::request_others::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 log;
|
|
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 std::collections::HashMap;
|
|
use tokio::time::*;
|
|
use super::{hmac_signature, REAL, SIMUL, TEST, RUNNING_MODE, FUTURES_URL, FUTURES_URL_TEST, API_KEY, API_KEY_TESTNET, FuturesExchangeInfo, PositionCoinList, FuturesTradeFee, EntryCoinInfo, select_listuped_positions};
|
|
use crate::strategy_team::future_strategy;
|
|
|
|
pub enum TimeInForce {
|
|
Gtc,
|
|
Ioc,
|
|
Fok,
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
pub enum MarginType {
|
|
Isolated,
|
|
Crossed
|
|
}
|
|
|
|
pub async fn entry_position(
|
|
price_map: &HashMap<String, f64>,
|
|
futures_exchange_info_map: &HashMap<String, FuturesExchangeInfo>,
|
|
future_trade_fee: &FuturesTradeFee,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let mut available_usdt = get_future_available_usdt().await?;
|
|
let mut unit_trade_usdt = get_unit_trade_usdt().await;
|
|
|
|
if !available_usdt.is_zero() && !unit_trade_usdt.is_zero() && available_usdt >= unit_trade_usdt && future_trade_fee.user_level.is_some() {
|
|
let listup_positions = select_listuped_positions().await?;
|
|
|
|
let server_epoch = get_server_epoch().await;
|
|
let client = ClientBuilder::new()
|
|
.timeout(tokio::time::Duration::from_millis(3000))
|
|
.build()
|
|
.unwrap();
|
|
for element in &listup_positions {
|
|
available_usdt = get_future_available_usdt().await?;
|
|
unit_trade_usdt = get_unit_trade_usdt().await;
|
|
if !available_usdt.is_zero() && !unit_trade_usdt.is_zero() && available_usdt >= unit_trade_usdt && futures_exchange_info_map.contains_key(&element.symbol) && price_map.get(&element.symbol).is_some()
|
|
&& server_epoch - element.registered_server_epoch < 10_000 {
|
|
let futures_exchange_info = futures_exchange_info_map.get(&element.symbol).unwrap();
|
|
let lot_step_size = futures_exchange_info.stepsize;
|
|
let tick_size = futures_exchange_info.ticksize;
|
|
let base_asset_precision = futures_exchange_info.base_asset_precision;
|
|
let trade_fee = future_trade_fee.maker_fee_percent;
|
|
let entry_price = rust_decimal::prelude::FromPrimitive::from_f64(*price_map.get(&element.symbol).unwrap()).unwrap();
|
|
|
|
// calculate minimum order usdt >= unit_trade_usdt
|
|
let notional = futures_exchange_info.notional;
|
|
let mut minimum_quantity = decimal_div(notional, entry_price)
|
|
.round_dp_with_strategy(lot_step_size.normalize().scale(), RoundingStrategy::ToPositiveInfinity);
|
|
let mut minimum_order_usdt = decimal_mul(minimum_quantity, entry_price)
|
|
.round_dp_with_strategy(tick_size.normalize().scale(), RoundingStrategy::ToPositiveInfinity);
|
|
|
|
if minimum_order_usdt <= unit_trade_usdt {
|
|
let mut base_qty_ordered = Decimal::new(0, 8);
|
|
base_qty_ordered = decimal_div(unit_trade_usdt, entry_price)
|
|
.round_dp_with_strategy(
|
|
lot_step_size.normalize().scale(),
|
|
RoundingStrategy::ToZero,
|
|
);
|
|
|
|
let mut used_usdt = Decimal::new(0, 8);
|
|
used_usdt = decimal_mul(base_qty_ordered, entry_price)
|
|
.round_dp_with_strategy(
|
|
tick_size.normalize().scale(),
|
|
RoundingStrategy::ToZero,
|
|
);
|
|
|
|
// order the symbol based on base_qty_ordered and current_price
|
|
limit_order_entry(
|
|
&element,
|
|
futures_exchange_info_map,
|
|
trade_fee,
|
|
TimeInForce::Gtc,
|
|
entry_price,
|
|
base_qty_ordered,
|
|
used_usdt,
|
|
&client,
|
|
)
|
|
.await;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn limit_order_entry(
|
|
entry_coin_info: &PositionCoinList,
|
|
exchange_info_map: &HashMap<String, FuturesExchangeInfo>,
|
|
trade_fee: Decimal,
|
|
tif: TimeInForce,
|
|
order_price: Decimal,
|
|
order_quantity: Decimal,
|
|
used_usdt: Decimal,
|
|
client: &Client,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let update_table_name = String::from("future_ordered_coin_list");
|
|
let server_epoch = get_server_epoch().await;
|
|
unsafe {
|
|
if RUNNING_MODE == SIMUL {
|
|
let mut update_values = vec![
|
|
(String::from("status"), String::from("SIMUL")),
|
|
(String::from("transact_time"), server_epoch.to_string()),
|
|
(String::from("close_time"), entry_coin_info.close_time.to_string()),
|
|
(String::from("used_usdt"), used_usdt.to_string()),
|
|
(String::from("entry_price"), order_price.to_string()),
|
|
(String::from("current_price"), order_price.to_string()),
|
|
(String::from("base_qty_ordered"), order_quantity.to_string()),
|
|
];
|
|
|
|
let update_condition = vec![(String::from("id"), entry_coin_info.id.to_string())];
|
|
update_record2(&update_table_name, &update_values, &update_condition)
|
|
.await.unwrap();
|
|
|
|
sub_future_available_usdt(used_usdt).await;
|
|
|
|
println!("SIMUL {} {}", entry_coin_info.position, entry_coin_info.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(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/v1/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(&entry_coin_info.symbol);
|
|
if entry_coin_info.position.contains("Long") {
|
|
url_build.push_str("&side=BUY");
|
|
} else {
|
|
url_build.push_str("&side=SELL");
|
|
}
|
|
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());
|
|
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");
|
|
}
|
|
}
|
|
|
|
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
|
|
let mut update_values = vec![];
|
|
update_values.push((String::from("order_type"), entry_coin_info.order_type.clone()));
|
|
update_values.push((String::from("order_id"), T.get("orderId").unwrap().as_u64().unwrap().to_string()));
|
|
update_values.push((String::from("transact_time"), server_epoch.to_string()));
|
|
update_values.push((String::from("close_time"), entry_coin_info.close_time.to_string()));
|
|
|
|
// status
|
|
if T.get("status").unwrap().as_str().unwrap() == "NEW" {
|
|
update_values.push((String::from("status"), String::from("NEW")));
|
|
} else if T.get("status").unwrap().as_str().unwrap() == "FILLED" {
|
|
update_values.push((String::from("status"), String::from("FILLED")));
|
|
println!("{} {}", entry_coin_info.position, entry_coin_info.symbol);
|
|
} else if T.get("status").unwrap().as_str().unwrap() == "PARTIALLY_FILLED" {
|
|
update_values.push((String::from("status"), String::from("PARTIALLY_FILLED")));
|
|
println!("Partially filled {} {}", entry_coin_info.position, entry_coin_info.symbol);
|
|
}
|
|
|
|
let base_qty_ordered = rust_decimal::prelude::FromStr::from_str(
|
|
T.get("origQty").unwrap().as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
let cummulative_quote_qty = rust_decimal::prelude::FromStr::from_str(
|
|
T.get("cumQuote").unwrap().as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
// let base_asset_precision = exchange_info_vec
|
|
// .iter()
|
|
// .find(|exchange_info| exchange_info.symbol == element.symbol)
|
|
// .unwrap()
|
|
// .base_asset_precision;
|
|
let entry_price = decimal_div(cummulative_quote_qty, base_qty_ordered)
|
|
.round_dp_with_strategy(8, RoundingStrategy::ToZero);
|
|
|
|
update_values.push((String::from("used_usdt"), cummulative_quote_qty.to_string()));
|
|
update_values.push((String::from("entry_price"), entry_price.to_string()));
|
|
update_values.push((String::from("base_qty_ordered"), base_qty_ordered.to_string()));
|
|
|
|
// reflect available_usdt in [asset_manage_announcement]
|
|
// sub_available_usdt(cummulative_quote_qty).await;
|
|
sub_future_available_usdt(cummulative_quote_qty).await;
|
|
|
|
let update_condition = vec![(String::from("id"), entry_coin_info.id.to_string())];
|
|
update_record2(&update_table_name, &update_values, &update_condition)
|
|
.await.unwrap();
|
|
}
|
|
},
|
|
Err(e) => {
|
|
log::warn!("order failed!: {}", body);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn monitoring_unfilled_order(
|
|
client: &Client,
|
|
future_trade_fee: &FuturesTradeFee,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let open_positioning_orders = select_open_positioning_orders().await;
|
|
|
|
if !open_positioning_orders.is_empty() {
|
|
let server_epoch = get_server_epoch().await;
|
|
let orders_outdated = open_positioning_orders
|
|
.iter()
|
|
.filter(|&element| server_epoch - element.registered_server_epoch >= 60_000)
|
|
.collect::<Vec<&PositionCoinList>>(); // wait up to 60 secs
|
|
let orders_to_be_queried = open_positioning_orders
|
|
.iter()
|
|
.filter(|&element| server_epoch - element.registered_server_epoch < 60_000)
|
|
.collect::<Vec<&PositionCoinList>>();
|
|
|
|
// 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_open_positioning_order(element, &client, future_trade_fee).await;
|
|
sleep(Duration::from_millis(200)).await;
|
|
}
|
|
}
|
|
|
|
if !orders_to_be_queried.is_empty() {
|
|
for element in orders_to_be_queried {
|
|
query_open_positioning_order(element, &client, future_trade_fee).await;
|
|
sleep(Duration::from_millis(200)).await;
|
|
}
|
|
}
|
|
}
|
|
|
|
let open_closing_orders = select_open_closing_orders().await;
|
|
if !open_closing_orders.is_empty() {
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Cancel an LISTUP or NEW or PARTIALLY FILLED order. (/api, Weight(IP) 1)
|
|
pub async fn cancel_open_positioning_order(
|
|
order: &PositionCoinList,
|
|
client: &Client,
|
|
future_trade_fee: &FuturesTradeFee,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
if order.status.contains("LISTUP") {
|
|
let table_name = String::from("future_ordered_coin_list");
|
|
let mut condition_build = String::from("WHERE id = ");
|
|
condition_build.push_str(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;
|
|
return Ok(());
|
|
}
|
|
|
|
// 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 if RUNNING_MODE == REAL {
|
|
url.push_str(FUTURES_URL);
|
|
api_key = API_KEY.to_string();
|
|
}
|
|
}
|
|
|
|
let endpoint_url = "/fapi/v1/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("future_ordered_coin_list");
|
|
let cummulative_quote_qty: Decimal = rust_decimal::prelude::FromStr::from_str(
|
|
T.get("cumQuote").unwrap().as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
if cummulative_quote_qty.is_zero() {
|
|
// NOT Filled case
|
|
// delete record in [future_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;
|
|
} else {
|
|
// Patially Filled case
|
|
// update values in [future_ordered_coin_list]
|
|
|
|
// calculate values to be updated
|
|
if future_trade_fee.user_level.is_some() {
|
|
let trade_fee = future_trade_fee.maker_fee_percent;
|
|
let base_qty_ordered = rust_decimal::prelude::FromStr::from_str(
|
|
T.get("executedQty").unwrap().as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
let entry_price = decimal_div(cummulative_quote_qty, base_qty_ordered)
|
|
.round_dp_with_strategy(8, RoundingStrategy::ToZero);
|
|
|
|
let update_values = vec![
|
|
(String::from("status"), String::from("FILLED")), // status
|
|
(String::from("used_usdt"), cummulative_quote_qty.to_string()), // used_usdt
|
|
(String::from("entry_price"), entry_price.to_string()), // entry_price
|
|
(String::from("base_qty_ordered"),base_qty_ordered.to_string()), // base_qty_ordered
|
|
];
|
|
let update_condition = vec![
|
|
(String::from("order_id"), order.order_id.to_string()),
|
|
(String::from("symbol"), order.symbol.clone()),
|
|
];
|
|
update_record2(&table_name, &update_values, &update_condition)
|
|
.await
|
|
.unwrap();
|
|
|
|
println!("partially Positioning {} {}", order.position, 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_open_positioning_order(order, &client, future_trade_fee).await;
|
|
}
|
|
}
|
|
Err(e) => {
|
|
query_open_positioning_order(order, &client, future_trade_fee).await;
|
|
log::warn!("cancel order buy failed!: {}", body);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// query order and update record in buy_ordered_coin_list (/api, Weight(IP) 1)
|
|
pub async fn query_open_positioning_order(
|
|
order: &PositionCoinList,
|
|
client: &Client,
|
|
future_trade_fee: &FuturesTradeFee,
|
|
) -> 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 if RUNNING_MODE == REAL {
|
|
url.push_str(FUTURES_URL);
|
|
api_key = API_KEY.to_string();
|
|
}
|
|
}
|
|
|
|
let endpoint_url = "/fapi/v1/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"
|
|
{
|
|
// update values in [future_ordered_coin_list]
|
|
let table_name = String::from("future_ordered_coin_list");
|
|
let mut value_build = String::new();
|
|
value_build.push_str(T.get("status").unwrap().as_str().unwrap());
|
|
|
|
// calculate values to be updated
|
|
if future_trade_fee.user_level.is_some() {
|
|
let trade_fee = future_trade_fee.maker_fee_percent;
|
|
let base_qty_ordered = rust_decimal::prelude::FromStr::from_str(
|
|
T.get("executedQty").unwrap().as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
let cummulative_quote_qty = rust_decimal::prelude::FromStr::from_str(
|
|
T.get("cumQuote").unwrap().as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
let entry_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("entry_price"), entry_price.to_string()), // entry_price
|
|
(String::from("base_qty_ordered"), base_qty_ordered.to_string()), // base_qty_ordered
|
|
];
|
|
let update_condition = vec![
|
|
(String::from("order_id"), order.order_id.to_string()),
|
|
(String::from("symbol"), order.symbol.clone()),
|
|
];
|
|
update_record2(&table_name, &update_values, &update_condition)
|
|
.await
|
|
.unwrap();
|
|
|
|
if T.get("status").unwrap().as_str().unwrap() == "FILLED" {
|
|
println!("Positioning {} {}", order.position, 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("future_ordered_coin_list");
|
|
let update_values = vec![(String::from("status"), String::from("CANCELED"))];
|
|
let update_condition = vec![
|
|
(String::from("order_id"), order.order_id.to_string()),
|
|
(String::from("symbol"), order.symbol.clone()),
|
|
];
|
|
update_record2(&update_table_name, &update_values, &update_condition)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::warn!("query order failed!: {}", body);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
//
|
|
pub async fn set_initial_leverage(symbol: &String, leverage_ratio: i32, 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/v1/leverage?";
|
|
url.push_str(endpoint_url);
|
|
|
|
let mut url_build = String::new();
|
|
url_build.push_str("&symbol=");
|
|
url_build.push_str(symbol);
|
|
url_build.push_str("&leverage=");
|
|
url_build.push_str(leverage_ratio.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?;
|
|
|
|
// deserialize JSON
|
|
let v = serde_json::from_str::<Value>(body.as_str());
|
|
match v {
|
|
Ok(T) => {
|
|
if let (Some(leverage), Some(symbol), Some(max_notional_value)) = (T.get("leverage"), T.get("symbol"), T.get("maxNotionalValue")) {
|
|
log::info!("symbol: {:?}. leverage: {:?}, max_notional_value: {:?}", symbol, leverage, max_notional_value);
|
|
} 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 set_margin_type(symbol: &String, margin_type: MarginType, 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/v1/marginType?";
|
|
url.push_str(endpoint_url);
|
|
|
|
let mut url_build = String::new();
|
|
url_build.push_str("&symbol=");
|
|
url_build.push_str(symbol);
|
|
url_build.push_str("&marginType=");
|
|
if margin_type == MarginType::Isolated {
|
|
url_build.push_str("ISOLATED");
|
|
} else {
|
|
url_build.push_str("CROSSED");
|
|
}
|
|
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?;
|
|
|
|
// deserialize JSON
|
|
let v = serde_json::from_str::<Value>(body.as_str());
|
|
match v {
|
|
Ok(T) => {
|
|
if let (Some(code), Some(msg)) = (T.get("code"), T.get("msg")) {
|
|
log::info!("code: {:?}. msg: {:?}", code, msg);
|
|
} else {
|
|
log::error!("Endpoint(/fapi/v1/marginType?) output changed!");
|
|
return Err("Err")?;
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::warn!("account information failed!: {}, {}", body, e);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// Basic: One-way Mode
|
|
pub async fn set_position_mode(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/v1/positionSide/dual?";
|
|
url.push_str(endpoint_url);
|
|
|
|
let mut url_build = String::new();
|
|
url_build.push_str("&dualSidePosition=");
|
|
url_build.push_str("false"); // true: Hedge Mode, false: One-way Mode
|
|
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?;
|
|
|
|
// deserialize JSON
|
|
let v = serde_json::from_str::<Value>(body.as_str());
|
|
match v {
|
|
Ok(T) => {
|
|
if let (Some(code), Some(msg)) = (T.get("code"), T.get("msg")) {
|
|
log::info!("code: {:?}. msg: {:?}", code, msg);
|
|
} else {
|
|
log::error!("Endpoint(/fapi/v1/marginType?) output changed!");
|
|
return Err("Err")?;
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::warn!("account information failed!: {}, {}", body, e);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// Basic: Single-Asset Mode
|
|
pub async fn set_asset_mode(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/v1/multiAssetsMargin?";
|
|
url.push_str(endpoint_url);
|
|
|
|
let mut url_build = String::new();
|
|
url_build.push_str("&multiAssetsMargin=");
|
|
url_build.push_str("false"); // true: Multi-Assets Mode, false: Single-Asset Mode
|
|
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?;
|
|
|
|
// deserialize JSON
|
|
let v = serde_json::from_str::<Value>(body.as_str());
|
|
match v {
|
|
Ok(T) => {
|
|
if let (Some(code), Some(msg)) = (T.get("code"), T.get("msg")) {
|
|
log::info!("code: {:?}. msg: {:?}", code, msg);
|
|
} else {
|
|
log::error!("Endpoint(/fapi/v1/marginType?) output changed!");
|
|
return Err("Err")?;
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::warn!("account information failed!: {}, {}", body, e);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_last_price(client: &Client, price_map: &mut HashMap<String, f64>,) -> 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/ticker/price";
|
|
url.push_str(endpoint_url);
|
|
|
|
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: Value = serde_json::from_str(body.as_str())?;
|
|
let into_vec = v.as_array();
|
|
if into_vec == None {
|
|
return Err("Err")?;
|
|
}
|
|
let mut object_map = &serde_json::map::Map::new();
|
|
|
|
let mut symbol = String::new();
|
|
let mut price = 0.0;
|
|
for element in into_vec.unwrap() {
|
|
object_map = element.as_object().unwrap();
|
|
let mut object_map_iter = object_map.iter();
|
|
|
|
for element in object_map_iter {
|
|
match element.0.as_str() {
|
|
"symbol" => symbol = element.1.as_str().unwrap().to_string(),
|
|
"price" => price = element.1.as_str().unwrap().parse::<f64>().unwrap(),
|
|
"time" => {},
|
|
_ => {
|
|
log::error!("Elements in body msg are changed. Please update both your coinprices table and vectors.");
|
|
}
|
|
}
|
|
price_map.insert(symbol.clone(), price);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// request exchange information. (/api, Weight(IP) 1)
|
|
pub async fn request_future_exchange_infomation(
|
|
client: &Client,
|
|
exchange_info_map: &mut HashMap<String, FuturesExchangeInfo>,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
// building URL and API-keys
|
|
let mut url = String::new();
|
|
|
|
url.push_str("https://fapi.binance.com");
|
|
|
|
let endpoint_url = "/fapi/v1/exchangeInfo";
|
|
url.push_str(endpoint_url);
|
|
|
|
let mut res = client.get(&url).send().await?;
|
|
|
|
while !res.status().is_success() {
|
|
sleep(Duration::from_secs(5)).await;
|
|
res = client.get(&url).send().await?;
|
|
}
|
|
|
|
let body = res.text_with_charset("utf-8").await?;
|
|
|
|
// deserialize JSON
|
|
let v: Value = serde_json::from_str(body.as_str())?;
|
|
let mut into_vec = v.as_object();
|
|
if into_vec == None {
|
|
return Err("Err")?;
|
|
}
|
|
|
|
let mut symbol = String::new();
|
|
let mut exchange_info = FuturesExchangeInfo::new();
|
|
let mut data_map_temp: HashMap<String, FuturesExchangeInfo> = HashMap::new();
|
|
|
|
for element in into_vec.unwrap() {
|
|
if element.0.contains("symbols") {
|
|
for element in element.1.as_array().unwrap() {
|
|
if element.is_object() {
|
|
if element
|
|
.get("symbol")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap()
|
|
.ends_with("USDT")
|
|
{
|
|
if element.get("contractType").is_some_and(|a| a.as_str().is_some_and(|b| b == "PERPETUAL")) {
|
|
symbol = (element.get("symbol").unwrap().as_str().unwrap().to_string());
|
|
exchange_info.base_asset_precision =
|
|
(element.get("baseAssetPrecision").unwrap().as_u64().unwrap()) as u32;
|
|
exchange_info.quote_precision =
|
|
(element.get("quotePrecision").unwrap().as_u64().unwrap()) as u32;
|
|
for element in element.get("filters").unwrap().as_array().unwrap() {
|
|
if element
|
|
.as_object()
|
|
.unwrap()
|
|
.get("filterType")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap()
|
|
.starts_with("LOT_SIZE")
|
|
{
|
|
exchange_info.stepsize = rust_decimal::prelude::FromStr::from_str(
|
|
element.get("stepSize").unwrap().as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
} else if element
|
|
.as_object()
|
|
.unwrap()
|
|
.get("filterType")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap()
|
|
.starts_with("PRICE_FILTER")
|
|
{
|
|
exchange_info.ticksize = rust_decimal::prelude::FromStr::from_str(
|
|
element.get("tickSize").unwrap().as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
} else if element
|
|
.as_object()
|
|
.unwrap()
|
|
.get("filterType")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap()
|
|
.starts_with("MIN_NOTIONAL")
|
|
{
|
|
exchange_info.notional = rust_decimal::prelude::FromStr::from_str(
|
|
element.get("notional").unwrap().as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|
|
data_map_temp.insert(symbol.clone(), exchange_info.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*exchange_info_map = data_map_temp;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// subtract available USDT
|
|
pub async fn sub_future_available_usdt(sub_usdt: Decimal) {
|
|
let update_table_name = String::from("future_available_balance");
|
|
let mut value_build = String::from("available_usdt - ");
|
|
value_build.push_str(sub_usdt.to_string().as_str());
|
|
|
|
let update_values = vec![(String::from("available_usdt"), value_build)];
|
|
let update_condition = vec![(String::from("id"), String::from("1"))];
|
|
update_record3(&update_table_name, &update_values, &update_condition)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
// get available USDT
|
|
pub async fn get_future_available_usdt() -> Result<Decimal, Box<dyn std::error::Error + Send + Sync>> {
|
|
let select_table_name = String::from("future_available_balance");
|
|
let select_columns = String::from("*");
|
|
let select_condition = None;
|
|
|
|
#[derive(FromRow)]
|
|
struct AvailableUsdt {
|
|
available_usdt: Decimal,
|
|
}
|
|
let data_struct = AvailableUsdt{ available_usdt: Decimal::new(0, 8) };
|
|
|
|
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().first().unwrap().available_usdt)
|
|
} else {
|
|
eprint!("get_future_available_usdt() error!");
|
|
Err("error")?
|
|
}
|
|
}
|
|
|
|
async fn select_open_positioning_orders() -> Vec<PositionCoinList> {
|
|
let select_table_name = String::from("future_ordered_coin_list");
|
|
let select_columns = String::from("*");
|
|
let select_condition = Some(String::from(
|
|
"WHERE order_type = 'POSITIONING' AND (status = 'LISTUP' OR status = 'NEW' OR status = 'PARTIALLY_FILLED')",
|
|
));
|
|
let data_struct = PositionCoinList::new();
|
|
let select_result = try_select_record(
|
|
&select_table_name,
|
|
&select_columns,
|
|
&select_condition,
|
|
&data_struct,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
select_result
|
|
}
|
|
|
|
async fn select_open_closing_orders() -> Vec<PositionCoinList> {
|
|
let select_table_name = String::from("future_ordered_coin_list");
|
|
let select_columns = String::from("*");
|
|
let select_condition = Some(String::from(
|
|
"WHERE order_type = 'CLOSING' AND (status = 'NEW' OR status = 'PARTIALLY_FILLED')",
|
|
));
|
|
let data_struct = PositionCoinList::new();
|
|
let select_result = try_select_record(
|
|
&select_table_name,
|
|
&select_columns,
|
|
&select_condition,
|
|
&data_struct,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
select_result
|
|
}
|