From 5e5f0cbc0541b508363676b8d36941c352db31ca Mon Sep 17 00:00:00 2001 From: Sik Yoon Date: Tue, 21 May 2024 14:16:45 +0900 Subject: [PATCH] Implement closing position --- src/future/order.rs | 148 ++++++++++++++++++++++++-- src/strategy_team/future_strategy.rs | 101 +++++++----------- src/strategy_team/strategy_manager.rs | 30 +++--- 3 files changed, 197 insertions(+), 82 deletions(-) diff --git a/src/future/order.rs b/src/future/order.rs index 78375dc..3d3b91e 100644 --- a/src/future/order.rs +++ b/src/future/order.rs @@ -88,8 +88,6 @@ pub async fn entry_position( // 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, @@ -108,8 +106,6 @@ pub async fn entry_position( pub async fn limit_order_entry( entry_coin_info: &PositionCoinList, - exchange_info_map: &HashMap, - trade_fee: Decimal, tif: TimeInForce, order_price: Decimal, order_quantity: Decimal, @@ -136,7 +132,7 @@ pub async fn limit_order_entry( sub_future_available_usdt(used_usdt).await; - println!("SIMUL {} {}", entry_coin_info.position, entry_coin_info.symbol); + println!("SIMUL positining {} {}", entry_coin_info.position, entry_coin_info.symbol); } else { // building URL and API-keys let mut url = String::new(); @@ -209,10 +205,148 @@ pub async fn limit_order_entry( 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); + println!("positioning {} {}", 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); + println!("positioning {} {} (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 limit_order_close( + entry_coin_info: &PositionCoinList, + tif: TimeInForce, + order_price: Decimal, + order_quantity: Decimal, + client: &Client, +) -> Result<(), Box> { + 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("order_type"), String::from("CLOSING")), + (String::from("status"), String::from("FILLED")), + ]; + + let update_condition = vec![(String::from("id"), entry_coin_info.id.to_string())]; + update_record2(&update_table_name, &update_values, &update_condition) + .await.unwrap(); + + println!("SIMUL closed {} {}", 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=SELL"); + } else { + url_build.push_str("&side=BUY"); + } + 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::(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"), (String::from("CLOSING")))); + 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())); + + // 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!("Closed {} {}", 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!("Closing {} {} (Partially filled)", entry_coin_info.position, entry_coin_info.symbol); } let base_qty_ordered = rust_decimal::prelude::FromStr::from_str( diff --git a/src/strategy_team/future_strategy.rs b/src/strategy_team/future_strategy.rs index 361c617..5d67992 100644 --- a/src/strategy_team/future_strategy.rs +++ b/src/strategy_team/future_strategy.rs @@ -13,6 +13,7 @@ use super::{ use crate::future::{Position, FuturesExchangeInfo}; use crate::future::table_mgmt::select_filled_positions; +use crate::future::order::{limit_order_close, TimeInForce}; // BUY conditions pub async fn list_up_for_buy( @@ -74,68 +75,48 @@ pub async fn list_up_for_buy( Ok(()) } -// pub async fn list_up_for_sell( -// all_data: &AllData, -// exchange_info_map: &HashMap, -// trade_fee_map: &HashMap, -// ) -> Result<(), Box> { -// let filled_positions = select_filled_positions().await?; +pub async fn list_up_for_sell() -> Result<(), Box> { + let filled_positions = select_filled_positions().await?; -// let client = ClientBuilder::new() -// .timeout(tokio::time::Duration::from_millis(5000)) -// .build() -// .unwrap(); -// let server_epoch = get_server_epoch().await; + let client = ClientBuilder::new() + .timeout(tokio::time::Duration::from_millis(5000)) + .build() + .unwrap(); + let server_epoch = get_server_epoch().await; -// for element in filled_positions { -// let mut is_sell = false; + for element in filled_positions { + let mut is_sell = false; -// if element.used_usdt >= dec!(10.0) { -// if let (Some(exchange_info), Some(tradefee)) = ( -// exchange_info_map.get(&element.symbol), -// trade_fee_map.get(&element.symbol), -// ) { -// let lot_step_size = exchange_info.stepsize; -// let quote_commission_precision = exchange_info.quote_commission_precision; + // TODO: BNB 코인이 있으면 + // let base_qty_to_be_ordered = + // element.base_qty_ordered.round_dp_with_strategy( + // lot_step_size.normalize().scale(), + // RoundingStrategy::ToZero, + // ); + // TODO: BNB 코인이 없으면 -// // TODO: BNB 코인이 있으면 -// // let base_qty_to_be_ordered = -// // element.base_qty_ordered.round_dp_with_strategy( -// // lot_step_size.normalize().scale(), -// // RoundingStrategy::ToZero, -// // ); -// // TODO: BNB 코인이 없으면 -// let base_qty_to_be_ordered = -// element.base_qty_fee_adjusted.round_dp_with_strategy( -// lot_step_size.normalize().scale(), -// RoundingStrategy::ToZero, -// ); + if !element.current_price.is_zero() { + if element.pure_profit_percent >= 1.0 { + is_sell = true; + } else if element.pure_profit_percent <= -1.0 { + is_sell = true; + } else if server_epoch - element.transact_time >= (1_800_000) * 1 { + // time up selling + is_sell = true; + } + + if is_sell == true { + limit_order_close( + &element, + TimeInForce::Gtc, + element.current_price, + element.base_qty_ordered, + &client + ) + .await; + } + } + } -// if !element.current_price.is_zero() { -// if element.pure_profit_percent >= 1.0 { -// is_sell = true; -// } else if element.pure_profit_percent <= -0.8 { -// is_sell = true; -// } else if server_epoch - element.transact_time >= (1_800_000) * 1 { -// // time up selling -// is_sell = true; -// } - -// if is_sell == true { -// limit_order_sell( -// &element, -// element.current_price, -// base_qty_to_be_ordered, -// &client, -// &exchange_info_map, -// &trade_fee_map, -// ) -// .await; -// } -// } -// } -// } -// } - -// Ok(()) -// } + Ok(()) +} diff --git a/src/strategy_team/strategy_manager.rs b/src/strategy_team/strategy_manager.rs index 7764a6d..264de85 100644 --- a/src/strategy_team/strategy_manager.rs +++ b/src/strategy_team/strategy_manager.rs @@ -41,8 +41,8 @@ pub async fn execute_list_up_for_buy( // crate::strategy_team::strategy_005::list_up_for_buy(all_data).await; // crate::strategy_team::strategy_006::list_up_for_buy(all_data).await; // crate::strategy_team::strategy_007::list_up_for_buy(all_data).await; - crate::strategy_team::strategy_008::list_up_for_buy(all_data).await; - crate::strategy_team::strategy_009::list_up_for_buy(all_data).await; + // crate::strategy_team::strategy_008::list_up_for_buy(all_data).await; + // crate::strategy_team::strategy_009::list_up_for_buy(all_data).await; crate::strategy_team::future_strategy::list_up_for_buy(all_data, &future_exchange_info_map).await; Ok(()) @@ -70,20 +70,20 @@ pub async fn execute_list_up_for_sell( // &trade_fee_map, // ) // .await; - crate::strategy_team::strategy_008::list_up_for_sell( - &all_data, - &exchange_info_map, - &trade_fee_map, - ) - .await; - - crate::strategy_team::strategy_009::list_up_for_sell( - &all_data, - &exchange_info_map, - &trade_fee_map, - ) - .await; + // crate::strategy_team::strategy_008::list_up_for_sell( + // &all_data, + // &exchange_info_map, + // &trade_fee_map, + // ) + // .await; + // crate::strategy_team::strategy_009::list_up_for_sell( + // &all_data, + // &exchange_info_map, + // &trade_fee_map, + // ) + // .await; + crate::strategy_team::future_strategy::list_up_for_sell().await; Ok(()) }