tradingbot/src/future/order.rs

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
}