diff --git a/src/coex/exchange_team.rs b/src/coex/exchange_team.rs index fa64ad1..fd782d7 100644 --- a/src/coex/exchange_team.rs +++ b/src/coex/exchange_team.rs @@ -1,14 +1,16 @@ use crate::coex::assets_managing_team::*; use crate::coex::order_team::*; -use crate::coin_health_check_team::request_others::{CoinPriceData, ExchangeInfo, TradeFee}; +use crate::coin_health_check_team::request_others::{ExchangeInfo, TradeFee}; use crate::database_control::*; use crate::decimal_funcs::{decimal, decimal_add, decimal_div, decimal_mul, decimal_sub}; use crate::signal_association::signal_decision::*; use crate::strategy_team::AllData; +use hmac_sha256::Hash; use reqwest::{Client, ClientBuilder}; use rust_decimal::{prelude::FromPrimitive, prelude::ToPrimitive, Decimal, RoundingStrategy}; use rust_decimal_macros::dec; use sqlx::FromRow; +use std::collections::HashMap; #[derive(Debug, FromRow)] struct IsTradableInfo { @@ -219,13 +221,13 @@ impl DBlist for MarketCapIndex { // buy coin pub async fn buy_coin( - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let mut suggested_coin = get_suggested_coin_list().await; - if !suggested_coin.is_empty() && exchange_info_vec.len() != 0 && trade_fee_vec.len() != 0 { - let server_epoch = server_epoch().await; + if !suggested_coin.is_empty() && exchange_info_map.len() != 0 && trade_fee_map.len() != 0 { + let server_epoch = get_server_epoch().await; let mut filtered_suggested_coin_vec: Vec<&SuggestedCoin> = Vec::new(); let mut is_exist_delete_symbol: bool = false; let mut delete_condition = String::from("WHERE "); @@ -255,22 +257,12 @@ pub async fn buy_coin( .unwrap(); for element in &filtered_suggested_coin_vec { if is_tradable == true { - let exchange_info_result = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - - let trade_fee_result = trade_fee_vec - .iter() - .position(|trade_fee| trade_fee.symbol == element.symbol); - - if exchange_info_result.is_some() && trade_fee_result.is_some() { - let lot_step_size = - exchange_info_vec[exchange_info_result.unwrap()].stepsize; - let tick_size = exchange_info_vec[exchange_info_result.unwrap()].ticksize; - let base_commission_precision = exchange_info_vec - [exchange_info_result.unwrap()] - .base_commission_precision; - let trade_fee = trade_fee_vec[trade_fee_result.unwrap()].takercommission; + if exchange_info_map.contains_key(&element.symbol) && trade_fee_map.contains_key(&element.symbol) { + let exchange_info = exchange_info_map.get(&element.symbol).unwrap(); + let lot_step_size = exchange_info.stepsize; + let tick_size = exchange_info.ticksize; + let base_commission_precision = exchange_info.base_commission_precision; + let trade_fee = trade_fee_map.get(&element.symbol).unwrap().takercommission; // buy the suggested coin and transfer it into [buy_ordered_coin_list] let mut base_qty_ordered = Decimal::new(0, 8); @@ -312,7 +304,7 @@ pub async fn buy_coin( // order the symbol based on base_qty_ordered and current_price limit_order_buy( element, - exchange_info_vec, + exchange_info_map, trade_fee, TimeInForce::Gtc, element.suggested_price, @@ -348,7 +340,6 @@ pub async fn buy_coin( delete_record(&suggested_coin_list_table_name, &delete_condition) .await .unwrap(); - println!("Delete suggested coin list"); } } @@ -358,16 +349,15 @@ pub async fn buy_coin( // buy coin for test pub async fn buy_coin_for_test( client: &Client, - coin_price_vec: &Vec, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let mut suggested_coin = get_suggested_coin_list().await; let mut delete_condition = String::from("WHERE "); let unit_trade_usdt = get_unit_trade_usdt().await; - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; let mut filtered_suggested_coin_vec: Vec<&SuggestedCoin> = Vec::new(); for element in &suggested_coin { @@ -385,74 +375,60 @@ pub async fn buy_coin_for_test( let insert_table_name = String::from("buy_ordered_coin_list"); for element in &filtered_suggested_coin_vec { - let lot_step_size = exchange_info_vec - .iter() - .find(|exchange_info| exchange_info.symbol == element.symbol) - .unwrap() - .stepsize; - let tick_size = exchange_info_vec - .iter() - .find(|exchange_info| exchange_info.symbol == element.symbol) - .unwrap() - .ticksize; - let base_commission_precision = exchange_info_vec - .iter() - .find(|exchange_info| exchange_info.symbol == element.symbol) - .unwrap() - .base_commission_precision; - let trade_fee = trade_fee_vec - .iter() - .find(|trade_fee| trade_fee.symbol == element.symbol) - .unwrap() - .takercommission; + if exchange_info_map.contains_key(&element.symbol) && trade_fee_map.contains_key(&element.symbol){ + let lot_step_size = exchange_info_map.get(&element.symbol).unwrap().stepsize; + let tick_size = exchange_info_map.get(&element.symbol).unwrap().ticksize; + let base_commission_precision = exchange_info_map.get(&element.symbol).unwrap().base_commission_precision; + let trade_fee = trade_fee_map.get(&element.symbol).unwrap().takercommission; - // buy the suggested coin and transfer it into [buy_ordered_coin_list] - // let price_index = coin_price_vec.iter().position(|x| *x.symbol == element.symbol); - // let current_price: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(coin_price_vec[price_index.unwrap()].current_price).unwrap(); - // let order_price = current_price; - let order_price = element.suggested_price; + // buy the suggested coin and transfer it into [buy_ordered_coin_list] + // let price_index = coin_price_vec.iter().position(|x| *x.symbol == element.symbol); + // let current_price: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(coin_price_vec[price_index.unwrap()].current_price).unwrap(); + // let order_price = current_price; + let order_price = element.suggested_price; - base_qty_ordered = decimal_div(unit_trade_usdt, order_price) - .round_dp_with_strategy(lot_step_size.normalize().scale(), RoundingStrategy::ToZero); - base_qty_fee_adjusted = decimal_mul(base_qty_ordered, decimal_sub(dec!(1), trade_fee)) - .round_dp_with_strategy(base_commission_precision, RoundingStrategy::ToZero); - used_usdt = decimal_mul(base_qty_ordered, order_price) - .round_dp_with_strategy(tick_size.normalize().scale(), RoundingStrategy::ToZero); - expected_pure_profit_percent = decimal_sub( - decimal_div( - decimal_mul(base_qty_fee_adjusted, order_price).round_dp_with_strategy( - tick_size.normalize().scale(), - RoundingStrategy::ToZero, + base_qty_ordered = decimal_div(unit_trade_usdt, order_price) + .round_dp_with_strategy(lot_step_size.normalize().scale(), RoundingStrategy::ToZero); + base_qty_fee_adjusted = decimal_mul(base_qty_ordered, decimal_sub(dec!(1), trade_fee)) + .round_dp_with_strategy(base_commission_precision, RoundingStrategy::ToZero); + used_usdt = decimal_mul(base_qty_ordered, order_price) + .round_dp_with_strategy(tick_size.normalize().scale(), RoundingStrategy::ToZero); + expected_pure_profit_percent = decimal_sub( + decimal_div( + decimal_mul(base_qty_fee_adjusted, order_price).round_dp_with_strategy( + tick_size.normalize().scale(), + RoundingStrategy::ToZero, + ), + used_usdt, ), - used_usdt, - ), - dec!(1), - ) - .to_f64() - .unwrap() - * 100.0; + dec!(1), + ) + .to_f64() + .unwrap() + * 100.0; - // order the symbol based on base_qty_ordered and current_price - limit_order_buy( - element, - exchange_info_vec, - trade_fee, - TimeInForce::Gtc, - order_price, - base_qty_ordered, - used_usdt, - &base_qty_fee_adjusted.to_string(), - &client, - ) - .await; - println!(" buy coin 완료"); + // order the symbol based on base_qty_ordered and current_price + limit_order_buy( + element, + exchange_info_map, + trade_fee, + TimeInForce::Gtc, + order_price, + base_qty_ordered, + used_usdt, + &base_qty_fee_adjusted.to_string(), + &client, + ) + .await; + println!(" buy coin 완료"); + } } Ok(()) } // monitoring the price change of pre-suggested coins before suggesting them and move them when conditions are agree pub async fn monitoring_pre_suggested_coins( - coin_price_vec: &Vec, + coin_price_map: &HashMap, ) -> Result<(), Box> { let select_table_name = String::from("pre_suggested_coin_list"); let pre_suggested_coin_list = select_pre_suggested_coin_list().await; @@ -468,14 +444,8 @@ pub async fn monitoring_pre_suggested_coins( let mut update_condition: Vec<(String, String)> = Vec::new(); for element in pre_suggested_coin_list { - let price_index = coin_price_vec - .iter() - .position(|x| *x.symbol == element.symbol); - if price_index.is_some() { - price = rust_decimal::prelude::FromPrimitive::from_f64( - coin_price_vec[price_index.unwrap()].current_price, - ) - .unwrap(); + if let Some(current_price) = coin_price_map.get(&element.symbol) { + price = rust_decimal::prelude::FromPrimitive::from_f64(*current_price).unwrap(); profit_percent = ((price - element.suggested_price) / element.suggested_price) .to_f64() .unwrap() @@ -561,7 +531,7 @@ pub async fn monitoring_pre_suggested_coins( insert_values.push(element.stoploss.to_string()); // stoploss insert_values.push(element.target_price.to_string()); // target_price insert_values.push(element.close_time.to_string()); // close_time - insert_values.push(server_epoch().await.to_string()); // registered_server_epoch + insert_values.push(get_server_epoch().await.to_string()); // registered_server_epoch insert_values.push(element.registerer.to_string()); // registerer insert_values.push(element.is_long.to_string()); // is_long insert_values.push(0.to_string()); // already_buy @@ -626,7 +596,7 @@ pub async fn monitoring_scoreboard( let scoreboard_list = select_scoreboard().await; let mut count_short_coins = 0; let mut count_long_coins = 0; - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; let mut timer_check = false; for element in &filled_buy_orders { @@ -756,12 +726,9 @@ pub async fn monitoring_scoreboard( let mut base_profit_percent = 0.0; let mut target_profit_percent = 0.0; - let opclo_1m_option = all_data - .rt_price_1m_vec - .iter() - .position(|x| *x.0 == String::from("BTCUSDT")); - if opclo_1m_option.is_some() { - let mut opclo_1m_vec = all_data.rt_price_1m_vec[opclo_1m_option.unwrap()].1.clone(); + let symbol = String::from("BTCUSDT"); + if let Some(rt_price_1m_vec) = all_data.rt_price_1m_vec.get(&symbol) { + let mut opclo_1m_vec = rt_price_1m_vec.clone(); let opclo_sample_length: usize = 5; // 10 candle samsples opclo_1m_vec.pop(); opclo_1m_vec.reverse(); @@ -949,7 +916,7 @@ async fn get_suggested_coin_list() -> Vec { select_result } -pub async fn server_epoch() -> i64 { +pub async fn get_server_epoch() -> i64 { let table_name = String::from("time"); let columns = String::from("*"); let condition = None; diff --git a/src/coex/order_team.rs b/src/coex/order_team.rs index 0236634..f405d36 100644 --- a/src/coex/order_team.rs +++ b/src/coex/order_team.rs @@ -11,7 +11,7 @@ use crate::URL_TEST; // use crates use crate::coex::assets_managing_team::*; use crate::coex::exchange_team::*; -use crate::coin_health_check_team::request_others::{CoinPriceData, ExchangeInfo, TradeFee}; +use crate::coin_health_check_team::request_others::{ExchangeInfo, TradeFee}; use crate::database_control::*; use crate::decimal_funcs::*; use crate::signal_association::signal_decision::*; @@ -30,6 +30,7 @@ use rust_decimal_macros::dec; use serde_json::Value; use sqlx::FromRow; use tokio::time::*; +use std::collections::HashMap; pub enum OrderSide { Buy, @@ -298,7 +299,7 @@ pub async fn limit_order_buy_test( pub async fn limit_order_buy( element: &SuggestedCoin, - exchange_info_vec: &Vec, + exchange_info_map: &HashMap, trade_fee: Decimal, tif: TimeInForce, order_price: Decimal, @@ -331,7 +332,7 @@ pub async fn limit_order_buy( ]; let mut insert_values: Vec> = Vec::new(); let mut insert_value_container: Vec = Vec::new(); - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; if RUNNING_MODE == SIMUL { insert_value_container.push(element.symbol.clone()); // symbol insert_value_container.push(0.to_string()); // order_id @@ -350,7 +351,6 @@ pub async fn limit_order_buy( insert_value_container.push(0.0.to_string()); // pure_profit_percent insert_value_container.push(0.0.to_string()); // minimum_profit_percent insert_value_container.push(0.0.to_string()); // maximum_profit_percent - insert_value_container.push(0.to_string()); // registerer insert_value_container.push(element.registerer.to_string()); // registerer insert_value_container.push(0.to_string()); // is_long @@ -498,13 +498,12 @@ pub async fn limit_order_buy( pub async fn monitoring_open_buy_order( client: &Client, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let open_buy_orders = select_open_buy_orders().await; if !open_buy_orders.is_empty() { - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; let orders_outdated = open_buy_orders .iter() .filter(|&element| server_epoch - element.transact_time >= 600_000) @@ -517,14 +516,14 @@ pub async fn monitoring_open_buy_order( // 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_buy_order(element, &client, exchange_info_vec, trade_fee_vec).await; + cancel_buy_order(element, &client, trade_fee_map).await; sleep(Duration::from_millis(500)).await; // Use max 30 LIMIT/min } } if !orders_to_be_queried.is_empty() { for element in orders_to_be_queried { - query_buy_order(element, &client, exchange_info_vec, trade_fee_vec).await; + query_buy_order(element, &client, trade_fee_map).await; sleep(Duration::from_millis(500)).await; // Use max 30 LIMIT/min } } @@ -534,9 +533,9 @@ pub async fn monitoring_open_buy_order( } pub async fn update_price_of_filled_buy_order( - coin_price_vec: &Vec, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + coin_price_map: &HashMap, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let filled_buy_orders = select_filled_buy_orders(0).await?; @@ -548,9 +547,9 @@ pub async fn update_price_of_filled_buy_order( for chunk in chunks { let chunk_vec = chunk.to_vec(); - let coin_price_vec_c = coin_price_vec.clone(); - let exchange_info_vec_c = exchange_info_vec.clone(); - let trade_fee_vec_c = trade_fee_vec.clone(); + let coin_price_vec_c = coin_price_map.clone(); + let exchange_info_vec_c = exchange_info_map.clone(); + let trade_fee_vec_c = trade_fee_map.clone(); task_vec.push(tokio::spawn(async move { update_repeat_task( chunk_vec, @@ -567,16 +566,16 @@ pub async fn update_price_of_filled_buy_order( async fn update_repeat_task( buy_ordered_coin_vec: Vec, - coin_price_vec: &Vec, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + coin_price_map: &HashMap, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let update_table_name = String::from("buy_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 server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; let update_colums = vec![ "current_price", @@ -594,96 +593,71 @@ async fn update_repeat_task( for element in buy_ordered_coin_vec { // build update values update_record_build.clear(); - let price_index_option = coin_price_vec - .iter() - .position(|x| *x.symbol == element.symbol); - let lot_step_size_result = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - let quote_commission_precision_result = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - let trade_fee_result = trade_fee_vec - .iter() - .position(|trade_fee| trade_fee.symbol == element.symbol); - - if price_index_option.is_some() - && lot_step_size_result.is_some() - && quote_commission_precision_result.is_some() - && trade_fee_result.is_some() - { - let price_result: Option = rust_decimal::prelude::FromPrimitive::from_f64( - coin_price_vec[price_index_option.unwrap()].current_price, - ); - - if price_result.is_some() { - price = price_result.unwrap(); - if !price.is_zero() { - // to get quote_commission_precision - let trade_fee = trade_fee_vec[trade_fee_result.unwrap()].takercommission; - let lot_step_size = exchange_info_vec[lot_step_size_result.unwrap()].stepsize; - let quote_commission_precision = exchange_info_vec - [quote_commission_precision_result.unwrap()] - .quote_commission_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_commission_precision, - RoundingStrategy::ToZero, - ), - decimal_sub(dec!(1), trade_fee), + if coin_price_map.contains_key(&element.symbol) && exchange_info_map.contains_key(&element.symbol) && trade_fee_map.contains_key(&element.symbol) { + 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 = trade_fee_map.get(&element.symbol).unwrap().takercommission; + let lot_step_size = exchange_info_map.get(&element.symbol).unwrap().stepsize; + let quote_commission_precision = exchange_info_map.get(&element.symbol).unwrap().quote_commission_precision; + let base_qty_to_be_ordered = + element.base_qty_fee_adjusted.round_dp_with_strategy( + lot_step_size.normalize().scale(), + RoundingStrategy::ToZero, ); - - // 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 pure_profit_percent = ((expected_get_usdt.to_f64().unwrap() - / element.used_usdt.to_f64().unwrap()) - - 1.0) - * 100.0; + let expected_get_usdt = decimal_mul( + decimal_mul(base_qty_to_be_ordered, price).round_dp_with_strategy( + quote_commission_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 pure_profit_percent = ((expected_get_usdt.to_f64().unwrap() + / element.used_usdt.to_f64().unwrap()) + - 1.0) + * 100.0; - 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 + 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()); + 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 - } 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 - } - - if server_epoch - element.transact_time >= 86_400_000 { - // turn is_long from 0 to 1 for orders whose transact time is over than a day (86,400,000 millis = a day) - update_record_build.push(1.to_string()); - } else { - update_record_build.push(0.to_string()); - } - update_record.push(update_record_build.clone()); } + + 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 + } + + if server_epoch - element.transact_time >= 86_400_000 { + // turn is_long from 0 to 1 for orders whose transact time is over than a day (86,400,000 millis = a day) + update_record_build.push(1.to_string()); + } else { + update_record_build.push(0.to_string()); + } + update_record.push(update_record_build.clone()); } } } @@ -698,8 +672,8 @@ pub async fn limit_order_sell( sell_base_price: Decimal, sell_base_quantity: Decimal, client: &Client, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let insert_table_name = String::from("sell_ordered_coin_list"); let insert_columns = vec![ @@ -724,18 +698,11 @@ pub async fn limit_order_sell( ]; let mut insert_values: Vec> = Vec::new(); let mut insert_value_container: Vec = Vec::new(); - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; if RUNNING_MODE == SIMUL && buy_ordered_coin.status == "SIMUL" { - let quote_asset_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == buy_ordered_coin.symbol); - let trade_fee_option = trade_fee_vec - .iter() - .position(|trade_fee| trade_fee.symbol == buy_ordered_coin.symbol); - if quote_asset_precision_option.is_some() && trade_fee_option.is_some() { - let quote_asset_precision = - exchange_info_vec[quote_asset_precision_option.unwrap()].quote_asset_precision; - let trade_fee = trade_fee_vec[trade_fee_option.unwrap()].takercommission; + if exchange_info_map.contains_key(&buy_ordered_coin.symbol) && trade_fee_map.contains_key(&buy_ordered_coin.symbol) { + let quote_asset_precision = exchange_info_map.get(&buy_ordered_coin.symbol).unwrap().quote_asset_precision; + let trade_fee = trade_fee_map.get(&buy_ordered_coin.symbol).unwrap().takercommission; let get_usdt = decimal_mul(sell_base_quantity, sell_base_price) .round_dp_with_strategy(quote_asset_precision, RoundingStrategy::ToZero); @@ -839,17 +806,9 @@ pub async fn limit_order_sell( .push(T.get("status").unwrap().as_str().unwrap().to_string()); // status insert_value_container.push(buy_ordered_coin.used_usdt.to_string()); // used_usdt - let quote_asset_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == buy_ordered_coin.symbol); - let trade_fee_option = trade_fee_vec - .iter() - .position(|trade_fee| trade_fee.symbol == buy_ordered_coin.symbol); - if quote_asset_precision_option.is_some() && trade_fee_option.is_some() { - let quote_asset_precision = exchange_info_vec - [quote_asset_precision_option.unwrap()] - .quote_asset_precision; - let trade_fee = trade_fee_vec[trade_fee_option.unwrap()].takercommission; + if exchange_info_map.contains_key(&buy_ordered_coin.symbol) && trade_fee_map.contains_key(&buy_ordered_coin.symbol) { + let quote_asset_precision = exchange_info_map.get(&buy_ordered_coin.symbol).unwrap().quote_asset_precision; + let trade_fee = trade_fee_map.get(&buy_ordered_coin.symbol).unwrap().takercommission; let get_usdt = rust_decimal::prelude::FromStr::from_str( T.get("cummulativeQuoteQty").unwrap().as_str().unwrap(), ) @@ -932,13 +891,13 @@ pub async fn limit_order_sell( pub async fn monitoring_open_sell_order( client: &Client, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let open_sell_orders = select_open_sell_orders().await; if !open_sell_orders.is_empty() { - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; let orders_outdated = open_sell_orders .iter() .filter(|&element| server_epoch - element.transact_time >= 5_000) @@ -951,14 +910,14 @@ pub async fn monitoring_open_sell_order( // cancel orders outdated over 30secs, delete its records in [sell_ordered_coin_list], and move them into [buy_ordered_coin_list] if !orders_outdated.is_empty() { for element in orders_outdated { - cancel_sell_order(element, &client, exchange_info_vec, trade_fee_vec).await; + cancel_sell_order(element, &client, exchange_info_map, trade_fee_map).await; sleep(Duration::from_millis(200)).await; // Use max 30 LIMIT/min } } if !orders_to_be_queried.is_empty() { for element in orders_to_be_queried { - query_sell_order(element, &client, exchange_info_vec, trade_fee_vec).await; + query_sell_order(element, &client, exchange_info_map, trade_fee_map).await; sleep(Duration::from_millis(300)).await; // Use max 30 LIMIT/min } } @@ -991,7 +950,7 @@ pub async fn monitoring_filled_sell_order( let mut insert_value_build: Vec = Vec::new(); let update_table_name = String::from("achievement_evaluation"); - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; let mut total_get_usdt = Decimal::new(0, 8); for element in filled_sell_orders { @@ -1119,8 +1078,7 @@ pub async fn market_order( pub async fn cancel_buy_order( order: &BuyOrderedCoinList, client: &Client, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + trade_fee_map: &HashMap, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); @@ -1195,18 +1153,8 @@ pub async fn cancel_buy_order( status_value_build.push('\''); // calculate values to be updated - let trade_fee_option = trade_fee_vec - .iter() - .position(|trade_fee| trade_fee.symbol == order.symbol); - - let base_asset_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == order.symbol); - - if trade_fee_option.is_some() && base_asset_precision_option.is_some() { - let trade_fee = - trade_fee_vec[trade_fee_option.unwrap()].takercommission; - + if trade_fee_map.contains_key(&order.symbol) { + let trade_fee = trade_fee_map.get(&order.symbol).unwrap().takercommission; let base_qty_ordered = rust_decimal::prelude::FromStr::from_str( T.get("executedQty").unwrap().as_str().unwrap(), ) @@ -1214,10 +1162,6 @@ pub async fn cancel_buy_order( let base_qty_fee_adjusted = decimal_mul(base_qty_ordered, decimal_sub(dec!(1), trade_fee)); - - let base_asset_precision = exchange_info_vec - [base_asset_precision_option.unwrap()] - .base_asset_precision; let buy_price = decimal_div(cummulative_quote_qty, base_qty_ordered) .round_dp_with_strategy(8, RoundingStrategy::ToZero); @@ -1261,11 +1205,11 @@ pub async fn cancel_buy_order( } 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_buy_order(order, &client, exchange_info_vec, trade_fee_vec).await; + query_buy_order(order, &client, trade_fee_map).await; } } Err(e) => { - query_buy_order(order, &client, exchange_info_vec, trade_fee_vec).await; + query_buy_order(order, &client, trade_fee_map).await; // println!("cancel order buy failed!: {}", body); } } @@ -1277,8 +1221,8 @@ pub async fn cancel_buy_order( pub async fn cancel_sell_order( order: &SellOrderedCoinList, client: &Client, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); @@ -1364,7 +1308,7 @@ pub async fn cancel_sell_order( // insert record in [buy_ordered_coin_list] let mut insert_values: Vec> = Vec::new(); let mut insert_value_container: Vec = Vec::new(); - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; insert_value_container.push(order.symbol.clone()); // symbol insert_value_container.push(order.buy_order_id.to_string()); // order_id insert_value_container.push(order.transact_time.to_string()); // transact_time @@ -1388,19 +1332,9 @@ pub async fn cancel_sell_order( insert_values.push(insert_value_container.clone()); insert_records(&insert_table_name, &insert_columns, &insert_values).await; } else { - let quote_asset_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == order.symbol); - let trade_fee_option = trade_fee_vec - .iter() - .position(|trade_fee| trade_fee.symbol == order.symbol); - - if quote_asset_precision_option.is_some() && trade_fee_option.is_some() { - let quote_asset_precision = exchange_info_vec - [quote_asset_precision_option.unwrap()] - .quote_asset_precision; - let trade_fee = - trade_fee_vec[trade_fee_option.unwrap()].takercommission; + if exchange_info_map.contains_key(&order.symbol) && trade_fee_map.contains_key(&order.symbol) { + let quote_asset_precision = exchange_info_map.get(&order.symbol).unwrap().quote_asset_precision; + let trade_fee = trade_fee_map.get(&order.symbol).unwrap().takercommission; if base_qty_executed == base_qty_ordered { // FILLED case // update status FILLED @@ -1514,7 +1448,7 @@ pub async fn cancel_sell_order( decimal_mul(rest_base_qty, decimal_sub(dec!(1), trade_fee)); let mut insert_values: Vec> = Vec::new(); let mut insert_value_container: Vec = Vec::new(); - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; insert_value_container.push(order.symbol.clone()); // symbol insert_value_container.push(order.buy_order_id.to_string()); // order_id insert_value_container.push(server_epoch.to_string()); // transact_time @@ -1542,12 +1476,12 @@ pub async fn cancel_sell_order( } } } else { - query_sell_order(&order, &client, exchange_info_vec, trade_fee_vec).await; + query_sell_order(&order, &client, exchange_info_map, trade_fee_map).await; } } 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_sell_order(&order, &client, exchange_info_vec, trade_fee_vec).await; + query_sell_order(&order, &client, exchange_info_map, trade_fee_map).await; } } Err(e) => { @@ -1626,8 +1560,7 @@ pub async fn all_orders( pub async fn query_buy_order( order: &BuyOrderedCoinList, client: &Client, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + trade_fee_map: &HashMap, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); @@ -1687,18 +1620,8 @@ pub async fn query_buy_order( value_build.push('\''); // calculate values to be updated - let trade_fee_option = trade_fee_vec - .iter() - .position(|trade_fee| trade_fee.symbol == order.symbol); - let base_asset_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == order.symbol); - - if trade_fee_option.is_some() && base_asset_precision_option.is_some() { - let trade_fee = trade_fee_vec[trade_fee_option.unwrap()].takercommission; - let base_asset_precision = exchange_info_vec - [base_asset_precision_option.unwrap()] - .base_asset_precision; + if trade_fee_map.contains_key(&order.symbol) { + let trade_fee = trade_fee_map.get(&order.symbol).unwrap().takercommission; let base_qty_ordered = rust_decimal::prelude::FromStr::from_str( T.get("executedQty").unwrap().as_str().unwrap(), ) @@ -1773,8 +1696,8 @@ pub async fn query_buy_order( pub async fn query_sell_order( order: &SellOrderedCoinList, client: &Client, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); @@ -1817,18 +1740,9 @@ pub async fn query_sell_order( if T.get("status").is_some_and(|a| a.as_str().unwrap() == "FILLED") || T.get("status").is_some_and(|a| a.as_str().unwrap() == "PARTIALLY_FILLED") { - let quote_asset_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == order.symbol); - let trade_fee_option = trade_fee_vec - .iter() - .position(|trade_fee| trade_fee.symbol == order.symbol); - - if quote_asset_precision_option.is_some() && trade_fee_option.is_some() { - let quote_asset_precision = exchange_info_vec - [quote_asset_precision_option.unwrap()] - .quote_asset_precision; - let trade_fee = trade_fee_vec[trade_fee_option.unwrap()].takercommission; + if exchange_info_map.contains_key(&order.symbol) && trade_fee_map.contains_key(&order.symbol) { + let quote_asset_precision = exchange_info_map.get(&order.symbol).unwrap().quote_asset_precision; + let trade_fee = trade_fee_map.get(&order.symbol).unwrap().takercommission; let get_usdt = rust_decimal::prelude::FromStr::from_str( T.get("cummulativeQuoteQty").unwrap().as_str().unwrap(), ) diff --git a/src/coin_health_check_team/monitors.rs b/src/coin_health_check_team/monitors.rs index 8f87bec..251c942 100644 --- a/src/coin_health_check_team/monitors.rs +++ b/src/coin_health_check_team/monitors.rs @@ -13,6 +13,7 @@ use sqlx::{Error, FromRow}; use std::borrow::{Borrow, BorrowMut}; use std::sync::Arc; use tokio::{join, sync::Mutex, time::*}; +use std::collections::{HashMap, HashSet}; #[derive(Debug, FromRow)] struct AllCoinProfitChangeAvgList { @@ -85,8 +86,8 @@ pub async fn initialize_valid_usde_trade() -> Result<(), Box, - exchange_info_vec: &Vec, + valid_usdt_trade_vec: &mut HashSet, + exchange_info_vec: &HashMap, ) -> Result<(), Box> { #[derive(Debug, FromRow)] struct UsdtTrades { @@ -138,27 +139,23 @@ pub async fn collect_valid_usde_trade( select_record(&fetch_table_name, &column_name, &condition, &usdt_trades).await?; // filtering usdt trades - let mut filtered_usdt_trades: Vec = Vec::new(); - let mut excluded_usdt_trades: Vec = Vec::new(); + let mut filtered_usdt_trades: HashSet = HashSet::new(); + let mut excluded_usdt_trades: HashSet = HashSet::new(); for usdt_trade in usdt_trades { // build update values - let step_size_result = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == usdt_trade.symbol); - - if step_size_result.is_some() { + if let Some(value) = exchange_info_vec.get(&usdt_trade.symbol) { let avg_price: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(usdt_trade.weightedavgprice) .unwrap(); - let step_size = exchange_info_vec[step_size_result.unwrap()].stepsize; + let step_size = value.stepsize; let step_price = decimal_mul(step_size, avg_price); let unit_trade_usdt = crate::coex::assets_managing_team::get_unit_trade_usdt().await; // exclude USDT trades whose step_price is over than 1% of unit_trade_usdt if step_price > decimal_mul(unit_trade_usdt, dec!(0.01)) { - excluded_usdt_trades.push(usdt_trade.symbol.clone()); + excluded_usdt_trades.insert(usdt_trade.symbol.clone()); } else { - filtered_usdt_trades.push(usdt_trade.symbol.clone()); + filtered_usdt_trades.insert(usdt_trade.symbol.clone()); } } } @@ -188,74 +185,6 @@ pub async fn collect_valid_usde_trade( delete_all_rows(&table_name).await?; insert_records(&table_name, &columns, &value_wrapper).await?; - // major trades - // if RUNNING_MODE == TEST || RUNNING_MODE == REAL || RUNNING_MODE == SIMUL { - // let table_name = String::from("valid_usdt_trades"); - // let columns = vec!["symbol"]; - // let mut symbol_vec: Vec = Vec::new(); - // let mut value_wrapper: Vec> = Vec::new(); - - // value_wrapper.push(vec![String::from("BTCUSDT")]); - // value_wrapper.push(vec![String::from("ETHUSDT")]); - // value_wrapper.push(vec![String::from("XRPUSDT")]); - // value_wrapper.push(vec![String::from("DOGEUSDT")]); - // value_wrapper.push(vec![String::from("LTCUSDT")]); - // value_wrapper.push(vec![String::from("TRXUSDT")]); - // value_wrapper.push(vec![String::from("DOTUSDT")]); - // value_wrapper.push(vec![String::from("LINKUSDT")]); - // value_wrapper.push(vec![String::from("AVAXUSDT")]); - // value_wrapper.push(vec![String::from("BCHUSDT")]); - // value_wrapper.push(vec![String::from("SHIBUSDT")]); - // value_wrapper.push(vec![String::from("APTUSDT")]); - // value_wrapper.push(vec![String::from("ARBUSDT")]); - // value_wrapper.push(vec![String::from("ETCUSDT")]); - // value_wrapper.push(vec![String::from("XMRUSDT")]); - // value_wrapper.push(vec![String::from("HBARUSDT")]); - // value_wrapper.push(vec![String::from("UNIUSDT")]); - // value_wrapper.push(vec![String::from("XLMUSDT")]); - // value_wrapper.push(vec![String::from("VETUSDT")]); - // value_wrapper.push(vec![String::from("QNTUSDT")]); - // value_wrapper.push(vec![String::from("GRTUSDT")]); - // value_wrapper.push(vec![String::from("AAVEUSDT")]); - // value_wrapper.push(vec![String::from("EGLDUSDT")]); - // value_wrapper.push(vec![String::from("LDOUSDT")]); - // value_wrapper.push(vec![String::from("STXUSDT")]); - // value_wrapper.push(vec![String::from("MKRUSDT")]); - // value_wrapper.push(vec![String::from("OPUSDT")]); - - // symbol_vec.push(String::from("BTCUSDT")); - // symbol_vec.push(String::from("ETHUSDT")); - // symbol_vec.push(String::from("XRPUSDT")); - // symbol_vec.push(String::from("DOGEUSDT")); - // symbol_vec.push(String::from("LTCUSDT")); - // symbol_vec.push(String::from("TRXUSDT")); - // symbol_vec.push(String::from("DOTUSDT")); - // symbol_vec.push(String::from("LINKUSDT")); - // symbol_vec.push(String::from("AVAXUSDT")); - // symbol_vec.push(String::from("BCHUSDT")); - // symbol_vec.push(String::from("SHIBUSDT")); - // symbol_vec.push(String::from("APTUSDT")); - // symbol_vec.push(String::from("ARBUSDT")); - // symbol_vec.push(String::from("ETCUSDT")); - // symbol_vec.push(String::from("XMRUSDT")); - // symbol_vec.push(String::from("HBARUSDT")); - // symbol_vec.push(String::from("UNIUSDT")); - // symbol_vec.push(String::from("XLMUSDT")); - // symbol_vec.push(String::from("VETUSDT")); - // symbol_vec.push(String::from("QNTUSDT")); - // symbol_vec.push(String::from("GRTUSDT")); - // symbol_vec.push(String::from("AAVEUSDT")); - // symbol_vec.push(String::from("EGLDUSDT")); - // symbol_vec.push(String::from("LDOUSDT")); - // symbol_vec.push(String::from("STXUSDT")); - // symbol_vec.push(String::from("MKRUSDT")); - // symbol_vec.push(String::from("OPUSDT")); - - // *valid_usdt_trade_vec = symbol_vec; - // delete_all_rows(&table_name).await?; - // insert_records(&table_name, &columns, &value_wrapper).await?; - // } - Ok(()) } diff --git a/src/coin_health_check_team/request_candles.rs b/src/coin_health_check_team/request_candles.rs index e13e521..5d401d4 100644 --- a/src/coin_health_check_team/request_candles.rs +++ b/src/coin_health_check_team/request_candles.rs @@ -8,6 +8,7 @@ use sqlx::{Error, FromRow}; use std::borrow::{Borrow, BorrowMut}; use std::sync::Arc; use tokio::{join, sync::Mutex, time::*}; +use std::collections::HashMap; #[derive(Debug, Clone)] pub struct CandleData { @@ -25,6 +26,26 @@ pub struct CandleData { pub ignore_this: f64, } +impl CandleData { + fn new() -> CandleData { + let candle_data = CandleData { + open_time: 0, + open_price: 0.0, + high_price: 0.0, + low_price: 0.0, + close_price: 0.0, + volume: 0.0, + close_time: 0, + quote_asset_volume: 0.0, + number_of_trades: 0, + taker_buy_base_asset_volume: 0.0, + taker_buy_quote_asset_volume: 0.0, + ignore_this: 0.0, + }; + candle_data + } +} + // fetch the list of valid usdt trades #[derive(Debug, FromRow, Clone)] struct Symbols { @@ -37,7 +58,7 @@ async fn request_candlestick_data( symbol: String, interval: &String, client: &Client, - candle_set: &mut Vec<(String, Vec)>, + candle_set: &mut HashMap>, ) -> Result<(), Box> { let mut query = String::from("https://api.binance.com/api/v3/klines?"); query.push_str("&symbol="); @@ -67,7 +88,7 @@ async fn de_candle_json( symbol: String, interval: &String, body: &String, - candle_set: &mut Vec<(String, Vec)>, + candle_map: &mut HashMap>, ) -> Result<(), Box> { let v: Value = serde_json::from_str(body.as_str())?; let mut into_vec = v.as_array(); @@ -75,21 +96,8 @@ async fn de_candle_json( return Err("Err")?; } - let mut candle_data = CandleData { - open_time: 0, - open_price: 0.0, - high_price: 0.0, - low_price: 0.0, - close_price: 0.0, - volume: 0.0, - close_time: 0, - quote_asset_volume: 0.0, - number_of_trades: 0, - taker_buy_base_asset_volume: 0.0, - taker_buy_quote_asset_volume: 0.0, - ignore_this: 0.0, - }; let mut candle_vec: Vec = Vec::new(); + let mut candle_data = CandleData::new(); for element in into_vec.unwrap() { let inner_into_vec = element.as_array().unwrap(); candle_data.open_time = element[0].as_i64().unwrap(); @@ -110,7 +118,7 @@ async fn de_candle_json( candle_vec.push(candle_data.clone()); } - candle_set.push((symbol, candle_vec)); + candle_map.insert(symbol, candle_vec); // let search_result = candle_set.iter().position(|x| x.0 == symbol); // match search_result { // Some(T) => { @@ -127,7 +135,7 @@ async fn de_candle_json2( symbol: String, interval: &String, body: &String, - candle_set: &mut Vec<(String, Vec)>, + candle_map: &mut HashMap>, ) -> Result<(), Box> { let v: Value = serde_json::from_str(body.as_str())?; let mut into_vec = v.as_array(); @@ -135,21 +143,8 @@ async fn de_candle_json2( return Err("Err")?; } - let mut candle_data = CandleData { - open_time: 0, - open_price: 0.0, - high_price: 0.0, - low_price: 0.0, - close_price: 0.0, - volume: 0.0, - close_time: 0, - quote_asset_volume: 0.0, - number_of_trades: 0, - taker_buy_base_asset_volume: 0.0, - taker_buy_quote_asset_volume: 0.0, - ignore_this: 0.0, - }; let mut candle_vec: Vec = Vec::new(); + let mut candle_data = CandleData::new(); for element in into_vec.unwrap() { let inner_into_vec = element.as_array().unwrap(); candle_data.open_time = element[0].as_i64().unwrap(); @@ -170,14 +165,10 @@ async fn de_candle_json2( candle_vec.push(candle_data.clone()); } - let search_result = candle_set.iter().position(|x| x.0 == symbol); - match search_result { - Some(T) => { - candle_set[T].1 = candle_vec; - } - None => { - candle_set.push((symbol, candle_vec)); - } + if let Some(value) = candle_map.get_mut(&symbol) { + *value = candle_vec; + } else { + candle_map.insert(symbol, candle_vec); } Ok(()) } @@ -262,7 +253,7 @@ pub async fn request_candlestick_initial( symbol: String, interval: &String, ) -> Result<(), Box> { - let mut candle_set: Vec<(String, Vec)> = Vec::new(); + let mut candle_set: HashMap> = HashMap::new(); let client = ClientBuilder::new() .timeout(tokio::time::Duration::from_millis(20000)) .build() @@ -449,7 +440,7 @@ pub async fn create_candle_table( // for fetching 1m and 30m candle pub async fn fetch_candle_parallel( interval: &String, - candle_vec: &mut Vec<(String, Vec)>, + candle_map: &mut HashMap>, ) -> Result<(), Box> { let instant = Instant::now(); let fetch_table_name = String::from("valid_usdt_trades"); @@ -466,10 +457,10 @@ pub async fn fetch_candle_parallel( let chunks = select_result.chunks(20); let nbr_chunks = chunks.len(); - let mut candle_vec_arc_wrapper: Vec)>>>> = Vec::new(); + let mut candle_vec_arc_wrapper: Vec>>>> = Vec::new(); for _ in 0..nbr_chunks { - let mut candle_vec_temp: Vec<(String, Vec)> = Vec::new(); - let mut candle_vec_arc = Arc::new(Mutex::new(candle_vec_temp)); + let mut candle_map_temp: HashMap> = HashMap::new(); + let mut candle_vec_arc = Arc::new(Mutex::new(candle_map_temp)); candle_vec_arc_wrapper.push(candle_vec_arc); } @@ -490,15 +481,15 @@ pub async fn fetch_candle_parallel( match result { Ok(T) => { - let mut candle_buffer: Vec<(String, Vec)> = Vec::new(); + let mut candle_buffer: HashMap> = HashMap::new(); for element in candle_vec_arc_wrapper { let a = element.lock().await.clone(); - for element in a { - candle_buffer.push(element); + for (symbol, candle_data) in a { + candle_buffer.insert(symbol, candle_data); } } - *candle_vec = candle_buffer; + *candle_map = candle_buffer; // println!(" candle {} 완료 elapsed:{:.2}s", interval.as_str(), instant.elapsed().as_secs_f32()); } Err(E) => { @@ -512,7 +503,7 @@ pub async fn fetch_candle_parallel( async fn repeat_task( interval: String, symbol_vec: Vec, - my_count: Arc)>>>, + my_count: Arc>>>, ) -> Result<(), Box> { let client = ClientBuilder::new() .timeout(tokio::time::Duration::from_millis(5000)) @@ -531,7 +522,7 @@ async fn repeat_task( // for fetching 1d, 1w, and 1mon candle pub async fn fetch_candle_delay( interval: &String, - candle_vec: &mut Vec<(String, Vec)>, + candle_vec: &mut HashMap>, ) -> Result<(), Box> { let instant_func = Instant::now(); let server_epoch = server_epoch().await; diff --git a/src/coin_health_check_team/request_others.rs b/src/coin_health_check_team/request_others.rs index 26f01ab..e932827 100644 --- a/src/coin_health_check_team/request_others.rs +++ b/src/coin_health_check_team/request_others.rs @@ -8,11 +8,11 @@ use serde_json::Value; use sqlx::{Error, FromRow}; use std::borrow::{Borrow, BorrowMut}; use std::sync::Arc; +use std::collections::HashMap; use tokio::{join, sync::Mutex, time::*}; #[derive(Debug, Clone)] pub struct TradeFee { - pub symbol: String, pub makercommission: Decimal, pub takercommission: Decimal, } @@ -20,7 +20,6 @@ pub struct TradeFee { impl TradeFee { fn new() -> TradeFee { let a = TradeFee { - symbol: String::new(), makercommission: Decimal::new(0, 8), takercommission: Decimal::new(0, 8), }; @@ -30,7 +29,6 @@ impl TradeFee { #[derive(Debug, FromRow, Clone)] pub struct ExchangeInfo { - pub symbol: String, pub stepsize: Decimal, pub ticksize: Decimal, pub base_asset_precision: u32, @@ -42,7 +40,6 @@ pub struct ExchangeInfo { impl ExchangeInfo { fn new() -> ExchangeInfo { let a = ExchangeInfo { - symbol: String::new(), stepsize: Decimal::new(0, 8), ticksize: Decimal::new(0, 8), base_asset_precision: 0, @@ -54,27 +51,12 @@ impl ExchangeInfo { } } -#[derive(Clone, Debug)] -pub struct CoinPriceData { - pub symbol: String, - pub current_price: f64, -} - -impl CoinPriceData { - fn new() -> CoinPriceData { - let a = CoinPriceData { - symbol: String::new(), - current_price: 0.0, - }; - a - } -} // request all coin price (/api, Weight(IP) 2) // request_all_coin_price -> de_all_coin_price_json -> store_coin_price_db // pub async fn request_all_coin_price(client: &Client, price_vec: &Arc>>) -> Result<(), Box> { pub async fn request_all_coin_price( client: &Client, - price_vec: &mut Vec, + price_vec: &mut HashMap, ) -> Result<(), Box> { let url = "https://api.binance.com/api/v3/ticker/price"; let mut response = client.get(url).send().await?; @@ -88,7 +70,7 @@ pub async fn request_all_coin_price( async fn de_all_coin_price_json( body: &String, - price_vec: &mut Vec, + price_vec: &mut HashMap, ) -> Result<(), Box> { let v: Value = serde_json::from_str(body.as_str())?; let into_vec = v.as_array(); @@ -97,27 +79,23 @@ async fn de_all_coin_price_json( } let mut object_map = &serde_json::map::Map::new(); - let mut de_price_vec: Vec = Vec::new(); - let mut data_temp = CoinPriceData::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" => data_temp.symbol = element.1.as_str().unwrap().to_string(), - "price" => { - data_temp.current_price = element.1.as_str().unwrap().parse::().unwrap() - } + "symbol" => symbol = element.1.as_str().unwrap().to_string(), + "price" => price = element.1.as_str().unwrap().parse::().unwrap(), _ => { println!("Elements in body msg are changed. Please update both your coinprices table and vectors."); } } + price_vec.insert(symbol.clone(), price); } - de_price_vec.push(data_temp.clone()); } - *price_vec = de_price_vec; Ok(()) } @@ -129,7 +107,7 @@ pub async fn request_trade_fee( local_epoch: u128, difference_epoch: i64, client: &Client, - tradefee_vec: &mut Vec, + tradefee_vec: &mut HashMap, ) -> Result<(), Box> { let mut base_url = String::from("https://api.binance.com/sapi/v1/asset/tradeFee?"); @@ -188,7 +166,7 @@ pub async fn request_trade_fee( async fn de_trade_fee_json( body: &String, - tradefee_vec: &mut Vec, + tradefee_map: &mut HashMap, ) -> Result<(), Box> { let v: Value = serde_json::from_str(body.as_str())?; @@ -197,17 +175,17 @@ async fn de_trade_fee_json( return Err("Err")?; } let mut object_map = &serde_json::map::Map::new(); - let mut de_tradefee_vec: Vec = Vec::new(); + let mut tradefee_map_build: HashMap = HashMap::new(); + let mut symbol = String::new(); let mut tradefee_data = TradeFee::new(); - 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" => tradefee_data.symbol = element.1.as_str().unwrap().to_string(), + "symbol" => symbol = element.1.as_str().unwrap().to_string(), "makerCommission" => { tradefee_data.makercommission = rust_decimal::prelude::FromStr::from_str(element.1.as_str().unwrap()) @@ -223,10 +201,9 @@ async fn de_trade_fee_json( } } } - de_tradefee_vec.push(tradefee_data.clone()); + tradefee_map_build.insert(symbol.clone(), tradefee_data.clone()); } - tradefee_vec.clear(); - *tradefee_vec = de_tradefee_vec; + *tradefee_map = tradefee_map_build; Ok(()) } @@ -354,7 +331,7 @@ async fn store_24h_change_db( // request exchange information. (/api, Weight(IP) 10) pub async fn request_exchange_infomation( client: &Client, - exchange_info_data: &mut Vec, + exchange_info_map: &mut HashMap, ) -> Result<(), Box> { // building URL and API-keys let mut url = String::new(); @@ -380,8 +357,9 @@ pub async fn request_exchange_infomation( return Err("Err")?; } - let mut data_temp = ExchangeInfo::new(); - let mut data_temp_vec: Vec = Vec::new(); + let mut symbol = String::new(); + let mut exchange_info = ExchangeInfo::new(); + let mut data_map_temp: HashMap = HashMap::new(); for element in into_vec.unwrap() { if element.0.contains("symbols") { @@ -394,23 +372,23 @@ pub async fn request_exchange_infomation( .unwrap() .ends_with("USDT") { - data_temp.symbol = + symbol = (element.get("symbol").unwrap().as_str().unwrap().to_string()); - data_temp.base_asset_precision = + exchange_info.base_asset_precision = (element.get("baseAssetPrecision").unwrap().as_u64().unwrap()) as u32; - data_temp.base_commission_precision = (element + exchange_info.base_commission_precision = (element .get("baseCommissionPrecision") .unwrap() .as_u64() .unwrap()) as u32; - data_temp.quote_asset_precision = (element + exchange_info.quote_asset_precision = (element .get("quoteAssetPrecision") .unwrap() .as_u64() .unwrap()) as u32; - data_temp.quote_commission_precision = (element + exchange_info.quote_commission_precision = (element .get("quoteCommissionPrecision") .unwrap() .as_u64() @@ -426,7 +404,7 @@ pub async fn request_exchange_infomation( .unwrap() .starts_with("LOT_SIZE") { - data_temp.stepsize = rust_decimal::prelude::FromStr::from_str( + exchange_info.stepsize = rust_decimal::prelude::FromStr::from_str( element.get("stepSize").unwrap().as_str().unwrap(), ) .unwrap(); @@ -439,20 +417,20 @@ pub async fn request_exchange_infomation( .unwrap() .starts_with("PRICE_FILTER") { - data_temp.ticksize = rust_decimal::prelude::FromStr::from_str( + exchange_info.ticksize = rust_decimal::prelude::FromStr::from_str( element.get("tickSize").unwrap().as_str().unwrap(), ) .unwrap(); } } - data_temp_vec.push(data_temp.clone()); + data_map_temp.insert(symbol.clone(), exchange_info.clone()); } } } } } - *exchange_info_data = data_temp_vec; + *exchange_info_map = data_map_temp; Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index abbd06d..8695f11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ pub enum RunningMode { TEST, REAL, } -pub const RUNNING_MODE: RunningMode = RunningMode::REAL; +pub const RUNNING_MODE: RunningMode = RunningMode::SIMUL; pub mod coex; pub mod coin_health_check_team; diff --git a/src/main.rs b/src/main.rs index d8f20a7..ac76742 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,15 @@ use crate::coin_health_check_team::*; use crate::request_candles::CandleData; -use crate::request_others::{CoinPriceData, ExchangeInfo, TradeFee}; +use crate::request_others::{ExchangeInfo, TradeFee}; use crate::server_health_check_team::ServerHealth; use crate::strategy_team::AllData; use crate::time_checking_team::UserTime; use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; use reqwest::{Client, ClientBuilder}; +use rust_decimal::Decimal; use sqlx::{mysql::*, Connection, Executor, FromRow, Row}; +use std::collections::{HashMap, HashSet}; use std::{ io::{self, Write}, sync::Arc, @@ -80,88 +82,88 @@ async fn main() -> Result<(), Box> { let (tx2, mut rx2) = watch::channel(0); // epoch_difference // trade fee data - let mut tradefee_vec: Vec = Vec::new(); // (symbol, makerCommission, takerCommission) - let (tx_tradefee_vec, mut rx_tradefee_vec) = watch::channel(tradefee_vec); - let mut rx2_tradefee_vec = rx_tradefee_vec.clone(); - let mut rx3_tradefee_vec = rx_tradefee_vec.clone(); - let mut rx4_tradefee_vec = rx_tradefee_vec.clone(); - let mut rx5_tradefee_vec = rx_tradefee_vec.clone(); - let mut tradefee_vec_capacity = rx_tradefee_vec.clone(); + let mut tradefee_map: HashMap = HashMap::new(); // + let (tx_tradefee_map, mut rx_tradefee_map) = watch::channel(tradefee_map); + let mut rx2_tradefee_map = rx_tradefee_map.clone(); + let mut rx3_tradefee_map = rx_tradefee_map.clone(); + let mut rx4_tradefee_map = rx_tradefee_map.clone(); + let mut rx5_tradefee_map = rx_tradefee_map.clone(); + let mut tradefee_map_capacity = rx_tradefee_map.clone(); // valid usdt trade data - let mut valid_usdt_trade_vec: Vec = Vec::new(); // symbol - let (tx_valid_usdt_trade_vec, mut rx_valid_usdt_trade_vec) = - watch::channel(valid_usdt_trade_vec); - let mut rx2_valid_usdt_trade_vec = rx_valid_usdt_trade_vec.clone(); - let mut rx3_valid_usdt_trade_vec = rx_valid_usdt_trade_vec.clone(); - let mut rx4_valid_usdt_trade_vec = rx_valid_usdt_trade_vec.clone(); - let mut valid_usdt_trade_vec_capacity = rx_valid_usdt_trade_vec.clone(); + let mut valid_usdt_trade_set: HashSet = HashSet::new(); // symbol + let (tx_valid_usdt_trade_set, mut rx_valid_usdt_trade_set) = + watch::channel(valid_usdt_trade_set); + let mut rx2_valid_usdt_trade_set = rx_valid_usdt_trade_set.clone(); + let mut rx3_valid_usdt_trade_set = rx_valid_usdt_trade_set.clone(); + let mut rx4_valid_usdt_trade_set = rx_valid_usdt_trade_set.clone(); + let mut valid_usdt_trade_set_capacity = rx_valid_usdt_trade_set.clone(); // price per second data and channels - let mut price_vec: Vec = Vec::new(); // (symbol, price) - let (tx_price_vec, mut rx_price_vec) = watch::channel(price_vec); - let mut rx3_price_vec = rx_price_vec.clone(); - let mut rx5_price_vec = rx_price_vec.clone(); - let mut price_vec_capacity = rx_price_vec.clone(); + let mut price_map: HashMap = HashMap::new(); // + let (tx_price_map, mut rx_price_map) = watch::channel(price_map); + let mut rx3_price_map = rx_price_map.clone(); + let mut rx5_price_map = rx_price_map.clone(); + let mut price_map_capacity = rx_price_map.clone(); // candle data from endpoint and channels - let mut candle_1m_vec: Vec<(String, Vec)> = Vec::new(); // (symbol, Vec - let (tx_candle_1m_vec, mut rx_candle_1m_vec) = watch::channel(candle_1m_vec); - let mut candle_1m_vec_capacity = rx_candle_1m_vec.clone(); + let mut candle_1m_map: HashMap> = HashMap::new(); // > + let (tx_candle_1m_map, mut rx_candle_1m_map) = watch::channel(candle_1m_map); + let mut candle_1m_map_capacity = rx_candle_1m_map.clone(); - let mut candle_30m_vec: Vec<(String, Vec)> = Vec::new(); - let (tx_candle_30m_vec, mut rx_candle_30m_vec) = watch::channel(candle_30m_vec); - let mut candle_30m_vec_capacity = rx_candle_30m_vec.clone(); + let mut candle_30m_map: HashMap> = HashMap::new(); + let (tx_candle_30m_map, mut rx_candle_30m_map) = watch::channel(candle_30m_map); + let mut candle_30m_map_capacity = rx_candle_30m_map.clone(); - let mut candle_1d_vec: Vec<(String, Vec)> = Vec::new(); - let (tx_candle_1d_vec, mut rx_candle_1d_vec) = watch::channel(candle_1d_vec); - let mut candle_1d_vec_capacity = rx_candle_1d_vec.clone(); + let mut candle_1d_map: HashMap> = HashMap::new(); + let (tx_candle_1d_map, mut rx_candle_1d_map) = watch::channel(candle_1d_map); + let mut candle_1d_map_capacity = rx_candle_1d_map.clone(); - let mut candle_1w_vec: Vec<(String, Vec)> = Vec::new(); - let (tx_candle_1w_vec, mut rx_candle_1w_vec) = watch::channel(candle_1w_vec); - let mut candle_1w_vec_capacity = rx_candle_1w_vec.clone(); + let mut candle_1w_map: HashMap> = HashMap::new(); + let (tx_candle_1w_map, mut rx_candle_1w_map) = watch::channel(candle_1w_map); + let mut candle_1w_map_capacity = rx_candle_1w_map.clone(); - let mut candle_1mon_vec: Vec<(String, Vec)> = Vec::new(); - let (tx_candle_1mon_vec, mut rx_candle_1mon_vec) = watch::channel(candle_1mon_vec); - let mut candle_1mon_vec_capacity = rx_candle_1mon_vec.clone(); + let mut candle_1mon_map: HashMap> = HashMap::new(); + let (tx_candle_1mon_map, mut rx_candle_1mon_map) = watch::channel(candle_1mon_map); + let mut candle_1mon_map_capacity = rx_candle_1mon_map.clone(); // real-time reflected price data and channels - let mut rt_price_1m_vec: Vec<(String, Vec)> = Vec::new(); // (symbol, Vec - let (tx_rt_price_1m_vec, mut rx_rt_price_1m_vec) = watch::channel(rt_price_1m_vec); - let mut rx2_rt_price_1m_vec = rx_rt_price_1m_vec.clone(); - let mut rx3_rt_price_1m_vec = rx_rt_price_1m_vec.clone(); - let mut rx4_rt_price_1m_vec = rx_rt_price_1m_vec.clone(); - let mut rx5_rt_price_1m_vec = rx_rt_price_1m_vec.clone(); - let mut rt_price_1m_vec_capacity = rx_rt_price_1m_vec.clone(); + let mut rt_price_1m_map: HashMap> = HashMap::new(); // > + let (tx_rt_price_1m_map, mut rx_rt_price_1m_map) = watch::channel(rt_price_1m_map); + let mut rx2_rt_price_1m_map = rx_rt_price_1m_map.clone(); + let mut rx3_rt_price_1m_map = rx_rt_price_1m_map.clone(); + let mut rx4_rt_price_1m_map = rx_rt_price_1m_map.clone(); + let mut rx5_rt_price_1m_map = rx_rt_price_1m_map.clone(); + let mut rt_price_1m_map_capacity = rx_rt_price_1m_map.clone(); - let mut rt_price_30m_vec: Vec<(String, Vec)> = Vec::new(); - let (tx_rt_price_30m_vec, mut rx_rt_price_30m_vec) = watch::channel(rt_price_30m_vec); - let mut rx2_rt_price_30m_vec = rx_rt_price_30m_vec.clone(); - let mut rx3_rt_price_30m_vec = rx_rt_price_30m_vec.clone(); - let mut rx4_rt_price_30m_vec = rx_rt_price_30m_vec.clone(); - let mut rx5_rt_price_30m_vec = rx_rt_price_30m_vec.clone(); - let mut rt_price_30m_vec_capacity = rx_rt_price_30m_vec.clone(); + let mut rt_price_30m_map: HashMap> = HashMap::new(); + let (tx_rt_price_30m_map, mut rx_rt_price_30m_map) = watch::channel(rt_price_30m_map); + let mut rx2_rt_price_30m_map = rx_rt_price_30m_map.clone(); + let mut rx3_rt_price_30m_map = rx_rt_price_30m_map.clone(); + let mut rx4_rt_price_30m_map = rx_rt_price_30m_map.clone(); + let mut rx5_rt_price_30m_map = rx_rt_price_30m_map.clone(); + let mut rt_price_30m_map_capacity = rx_rt_price_30m_map.clone(); - let mut rt_price_1d_vec: Vec<(String, Vec)> = Vec::new(); - let (tx_rt_price_1d_vec, mut rx_rt_price_1d_vec) = watch::channel(rt_price_1d_vec); - let mut rx2_rt_price_1d_vec = rx_rt_price_1d_vec.clone(); - let mut rx3_rt_price_1d_vec = rx_rt_price_1d_vec.clone(); - let mut rx4_rt_price_1d_vec = rx_rt_price_1d_vec.clone(); - let mut rt_price_1d_vec_capacity = rx_rt_price_1d_vec.clone(); + let mut rt_price_1d_map: HashMap> = HashMap::new(); + let (tx_rt_price_1d_map, mut rx_rt_price_1d_map) = watch::channel(rt_price_1d_map); + let mut rx2_rt_price_1d_map = rx_rt_price_1d_map.clone(); + let mut rx3_rt_price_1d_map = rx_rt_price_1d_map.clone(); + let mut rx4_rt_price_1d_map = rx_rt_price_1d_map.clone(); + let mut rt_price_1d_map_capacity = rx_rt_price_1d_map.clone(); - let mut rt_price_1w_vec: Vec<(String, Vec)> = Vec::new(); - let (tx_rt_price_1w_vec, mut rx_rt_price_1w_vec) = watch::channel(rt_price_1w_vec); - let mut rx2_rt_price_1w_vec = rx_rt_price_1w_vec.clone(); - let mut rx3_rt_price_1w_vec = rx_rt_price_1w_vec.clone(); - let mut rx4_rt_price_1w_vec = rx_rt_price_1w_vec.clone(); - let mut rt_price_1w_vec_capacity = rx_rt_price_1w_vec.clone(); + let mut rt_price_1w_map: HashMap> = HashMap::new(); + let (tx_rt_price_1w_map, mut rx_rt_price_1w_map) = watch::channel(rt_price_1w_map); + let mut rx2_rt_price_1w_map = rx_rt_price_1w_map.clone(); + let mut rx3_rt_price_1w_map = rx_rt_price_1w_map.clone(); + let mut rx4_rt_price_1w_map = rx_rt_price_1w_map.clone(); + let mut rt_price_1w_map_capacity = rx_rt_price_1w_map.clone(); - let mut rt_price_1mon_vec: Vec<(String, Vec)> = Vec::new(); - let (tx_rt_price_1mon_vec, mut rx_rt_price_1mon_vec) = watch::channel(rt_price_1mon_vec); - let mut rx2_rt_price_1mon_vec = rx_rt_price_1mon_vec.clone(); - let mut rx3_rt_price_1mon_vec = rx_rt_price_1mon_vec.clone(); - let mut rx4_rt_price_1mon_vec = rx_rt_price_1mon_vec.clone(); - let mut rt_price_1mon_vec_capacity = rx_rt_price_1mon_vec.clone(); + let mut rt_price_1mon_map: HashMap> = HashMap::new(); + let (tx_rt_price_1mon_map, mut rx_rt_price_1mon_map) = watch::channel(rt_price_1mon_map); + let mut rx2_rt_price_1mon_map = rx_rt_price_1mon_map.clone(); + let mut rx3_rt_price_1mon_map = rx_rt_price_1mon_map.clone(); + let mut rx4_rt_price_1mon_map = rx_rt_price_1mon_map.clone(); + let mut rt_price_1mon_map_capacity = rx_rt_price_1mon_map.clone(); // TEMA data // let mut tema3_1m_data: Vec<(String, Vec<(f64, i64)>)> = Vec::new(); // Vec<(symbol, Vec<(price, closetime)>)> @@ -198,14 +200,14 @@ async fn main() -> Result<(), Box> { // let (tx_tema30_1mon_data, mut rx_tema30_1mon_data) = watch::channel(tema30_1mon_data); // Exchange Information data - let mut exchange_info_data: Vec = Vec::new(); - let (tx_exchange_info_data, mut rx_exchange_info_data) = watch::channel(exchange_info_data); - let mut rx2_exchange_info_data = rx_exchange_info_data.clone(); - let mut rx3_exchange_info_data = rx_exchange_info_data.clone(); - let mut rx4_exchange_info_data = rx_exchange_info_data.clone(); - let mut rx5_exchange_info_data = rx_exchange_info_data.clone(); - let mut rx6_exchange_info_data = rx_exchange_info_data.clone(); - let mut exchange_info_data_capacity = rx_exchange_info_data.clone(); + let mut exchange_info_map: HashMap = HashMap::new(); + let (tx_exchange_info_map, mut rx_exchange_info_map) = watch::channel(exchange_info_map); + let mut rx2_exchange_info_map = rx_exchange_info_map.clone(); + let mut rx3_exchange_info_map = rx_exchange_info_map.clone(); + let mut rx4_exchange_info_map = rx_exchange_info_map.clone(); + let mut rx5_exchange_info_map = rx_exchange_info_map.clone(); + let mut rx6_exchange_info_map = rx_exchange_info_map.clone(); + let mut exchange_info_map_capacity = rx_exchange_info_map.clone(); { if RUNNING_MODE == RunningMode::REAL { @@ -225,20 +227,20 @@ async fn main() -> Result<(), Box> { let interval_1w = String::from("1w"); let interval_1mon = String::from("1mon"); - let mut candle_30m_vec_temp: Vec<(String, Vec)> = Vec::new(); - let mut candle_1d_vec_temp: Vec<(String, Vec)> = Vec::new(); - let mut candle_1w_vec_temp: Vec<(String, Vec)> = Vec::new(); - let mut candle_1mon_vec_temp: Vec<(String, Vec)> = Vec::new(); + let mut candle_30m_map_temp: HashMap> = HashMap::new(); + let mut candle_1d_map_temp: HashMap> = HashMap::new(); + let mut candle_1w_map_temp: HashMap> = HashMap::new(); + let mut candle_1mon_map_temp: HashMap> = HashMap::new(); - request_candles::fetch_candle_parallel(&interval_30m, &mut candle_30m_vec_temp).await; - request_candles::fetch_candle_parallel(&interval_1d, &mut candle_1d_vec_temp).await; - request_candles::fetch_candle_parallel(&interval_1w, &mut candle_1w_vec_temp).await; - request_candles::fetch_candle_parallel(&interval_1mon, &mut candle_1mon_vec_temp).await; + request_candles::fetch_candle_parallel(&interval_30m, &mut candle_30m_map_temp).await; + request_candles::fetch_candle_parallel(&interval_1d, &mut candle_1d_map_temp).await; + request_candles::fetch_candle_parallel(&interval_1w, &mut candle_1w_map_temp).await; + request_candles::fetch_candle_parallel(&interval_1mon, &mut candle_1mon_map_temp).await; - tx_candle_30m_vec.send_modify(|vec| *vec = candle_30m_vec_temp); - tx_candle_1d_vec.send_modify(|vec| *vec = candle_1d_vec_temp); - tx_candle_1w_vec.send_modify(|vec| *vec = candle_1w_vec_temp); - tx_candle_1mon_vec.send_modify(|vec| *vec = candle_1mon_vec_temp); + tx_candle_30m_map.send_modify(|map| *map = candle_30m_map_temp); + tx_candle_1d_map.send_modify(|map| *map = candle_1d_map_temp); + tx_candle_1w_map.send_modify(|map| *map = candle_1w_map_temp); + tx_candle_1mon_map.send_modify(|map| *map = candle_1mon_map_temp); // sleep as much as the loop recurs per 60 seconds, expecting child threads will have finished within 60 seconds. println!("Ok.."); @@ -515,10 +517,9 @@ async fn main() -> Result<(), Box> { match tx1_changed { Ok(T) => match tx2_changed { Ok(T) => { - let mut tradefee_vec_temp: Vec = Vec::new(); - let mut result; + let mut tradefee_vec_temp: HashMap = HashMap::new(); loop { - result = request_others::request_trade_fee( + let result = request_others::request_trade_fee( API_KEY, SECRET_KEY, local_epoch, @@ -535,13 +536,9 @@ async fn main() -> Result<(), Box> { } } - match result { - Ok(T) => { - tx_tradefee_vec.send_modify(|vec| *vec = tradefee_vec_temp); - tx_task2.send(2).expect("The mpsc channel has been closed."); - } - Err(E) => {} - } + tx_tradefee_map.send_modify(|vec| *vec = tradefee_vec_temp); + tx_task2.send(2).expect("The mpsc channel has been closed."); + } Err(E) => { panic!("tx2-rx2 channel has been closed.") @@ -567,30 +564,27 @@ async fn main() -> Result<(), Box> { .timeout(tokio::time::Duration::from_millis(3000)) .build() .unwrap(); - let mut exchange_info_data_temp: Vec = Vec::new(); + let mut exchange_info_map_temp: HashMap = HashMap::new(); let mut result; loop { result = coin_health_check_team::request_others::request_exchange_infomation( &client, - &mut exchange_info_data_temp, + &mut exchange_info_map_temp, ) .await; // retry - if exchange_info_data_temp.len() == 0 { + if exchange_info_map_temp.len() == 0 { sleep(Duration::from_secs(3)).await; } else { break; } } - match result { - Ok(T) => { - tx_exchange_info_data.send_modify(|vec| *vec = exchange_info_data_temp); - tx_task3.send(3).expect("The mpsc channel has been closed."); - } - Err(E) => {} - } + + tx_exchange_info_map.send_modify(|vec| *vec = exchange_info_map_temp); + tx_task3.send(3).expect("The mpsc channel has been closed."); + sleep(Duration::from_secs(300)).await; // sleep for 5 mins } }); @@ -613,22 +607,17 @@ async fn main() -> Result<(), Box> { let result = request_others::request_24hr_ticker_price_change_statistics(&client).await; match result { Ok(T) => { - let exchange_info_vec = rx5_exchange_info_data.borrow().clone(); - let mut valid_usdt_trade_vec_temp: Vec = Vec::new(); - let result = monitors::collect_valid_usde_trade( - &mut valid_usdt_trade_vec_temp, - &exchange_info_vec, + let exchange_info_map = rx5_exchange_info_map.borrow().clone(); + let mut valid_usdt_trade_set_temp: HashSet = HashSet::new(); + monitors::collect_valid_usde_trade( + &mut valid_usdt_trade_set_temp, + &exchange_info_map, ) .await; - match result { - Ok(T) => { - tx_valid_usdt_trade_vec - .send_modify(|vec| *vec = valid_usdt_trade_vec_temp); - tx_task4.send(4).expect("The mpsc channel has been closed."); - } - Err(E) => {} - } + tx_valid_usdt_trade_set + .send_modify(|vec| *vec = valid_usdt_trade_set_temp); + tx_task4.send(4).expect("The mpsc channel has been closed."); } Err(E) => { println!(">>> Failed to monitor usdt_24h_change_profit_index."); @@ -676,7 +665,6 @@ async fn main() -> Result<(), Box> { // Task#5: price per second tokio::task::spawn(async move { sleep(Duration::from_secs(20)).await; - let mut server_epoch = 0; let mut elapsed_time = 0; loop { let instant = Instant::now(); @@ -684,101 +672,85 @@ async fn main() -> Result<(), Box> { .timeout(tokio::time::Duration::from_millis(1000)) .build() .unwrap(); - let mut price_vec_temp: Vec = Vec::new(); - let result = request_others::request_all_coin_price(&client, &mut price_vec_temp).await; - let mut price_vec_temp_c: Vec = Vec::new(); - match result { - Ok(T) => { - price_vec_temp_c = price_vec_temp.clone(); - tx_price_vec.send_modify(|vec| *vec = price_vec_temp); - tx_task5.send(5).expect("The mpsc channel has been closed."); - } - Err(E) => {} - } + let mut price_vec_temp: HashMap = HashMap::new(); + let mut price_vec_temp_c: HashMap = HashMap::new(); + request_others::request_all_coin_price(&client, &mut price_vec_temp).await; + price_vec_temp_c = price_vec_temp.clone(); + tx_price_map.send_modify(|vec| *vec = price_vec_temp); + tx_task5.send(5).expect("The mpsc channel has been closed."); // Task#10: make realtime price data of 1m, 30m, 1d, 1w and 1mon - let valid_usdt_trade_vec = rx_valid_usdt_trade_vec.borrow().clone(); + let valid_usdt_trade_set = rx_valid_usdt_trade_set.borrow().clone(); // 1m let interval = String::from("1m"); - let candle_1m_vec = rx_candle_1m_vec.borrow().clone(); - let rt_price_1m_vec_read_temp: Vec<(String, Vec)> = Vec::new(); - let mut rt_price_1m_vec_write_temp: Vec<(String, Vec)> = Vec::new(); + let candle_1m_vec = rx_candle_1m_map.borrow().clone(); + let dummy_data: HashMap> = HashMap::new(); + let mut rt_price_1m_map_write_temp: HashMap> = HashMap::new(); + let mut rt_price_1m_map_write_temp_c: HashMap> = HashMap::new(); let result = value_estimation_team::datapoints::price_data::update_realtime_price_data( &interval, &candle_1m_vec, - &rt_price_1m_vec_read_temp, - &mut rt_price_1m_vec_write_temp, + &dummy_data, + &mut rt_price_1m_map_write_temp, &price_vec_temp_c, - &valid_usdt_trade_vec, + &valid_usdt_trade_set, ) .await; - - match result { - Ok(T) => { - if tx_rt_price_1m_vec.is_closed() { - eprintln!("tx_rt_price_1m_vec has been closed!"); - } else { - tx_rt_price_1m_vec.send_modify(|vec| *vec = rt_price_1m_vec_write_temp); - } - } - Err(E) => {} + rt_price_1m_map_write_temp_c = rt_price_1m_map_write_temp.clone(); + if tx_rt_price_1m_map.is_closed() { + eprintln!("tx_rt_price_1m_vec has been closed!"); + } else { + tx_rt_price_1m_map.send_modify(|vec| *vec = rt_price_1m_map_write_temp); } // 30m let interval = String::from("30m"); - let candle_30m_vec = rx_candle_30m_vec.borrow().clone(); - let rt_price_1m_vec = rx_rt_price_1m_vec.borrow().clone(); - let mut rt_price_30m_vec_write_temp: Vec<(String, Vec)> = Vec::new(); - - if !rt_price_1m_vec.is_empty() { + let candle_30m_map = rx_candle_30m_map.borrow().clone(); + let mut rt_price_30m_map_write_temp: HashMap> = HashMap::new(); + let mut rt_price_30m_map_write_temp_c: HashMap> = HashMap::new(); + if !rt_price_1m_map_write_temp_c.is_empty() { let result = value_estimation_team::datapoints::price_data::update_realtime_price_data( &interval, - &candle_30m_vec, - &rt_price_1m_vec, - &mut rt_price_30m_vec_write_temp, + &candle_30m_map, + &rt_price_1m_map_write_temp_c, + &mut rt_price_30m_map_write_temp, &price_vec_temp_c, - &valid_usdt_trade_vec, + &valid_usdt_trade_set, ) .await; - - match result { - Ok(T) => { - if tx_rt_price_30m_vec.is_closed() { - eprintln!("tx_rt_price_30m_vec has been closed!"); - } else { - tx_rt_price_30m_vec - .send_modify(|vec| *vec = rt_price_30m_vec_write_temp); - } - } - Err(E) => {} - } + rt_price_30m_map_write_temp_c = rt_price_30m_map_write_temp.clone(); + if tx_rt_price_30m_map.is_closed() { + eprintln!("tx_rt_price_30m_vec has been closed!"); + } else { + tx_rt_price_30m_map + .send_modify(|map: &mut HashMap>| *map = rt_price_30m_map_write_temp); + } } // 1d let interval = String::from("1d"); - let candle_1d_vec = rx_candle_1d_vec.borrow().clone(); - let rt_price_30m_vec = rx_rt_price_30m_vec.borrow().clone(); - let mut rt_price_1d_vec_write_temp: Vec<(String, Vec)> = Vec::new(); + let candle_1d_vec = rx_candle_1d_map.borrow().clone(); + let mut rt_price_1d_map_write_temp: HashMap> = HashMap::new(); - if !rt_price_30m_vec.is_empty() { + if !rt_price_30m_map_write_temp_c.is_empty() { let result = value_estimation_team::datapoints::price_data::update_realtime_price_data( &interval, &candle_1d_vec, - &rt_price_30m_vec, - &mut rt_price_1d_vec_write_temp, + &rt_price_30m_map_write_temp_c, + &mut rt_price_1d_map_write_temp, &price_vec_temp_c, - &valid_usdt_trade_vec, + &valid_usdt_trade_set, ) .await; match result { Ok(T) => { - if tx_rt_price_1d_vec.is_closed() { + if tx_rt_price_1d_map.is_closed() { eprintln!("tx_rt_price_1d_vec has been closed!"); } else { - tx_rt_price_1d_vec.send_modify(|vec| *vec = rt_price_1d_vec_write_temp); + tx_rt_price_1d_map.send_modify(|map| *map = rt_price_1d_map_write_temp); } } Err(E) => {} @@ -831,13 +803,13 @@ async fn main() -> Result<(), Box> { let interval = String::from("1m"); loop { let instant = Instant::now(); - let mut candle_1m_vec_temp: Vec<(String, Vec)> = Vec::new(); + let mut candle_1m_map_temp: HashMap> = HashMap::new(); let result = - request_candles::fetch_candle_parallel(&interval, &mut candle_1m_vec_temp).await; + request_candles::fetch_candle_parallel(&interval, &mut candle_1m_map_temp).await; match result { Ok(T) => { - tx_candle_1m_vec.send_modify(|vec| *vec = candle_1m_vec_temp); + tx_candle_1m_map.send_modify(|vec| *vec = candle_1m_map_temp); tx_task6.send(6).expect("The mpsc channel has been closed."); } Err(E) => {} @@ -858,14 +830,14 @@ async fn main() -> Result<(), Box> { let interval = String::from("30m"); loop { let instant = Instant::now(); - let mut candle_30m_vec_temp: Vec<(String, Vec)> = Vec::new(); + let mut candle_30m_map_temp: HashMap> = HashMap::new(); let result = - request_candles::fetch_candle_delay(&interval, &mut candle_30m_vec_temp).await; + request_candles::fetch_candle_delay(&interval, &mut candle_30m_map_temp).await; // request_candles::fetch_candle_parallel(&interval, &mut candle_30m_vec_temp).await; match result { Ok(T) => { - tx_candle_30m_vec.send_modify(|vec| *vec = candle_30m_vec_temp); + tx_candle_30m_map.send_modify(|vec| *vec = candle_30m_map_temp); tx_task7.send(7).expect("The mpsc channel has been closed."); } Err(E) => {} @@ -886,13 +858,13 @@ async fn main() -> Result<(), Box> { sleep(Duration::from_secs(600)).await; loop { let instant = Instant::now(); - let mut candle_1d_vec_temp: Vec<(String, Vec)> = Vec::new(); + let mut candle_1d_map_temp: HashMap> = HashMap::new(); let result = - request_candles::fetch_candle_delay(&interval, &mut candle_1d_vec_temp).await; + request_candles::fetch_candle_delay(&interval, &mut candle_1d_map_temp).await; match result { Ok(T) => { - tx_candle_1d_vec.send_modify(|vec| *vec = candle_1d_vec_temp); + tx_candle_1d_map.send_modify(|vec| *vec = candle_1d_map_temp); tx_task8.send(8).expect("The mpsc channel has been closed."); } Err(E) => {} @@ -1039,14 +1011,14 @@ async fn main() -> Result<(), Box> { loop { let instant = Instant::now(); let mut all_data = AllData::new(); - all_data.valid_symbol_vec = rx3_valid_usdt_trade_vec.borrow().clone(); + all_data.valid_symbol_vec = rx3_valid_usdt_trade_set.borrow().clone(); // realtime price data - all_data.rt_price_1m_vec = rx3_rt_price_1m_vec.borrow().clone(); - all_data.rt_price_30m_vec = rx3_rt_price_30m_vec.borrow().clone(); - all_data.rt_price_1d_vec = rx3_rt_price_1d_vec.borrow().clone(); - all_data.rt_price_1w_vec = rx3_rt_price_1w_vec.borrow().clone(); - all_data.rt_price_1mon_vec = rx3_rt_price_1mon_vec.borrow().clone(); + all_data.rt_price_1m_vec = rx3_rt_price_1m_map.borrow().clone(); + all_data.rt_price_30m_vec = rx3_rt_price_30m_map.borrow().clone(); + all_data.rt_price_1d_vec = rx3_rt_price_1d_map.borrow().clone(); + all_data.rt_price_1w_vec = rx3_rt_price_1w_map.borrow().clone(); + all_data.rt_price_1mon_vec = rx3_rt_price_1mon_map.borrow().clone(); let result = strategy_team::strategy_manager::execute_list_up_for_buy(&all_data).await; @@ -1076,13 +1048,13 @@ async fn main() -> Result<(), Box> { let mut all_data = AllData::new(); loop { let instant = Instant::now(); - all_data.valid_symbol_vec = rx3_valid_usdt_trade_vec.borrow().clone(); + all_data.valid_symbol_vec = rx3_valid_usdt_trade_set.borrow().clone(); // realtime price data - all_data.rt_price_1m_vec = rx3_rt_price_1m_vec.borrow().clone(); - all_data.rt_price_30m_vec = rx3_rt_price_30m_vec.borrow().clone(); - all_data.rt_price_1d_vec = rx3_rt_price_1d_vec.borrow().clone(); - all_data.rt_price_1w_vec = rx3_rt_price_1w_vec.borrow().clone(); - all_data.rt_price_1mon_vec = rx3_rt_price_1mon_vec.borrow().clone(); + all_data.rt_price_1m_vec = rx3_rt_price_1m_map.borrow().clone(); + all_data.rt_price_30m_vec = rx3_rt_price_30m_map.borrow().clone(); + all_data.rt_price_1d_vec = rx3_rt_price_1d_map.borrow().clone(); + all_data.rt_price_1w_vec = rx3_rt_price_1w_map.borrow().clone(); + all_data.rt_price_1mon_vec = rx3_rt_price_1mon_map.borrow().clone(); // let result = coex::strategy_team::execute_strategist_for_test(&all_data).await; @@ -1112,23 +1084,23 @@ async fn main() -> Result<(), Box> { loop { let instant = Instant::now(); let mut all_data = AllData::new(); - let mut exchange_info_vec: Vec = Vec::new(); - let mut trade_fee_vec: Vec = Vec::new(); + let mut exchange_info_map: HashMap = HashMap::new(); + let mut trade_fee_map: HashMap = HashMap::new(); - all_data.valid_symbol_vec = rx4_valid_usdt_trade_vec.borrow().clone(); - exchange_info_vec = rx6_exchange_info_data.borrow().clone(); - trade_fee_vec = rx5_tradefee_vec.borrow().clone(); + all_data.valid_symbol_vec = rx4_valid_usdt_trade_set.borrow().clone(); + exchange_info_map = rx6_exchange_info_map.borrow().clone(); + trade_fee_map = rx5_tradefee_map.borrow().clone(); // realtime price data - all_data.rt_price_1m_vec = rx5_rt_price_1m_vec.borrow().clone(); - all_data.rt_price_30m_vec = rx5_rt_price_30m_vec.borrow().clone(); - all_data.rt_price_1d_vec = rx4_rt_price_1d_vec.borrow().clone(); - all_data.rt_price_1w_vec = rx4_rt_price_1w_vec.borrow().clone(); - all_data.rt_price_1mon_vec = rx4_rt_price_1mon_vec.borrow().clone(); + all_data.rt_price_1m_vec = rx5_rt_price_1m_map.borrow().clone(); + all_data.rt_price_30m_vec = rx5_rt_price_30m_map.borrow().clone(); + all_data.rt_price_1d_vec = rx4_rt_price_1d_map.borrow().clone(); + all_data.rt_price_1w_vec = rx4_rt_price_1w_map.borrow().clone(); + all_data.rt_price_1mon_vec = rx4_rt_price_1mon_map.borrow().clone(); let result = strategy_team::strategy_manager::execute_list_up_for_sell( &all_data, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; @@ -1158,7 +1130,7 @@ async fn main() -> Result<(), Box> { let mut elapsed_time = 0; loop { let instant = Instant::now(); - let coin_price_vec = rx3_price_vec.borrow().clone(); + let coin_price_vec = rx3_price_map.borrow().clone(); let result = coex::exchange_team::monitoring_pre_suggested_coins(&coin_price_vec).await; // send Task#0 a message to notify running on @@ -1187,10 +1159,10 @@ async fn main() -> Result<(), Box> { let mut elapsed_time = 0; loop { let instant = Instant::now(); - let exchange_info_vec = rx_exchange_info_data.borrow().clone(); - let trade_fee_vec = rx_tradefee_vec.borrow().clone(); + let exchange_info_map = rx_exchange_info_map.borrow().clone(); + let trade_fee_map = rx_tradefee_map.borrow().clone(); let result = - coex::exchange_team::buy_coin(&exchange_info_vec, &trade_fee_vec).await; + coex::exchange_team::buy_coin(&exchange_info_map, &trade_fee_map).await; // send Task#0 a message to notify running on match result { @@ -1217,8 +1189,8 @@ async fn main() -> Result<(), Box> { let mut elapsed_time = 0; let instant = Instant::now(); - let exchange_info_vec = rx_exchange_info_data.borrow().clone(); - let trade_fee_vec = rx_tradefee_vec.borrow().clone(); + let exchange_info_vec = rx_exchange_info_map.borrow().clone(); + let trade_fee_vec = rx_tradefee_map.borrow().clone(); // let result = coex::exchange_team::buy_coin_for_test(&client, &coin_price_vec, &exchange_info_vec, &trade_fee_vec).await; let result = coex::exchange_team::buy_coin(&exchange_info_vec, &trade_fee_vec).await; @@ -1255,12 +1227,10 @@ async fn main() -> Result<(), Box> { .timeout(tokio::time::Duration::from_millis(3000)) .build() .unwrap(); - let exchange_info_vec = rx3_exchange_info_data.borrow().clone(); - let trade_fee_vec = rx2_tradefee_vec.borrow().clone(); + let trade_fee_map = rx2_tradefee_map.borrow().clone(); let result = coex::order_team::monitoring_open_buy_order( &client, - &exchange_info_vec, - &trade_fee_vec, + &trade_fee_map, ) .await; @@ -1293,9 +1263,9 @@ async fn main() -> Result<(), Box> { let mut elapsed_time = 0; loop { let instant = Instant::now(); - let coin_price_vec = rx5_price_vec.borrow().clone(); - let exchange_info_vec = rx2_exchange_info_data.borrow().clone(); - let trade_fee_vec = rx3_tradefee_vec.borrow().clone(); + let coin_price_vec = rx5_price_map.borrow().clone(); + let exchange_info_vec = rx2_exchange_info_map.borrow().clone(); + let trade_fee_vec = rx3_tradefee_map.borrow().clone(); let result = coex::order_team::update_price_of_filled_buy_order( &coin_price_vec, @@ -1338,8 +1308,8 @@ async fn main() -> Result<(), Box> { .timeout(tokio::time::Duration::from_millis(3000)) .build() .unwrap(); - let exchange_info_vec = rx4_exchange_info_data.borrow().clone(); - let trade_fee_vec = rx4_tradefee_vec.borrow().clone(); + let exchange_info_vec = rx4_exchange_info_map.borrow().clone(); + let trade_fee_vec = rx4_tradefee_map.borrow().clone(); let result = coex::order_team::monitoring_open_sell_order( &client, &exchange_info_vec, @@ -1403,7 +1373,7 @@ async fn main() -> Result<(), Box> { let mut all_data = AllData::new(); // realtime price data - all_data.rt_price_1m_vec = rx4_rt_price_1m_vec.borrow().clone(); + all_data.rt_price_1m_vec = rx4_rt_price_1m_map.borrow().clone(); let result = coex::exchange_team::monitoring_scoreboard(&all_data).await; diff --git a/src/strategy_team/mod.rs b/src/strategy_team/mod.rs index 9fc8f61..527e77f 100644 --- a/src/strategy_team/mod.rs +++ b/src/strategy_team/mod.rs @@ -3,16 +3,16 @@ pub mod strategy_002; pub mod strategy_003; pub mod strategy_004; pub mod strategy_005; -pub mod strategy_006; -pub mod strategy_test; +// pub mod strategy_006; +// pub mod strategy_test; pub mod strategy_manager; use crate::coex::order_team::{limit_order_sell, select_filled_buy_orders}; -use crate::coex::exchange_team::server_epoch; +use crate::coex::exchange_team::get_server_epoch; use crate::coin_health_check_team::request_others::{ExchangeInfo, TradeFee}; use crate::database_control::*; use crate::decimal_funcs::*; -use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; +use crate::value_estimation_team::datapoints::price_data::{RealtimePriceData, CandleType}; use crate::value_estimation_team::indicators::bollingerband::{bollingerband, BollingerBandData}; use crate::value_estimation_team::indicators::ema::{ema, ema_opclo, EmaData}; use crate::value_estimation_team::indicators::heatmap_volume::{ @@ -22,7 +22,7 @@ use crate::value_estimation_team::indicators::macd::{ema_macd, MacdData}; use crate::value_estimation_team::indicators::rsi::{rsi, RsiData}; use crate::value_estimation_team::indicators::sma::{sma, sma_opclo, SmaData}; use crate::value_estimation_team::indicators::stoch_rsi::{stoch_rsi, StochRsiData}; -use crate::value_estimation_team::indicators::supertrend::{supertrend, SupertrendData}; +use crate::value_estimation_team::indicators::supertrend::{supertrend, SupertrendData, SuperTrendArea, SuperTrendSignal}; use crate::value_estimation_team::indicators::adx::{AdxData, adx}; use futures::future::try_join_all; use reqwest::{Client, ClientBuilder}; @@ -32,25 +32,26 @@ use sqlx::FromRow; use std::sync::Arc; use strategy_manager::insert_pre_suggested_coins; use tokio::sync::Mutex; +use std::collections::{HashMap, HashSet}; #[derive(Debug, Clone)] pub struct AllData { - pub valid_symbol_vec: Vec, - pub rt_price_1m_vec: Vec<(String, Vec)>, - pub rt_price_30m_vec: Vec<(String, Vec)>, - pub rt_price_1d_vec: Vec<(String, Vec)>, - pub rt_price_1w_vec: Vec<(String, Vec)>, - pub rt_price_1mon_vec: Vec<(String, Vec)>, + pub valid_symbol_vec: HashSet, + pub rt_price_1m_vec: HashMap>, + pub rt_price_30m_vec: HashMap>, + pub rt_price_1d_vec: HashMap>, + pub rt_price_1w_vec: HashMap>, + pub rt_price_1mon_vec: HashMap>, } impl AllData { pub fn new() -> AllData { let a = AllData { - valid_symbol_vec: Vec::new(), - rt_price_1m_vec: Vec::new(), - rt_price_30m_vec: Vec::new(), - rt_price_1d_vec: Vec::new(), - rt_price_1w_vec: Vec::new(), - rt_price_1mon_vec: Vec::new(), + valid_symbol_vec: HashSet::new(), + rt_price_1m_vec: HashMap::new(), + rt_price_30m_vec: HashMap::new(), + rt_price_1d_vec: HashMap::new(), + rt_price_1w_vec: HashMap::new(), + rt_price_1mon_vec: HashMap::new(), }; a } @@ -68,18 +69,16 @@ pub struct TimeData { } #[derive(Clone)] -pub struct FilteredData { - pub symbol: String, +pub struct FilteredDataValue { pub closetime: i64, pub stoploss: Decimal, pub target_price: Decimal, pub current_price: Decimal, } -impl FilteredData { - fn new() -> FilteredData { - let a = FilteredData { - symbol: String::new(), +impl FilteredDataValue { + fn new() -> FilteredDataValue { + let a = FilteredDataValue { closetime: 0, stoploss: Decimal::new(0, 8), target_price: Decimal::new(0, 8), @@ -89,22 +88,22 @@ impl FilteredData { } } -pub async fn duplicate_filter(registerer: i32, original_filtered_data: &Vec) --> Result, Box> +pub async fn duplicate_filter(registerer: i32, original_filtered_data: &HashMap) +-> Result, Box> { let inspect_table_name_1 = String::from("buy_ordered_coin_list"); let inspect_table_name_2 = String::from("sell_ordered_coin_list"); let inspect_table_name_3 = String::from("pre_suggested_coin_list"); let inspect_table_name_4 = String::from("suggested_coin_list"); - let mut filtered_data: Vec = Vec::new(); - let mut filtered_data_arc: Arc>> = + let mut filtered_data: HashMap = HashMap::new(); + let mut filtered_data_arc: Arc>> = Arc::new(Mutex::new(filtered_data)); let mut task_vec = Vec::new(); - for element in original_filtered_data { + for (symbol, filtered_data) in original_filtered_data { let mut exists_condition_build = String::from("symbol=\'"); - exists_condition_build.push_str(element.symbol.as_str()); + exists_condition_build.push_str(symbol.as_str()); exists_condition_build.push_str("\' AND registerer="); exists_condition_build.push_str(registerer.to_string().as_str()); // exists_condition_build.push_str("\' AND close_time="); @@ -115,7 +114,8 @@ pub async fn duplicate_filter(registerer: i32, original_filtered_data: &Vec, keys_to_remove: HashSet) { + let len_prev = filtered_data.len(); + // remove key-value in filtered_data + for symbol in keys_to_remove { + filtered_data.remove(&symbol); + } + let len_now = filtered_data.len(); + + // shrink capacity of filtered_data + if len_now != len_prev { + filtered_data.shrink_to_fit(); + } +} + +// useful functions for strategists +pub async fn get_current_price( + symbol: &String, + rt_price_map: &HashMap>, +) -> Option { + if let Some(rt_vec) = rt_price_map.get(symbol) { + if rt_vec.last().is_some() { + return Some(rt_vec.last().unwrap().close_price); + } + } + None +} diff --git a/src/strategy_team/strategy_001.rs b/src/strategy_team/strategy_001.rs index be198e7..3c53976 100644 --- a/src/strategy_team/strategy_001.rs +++ b/src/strategy_team/strategy_001.rs @@ -3,9 +3,9 @@ use crate::value_estimation_team::indicators::bollingerband::bollingerband; use super::{ dec, decimal_add, decimal_sub, decimal_div, ema, exists_record, insert_pre_suggested_coins, limit_order_sell, rsi, select_filled_buy_orders, stoch_rsi, supertrend, try_join_all, AllData, - Arc, Client, ClientBuilder, Decimal, EmaData, ExchangeInfo, FilteredData, Mutex, - RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SupertrendData, TradeFee, update_record3, adx, AdxData, server_epoch, MacdData, ema_macd, - BollingerBandData, ToPrimitive, duplicate_filter + Arc, Client, ClientBuilder, Decimal, EmaData, ExchangeInfo, FilteredDataValue, Mutex, + RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SupertrendData, TradeFee, update_record3, adx, AdxData, get_server_epoch, MacdData, ema_macd, + BollingerBandData, ToPrimitive, duplicate_filter, HashMap, HashSet, remove_keys, SuperTrendArea, SuperTrendSignal }; // BB lowerband + SuperTrend + StochRSI @@ -19,115 +19,19 @@ pub async fn list_up_for_buy( // println!("BTCUSDT: {:?}", alldata.rt_price_30m_vec[a.unwrap()].1.last().unwrap()); // 1st filtering: filtering valid trade pair - let mut filtered_data_1st: Vec = Vec::new(); + let mut filtered_data: HashMap = HashMap::new(); for symbol in &alldata.valid_symbol_vec { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = symbol.clone(); - filtered_data_1st.push(filtered_data); - } - - // 2nd filtering: supertrend(ATR period 20, multiplier: 2, 30m close price) - let mut filtered_data_2nd: Vec = Vec::new(); - let mut filtered_data_2nd_arc: Arc>> = - Arc::new(Mutex::new(filtered_data_2nd)); - let mut task_vec = Vec::new(); - - for element in filtered_data_1st { - let mut rt_30m_vec: Vec = Vec::new(); - let mut supertrend_vec: Vec = Vec::new(); - let rt_price_30m_vec_c = alldata.rt_price_30m_vec.clone(); - let filtered_data_2nd_arc_c = Arc::clone(&filtered_data_2nd_arc); - task_vec.push(tokio::spawn(async move { - let rt_30m_option = rt_price_30m_vec_c - .iter() - .position(|x| *x.0 == element.symbol); - let supertrend_option_30m = - supertrend(&element.symbol, &rt_price_30m_vec_c, 20, 2.0, true).await; - - if rt_30m_option.is_some() && supertrend_option_30m.is_some() { - rt_30m_vec = rt_price_30m_vec_c[rt_30m_option.unwrap()].1.clone(); - supertrend_vec = supertrend_option_30m.unwrap(); - let server_epoch = server_epoch().await; - if rt_30m_vec.len() >= 3 && supertrend_vec.len() >= 3 && rt_30m_vec.last().unwrap().close_time > server_epoch { - let supertrend_search_result = supertrend_vec.binary_search_by_key( - &rt_30m_vec.last().unwrap().close_time, - |SupertrendData { - band_value, - signal, - area, - close_time, - }| *close_time, - ); - if supertrend_search_result.is_ok() { - let current_price: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(rt_30m_vec.last().unwrap().close_price).unwrap(); - let mut filtered_data_2nd_lock = filtered_data_2nd_arc_c.lock().await; - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = rt_30m_vec.last().unwrap().close_time; - filtered_data.current_price = current_price; - - if supertrend_vec[supertrend_search_result.unwrap()].area.contains("DOWN") - && supertrend_vec[supertrend_search_result.unwrap()].band_value > element.current_price.to_f64().unwrap() - { - let band_value: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(supertrend_vec[supertrend_search_result.unwrap()].band_value).unwrap(); - let stop_loss = decimal_sub(filtered_data.current_price, decimal_div(decimal_sub(band_value, filtered_data.current_price), dec!(2))); - filtered_data.stoploss = stop_loss; - - filtered_data_2nd_lock.push(filtered_data); - } else if supertrend_vec[supertrend_search_result.unwrap()].area.contains("UP") - && supertrend_vec[supertrend_search_result.unwrap()].band_value < element.current_price.to_f64().unwrap() - { - let band_value: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(supertrend_vec[supertrend_search_result.unwrap()].band_value).unwrap(); - filtered_data.stoploss = band_value; - - filtered_data_2nd_lock.push(filtered_data); - } - } - } - } - })); - } - try_join_all(task_vec).await?; - - // 3rd filtering: the latest 5 30m candle close prices > EMA 200 - let filtered_data_2nd = filtered_data_2nd_arc.lock().await.clone(); - let mut filtered_data_3rd: Vec = Vec::new(); - let ema_vec = ema(200, &alldata.rt_price_30m_vec, &filtered_data_2nd).await?; - for element in filtered_data_2nd { - let ema_search_result = ema_vec.iter().position(|x| x.0 == element.symbol); - let candle_search_result = alldata.rt_price_30m_vec.iter().position(|x| x.0 == element.symbol); - if ema_search_result.is_some() && candle_search_result.is_some() { - let search_result = ema_vec[ema_search_result.unwrap()].1.binary_search_by_key( - &alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.last().unwrap().close_time, - |EmaData { - ema_value, - close_time, - }| *close_time); - if search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-1].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-1].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-2].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-2].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-3].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-3].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-4].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-4].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-5].close_price) { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_data_3rd.push(filtered_data); - } - } + filtered_data.insert(symbol.clone(), FilteredDataValue::new()); } // 4th filtering: 0.5% <= the average amplitude of the latest 10 30m candles <= 1.0% - let mut filtered_data_4th: Vec = Vec::new(); - for element in filtered_data_3rd { - let position_idx = alldata.rt_price_30m_vec.iter().position(|elem| elem.0 == element.symbol); - if position_idx.is_some() { - let vec_len = alldata.rt_price_30m_vec[position_idx.unwrap()].1.len(); + let mut keys_to_remove: HashSet = HashSet::new(); + for (symbol, values) in &mut filtered_data { + if alldata.rt_price_30m_vec.contains_key(symbol) { + let rt_price_30m = alldata.rt_price_30m_vec.get(symbol).unwrap(); + let vec_len = rt_price_30m.len(); if vec_len >= 11 { - let candles = alldata.rt_price_30m_vec[position_idx.unwrap()].1.get(vec_len-12..vec_len-1).unwrap(); + let candles = rt_price_30m.get(vec_len-12..vec_len-1).unwrap(); let windows = candles.windows(2); let mut average_amplitude = 0.0; @@ -137,41 +41,143 @@ pub async fn list_up_for_buy( average_amplitude /= 10.0; if 0.005 <= average_amplitude && average_amplitude <= 0.01 { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_data_4th.push(filtered_data); + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } } - - // 5th filtering: 30m StochRSI (RSI_len: 10, StochRSI_len: 10, K: 3, D: 3) previous K < 5 && current K > previous K - let mut filtered_data_5th: Vec = Vec::new(); - let stoch_rsis = stoch_rsi(10, 10, 3, 3, &alldata.rt_price_30m_vec, &filtered_data_4th).await?; - for element in filtered_data_4th { - let position_idx = stoch_rsis.iter().position(|elem| elem.0 == element.symbol); - if position_idx.is_some() { - let stoch_rsi_vec = stoch_rsis[position_idx.unwrap()].1.clone(); - let search_result = stoch_rsi_vec.iter().position(|x| x.close_time == element.closetime); + remove_keys(&mut filtered_data, keys_to_remove).await; + + // 2nd filtering: supertrend(ATR period 20, multiplier: 2, 30m close price) + let mut keys_to_remove: HashSet = HashSet::new(); + let server_epoch = get_server_epoch().await; + let supertrend_30m_map = supertrend(20, 2.0, true, &alldata.rt_price_30m_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if let (Some(supertrend_vec), Some(rt_price_vec)) = (supertrend_30m_map.get(symbol), alldata.rt_price_30m_vec.get(symbol)) { + if supertrend_vec.last().unwrap().close_time == rt_price_vec.last().unwrap().close_time && + rt_price_vec.last().unwrap().close_time > server_epoch { + // input closetime, current_price + values.closetime = rt_price_vec.last().unwrap().close_time; + values.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_price_vec.last().unwrap().close_price).unwrap(); + + // input stoploss + let band_value: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(supertrend_vec.last().unwrap().band_value).unwrap(); + if supertrend_vec.last().unwrap().area == SuperTrendArea::DOWN && + supertrend_vec.last().unwrap().band_value > values.current_price.to_f64().unwrap() + { + values.stoploss = decimal_sub(values.current_price, decimal_sub(band_value, values.current_price)); + + } else if supertrend_vec.last().unwrap().area == SuperTrendArea::UP && + supertrend_vec.last().unwrap().band_value < values.current_price.to_f64().unwrap() + { + values.stoploss = band_value; + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; + + // filtering: the latest 5 30m candle close prices > EMA 200 + let mut keys_to_remove: HashSet = HashSet::new(); + let emas = ema(200, &alldata.rt_price_30m_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if emas.contains_key(symbol) && alldata.rt_price_30m_vec.contains_key(symbol) { + let ema = emas.get(symbol).unwrap(); + let rt_price_30m = alldata.rt_price_30m_vec.get(symbol).unwrap(); + let rt_price_30m_len = rt_price_30m.len(); + let search_result = ema.binary_search_by_key( + &alldata.rt_price_30m_vec.get(symbol).unwrap().last().unwrap().close_time, + |EmaData { + ema_value, + close_time, + }| *close_time); + if search_result.is_ok_and(|x| ema[search_result.unwrap()].ema_value < rt_price_30m[rt_price_30m_len-1].close_price) && + search_result.is_ok_and(|x| ema[search_result.unwrap()-1].ema_value < rt_price_30m[rt_price_30m_len-2].close_price) && + search_result.is_ok_and(|x| ema[search_result.unwrap()-2].ema_value < rt_price_30m[rt_price_30m_len-3].close_price) && + search_result.is_ok_and(|x| ema[search_result.unwrap()-3].ema_value < rt_price_30m[rt_price_30m_len-4].close_price) && + search_result.is_ok_and(|x| ema[search_result.unwrap()-4].ema_value < rt_price_30m[rt_price_30m_len-5].close_price) { + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; + + // filtering: 30m StochRSI (RSI_len: 10, StochRSI_len: 10, K: 3, D: 3) previous K < 5 && current K > previous K + let mut keys_to_remove: HashSet = HashSet::new(); + let stoch_rsis = stoch_rsi(10, 10, 3, 3, &alldata.rt_price_30m_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if alldata.rt_price_30m_vec.contains_key(symbol) && stoch_rsis.contains_key(symbol) { + let stoch_rsi_vec = stoch_rsis.get(symbol).unwrap(); + let search_result = stoch_rsi_vec.iter().position(|x| x.close_time == values.closetime); if search_result.is_some_and(|a| stoch_rsi_vec[a-1].k < 5.0 && stoch_rsi_vec[a].k > stoch_rsi_vec[a-1].k) { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_data_5th.push(filtered_data); + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } } + remove_keys(&mut filtered_data, keys_to_remove).await; + + // filtering: 1d MACD (3, 7, 30) current MACD-signal > prev MACD-signal + let mut keys_to_remove: HashSet = HashSet::new(); + let macd_1d_map = ema_macd(3, 7, 30, &alldata.rt_price_1d_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if let (Some(macd_vec), Some(rt_price_vec)) = (macd_1d_map.get(symbol), alldata.rt_price_1d_vec.get(symbol)) { + if macd_vec.last().unwrap().close_time == rt_price_vec.last().unwrap().close_time && + rt_price_vec.last().unwrap().close_time > server_epoch { + if macd_vec[macd_vec.len()-1].macd_value - macd_vec[macd_vec.len()-1].signal_value > macd_vec[macd_vec.len()-2].macd_value - macd_vec[macd_vec.len()-2].signal_value + { + values.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_price_vec.last().unwrap().close_price).unwrap(); + values.closetime = rt_price_vec.last().unwrap().close_time; + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; - let final_filtered_data = duplicate_filter(1, &filtered_data_5th).await?; + // filtering: the 2 previous ADX(10, 10)s decrease + let mut keys_to_remove: HashSet = HashSet::new(); + let adx_vec = adx(10, 10, &alldata.rt_price_30m_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if let Some(adx_vec) = adx_vec.get(symbol) { + if let Some(last_idx) = adx_vec.iter().position(|elem| elem.close_time == values.closetime) { + if + adx_vec[last_idx].adx < adx_vec[last_idx-1].adx && + adx_vec[last_idx-1].adx < adx_vec[last_idx-2].adx { + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; + + let final_filtered_data = duplicate_filter(1, &filtered_data).await?; insert_pre_suggested_coins(1, false, &final_filtered_data, &alldata).await; Ok(()) @@ -179,8 +185,8 @@ pub async fn list_up_for_buy( pub async fn list_up_for_sell( all_data: &AllData, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let filled_buy_orders = select_filled_buy_orders(1).await?; @@ -190,47 +196,21 @@ pub async fn list_up_for_sell( .build() .unwrap(); let mut supertrend_vec: Vec = Vec::new(); - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; - let mut filtered_symbol_vec: Vec = Vec::new(); + let mut filtered_symbols: HashMap = HashMap::new(); for element in &filled_buy_orders { - let filltered_data = FilteredData{ - symbol: element.symbol.clone(), - closetime: 0, - stoploss: dec!(0), - target_price: dec!(0), - current_price: dec!(0), - }; - filtered_symbol_vec.push(filltered_data); + filtered_symbols.insert(element.symbol.clone(), FilteredDataValue::new()); } - let stoch_rsis = stoch_rsi(10, 10, 3, 3, &all_data.rt_price_30m_vec, &filtered_symbol_vec).await?; + let supertrend_30m = supertrend(20, 2.0, true, &all_data.rt_price_30m_vec, &filtered_symbols).await?; + let stoch_rsis = stoch_rsi(10, 10, 3, 3, &all_data.rt_price_30m_vec, &filtered_symbols).await?; for element in filled_buy_orders { if element.used_usdt >= dec!(10.0) { - let lot_step_size_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - let quote_commission_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - let search_result = stoch_rsis.iter().position(|x| x.0 == element.symbol); - - let opclo_30m_option = all_data - .rt_price_30m_vec - .iter() - .position(|x| *x.0 == element.symbol); - let supertrend_option_30m = - supertrend(&element.symbol, &all_data.rt_price_30m_vec, 20, 2.0, true).await; - - if lot_step_size_option.is_some() - && quote_commission_precision_option.is_some() - && search_result.is_some() - && opclo_30m_option.is_some() - && supertrend_option_30m.is_some() - { + if let (Some(exchange_info), Some(tradefee), Some(stoch_rsi), Some(supertrend_vec)) = + (exchange_info_map.get(&element.symbol), trade_fee_map.get(&element.symbol), stoch_rsis.get(&element.symbol), supertrend_30m.get(&element.symbol)) { // update stoploss - supertrend_vec = supertrend_option_30m.unwrap(); let band_value: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(supertrend_vec.last().unwrap().band_value).unwrap(); - if supertrend_vec.last().unwrap().area.contains("UP") + if supertrend_vec.last().unwrap().area == SuperTrendArea::UP && band_value > element.stoploss { let update_table_name = String::from("buy_ordered_coin_list"); let update_value = vec![ @@ -242,19 +222,17 @@ pub async fn list_up_for_sell( .unwrap(); } - let lot_step_size = exchange_info_vec[lot_step_size_option.unwrap()].stepsize; - let quote_commission_precision = exchange_info_vec - [quote_commission_precision_option.unwrap()] - .quote_commission_precision; + let lot_step_size = exchange_info.stepsize; + let quote_commission_precision = exchange_info.quote_commission_precision; let base_qty_to_be_ordered = element.base_qty_ordered.round_dp_with_strategy( lot_step_size.normalize().scale(), RoundingStrategy::ToZero, ); - let stoch_rsi_k = stoch_rsis[search_result.unwrap()].1.last().unwrap().k; - let stoch_rsi_k_prev = stoch_rsis[search_result.unwrap()].1[stoch_rsis[search_result.unwrap()].1.len()-2].k; - let stoch_rsi_d = stoch_rsis[search_result.unwrap()].1.last().unwrap().d; - let stoch_rsi_d_prev = stoch_rsis[search_result.unwrap()].1[stoch_rsis[search_result.unwrap()].1.len()-2].d; + let stoch_rsi_k = stoch_rsi.last().unwrap().k; + let stoch_rsi_k_prev = stoch_rsi[stoch_rsi.len()-2].k; + let stoch_rsi_d = stoch_rsi.last().unwrap().d; + let stoch_rsi_d_prev = stoch_rsi[stoch_rsi.len()-2].d; if (element.is_long == 0 || element.is_long == 1) && !element.current_price.is_zero() { @@ -264,49 +242,40 @@ pub async fn list_up_for_sell( element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; - } else if server_epoch - element.transact_time > (1_800_000) * 20 { + } else if server_epoch - element.transact_time > (1_800_000) * 15 { limit_order_sell( &element, element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; - } else if element.pure_profit_percent > 5.0 { + } else if element.pure_profit_percent > 3.0 { limit_order_sell( &element, element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; - } else if stoch_rsi_k >= 95.0 { + } else if stoch_rsi_k >= 90.0 && + (stoch_rsi_k < stoch_rsi_d && + stoch_rsi_k_prev > stoch_rsi_d_prev) { limit_order_sell( &element, element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, - ) - .await; - } else if stoch_rsi_k < stoch_rsi_d && - stoch_rsi_k_prev >= stoch_rsi_d_prev { - limit_order_sell( - &element, - element.current_price, - base_qty_to_be_ordered, - &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; } @@ -336,6 +305,7 @@ pub async fn list_up_for_sell( // } } } + } } } diff --git a/src/strategy_team/strategy_002.rs b/src/strategy_team/strategy_002.rs index 4048004..968b2ad 100644 --- a/src/strategy_team/strategy_002.rs +++ b/src/strategy_team/strategy_002.rs @@ -3,9 +3,9 @@ use crate::value_estimation_team::indicators::bollingerband::bollingerband; use super::{ dec, decimal_add, decimal_sub, decimal_div, ema, exists_record, insert_pre_suggested_coins, limit_order_sell, rsi, select_filled_buy_orders, stoch_rsi, supertrend, try_join_all, AllData, - Arc, Client, ClientBuilder, Decimal, EmaData, ExchangeInfo, FilteredData, Mutex, - RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SupertrendData, TradeFee, update_record3, adx, AdxData, server_epoch, MacdData, ema_macd, - BollingerBandData, ToPrimitive, duplicate_filter + Arc, Client, ClientBuilder, Decimal, EmaData, ExchangeInfo, FilteredDataValue, Mutex, + RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SupertrendData, TradeFee, update_record3, adx, AdxData, get_server_epoch, MacdData, ema_macd, + BollingerBandData, ToPrimitive, duplicate_filter, HashMap, HashSet, remove_keys, SuperTrendArea, SuperTrendSignal }; // BB 30m lowerband + BB 1m lowerband @@ -18,82 +18,83 @@ pub async fn list_up_for_buy( // println!("BTCUSDT: {:?}", alldata.rt_price_30m_vec[a.unwrap()].1.last().unwrap()); // 1st filtering: filtering valid trade pair - let mut filtered_data_1st: Vec = Vec::new(); + let mut filtered_data: HashMap = HashMap::new(); for symbol in &alldata.valid_symbol_vec { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = symbol.clone(); - filtered_data_1st.push(filtered_data); + filtered_data.insert(symbol.clone(), FilteredDataValue::new()); } - // 2nd filtering: BollingerBand (len:10, multiplier 2.5) previous_30m_price (close or low price) < lower_band - let mut filtered_data_2nd: Vec = Vec::new(); - let mut filtered_data_2nd_arc: Arc>> = - Arc::new(Mutex::new(filtered_data_2nd)); - let mut task_vec = Vec::new(); - let bollingerbands_30m = bollingerband(10, 2.5, &alldata.rt_price_30m_vec, &filtered_data_1st).await?; - let bollingerbands_1m = bollingerband(30, 3.0, &alldata.rt_price_1m_vec, &filtered_data_1st).await?; - for element in filtered_data_1st { - let mut rt_30m_vec: Vec = Vec::new(); - let mut bb_30m_vec: Vec = Vec::new(); - let mut bb_1m_vec: Vec = Vec::new(); - let rt_price_30m_vec_c: Vec<(String, Vec)> = alldata.rt_price_30m_vec.clone(); - let bollingerbands_30m_c = bollingerbands_30m.clone(); - let bollingerbands_1m_c = bollingerbands_1m.clone(); - let filtered_data_2nd_arc_c = Arc::clone(&filtered_data_2nd_arc); - task_vec.push(tokio::spawn(async move { - let rt_30m_option = rt_price_30m_vec_c - .iter() - .position(|x| *x.0 == element.symbol); - let bb_option_30m = bollingerbands_30m_c.iter().position(|x| x.0 == element.symbol); - let bb_option_1m = bollingerbands_1m_c.iter().position(|x| x.0 == element.symbol); - if rt_30m_option.is_some() && bb_option_30m.is_some() && bb_option_1m.is_some() { - rt_30m_vec = rt_price_30m_vec_c[rt_30m_option.unwrap()].1.clone(); - bb_30m_vec = bollingerbands_30m_c[bb_option_30m.unwrap()].1.clone(); - bb_1m_vec = bollingerbands_1m_c[bb_option_1m.unwrap()].1.clone(); - let server_epoch = server_epoch().await; - if rt_30m_vec.len() >= 3 && bb_30m_vec.len() >= 3 && bb_1m_vec.len() >= 3 && rt_30m_vec.last().unwrap().close_time > server_epoch { - let bb_30m_search_result = bb_30m_vec.binary_search_by_key( - &rt_30m_vec.last().unwrap().close_time, - |BollingerBandData { - sma, - upperband, - lowerband, - close_time, - }| *close_time, - ); - if bb_30m_search_result.is_ok() { - if bb_30m_vec[bb_30m_search_result.unwrap()].lowerband > rt_30m_vec[rt_30m_vec.len()-1].close_price && - bb_1m_vec.last().unwrap().lowerband > rt_30m_vec[rt_30m_vec.len()-1].close_price - { - let mut filtered_data_2nd_lock = filtered_data_2nd_arc_c.lock().await; - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = rt_30m_vec.last().unwrap().close_time; - filtered_data.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_30m_vec.last().unwrap().close_price).unwrap(); - filtered_data_2nd_lock.push(filtered_data); - } - } + // 5th filtering: 0.5% <= the average amplitude of the latest 10 30m candles <= 1.0% + let mut keys_to_remove: HashSet = HashSet::new(); + for (symbol, filtered_data) in &mut filtered_data { + if let Some(rt_price_30m_vec) = alldata.rt_price_30m_vec.get(symbol) { + let vec_len = rt_price_30m_vec.len(); + if vec_len >= 11 { + let candles = rt_price_30m_vec.get(vec_len-12..vec_len-1).unwrap(); + let windows = candles.windows(2); + let mut average_amplitude = 0.0; + + for window in windows { + average_amplitude += (window.last().unwrap().high_price - window.last().unwrap().low_price) / window.first().unwrap().close_price; } + average_amplitude /= 10.0; + + if 0.005 <= average_amplitude && average_amplitude <= 0.01 { + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); } - })); + } else { + keys_to_remove.insert(symbol.clone()); + } } - try_join_all(task_vec).await?; + remove_keys(&mut filtered_data, keys_to_remove).await; + + // 2nd filtering: BollingerBand (len:10, multiplier 2.5) previous_30m_price (close or low price) < lower_band + let mut keys_to_remove: HashSet = HashSet::new(); + let bollingerbands_30m_map = bollingerband(10, 2.5, &alldata.rt_price_30m_vec, &filtered_data).await?; + let bollingerbands_1m_map = bollingerband(30, 3.0, &alldata.rt_price_1m_vec, &filtered_data).await?; + let server_epoch = get_server_epoch().await; + for (symbol, filtered_data) in &mut filtered_data { + if let (Some(bb_30m_vec), Some(bb_1m_vec), Some(rt_30m_vec)) = (bollingerbands_30m_map.get(symbol), bollingerbands_1m_map.get(symbol), alldata.rt_price_30m_vec.get(symbol)) { + if rt_30m_vec.len() >= 3 && bb_30m_vec.len() >= 3 && bb_1m_vec.len() >= 3 && rt_30m_vec.last().unwrap().close_time > server_epoch { + let bb_30m_search_result = bb_30m_vec.binary_search_by_key( + &rt_30m_vec.last().unwrap().close_time, + |BollingerBandData { + sma, + upperband, + lowerband, + close_time, + }| *close_time, + ); + if bb_30m_search_result.is_ok() { + if bb_30m_vec[bb_30m_search_result.unwrap()].lowerband > rt_30m_vec[rt_30m_vec.len()-1].close_price && + bb_1m_vec.last().unwrap().lowerband > rt_30m_vec[rt_30m_vec.len()-1].close_price + { + filtered_data.closetime = rt_30m_vec.last().unwrap().close_time; + filtered_data.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_30m_vec.last().unwrap().close_price).unwrap(); + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; // 3rd filtering: supertrend(ATR period 7, multiplier: 1.5, 30m close price), area should be DOWN - let filtered_data_2nd = filtered_data_2nd_arc.lock().await.clone(); - let mut filtered_data_3rd: Vec = Vec::new(); - - for element in filtered_data_2nd { - let rt_30m_option = alldata.rt_price_30m_vec - .iter() - .position(|x| *x.0 == element.symbol); - let supertrend_option_30m = - supertrend(&element.symbol, &alldata.rt_price_30m_vec, 7, 1.5, true).await; - - if rt_30m_option.is_some() && supertrend_option_30m.is_some() { - let rt_30m_vec = alldata.rt_price_30m_vec[rt_30m_option.unwrap()].1.clone(); - let supertrend_vec = supertrend_option_30m.unwrap(); - let server_epoch = server_epoch().await; + let mut keys_to_remove: HashSet = HashSet::new(); + let supertrend_30m_map = supertrend( 7, 1.5, true, &alldata.rt_price_30m_vec, &filtered_data).await?; + let server_epoch = get_server_epoch().await; + for (symbol, filtered_data) in &mut filtered_data { + if let (Some(rt_30m_vec), Some(supertrend_vec)) = (alldata.rt_price_30m_vec.get(symbol), supertrend_30m_map.get(symbol)) { if rt_30m_vec.len() >= 3 && supertrend_vec.len() >= 3 && rt_30m_vec.last().unwrap().close_time > server_epoch { let supertrend_search_result = supertrend_vec.binary_search_by_key( &rt_30m_vec.last().unwrap().close_time, @@ -105,88 +106,76 @@ pub async fn list_up_for_buy( }| *close_time, ); if supertrend_search_result.is_ok() { - if supertrend_vec[supertrend_search_result.unwrap()].area.contains("DOWN") - && supertrend_vec[supertrend_search_result.unwrap()].band_value > element.current_price.to_f64().unwrap() + if supertrend_vec[supertrend_search_result.unwrap()].area == SuperTrendArea::DOWN + && supertrend_vec[supertrend_search_result.unwrap()].band_value > filtered_data.current_price.to_f64().unwrap() { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; filtered_data.target_price = rust_decimal::prelude::FromPrimitive::from_f64(supertrend_vec[supertrend_search_result.unwrap()].band_value).unwrap(); let stop_loss = decimal_sub(filtered_data.current_price, decimal_div(decimal_sub(filtered_data.target_price, filtered_data.current_price), dec!(2))); filtered_data.stoploss = stop_loss; - - filtered_data_3rd.push(filtered_data); + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } - } + remove_keys(&mut filtered_data, keys_to_remove).await; // 4th filtering: the latest 5 30m candle close prices > EMA 200 - let mut filtered_data_4th: Vec = Vec::new(); - let ema_vec = ema(200, &alldata.rt_price_30m_vec, &filtered_data_3rd).await?; - for element in filtered_data_3rd { - let mut opclo_30m_vec = alldata.rt_price_30m_vec.clone(); - let ema_search_result = ema_vec.iter().position(|x| x.0 == element.symbol); - let candle_search_result = alldata.rt_price_30m_vec.iter().position(|x| x.0 == element.symbol); - if ema_search_result.is_some() && candle_search_result.is_some() { - let search_result = ema_vec[ema_search_result.unwrap()].1.binary_search_by_key( - &alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.last().unwrap().close_time, + let mut keys_to_remove: HashSet = HashSet::new(); + let ema_map = ema(200, &alldata.rt_price_30m_vec, &filtered_data).await?; + for (symbol, filtered_data) in &mut filtered_data { + if let (Some(ema_vec), Some(rt_30m_vec)) = (ema_map.get(symbol), alldata.rt_price_30m_vec.get(symbol)) { + let search_result = ema_vec.binary_search_by_key( + &rt_30m_vec.last().unwrap().close_time, |EmaData { ema_value, close_time, }| *close_time); - if search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-1].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-1].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-2].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-2].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-3].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-3].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-4].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-4].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-5].close_price) { - - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_data_4th.push(filtered_data); + if search_result.is_ok_and(|x| ema_vec[x].ema_value < rt_30m_vec[rt_30m_vec.len()-1].close_price) && + search_result.is_ok_and(|x| ema_vec[x-1].ema_value < rt_30m_vec[rt_30m_vec.len()-2].close_price) && + search_result.is_ok_and(|x| ema_vec[x-2].ema_value < rt_30m_vec[rt_30m_vec.len()-3].close_price) && + search_result.is_ok_and(|x| ema_vec[x-3].ema_value < rt_30m_vec[rt_30m_vec.len()-4].close_price) && + search_result.is_ok_and(|x| ema_vec[x-4].ema_value < rt_30m_vec[rt_30m_vec.len()-5].close_price) { + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } } + remove_keys(&mut filtered_data, keys_to_remove).await; + + // filtering: 1d MACD (3, 7, 30) current MACD-signal > prev MACD-signal + let mut keys_to_remove: HashSet = HashSet::new(); + let macd_1d_map = ema_macd(3, 7, 30, &alldata.rt_price_1d_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if let (Some(macd_vec), Some(rt_price_vec)) = (macd_1d_map.get(symbol), alldata.rt_price_1d_vec.get(symbol)) { + if macd_vec.last().unwrap().close_time == rt_price_vec.last().unwrap().close_time && + rt_price_vec.last().unwrap().close_time > server_epoch { + if macd_vec[macd_vec.len()-1].macd_value - macd_vec[macd_vec.len()-1].signal_value > macd_vec[macd_vec.len()-2].macd_value - macd_vec[macd_vec.len()-2].signal_value + { + values.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_price_vec.last().unwrap().close_price).unwrap(); + values.closetime = rt_price_vec.last().unwrap().close_time; + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; - // 5th filtering: 0.5% <= the average amplitude of the latest 10 30m candles <= 1.0% - let mut filtered_data_5th: Vec = Vec::new(); - for element in filtered_data_4th { - let position_idx = alldata.rt_price_30m_vec.iter().position(|elem| elem.0 == element.symbol); - if position_idx.is_some() { - let vec_len = alldata.rt_price_30m_vec[position_idx.unwrap()].1.len(); - if vec_len >= 11 { - let candles = alldata.rt_price_30m_vec[position_idx.unwrap()].1.get(vec_len-12..vec_len-1).unwrap(); - let windows = candles.windows(2); - let mut average_amplitude = 0.0; - - for window in windows { - average_amplitude += (window.last().unwrap().high_price - window.last().unwrap().low_price) / window.first().unwrap().close_price; - } - average_amplitude /= 10.0; - - if 0.005 <= average_amplitude && average_amplitude <= 0.01 { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_data_5th.push(filtered_data); - } - } - } - } - - let final_filtered_data = duplicate_filter(2, &filtered_data_5th).await?; + let final_filtered_data = duplicate_filter(2, &filtered_data).await?; insert_pre_suggested_coins(2, false, &final_filtered_data, &alldata).await; Ok(()) @@ -194,8 +183,8 @@ pub async fn list_up_for_buy( pub async fn list_up_for_sell( all_data: &AllData, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let filled_buy_orders = select_filled_buy_orders(2).await?; @@ -204,24 +193,12 @@ pub async fn list_up_for_sell( .timeout(tokio::time::Duration::from_millis(5000)) .build() .unwrap(); - let mut supertrend_vec: Vec = Vec::new(); - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; for element in filled_buy_orders { if element.used_usdt >= dec!(10.0) { - let lot_step_size_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - let quote_commission_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - - if lot_step_size_option.is_some() - && quote_commission_precision_option.is_some() - { - let lot_step_size = exchange_info_vec[lot_step_size_option.unwrap()].stepsize; - let quote_commission_precision = exchange_info_vec - [quote_commission_precision_option.unwrap()] - .quote_commission_precision; + if let Some(exchange_info) = exchange_info_map.get(&element.symbol) { + let lot_step_size = exchange_info.stepsize; + let quote_commission_precision = exchange_info.quote_commission_precision; let base_qty_to_be_ordered = element.base_qty_ordered.round_dp_with_strategy( lot_step_size.normalize().scale(), @@ -238,8 +215,8 @@ pub async fn list_up_for_sell( element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; } else if element.current_price <= element.stoploss { @@ -248,8 +225,8 @@ pub async fn list_up_for_sell( element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; } else if server_epoch - element.transact_time > (1_800_000) * 20 { @@ -258,8 +235,8 @@ pub async fn list_up_for_sell( element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; } diff --git a/src/strategy_team/strategy_003.rs b/src/strategy_team/strategy_003.rs index 4c82aff..abd35dd 100644 --- a/src/strategy_team/strategy_003.rs +++ b/src/strategy_team/strategy_003.rs @@ -3,188 +3,129 @@ use crate::value_estimation_team::indicators::bollingerband::bollingerband; use super::{ dec, decimal_add, decimal_sub, decimal_div, ema, ema_opclo, sma, sma_opclo, exists_record, insert_pre_suggested_coins, limit_order_sell, rsi, select_filled_buy_orders, stoch_rsi, supertrend, try_join_all, AllData, - Arc, Client, ClientBuilder, Decimal, EmaData, ExchangeInfo, FilteredData, Mutex, SmaData, - RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SupertrendData, TradeFee, update_record3, adx, AdxData, server_epoch, MacdData, ema_macd, - BollingerBandData, ToPrimitive, duplicate_filter + Arc, Client, ClientBuilder, Decimal, EmaData, ExchangeInfo, FilteredDataValue, Mutex, SmaData, + RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SupertrendData, TradeFee, update_record3, adx, AdxData, get_server_epoch, MacdData, ema_macd, + BollingerBandData, ToPrimitive, duplicate_filter, HashMap, HashSet, remove_keys, SuperTrendArea, SuperTrendSignal }; // BUY: 30m SMA5 (opclo_price) < 30m EMA3 (opclo_price) // SELL: 30m SMA5 (opclo_price) > 30m EMA3 (opclo_price) pub async fn list_up_for_buy( - alldata: AllData, + alldata: &AllData, ) -> Result<(), Box> { // print rt_price for debugging // let a = alldata.rt_price_30m_vec.iter().position(|a| a.0 == "BTCUSDT"); // println!("BTCUSDT: {:?}", alldata.rt_price_30m_vec[a.unwrap()].1.last().unwrap()); // 1st filtering: filtering valid trade pair - let mut filtered_data_1st: Vec = Vec::new(); + let mut filtered_data: HashMap = HashMap::new(); for symbol in &alldata.valid_symbol_vec { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = symbol.clone(); - filtered_data_1st.push(filtered_data); + filtered_data.insert(symbol.clone(), FilteredDataValue::new()); } - // 2nd filtering: SMA5 (opclo_price) < EMA3 (opclo_price) && 5 previous MA > current MA - let mut filtered_data_2nd: Vec = Vec::new(); - let mut filtered_data_2nd_arc: Arc>> = - Arc::new(Mutex::new(filtered_data_2nd)); - let mut task_vec = Vec::new(); + // 3rd filtering: supertrend(ATR period 20, multiplier: 4.0, 30m close price), area should be UP + let mut keys_to_remove: HashSet = HashSet::new(); + let supertrend_30m_map = supertrend(20, 4.0, true, &alldata.rt_price_30m_vec, &filtered_data).await?; + let server_epoch = get_server_epoch().await; + for (symbol, filtered_data) in &mut filtered_data { + if let (Some(rt_30m_vec), Some(supertrend_vec)) = (alldata.rt_price_30m_vec.get(symbol), supertrend_30m_map.get(symbol)) { + if rt_30m_vec.len() >= 3 && supertrend_vec.len() >= 3 && rt_30m_vec.last().unwrap().close_time > server_epoch { + let supertrend_search_result = supertrend_vec.binary_search_by_key( + &rt_30m_vec.last().unwrap().close_time, + |SupertrendData { + band_value, + signal, + area, + close_time, + }| *close_time, + ); + if supertrend_search_result.is_ok() { + if supertrend_vec[supertrend_search_result.unwrap()].area == SuperTrendArea::UP + && supertrend_vec[supertrend_search_result.unwrap()].band_value < filtered_data.current_price.to_f64().unwrap() + { + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; - let sma_close = sma(5, &alldata.rt_price_30m_vec, &filtered_data_1st).await?; - let ema_close = ema(3, &alldata.rt_price_30m_vec, &filtered_data_1st).await?; - for element in filtered_data_1st { - let mut rt_30m_vec: Vec = Vec::new(); - let mut sma_vec: Vec = Vec::new(); - let mut ema_vec: Vec = Vec::new(); - let rt_price_30m_vec_c: Vec<(String, Vec)> = alldata.rt_price_30m_vec.clone(); - let sma_opclos_c = sma_close.clone(); - let ema_opclos_c = ema_close.clone(); - - let filtered_data_2nd_arc_c = Arc::clone(&filtered_data_2nd_arc); - task_vec.push(tokio::spawn(async move { - let rt_30m_option = rt_price_30m_vec_c - .iter() - .position(|x: &(String, Vec)| *x.0 == element.symbol); - let sma_option_30m = sma_opclos_c.iter().position(|x| x.0 == element.symbol); - let ema_option_30m = ema_opclos_c.iter().position(|x| x.0 == element.symbol); - - if rt_30m_option.is_some() && sma_option_30m.is_some() && ema_option_30m.is_some() { - rt_30m_vec = rt_price_30m_vec_c[rt_30m_option.unwrap()].1.clone(); - sma_vec = sma_opclos_c[sma_option_30m.unwrap()].1.clone(); - ema_vec = ema_opclos_c[ema_option_30m.unwrap()].1.clone(); - let server_epoch = server_epoch().await; - if rt_30m_vec.len() >= 3 && sma_vec.len() >= 3 && ema_vec.len() >= 3 && rt_30m_vec.last().unwrap().close_time > server_epoch { - let sma_search_result = sma_vec.binary_search_by_key( - &rt_30m_vec.last().unwrap().close_time, - |SmaData { - sma_value, - close_time, - }| *close_time, - ); - let ema_search_result = ema_vec.binary_search_by_key( - &rt_30m_vec.last().unwrap().close_time, - |EmaData { - ema_value, - close_time, - }| *close_time, - ); - if sma_search_result.is_ok() && ema_search_result.is_ok() { - if sma_vec[sma_search_result.unwrap()].sma_value < ema_vec[ema_search_result.unwrap()].ema_value && - sma_vec[sma_search_result.unwrap()-1].sma_value > ema_vec[ema_search_result.unwrap()-1].ema_value && - sma_vec[sma_search_result.unwrap()-4].sma_value > sma_vec[sma_search_result.unwrap()].sma_value { - let mut filtered_data_2nd_lock = filtered_data_2nd_arc_c.lock().await; - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = rt_30m_vec.last().unwrap().close_time; - filtered_data.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_30m_vec.last().unwrap().close_price).unwrap();; - filtered_data_2nd_lock.push(filtered_data); - } - } - } + // 5th filtering: 30m StochRSI (RSI_len: 30, StochRSI_len: 30, K: 3, D: 3) previous K, D / current K, D < 10 && current K > current D + let mut keys_to_remove: HashSet = HashSet::new(); + let stoch_rsis = stoch_rsi(30, 30, 3, 3, &alldata.rt_price_30m_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if alldata.rt_price_30m_vec.contains_key(symbol) && stoch_rsis.contains_key(symbol) { + let stoch_rsi_vec = stoch_rsis.get(symbol).unwrap(); + let search_result = stoch_rsi_vec.iter().position(|x| x.close_time == values.closetime); + if search_result.is_some_and(|a| stoch_rsi_vec[a-1].k < 10.0 && stoch_rsi_vec[a].k < 10.0 + && stoch_rsi_vec[a-1].d < 10.0 && stoch_rsi_vec[a].d< 10.0 && + stoch_rsi_vec[a-1].k <= stoch_rsi_vec[a-1].d && stoch_rsi_vec[a].k > stoch_rsi_vec[a].d) { + } else { + keys_to_remove.insert(symbol.clone()); } - })); + } else { + keys_to_remove.insert(symbol.clone()); + } } - try_join_all(task_vec).await?; + remove_keys(&mut filtered_data, keys_to_remove).await; + + // 3rd filtering: the latest 5 30m candle close prices > EMA 200 + let mut keys_to_remove: HashSet = HashSet::new(); + let emas = ema(200, &alldata.rt_price_30m_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if emas.contains_key(symbol) && alldata.rt_price_30m_vec.contains_key(symbol) { + let ema = emas.get(symbol).unwrap(); + let rt_price_30m = alldata.rt_price_30m_vec.get(symbol).unwrap(); + let rt_price_30m_len = rt_price_30m.len(); + let search_result = ema.binary_search_by_key( + &alldata.rt_price_30m_vec.get(symbol).unwrap().last().unwrap().close_time, + |EmaData { + ema_value, + close_time, + }| *close_time); + if search_result.is_ok_and(|x| ema[search_result.unwrap()].ema_value < rt_price_30m[rt_price_30m_len-1].close_price) && + search_result.is_ok_and(|x| ema[search_result.unwrap()-1].ema_value < rt_price_30m[rt_price_30m_len-2].close_price) { + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; + + // filtering: 1d MACD (3, 7, 30) current MACD-signal > prev MACD-signal + let mut keys_to_remove: HashSet = HashSet::new(); + let macd_1d_map = ema_macd(3, 7, 30, &alldata.rt_price_1d_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if let (Some(macd_vec), Some(rt_price_vec)) = (macd_1d_map.get(symbol), alldata.rt_price_1d_vec.get(symbol)) { + if macd_vec.last().unwrap().close_time == rt_price_vec.last().unwrap().close_time && + rt_price_vec.last().unwrap().close_time > server_epoch { + if macd_vec[macd_vec.len()-1].macd_value - macd_vec[macd_vec.len()-1].signal_value > macd_vec[macd_vec.len()-2].macd_value - macd_vec[macd_vec.len()-2].signal_value + { + values.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_price_vec.last().unwrap().close_price).unwrap(); + values.closetime = rt_price_vec.last().unwrap().close_time; + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; - // 3rd filtering: 0.5% <= the average amplitude of the latest 10 30m candles <= 1.0% - let filtered_data_2nd_c = filtered_data_2nd_arc.lock().await.clone(); - let mut filtered_data_3rd: Vec = Vec::new(); - let mut filtered_data_3rd_arc: Arc>> = - Arc::new(Mutex::new(filtered_data_3rd)); - let mut task_vec = Vec::new(); - for element in filtered_data_2nd_c { - let mut supertrend_vec: Vec = Vec::new(); - let rt_price_30m_vec_c = alldata.rt_price_30m_vec.clone(); - let filtered_data_3rd_arc_c = Arc::clone(&filtered_data_3rd_arc); - - task_vec.push(tokio::spawn(async move { - let position_idx = rt_price_30m_vec_c.iter().position(|elem| elem.0 == element.symbol); - - if position_idx.is_some() { - let vec_len = rt_price_30m_vec_c[position_idx.unwrap()].1.len(); - if vec_len >= 11 { - let candles = rt_price_30m_vec_c[position_idx.unwrap()].1.get(vec_len-12..vec_len-1).unwrap(); - let windows = candles.windows(2); - let mut average_amplitude = 0.0; - - for window in windows { - average_amplitude += (window.last().unwrap().high_price - window.last().unwrap().low_price) / window.first().unwrap().close_price; - } - average_amplitude /= 10.0; - - if 0.005 <= average_amplitude && average_amplitude <= 0.01 { - let mut filtered_data_3rd_lock = filtered_data_3rd_arc_c.lock().await; - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_data_3rd_lock.push(filtered_data); - } - } - } - })); - } - try_join_all(task_vec).await?; - - // 4th filtering: BollingerBand (len:20, multiplier 2) 5 previous_30m_price (close or low price) < lower_band - let filtered_data_3rd = filtered_data_3rd_arc.lock().await.clone(); - let mut filtered_data_4th: Vec = Vec::new(); - let mut filtered_data_4th_arc: Arc>> = - Arc::new(Mutex::new(filtered_data_4th)); - let mut task_vec = Vec::new(); - let bollingerbands = bollingerband(20, 2.0, &alldata.rt_price_30m_vec, &filtered_data_3rd).await?; - for element in filtered_data_3rd { - let mut rt_30m_vec: Vec = Vec::new(); - let mut bb_vec: Vec = Vec::new(); - let rt_price_30m_vec_c: Vec<(String, Vec)> = alldata.rt_price_30m_vec.clone(); - let bollingerbands_c = bollingerbands.clone(); - let filtered_data_4th_arc_c = Arc::clone(&filtered_data_4th_arc); - task_vec.push(tokio::spawn(async move { - let rt_30m_option = rt_price_30m_vec_c - .iter() - .position(|x| *x.0 == element.symbol); - let bb_option_30m = bollingerbands_c.iter().position(|x| x.0 == element.symbol); - - if rt_30m_option.is_some() && bb_option_30m.is_some() { - rt_30m_vec = rt_price_30m_vec_c[rt_30m_option.unwrap()].1.clone(); - bb_vec = bollingerbands_c[bb_option_30m.unwrap()].1.clone(); - let server_epoch = server_epoch().await; - if rt_30m_vec.len() >= 6 && bb_vec.len() >= 6 && rt_30m_vec.last().unwrap().close_time > server_epoch { - let bb_search_result = bb_vec.binary_search_by_key( - &rt_30m_vec.last().unwrap().close_time, - |BollingerBandData { - sma, - upperband, - lowerband, - close_time, - }| *close_time, - ); - if bb_search_result.is_ok() { - if bb_vec[bb_search_result.unwrap()-1].lowerband > rt_30m_vec[rt_30m_vec.len()-2].low_price || - bb_vec[bb_search_result.unwrap()-2].lowerband > rt_30m_vec[rt_30m_vec.len()-3].low_price || - bb_vec[bb_search_result.unwrap()-3].lowerband > rt_30m_vec[rt_30m_vec.len()-4].low_price || - bb_vec[bb_search_result.unwrap()-4].lowerband > rt_30m_vec[rt_30m_vec.len()-5].low_price || - bb_vec[bb_search_result.unwrap()-5].lowerband > rt_30m_vec[rt_30m_vec.len()-6].low_price - { - let mut filtered_data_4th_lock = filtered_data_4th_arc_c.lock().await; - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = rt_30m_vec.last().unwrap().close_time; - filtered_data.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_30m_vec.last().unwrap().close_price).unwrap();; - filtered_data_4th_lock.push(filtered_data); - } - } - } - } - })); - } - try_join_all(task_vec).await?; - - let final_filtered_data = duplicate_filter(3, &filtered_data_4th_arc.lock().await.clone()).await?; + let final_filtered_data = duplicate_filter(3, &filtered_data).await?; insert_pre_suggested_coins(3, false, &final_filtered_data, &alldata).await; Ok(()) @@ -192,8 +133,8 @@ pub async fn list_up_for_buy( pub async fn list_up_for_sell( all_data: &AllData, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let filled_buy_orders = select_filled_buy_orders(3).await?; @@ -202,62 +143,110 @@ pub async fn list_up_for_sell( .timeout(tokio::time::Duration::from_millis(5000)) .build() .unwrap(); - let mut supertrend_vec: Vec = Vec::new(); - let server_epoch = server_epoch().await; - - let mut filtered_symbol_vec: Vec = Vec::new(); + let mut filtered_symbols: HashMap = HashMap::new(); for element in &filled_buy_orders { - let filltered_data = FilteredData{ - symbol: element.symbol.clone(), - closetime: 0, - stoploss: dec!(0), - target_price: dec!(0), - current_price: dec!(0), - }; - filtered_symbol_vec.push(filltered_data); + filtered_symbols.insert(element.symbol.clone(), FilteredDataValue::new()); } - let sma_close = sma(5, &all_data.rt_price_30m_vec, &filtered_symbol_vec).await?; - let ema_close = ema(3, &all_data.rt_price_30m_vec, &filtered_symbol_vec).await?; + let server_epoch = get_server_epoch().await; + let stoch_rsis = stoch_rsi(30, 30, 3, 3, &all_data.rt_price_30m_vec, &filtered_symbols).await?; for element in filled_buy_orders { - let sma_result = sma_close.iter().position(|x| x.0 == element.symbol); - let ema_result = ema_close.iter().position(|x| x.0 == element.symbol); - if sma_result.is_some() && ema_result.is_some() && element.used_usdt >= dec!(10.0) { - let lot_step_size_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - let quote_commission_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - - if lot_step_size_option.is_some() - && quote_commission_precision_option.is_some() - { - let lot_step_size = exchange_info_vec[lot_step_size_option.unwrap()].stepsize; - let quote_commission_precision = exchange_info_vec - [quote_commission_precision_option.unwrap()] - .quote_commission_precision; + if element.used_usdt >= dec!(10.0) { + if let (Some(exchange_info), Some(stoch_rsi)) = (exchange_info_map.get(&element.symbol), stoch_rsis.get(&element.symbol)) { + let lot_step_size = exchange_info.stepsize; + let quote_commission_precision = exchange_info.quote_commission_precision; let base_qty_to_be_ordered = element.base_qty_ordered.round_dp_with_strategy( lot_step_size.normalize().scale(), RoundingStrategy::ToZero, ); + let stoch_rsi_k = stoch_rsi.last().unwrap().k; + let stoch_rsi_k_prev = stoch_rsi[stoch_rsi.len()-2].k; + let stoch_rsi_d = stoch_rsi.last().unwrap().d; + let stoch_rsi_d_prev = stoch_rsi[stoch_rsi.len()-2].d; if (element.is_long == 0 || element.is_long == 1) && !element.current_price.is_zero() { - if sma_close[sma_result.unwrap()].1.last().unwrap().sma_value > ema_close[ema_result.unwrap()].1.last().unwrap().ema_value && - sma_close[sma_result.unwrap()].1[sma_close.len()-2].sma_value < ema_close[ema_result.unwrap()].1[ema_close.len()-2].ema_value + if element.current_price >= element.target_price { limit_order_sell( &element, element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, + ) + .await; + } else if element.current_price <= element.stoploss { + limit_order_sell( + &element, + element.current_price, + base_qty_to_be_ordered, + &client, + &exchange_info_map, + &trade_fee_map, + ) + .await; + } else if server_epoch - element.transact_time > (1_800_000) * 35 { + limit_order_sell( + &element, + element.current_price, + base_qty_to_be_ordered, + &client, + &exchange_info_map, + &trade_fee_map, + ) + .await; + } else if stoch_rsi_k >= 90.0 { + limit_order_sell( + &element, + element.current_price, + base_qty_to_be_ordered, + &client, + &exchange_info_map, + &trade_fee_map, + ) + .await; + } else if stoch_rsi_k >= 20.0 && stoch_rsi_k >= 20.0 && + stoch_rsi_k_prev >= 20.0 && stoch_rsi_k_prev >= 20.0 && + stoch_rsi_k < stoch_rsi_d && + stoch_rsi_k_prev >= stoch_rsi_d_prev + { + limit_order_sell( + &element, + element.current_price, + base_qty_to_be_ordered, + &client, + &exchange_info_map, + &trade_fee_map, ) .await; } + // TODO: sell_count가 1일 때 적용하기 + // else if (supertrend_vec + // .last() + // .unwrap() + // .signal + // .as_ref() + // .is_some_and(|x| x.contains("SELL")) + // || supertrend_vec.last().unwrap().area.contains("DOWN")) + // && (supertrend_vec.last().unwrap().close_time > element.close_time) + // { + // println!( + // "SELL signal selling {} {:.2}", + // element.symbol, element.pure_profit_percent + // ); + // limit_order_sell( + // &element, + // element.current_price, + // base_qty_to_be_ordered, + // &client, + // &exchange_info_vec, + // &trade_fee_vec, + // ) + // .await; + // } } } } diff --git a/src/strategy_team/strategy_004.rs b/src/strategy_team/strategy_004.rs index aeb23f1..c63ae0e 100644 --- a/src/strategy_team/strategy_004.rs +++ b/src/strategy_team/strategy_004.rs @@ -3,9 +3,9 @@ use crate::value_estimation_team::indicators::bollingerband::bollingerband; use super::{ dec, decimal_add, decimal_sub, decimal_div, ema, exists_record, insert_pre_suggested_coins, limit_order_sell, rsi, select_filled_buy_orders, stoch_rsi, supertrend, try_join_all, AllData, - Arc, Client, ClientBuilder, Decimal, EmaData, ExchangeInfo, FilteredData, Mutex, - RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SupertrendData, TradeFee, update_record3, adx, AdxData, server_epoch, MacdData, ema_macd, - BollingerBandData, ToPrimitive, duplicate_filter + Arc, Client, ClientBuilder, Decimal, EmaData, ExchangeInfo, FilteredDataValue, Mutex, + RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SupertrendData, TradeFee, update_record3, adx, AdxData, get_server_epoch, MacdData, ema_macd, + BollingerBandData, ToPrimitive, duplicate_filter, HashMap, HashSet, remove_keys, SuperTrendArea, SuperTrendSignal, CandleType }; // BB lowerband + SuperTrend + StochRSI @@ -19,81 +19,84 @@ pub async fn list_up_for_buy( // println!("BTCUSDT: {:?}", alldata.rt_price_30m_vec[a.unwrap()].1.last().unwrap()); // 1st filtering: filtering valid trade pair - let mut filtered_data_1st: Vec = Vec::new(); + let mut filtered_data: HashMap = HashMap::new(); for symbol in &alldata.valid_symbol_vec { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = symbol.clone(); - filtered_data_1st.push(filtered_data); + filtered_data.insert(symbol.clone(), FilteredDataValue::new()); } - // 2nd filtering: BollingerBand (len:30, multiplier 3) previous_30m_price (close or low price) < lower_band - let mut filtered_data_2nd: Vec = Vec::new(); - let mut filtered_data_2nd_arc: Arc>> = - Arc::new(Mutex::new(filtered_data_2nd)); - let mut task_vec = Vec::new(); - let bollingerbands = bollingerband(30, 3.0, &alldata.rt_price_30m_vec, &filtered_data_1st).await?; - for element in filtered_data_1st { - let mut rt_30m_vec: Vec = Vec::new(); - let mut bb_vec: Vec = Vec::new(); - let rt_price_30m_vec_c: Vec<(String, Vec)> = alldata.rt_price_30m_vec.clone(); - let bollingerbands_c = bollingerbands.clone(); - let filtered_data_2nd_arc_c = Arc::clone(&filtered_data_2nd_arc); - task_vec.push(tokio::spawn(async move { - let rt_30m_option = rt_price_30m_vec_c - .iter() - .position(|x| *x.0 == element.symbol); - let bb_option_30m = bollingerbands_c.iter().position(|x| x.0 == element.symbol); + // 5th filtering: 0.5% <= the average amplitude of the latest 10 30m candles <= 1.0% + let mut keys_to_remove: HashSet = HashSet::new(); + for (symbol, filtered_data) in &mut filtered_data { + if let Some(rt_price_30m_vec) = alldata.rt_price_30m_vec.get(symbol) { + let vec_len = rt_price_30m_vec.len(); + if vec_len >= 11 { + let candles = rt_price_30m_vec.get(vec_len-12..vec_len-1).unwrap(); + let windows = candles.windows(2); + let mut average_amplitude = 0.0; - if rt_30m_option.is_some() && bb_option_30m.is_some() { - rt_30m_vec = rt_price_30m_vec_c[rt_30m_option.unwrap()].1.clone(); - bb_vec = bollingerbands_c[bb_option_30m.unwrap()].1.clone(); - let server_epoch = server_epoch().await; - if rt_30m_vec.len() >= 3 && bb_vec.len() >= 3 && rt_30m_vec.last().unwrap().close_time > server_epoch { - let bb_search_result = bb_vec.binary_search_by_key( - &rt_30m_vec.last().unwrap().close_time, - |BollingerBandData { - sma, - upperband, - lowerband, - close_time, - }| *close_time, - ); - if bb_search_result.is_ok() { - if bb_vec[bb_search_result.unwrap()-1].lowerband > rt_30m_vec[rt_30m_vec.len()-2].low_price && - rt_30m_vec[rt_30m_vec.len()-2].opclo_price > rt_30m_vec.last().unwrap().close_price && - rt_30m_vec[rt_30m_vec.len()-2].candle_type.contains("DOWN") && - rt_30m_vec[rt_30m_vec.len()-2].high_price < bb_vec[bb_search_result.unwrap()-1].sma - { - let mut filtered_data_2nd_lock = filtered_data_2nd_arc_c.lock().await; - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = rt_30m_vec.last().unwrap().close_time; - filtered_data.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_30m_vec.last().unwrap().close_price).unwrap(); - filtered_data_2nd_lock.push(filtered_data); - } - } + for window in windows { + average_amplitude += (window.last().unwrap().high_price - window.last().unwrap().low_price) / window.first().unwrap().close_price; } + average_amplitude /= 10.0; + + if 0.005 <= average_amplitude && average_amplitude <= 0.01 { + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); } - })); + } else { + keys_to_remove.insert(symbol.clone()); + } } - try_join_all(task_vec).await?; + remove_keys(&mut filtered_data, keys_to_remove).await; + + // 2nd filtering: BollingerBand (len:30, multiplier 3) previous_30m_price (close or low price) < lower_band + let mut keys_to_remove: HashSet = HashSet::new(); + let bollingerband_map = bollingerband(30, 3.0, &alldata.rt_price_30m_vec, &filtered_data).await?; + let server_epoch = get_server_epoch().await; + for (symbol, filtered_data) in &mut filtered_data { + if let (Some(bb_vec), Some(rt_30m_vec)) = (bollingerband_map.get(symbol), alldata.rt_price_30m_vec.get(symbol)) { + if rt_30m_vec.len() >= 3 && bb_vec.len() >= 3 && rt_30m_vec.last().unwrap().close_time > server_epoch { + let bb_search_result = bb_vec.binary_search_by_key( + &rt_30m_vec.last().unwrap().close_time, + |BollingerBandData { + sma, + upperband, + lowerband, + close_time, + }| *close_time, + ); + if bb_search_result.is_ok() { + if bb_vec[bb_search_result.unwrap()-1].lowerband > rt_30m_vec[rt_30m_vec.len()-2].low_price && + rt_30m_vec[rt_30m_vec.len()-2].opclo_price > rt_30m_vec.last().unwrap().close_price && + rt_30m_vec[rt_30m_vec.len()-2].candle_type == CandleType::DOWN && + rt_30m_vec[rt_30m_vec.len()-2].high_price < bb_vec[bb_search_result.unwrap()-1].sma + { + filtered_data.closetime = rt_30m_vec.last().unwrap().close_time; + filtered_data.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_30m_vec.last().unwrap().close_price).unwrap(); + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; // 3rd filtering: supertrend(ATR period 10, multiplier: 2, 30m close price), area should be DOWN - let filtered_data_2nd = filtered_data_2nd_arc.lock().await.clone(); - let mut filtered_data_3rd: Vec = Vec::new(); - for element in filtered_data_2nd { - let mut rt_30m_vec: Vec = Vec::new(); - let mut supertrend_vec: Vec = Vec::new(); - let rt_30m_option = alldata.rt_price_30m_vec - .iter() - .position(|x| *x.0 == element.symbol); - let supertrend_option_30m = - supertrend(&element.symbol, &alldata.rt_price_30m_vec, 10, 2.0, true).await; - - if rt_30m_option.is_some() && supertrend_option_30m.is_some() { - rt_30m_vec = alldata.rt_price_30m_vec[rt_30m_option.unwrap()].1.clone(); - supertrend_vec = supertrend_option_30m.unwrap(); - let server_epoch = server_epoch().await; + let mut keys_to_remove: HashSet = HashSet::new(); + let server_epoch = get_server_epoch().await; + let supertrend_30m_map = supertrend( 10, 2.0, true, &alldata.rt_price_30m_vec, &filtered_data).await?; + for (symbol, filtered_data) in &mut filtered_data { + if let (Some(supertrend_vec), Some(rt_30m_vec)) = (supertrend_30m_map.get(symbol), alldata.rt_price_30m_vec.get(symbol)) { if rt_30m_vec.len() >= 3 && supertrend_vec.len() >= 3 && rt_30m_vec.last().unwrap().close_time > server_epoch { let supertrend_search_result = supertrend_vec.binary_search_by_key( &rt_30m_vec.last().unwrap().close_time, @@ -105,106 +108,87 @@ pub async fn list_up_for_buy( }| *close_time, ); if supertrend_search_result.is_ok_and(|x| - supertrend_vec[x].area.contains("DOWN") && supertrend_vec[x].band_value > element.current_price.to_f64().unwrap()) { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; + supertrend_vec[x].area == SuperTrendArea::DOWN && supertrend_vec[x].band_value > filtered_data.current_price.to_f64().unwrap()) { filtered_data.target_price = rust_decimal::prelude::FromPrimitive::from_f64(supertrend_vec[supertrend_search_result.unwrap()].band_value).unwrap(); let stop_loss = decimal_sub(filtered_data.current_price, decimal_div(decimal_sub(filtered_data.target_price, filtered_data.current_price), dec!(2))); filtered_data.stoploss = stop_loss; - - filtered_data_3rd.push(filtered_data); - + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } } + remove_keys(&mut filtered_data, keys_to_remove).await; // 4th filtering: the latest 5 30m candle close prices > EMA 200 - let mut filtered_data_4th: Vec = Vec::new(); - let ema_vec = ema(200, &alldata.rt_price_30m_vec, &filtered_data_3rd).await?; - for element in filtered_data_3rd { - let ema_search_result = ema_vec.iter().position(|x| x.0 == element.symbol); - let candle_search_result = alldata.rt_price_30m_vec.iter().position(|x| x.0 == element.symbol); - if ema_search_result.is_some() && candle_search_result.is_some() { - let search_result = ema_vec[ema_search_result.unwrap()].1.binary_search_by_key( - &alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.last().unwrap().close_time, + let mut keys_to_remove: HashSet = HashSet::new(); + let ema_map = ema(200, &alldata.rt_price_30m_vec, &filtered_data).await?; + for (symbol, filtered_data) in &mut filtered_data { + if let (Some(ema_vec), Some(rt_30m_vec)) = (ema_map.get(symbol), alldata.rt_price_30m_vec.get(symbol)) { + let search_result = ema_vec.binary_search_by_key( + &rt_30m_vec.last().unwrap().close_time, |EmaData { ema_value, close_time, }| *close_time); - if search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-1].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-1].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-2].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-2].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-3].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-3].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-4].close_price) && - search_result.is_ok_and(|x| ema_vec[ema_search_result.unwrap()].1[search_result.unwrap()-4].ema_value < alldata.rt_price_30m_vec[candle_search_result.unwrap()].1[alldata.rt_price_30m_vec[candle_search_result.unwrap()].1.len()-5].close_price) { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_data_4th.push(filtered_data); - } - } - } - - // 5th filtering: 0.5% <= the average amplitude of the latest 10 30m candles <= 1.0% - let mut filtered_data_5th: Vec = Vec::new(); - for element in filtered_data_4th { - let position_idx = alldata.rt_price_30m_vec.iter().position(|elem| elem.0 == element.symbol); - - if position_idx.is_some() { - let vec_len = alldata.rt_price_30m_vec[position_idx.unwrap()].1.len(); - if vec_len >= 11 { - let candles = alldata.rt_price_30m_vec[position_idx.unwrap()].1.get(vec_len-12..vec_len-1).unwrap(); - let windows = candles.windows(2); - let mut average_amplitude = 0.0; - - for window in windows { - average_amplitude += (window.last().unwrap().high_price - window.last().unwrap().low_price) / window.first().unwrap().close_price; - } - average_amplitude /= 10.0; - - if 0.005 <= average_amplitude && average_amplitude <= 0.01 { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_data_5th.push(filtered_data); - } + if search_result.is_ok_and(|x| ema_vec[search_result.unwrap()].ema_value < rt_30m_vec[rt_30m_vec.len()-1].close_price) && + search_result.is_ok_and(|x| ema_vec[search_result.unwrap()-1].ema_value < rt_30m_vec[rt_30m_vec.len()-2].close_price) && + search_result.is_ok_and(|x| ema_vec[search_result.unwrap()-2].ema_value < rt_30m_vec[rt_30m_vec.len()-3].close_price) && + search_result.is_ok_and(|x| ema_vec[search_result.unwrap()-3].ema_value < rt_30m_vec[rt_30m_vec.len()-4].close_price) && + search_result.is_ok_and(|x| ema_vec[search_result.unwrap()-4].ema_value < rt_30m_vec[rt_30m_vec.len()-5].close_price) { + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } } + remove_keys(&mut filtered_data, keys_to_remove).await; // 6th filtering: 30m StochRSI (RSI_len: 10, StochRSI_len: 10, K: 3, D: 3) current K, D < 20 - let mut filtered_data_6th: Vec = Vec::new(); - let stoch_rsis = stoch_rsi(10, 10, 3, 3, &alldata.rt_price_30m_vec, &filtered_data_5th).await?; - for element in filtered_data_5th { - let position_idx = stoch_rsis.iter().position(|elem| elem.0 == element.symbol); - - if position_idx.is_some() { - let stoch_rsi_vec = stoch_rsis[position_idx.unwrap()].1.clone(); - let search_result = stoch_rsi_vec.iter().position(|x| x.close_time == element.closetime); + let mut keys_to_remove: HashSet = HashSet::new(); + let stoch_rsi_map = stoch_rsi(10, 10, 3, 3, &alldata.rt_price_30m_vec, &filtered_data).await?; + for (symbol, filtered_data) in &mut filtered_data { + if let Some(stoch_rsi_vec) = stoch_rsi_map.get(symbol) { + let search_result = stoch_rsi_vec.iter().position(|x| x.close_time == filtered_data.closetime); if search_result.is_some_and(|a| stoch_rsi_vec[a].k < 15.0 && stoch_rsi_vec[a].d < 15.0) { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_data_6th.push(filtered_data); + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } } + remove_keys(&mut filtered_data, keys_to_remove).await; - let final_filtered_data = duplicate_filter(4, &filtered_data_6th).await?; + // filtering: 1d MACD (3, 7, 30) current MACD-signal > prev MACD-signal + let mut keys_to_remove: HashSet = HashSet::new(); + let macd_1d_map = ema_macd(3, 7, 30, &alldata.rt_price_1d_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if let (Some(macd_vec), Some(rt_price_vec)) = (macd_1d_map.get(symbol), alldata.rt_price_1d_vec.get(symbol)) { + if macd_vec.last().unwrap().close_time == rt_price_vec.last().unwrap().close_time && + rt_price_vec.last().unwrap().close_time > server_epoch { + if macd_vec[macd_vec.len()-1].macd_value - macd_vec[macd_vec.len()-1].signal_value > macd_vec[macd_vec.len()-2].macd_value - macd_vec[macd_vec.len()-2].signal_value + { + values.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_price_vec.last().unwrap().close_price).unwrap(); + values.closetime = rt_price_vec.last().unwrap().close_time; + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; + + let final_filtered_data = duplicate_filter(4, &filtered_data).await?; insert_pre_suggested_coins(4, false, &final_filtered_data, &alldata).await; Ok(()) @@ -212,8 +196,8 @@ pub async fn list_up_for_buy( pub async fn list_up_for_sell( all_data: &AllData, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let filled_buy_orders = select_filled_buy_orders(4).await?; @@ -223,23 +207,12 @@ pub async fn list_up_for_sell( .build() .unwrap(); let mut supertrend_vec: Vec = Vec::new(); - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; for element in filled_buy_orders { if element.used_usdt >= dec!(10.0) { - let lot_step_size_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - let quote_commission_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - - if lot_step_size_option.is_some() - && quote_commission_precision_option.is_some() - { - let lot_step_size = exchange_info_vec[lot_step_size_option.unwrap()].stepsize; - let quote_commission_precision = exchange_info_vec - [quote_commission_precision_option.unwrap()] - .quote_commission_precision; + if let Some(exchange_info) = exchange_info_map.get(&element.symbol) { + let lot_step_size = exchange_info.stepsize; + let quote_commission_precision = exchange_info.quote_commission_precision; let base_qty_to_be_ordered = element.base_qty_ordered.round_dp_with_strategy( lot_step_size.normalize().scale(), @@ -256,8 +229,8 @@ pub async fn list_up_for_sell( element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; } else if element.current_price <= element.stoploss { @@ -266,8 +239,8 @@ pub async fn list_up_for_sell( element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; } else if server_epoch - element.transact_time > (1_800_000) * 20 { @@ -276,8 +249,8 @@ pub async fn list_up_for_sell( element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; } diff --git a/src/strategy_team/strategy_005.rs b/src/strategy_team/strategy_005.rs index 5c43031..a75f736 100644 --- a/src/strategy_team/strategy_005.rs +++ b/src/strategy_team/strategy_005.rs @@ -1,217 +1,163 @@ use super::{ - dec, decimal_add, decimal_sub, ema, exists_record, insert_pre_suggested_coins, + dec, decimal_add, decimal_sub, decimal_div, decimal_mul, ema, exists_record, insert_pre_suggested_coins, limit_order_sell, rsi, select_filled_buy_orders, stoch_rsi, supertrend, try_join_all, AllData, - Arc, Client, ClientBuilder, Decimal, EmaData, ExchangeInfo, FilteredData, Mutex, - RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SupertrendData, TradeFee, update_record3, adx, AdxData, server_epoch, MacdData, ema_macd, - duplicate_filter + Arc, Client, ClientBuilder, Decimal, EmaData, ExchangeInfo, FilteredDataValue, Mutex, + RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SupertrendData, TradeFee, update_record3, adx, AdxData, get_server_epoch, MacdData, ema_macd, + BollingerBandData, ToPrimitive, duplicate_filter, HashMap, HashSet, remove_keys, SuperTrendArea, SuperTrendSignal, get_current_price }; -// Triple SuperTrend strategy -// SuperTrend length: 20, multiplier: 1.5, BUY signal -// ADX(10, 10) < 25.0 -// the latest 5 candle close prices > EMA 200 +// BUY conditions +// (1) 1d MACD (3, 7, 30) cross +// (2) supertrend (30, 3): UP area +// stoploss: (update) supertrend(10, 1.5) lowerband +// target price: (fixed) stoploss inverse x 3 times profit pub async fn list_up_for_buy( - alldata: AllData, + alldata: &AllData, ) -> Result<(), Box> { // print rt_price for debugging // let a = alldata.rt_price_30m_vec.iter().position(|a| a.0 == "BTCUSDT"); // println!("BTCUSDT: {:?}", alldata.rt_price_30m_vec[a.unwrap()].1.last().unwrap()); // 1st filtering: filtering valid trade pair - let mut filtered_data_1st: Vec = Vec::new(); + let mut filtered_data: HashMap = HashMap::new(); for symbol in &alldata.valid_symbol_vec { - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = symbol.clone(); - filtered_data_1st.push(filtered_data); + filtered_data.insert(symbol.clone(), FilteredDataValue::new()); } - // 2nd filtering: supertrend(ATR period 20, multiplier: 1.5, 30m close price), signal should be BUY - let mut filtered_data_2nd: Vec = Vec::new(); - let mut filtered_data_2nd_arc: Arc>> = - Arc::new(Mutex::new(filtered_data_2nd)); - let mut task_vec = Vec::new(); - - for element in filtered_data_1st { - let mut rt_30m_vec: Vec = Vec::new(); - let mut supertrend_vec: Vec = Vec::new(); - let rt_price_30m_vec_c = alldata.rt_price_30m_vec.clone(); - let filtered_data_2nd_arc_c = Arc::clone(&filtered_data_2nd_arc); - task_vec.push(tokio::spawn(async move { - let rt_30m_option = rt_price_30m_vec_c - .iter() - .position(|x| *x.0 == element.symbol); - let supertrend_option_30m = - supertrend(&element.symbol, &rt_price_30m_vec_c, 20, 1.5, true).await; - - if rt_30m_option.is_some() && supertrend_option_30m.is_some() { - rt_30m_vec = rt_price_30m_vec_c[rt_30m_option.unwrap()].1.clone(); - supertrend_vec = supertrend_option_30m.unwrap(); - let server_epoch = server_epoch().await; - if rt_30m_vec.len() >= 3 && supertrend_vec.len() >= 3 && rt_30m_vec.last().unwrap().close_time > server_epoch { - let supertrend_search_result = supertrend_vec.binary_search_by_key( - &rt_30m_vec.last().unwrap().close_time, - |SupertrendData { - band_value, - signal, - area, - close_time, - }| *close_time, - ); - if supertrend_search_result.is_ok() { - let current_price: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(rt_30m_vec.last().unwrap().close_price).unwrap(); - - if supertrend_vec[supertrend_search_result.unwrap()].signal.as_ref().is_some_and(|x| x.contains("BUY")) - && current_price - < rust_decimal::prelude::FromPrimitive::from_f64( - supertrend_vec[supertrend_search_result.unwrap()-1].band_value * 1.002, - ) - .unwrap() - && supertrend_vec[supertrend_search_result.unwrap()-1].band_value > supertrend_vec[supertrend_search_result.unwrap()].band_value - { - let mut filtered_data_2nd_lock = filtered_data_2nd_arc_c.lock().await; - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = supertrend_vec[supertrend_search_result.unwrap()].close_time; - filtered_data.current_price = current_price; - filtered_data.stoploss = rust_decimal::prelude::FromPrimitive::from_f64(supertrend_vec[supertrend_search_result.unwrap()].band_value).unwrap(); - let target_price = decimal_add( - filtered_data.current_price, - decimal_sub(filtered_data.current_price, filtered_data.stoploss), - ); - filtered_data.target_price = target_price; - - filtered_data_2nd_lock.push(filtered_data); - } - } + // 2nd filtering: supertrend(ATR period 14, multiplier: 1.2, 1d close price) + let mut keys_to_remove: HashSet = HashSet::new(); + let server_epoch = get_server_epoch().await; + let supertrend_1d_map = supertrend(14, 1.2, true, &alldata.rt_price_1d_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if let (Some(supertrend_vec), Some(rt_price_vec)) = (supertrend_1d_map.get(symbol), alldata.rt_price_1d_vec.get(symbol)) { + if supertrend_vec.last().unwrap().close_time == rt_price_vec.last().unwrap().close_time && + rt_price_vec.last().unwrap().close_time > server_epoch { + if supertrend_vec.last().unwrap().area == SuperTrendArea::UP{ + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } - })); - } - try_join_all(task_vec).await?; - - // 3rd filtering: ADX(10, 10) < 25.0 - let filtered_data_2nd = filtered_data_2nd_arc.lock().await.clone(); - let mut filtered_data_3rd: Vec = Vec::new(); - let mut filtered_data_3rd_arc: Arc>> = - Arc::new(Mutex::new(filtered_data_3rd)); - let mut task_vec = Vec::new(); - let valid_symbol_vec_c = alldata.valid_symbol_vec.clone(); - let adx_vec = adx(10, 10, &alldata.rt_price_30m_vec, &filtered_data_2nd).await?; - for element in filtered_data_2nd { - let mut adx_vec_c = adx_vec.clone(); - let symbol = element.symbol.clone(); - let close_time = element.closetime; - let idx_result = adx_vec.iter().position(|elem| elem.0 == symbol); - let filtered_data_3rd_arc_c = Arc::clone(&filtered_data_3rd_arc); - if idx_result.is_some(){ - task_vec.push(tokio::spawn(async move { - let closetime_idx_result = adx_vec_c[idx_result.unwrap()].1.iter().position(|elem| elem.close_time==close_time); - if closetime_idx_result.is_some() { - if adx_vec_c[idx_result.unwrap()].1[closetime_idx_result.unwrap()].adx < 25.0 { - let mut filtered_3rd_symbols_lock = - filtered_data_3rd_arc_c.lock().await; - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_3rd_symbols_lock.push(filtered_data); - } - } - })); + } else { + keys_to_remove.insert(symbol.clone()); } } - try_join_all(task_vec).await?; + remove_keys(&mut filtered_data, keys_to_remove).await; - // 4th filtering: the latest 5 30m candle close prices > EMA 200 - let filtered_data_3rd = filtered_data_3rd_arc.lock().await.clone(); - let mut filtered_data_4th: Vec = Vec::new(); - let mut filtered_data_4th_arc: Arc>> = - Arc::new(Mutex::new(filtered_data_4th)); - let mut task_vec = Vec::new(); - let ema_vec = ema(200, &alldata.rt_price_30m_vec, &filtered_data_3rd).await?; - for element in filtered_data_3rd { - let mut opclo_30m_vec = alldata.rt_price_30m_vec.clone(); - let mut supertrend_vec: Vec = Vec::new(); - let ema_vec_c = ema_vec.clone(); - let filtered_data_4th_arc_c = Arc::clone(&filtered_data_4th_arc); - task_vec.push(tokio::spawn(async move { - let ema_search_result = ema_vec_c.iter().position(|x| x.0 == element.symbol); - let candle_search_result = opclo_30m_vec.iter().position(|x| x.0 == element.symbol); - if ema_search_result.is_some() && candle_search_result.is_some() { - let search_result = ema_vec_c[ema_search_result.unwrap()].1.binary_search_by_key( - &opclo_30m_vec[candle_search_result.unwrap()].1.last().unwrap().close_time, - |EmaData { - ema_value, - close_time, - }| *close_time); - if search_result.is_ok_and(|x| ema_vec_c[ema_search_result.unwrap()].1[search_result.unwrap()].ema_value < opclo_30m_vec[candle_search_result.unwrap()].1[opclo_30m_vec[candle_search_result.unwrap()].1.len()-1].close_price) && - search_result.is_ok_and(|x| ema_vec_c[ema_search_result.unwrap()].1[search_result.unwrap()-1].ema_value < opclo_30m_vec[candle_search_result.unwrap()].1[opclo_30m_vec[candle_search_result.unwrap()].1.len()-2].close_price) && - search_result.is_ok_and(|x| ema_vec_c[ema_search_result.unwrap()].1[search_result.unwrap()-2].ema_value < opclo_30m_vec[candle_search_result.unwrap()].1[opclo_30m_vec[candle_search_result.unwrap()].1.len()-3].close_price) && - search_result.is_ok_and(|x| ema_vec_c[ema_search_result.unwrap()].1[search_result.unwrap()-3].ema_value < opclo_30m_vec[candle_search_result.unwrap()].1[opclo_30m_vec[candle_search_result.unwrap()].1.len()-4].close_price) && - search_result.is_ok_and(|x| ema_vec_c[ema_search_result.unwrap()].1[search_result.unwrap()-4].ema_value < opclo_30m_vec[candle_search_result.unwrap()].1[opclo_30m_vec[candle_search_result.unwrap()].1.len()-5].close_price) { - let mut filtered_4th_symbols_lock = - filtered_data_4th_arc_c.lock().await; - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_4th_symbols_lock.push(filtered_data); + // 2nd filtering: 1d MACD (3, 5, 30) cross + let mut keys_to_remove: HashSet = HashSet::new(); + let macd_1d_map = ema_macd(3, 5, 30, &alldata.rt_price_1d_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if let (Some(macd_vec), Some(rt_price_vec)) = (macd_1d_map.get(symbol), alldata.rt_price_1d_vec.get(symbol)) { + if macd_vec.last().unwrap().close_time == rt_price_vec.last().unwrap().close_time && + rt_price_vec.last().unwrap().close_time > server_epoch { + if macd_vec[macd_vec.len()-1].macd_value > macd_vec[macd_vec.len()-1].signal_value && + macd_vec[macd_vec.len()-2].macd_value < macd_vec[macd_vec.len()-2].signal_value { + values.current_price = rust_decimal::prelude::FromPrimitive::from_f64(rt_price_vec.last().unwrap().close_price).unwrap(); + values.closetime = rt_price_vec.last().unwrap().close_time; + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } - })); + } else { + keys_to_remove.insert(symbol.clone()); + } } - try_join_all(task_vec).await?; - - // 5th filtering: 0.5% <= the average amplitude of the latest 10 30m candles <= 1.0% - let filtered_data_4th_c = filtered_data_4th_arc.lock().await.clone(); - let mut filtered_data_5th: Vec = Vec::new(); - let mut filtered_data_5th_arc: Arc>> = - Arc::new(Mutex::new(filtered_data_5th)); - let mut task_vec = Vec::new(); - for element in filtered_data_4th_c { - let mut supertrend_vec: Vec = Vec::new(); - let rt_price_30m_vec_c = alldata.rt_price_30m_vec.clone(); - let filtered_data_5th_arc_c = Arc::clone(&filtered_data_5th_arc); + remove_keys(&mut filtered_data, keys_to_remove).await; - task_vec.push(tokio::spawn(async move { - let position_idx = rt_price_30m_vec_c.iter().position(|elem| elem.0 == element.symbol); - - if position_idx.is_some() { - let vec_len = rt_price_30m_vec_c[position_idx.unwrap()].1.len(); - if vec_len >= 11 { - let candles = rt_price_30m_vec_c[position_idx.unwrap()].1.get(vec_len-12..vec_len-1).unwrap(); - let windows = candles.windows(2); - let mut average_amplitude = 0.0; - - for window in windows { - average_amplitude += (window.last().unwrap().high_price - window.last().unwrap().low_price) / window.first().unwrap().close_price; - } - average_amplitude /= 10.0; - - if 0.005 <= average_amplitude && average_amplitude <= 0.01 { - let mut filtered_data_5th_lock = filtered_data_5th_arc_c.lock().await; - let mut filtered_data = FilteredData::new(); - filtered_data.symbol = element.symbol.clone(); - filtered_data.closetime = element.closetime; - filtered_data.current_price = element.current_price; - filtered_data.stoploss = element.stoploss; - filtered_data.target_price = element.target_price; - - filtered_data_5th_lock.push(filtered_data); - } + // 2nd filtering: supertrend(ATR period 14, multiplier: 1.2, 1d close price) + let mut keys_to_remove: HashSet = HashSet::new(); + let server_epoch = get_server_epoch().await; + let supertrend_1d_map = supertrend(14, 1.2, true, &alldata.rt_price_1d_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if let (Some(supertrend_vec), Some(rt_price_vec)) = (supertrend_1d_map.get(symbol), alldata.rt_price_1d_vec.get(symbol)) { + if supertrend_vec.last().unwrap().close_time == rt_price_vec.last().unwrap().close_time && + rt_price_vec.last().unwrap().close_time > server_epoch { + // input stoploss, target_price + let band_value: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(supertrend_vec.last().unwrap().band_value).unwrap(); + if supertrend_vec.last().unwrap().area == SuperTrendArea::DOWN && + supertrend_vec.last().unwrap().band_value > values.current_price.to_f64().unwrap() + { + values.stoploss = decimal_sub(values.current_price, decimal_sub(band_value, values.current_price)); + values.target_price = decimal_add(decimal_mul(decimal_sub(values.current_price, values.stoploss), dec!(2.0)), values.current_price); + + } else if supertrend_vec.last().unwrap().area == SuperTrendArea::UP && + supertrend_vec.last().unwrap().band_value < values.current_price.to_f64().unwrap() + { + values.stoploss = band_value; + values.target_price = decimal_add(decimal_mul(decimal_sub(values.current_price, values.stoploss), dec!(2.0)), values.current_price); + } else { + keys_to_remove.insert(symbol.clone()); } + } else { + keys_to_remove.insert(symbol.clone()); } - })); + } else { + keys_to_remove.insert(symbol.clone()); + } } - try_join_all(task_vec).await?; + remove_keys(&mut filtered_data, keys_to_remove).await; - let final_filtered_data = duplicate_filter(5, &filtered_data_5th_arc.lock().await.clone()).await?; + // filtering: the 1 previous ADX(3, 5)s increase + let mut keys_to_remove: HashSet = HashSet::new(); + let adx_vec = adx(3, 5, &alldata.rt_price_1d_vec, &filtered_data).await?; + for (symbol, values) in &mut filtered_data { + if let Some(adx_vec) = adx_vec.get(symbol) { + if let Some(last_idx) = adx_vec.iter().position(|elem| elem.close_time == values.closetime) { + if + adx_vec[last_idx].adx > adx_vec[last_idx-1].adx { + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; + + // limit buy price: 3 * abs(이전 3 개 중 최대값 제거 한 opclo 값 평균 - 현재 open 값) + 현재 open 값 > current_price + let mut keys_to_remove: HashSet = HashSet::new(); + let server_epoch = get_server_epoch().await; + for (symbol, values) in &mut filtered_data { + if let Some(rt_price_vec) = alldata.rt_price_1d_vec.get(symbol) { + if rt_price_vec.last().unwrap().close_time > server_epoch && rt_price_vec.len() >= 6 { + let mut opclo_vec: Vec = Vec::new(); + opclo_vec.push(rt_price_vec[rt_price_vec.len()-2].opclo_price); + opclo_vec.push(rt_price_vec[rt_price_vec.len()-3].opclo_price); + opclo_vec.push(rt_price_vec[rt_price_vec.len()-4].opclo_price); + opclo_vec.push(rt_price_vec[rt_price_vec.len()-5].opclo_price); + opclo_vec.push(rt_price_vec[rt_price_vec.len()-6].opclo_price); + let max_idx = opclo_vec.iter().position(|&x| x == *opclo_vec.iter().max_by(|&a, &b| a.partial_cmp(b).unwrap()).unwrap()); + opclo_vec.remove(max_idx.unwrap()); + + let mut mean = 0.0; + for element in &opclo_vec { + mean += element; + } + mean /= opclo_vec.len() as f64; + let current_price = rt_price_vec.last().unwrap().close_price; + let difference = (mean - rt_price_vec.last().unwrap().open_price).abs(); + + if current_price < rt_price_vec.last().unwrap().open_price + (3.0 * difference) { + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } else { + keys_to_remove.insert(symbol.clone()); + } + } + remove_keys(&mut filtered_data, keys_to_remove).await; + + let final_filtered_data = duplicate_filter(5, &filtered_data).await?; insert_pre_suggested_coins(5, false, &final_filtered_data, &alldata).await; Ok(()) @@ -219,8 +165,8 @@ pub async fn list_up_for_buy( pub async fn list_up_for_sell( all_data: &AllData, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { let filled_buy_orders = select_filled_buy_orders(5).await?; @@ -230,32 +176,19 @@ pub async fn list_up_for_sell( .build() .unwrap(); let mut supertrend_vec: Vec = Vec::new(); - let server_epoch = server_epoch().await; + let server_epoch = get_server_epoch().await; + let mut filtered_symbols: HashMap = HashMap::new(); + for element in &filled_buy_orders { + filtered_symbols.insert(element.symbol.clone(), FilteredDataValue::new()); + } + let supertrend_1d = supertrend(14, 1.2, true, &all_data.rt_price_1d_vec, &filtered_symbols).await?; for element in filled_buy_orders { if element.used_usdt >= dec!(10.0) { - let lot_step_size_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - let quote_commission_precision_option = exchange_info_vec - .iter() - .position(|exchange_info| exchange_info.symbol == element.symbol); - - let opclo_30m_option = all_data - .rt_price_30m_vec - .iter() - .position(|x| *x.0 == element.symbol); - let supertrend_option_30m = - supertrend(&element.symbol, &all_data.rt_price_30m_vec, 20, 1.5, true).await; - - if lot_step_size_option.is_some() - && quote_commission_precision_option.is_some() - && opclo_30m_option.is_some() - && supertrend_option_30m.is_some() - { + if let (Some(exchange_info), Some(tradefee), Some(supertrend_vec)) = + (exchange_info_map.get(&element.symbol), trade_fee_map.get(&element.symbol), supertrend_1d.get(&element.symbol)) { // update stoploss - supertrend_vec = supertrend_option_30m.unwrap(); let band_value: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(supertrend_vec.last().unwrap().band_value).unwrap(); - if supertrend_vec.last().unwrap().area.contains("UP") + if supertrend_vec.last().unwrap().area == SuperTrendArea::UP && band_value > element.stoploss { let update_table_name = String::from("buy_ordered_coin_list"); let update_value = vec![ @@ -267,52 +200,38 @@ pub async fn list_up_for_sell( .unwrap(); } - let lot_step_size = exchange_info_vec[lot_step_size_option.unwrap()].stepsize; - let quote_commission_precision = exchange_info_vec - [quote_commission_precision_option.unwrap()] - .quote_commission_precision; + let lot_step_size = exchange_info.stepsize; + let quote_commission_precision = exchange_info.quote_commission_precision; let base_qty_to_be_ordered = element.base_qty_ordered.round_dp_with_strategy( lot_step_size.normalize().scale(), RoundingStrategy::ToZero, ); - + if (element.is_long == 0 || element.is_long == 1) && !element.current_price.is_zero() { - if element.current_price >= element.target_price - && element.pure_profit_percent >= 0.1 - { + if element.current_price <= element.stoploss { limit_order_sell( &element, element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; - } else if element.current_price <= element.stoploss { + } else if element.current_price >= element.target_price { limit_order_sell( &element, element.current_price, base_qty_to_be_ordered, &client, - &exchange_info_vec, - &trade_fee_vec, + &exchange_info_map, + &trade_fee_map, ) .await; - } else if server_epoch - element.transact_time > (1_800_000) * 10 && element.pure_profit_percent.is_sign_positive() { - limit_order_sell( - &element, - element.current_price, - base_qty_to_be_ordered, - &client, - &exchange_info_vec, - &trade_fee_vec, - ) - .await; - } + } // TODO: sell_count가 1일 때 적용하기 // else if (supertrend_vec // .last() diff --git a/src/strategy_team/strategy_manager.rs b/src/strategy_team/strategy_manager.rs index 505645d..a6f0add 100644 --- a/src/strategy_team/strategy_manager.rs +++ b/src/strategy_team/strategy_manager.rs @@ -1,15 +1,14 @@ use crate::coex::exchange_team::*; use crate::coex::order_team::*; -use crate::coin_health_check_team::request_others::CoinPriceData; use csv::{DeserializeRecordsIter, StringRecord}; use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; use serde::Deserialize; -use super::strategy_test; +// use super::strategy_test; use super::{ exists_record, insert_one_record, try_join_all, try_select_record, AllData, ExchangeInfo, - FilteredData, FromRow, RealtimePriceData, TradeFee, + FilteredDataValue, FromRow, RealtimePriceData, TradeFee, HashMap }; use crate::signal_association::signal_decision::*; use tokio::time::{sleep, Duration, Instant}; @@ -34,46 +33,31 @@ pub async fn execute_list_up_for_buy( ) -> Result<(), Box> { crate::strategy_team::strategy_001::list_up_for_buy(all_data).await; crate::strategy_team::strategy_002::list_up_for_buy(all_data).await; + crate::strategy_team::strategy_003::list_up_for_buy(all_data).await; crate::strategy_team::strategy_004::list_up_for_buy(all_data).await; - + crate::strategy_team::strategy_005::list_up_for_buy(all_data).await; Ok(()) } pub async fn execute_list_up_for_sell( all_data: &AllData, - exchange_info_vec: &Vec, - trade_fee_vec: &Vec, + exchange_info_map: &HashMap, + trade_fee_map: &HashMap, ) -> Result<(), Box> { - crate::strategy_team::strategy_001::list_up_for_sell(&all_data, &exchange_info_vec, &trade_fee_vec).await; - crate::strategy_team::strategy_002::list_up_for_sell(&all_data, &exchange_info_vec, &trade_fee_vec).await; - crate::strategy_team::strategy_004::list_up_for_sell(&all_data, &exchange_info_vec, &trade_fee_vec).await; + crate::strategy_team::strategy_001::list_up_for_sell(&all_data, &exchange_info_map, &trade_fee_map).await; + crate::strategy_team::strategy_002::list_up_for_sell(&all_data, &exchange_info_map, &trade_fee_map).await; + crate::strategy_team::strategy_003::list_up_for_sell(&all_data, &exchange_info_map, &trade_fee_map).await; + crate::strategy_team::strategy_004::list_up_for_sell(&all_data, &exchange_info_map, &trade_fee_map).await; + crate::strategy_team::strategy_005::list_up_for_sell(&all_data, &exchange_info_map, &trade_fee_map).await; Ok(()) } -// useful functions for strategists -pub async fn get_current_price( - symbol: &String, - rt_price_vec: &Vec<(String, Vec)>, -) -> Option { - let index_result = rt_price_vec.iter().position(|x| *x.0 == *symbol); - match index_result { - Some(T) => { - if rt_price_vec[T].1.last().is_some() { - Some(rt_price_vec[T].1.last().unwrap().close_price) - } else { - None - } - } - None => None, - } -} - pub async fn insert_pre_suggested_coins( registerer: i32, is_long: bool, - filtered_coins: &Vec, + filtered_coins: &HashMap, alldata: &AllData, ) -> Result<(), Box> { // Check the existance of record that is registered by this strategist @@ -150,12 +134,12 @@ pub async fn insert_pre_suggested_coins( .await?; // insert record without duplicate(registerer, closetime, symbol) into [pre_suggested_coin_list] - for filtered_coin in filtered_coins { + for (symbol, filtered_data) in filtered_coins { let mut is_dupe = false; // initialize for list_element in &suggested_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -163,8 +147,8 @@ pub async fn insert_pre_suggested_coins( } for list_element in &ordered_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -172,8 +156,8 @@ pub async fn insert_pre_suggested_coins( } for list_element in &pre_suggested_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -182,13 +166,13 @@ pub async fn insert_pre_suggested_coins( if is_dupe == false { let mut insert_values = vec![ - filtered_coin.symbol.clone(), // symbol - filtered_coin.closetime.to_string(), // close_time - filtered_coin.current_price.to_string(), // suggested_price - filtered_coin.current_price.to_string(), // current_price - filtered_coin.stoploss.to_string(), // stoploss - filtered_coin.target_price.to_string(), // target_price - server_epoch().await.to_string(), // registered_server_epoch + symbol.clone(), // symbol + filtered_data.closetime.to_string(), // close_time + filtered_data.current_price.to_string(), // suggested_price + filtered_data.current_price.to_string(), // current_price + filtered_data.stoploss.to_string(), // stoploss + filtered_data.target_price.to_string(), // target_price + get_server_epoch().await.to_string(), // registered_server_epoch 0.0.to_string(), // profit_percent 0.0.to_string(), // minimum_profit_percent 0.0.to_string(), // maximum_profit_percent @@ -245,12 +229,12 @@ pub async fn insert_pre_suggested_coins( .await?; // insert record without duplicate(registerer, closetime, symbol) into [pre_suggested_coin_list] - for filtered_coin in filtered_coins { + for (symbol, filtered_data) in filtered_coins { let mut is_dupe = false; // initialize for list_element in &suggested_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -258,8 +242,8 @@ pub async fn insert_pre_suggested_coins( } for list_element in &ordered_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -268,13 +252,13 @@ pub async fn insert_pre_suggested_coins( if is_dupe == false { let mut insert_values = vec![ - filtered_coin.symbol.clone(), // symbol - filtered_coin.closetime.to_string(), // close_time - filtered_coin.current_price.to_string(), // suggested_price - filtered_coin.current_price.to_string(), // current_price - filtered_coin.stoploss.to_string(), // stoploss - filtered_coin.target_price.to_string(), // target_price - server_epoch().await.to_string(), // registered_server_epoch + symbol.clone(), // symbol + filtered_data.closetime.to_string(), // close_time + filtered_data.current_price.to_string(), // suggested_price + filtered_data.current_price.to_string(), // current_price + filtered_data.stoploss.to_string(), // stoploss + filtered_data.target_price.to_string(), // target_price + get_server_epoch().await.to_string(), // registered_server_epoch 0.0.to_string(), // profit_percent 0.0.to_string(), // minimum_profit_percent 0.0.to_string(), // maximum_profit_percent @@ -321,12 +305,12 @@ pub async fn insert_pre_suggested_coins( .await?; // insert record without duplicate(registerer, closetime, symbol) into [pre_suggested_coin_list] - for filtered_coin in filtered_coins { + for (symbol, filtered_data) in filtered_coins { let mut is_dupe = false; // initialize for list_element in &suggested_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -334,8 +318,8 @@ pub async fn insert_pre_suggested_coins( } for list_element in &pre_suggested_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -344,13 +328,13 @@ pub async fn insert_pre_suggested_coins( if is_dupe == false { let mut insert_values = vec![ - filtered_coin.symbol.clone(), // symbol - filtered_coin.closetime.to_string(), // close_time - filtered_coin.current_price.to_string(), // suggested_price - filtered_coin.current_price.to_string(), // current_price - filtered_coin.stoploss.to_string(), // stoploss - filtered_coin.target_price.to_string(), // target_price - server_epoch().await.to_string(), // registered_server_epoch + symbol.clone(), // symbol + filtered_data.closetime.to_string(), // close_time + filtered_data.current_price.to_string(), // suggested_price + filtered_data.current_price.to_string(), // current_price + filtered_data.stoploss.to_string(), // stoploss + filtered_data.target_price.to_string(), // target_price + get_server_epoch().await.to_string(), // registered_server_epoch 0.0.to_string(), // profit_percent 0.0.to_string(), // minimum_profit_percent 0.0.to_string(), // maximum_profit_percent @@ -390,12 +374,12 @@ pub async fn insert_pre_suggested_coins( .await?; // insert record without duplicate(registerer, closetime, symbol) into [pre_suggested_coin_list] - for filtered_coin in filtered_coins { + for (symbol, filtered_data) in filtered_coins { let mut is_dupe = false; // initialize for list_element in &suggested_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -404,13 +388,13 @@ pub async fn insert_pre_suggested_coins( if is_dupe == false { let mut insert_values = vec![ - filtered_coin.symbol.clone(), // symbol - filtered_coin.closetime.to_string(), // close_time - filtered_coin.current_price.to_string(), // suggested_price - filtered_coin.current_price.to_string(), // current_price - filtered_coin.stoploss.to_string(), // stoploss - filtered_coin.target_price.to_string(), // target_price - server_epoch().await.to_string(), // registered_server_epoch + symbol.clone(), // symbol + filtered_data.closetime.to_string(), // close_time + filtered_data.current_price.to_string(), // suggested_price + filtered_data.current_price.to_string(), // current_price + filtered_data.stoploss.to_string(), // stoploss + filtered_data.target_price.to_string(), // target_price + get_server_epoch().await.to_string(), // registered_server_epoch 0.0.to_string(), // profit_percent 0.0.to_string(), // minimum_profit_percent 0.0.to_string(), // maximum_profit_percent @@ -467,12 +451,12 @@ pub async fn insert_pre_suggested_coins( .await?; // insert record without duplicate(registerer, close_time, symbol) into [pre_suggested_coin_list] - for filtered_coin in filtered_coins { + for (symbol, filtered_data) in filtered_coins { let mut is_dupe = false; // initialize for list_element in &ordered_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -480,8 +464,8 @@ pub async fn insert_pre_suggested_coins( } for list_element in &pre_suggested_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -490,13 +474,13 @@ pub async fn insert_pre_suggested_coins( if is_dupe == false { let mut insert_values = vec![ - filtered_coin.symbol.clone(), // symbol - filtered_coin.closetime.to_string(), // close_time - filtered_coin.current_price.to_string(), // suggested_price - filtered_coin.current_price.to_string(), // current_price - filtered_coin.stoploss.to_string(), // stoploss - filtered_coin.target_price.to_string(), // target_price - server_epoch().await.to_string(), // registered_server_epoch + symbol.clone(), // symbol + filtered_data.closetime.to_string(), // close_time + filtered_data.current_price.to_string(), // suggested_price + filtered_data.current_price.to_string(), // current_price + filtered_data.stoploss.to_string(), // stoploss + filtered_data.target_price.to_string(), // target_price + get_server_epoch().await.to_string(), // registered_server_epoch 0.0.to_string(), // profit_percent 0.0.to_string(), // minimum_profit_percent 0.0.to_string(), // maximum_profit_percent @@ -546,12 +530,12 @@ pub async fn insert_pre_suggested_coins( .await?; // insert record without duplicate(registerer, closetime, symbol) into [pre_suggested_coin_list] - for filtered_coin in filtered_coins { + for (symbol, filtered_data) in filtered_coins { let mut is_dupe = false; // initialize for list_element in &ordered_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -560,13 +544,13 @@ pub async fn insert_pre_suggested_coins( if is_dupe == false { let mut insert_values = vec![ - filtered_coin.symbol.clone(), // symbol - filtered_coin.closetime.to_string(), // close_time - filtered_coin.current_price.to_string(), // suggested_price - filtered_coin.current_price.to_string(), // current_price - filtered_coin.stoploss.to_string(), // stoploss - filtered_coin.target_price.to_string(), // target_price - server_epoch().await.to_string(), // registered_server_epoch + symbol.clone(), // symbol + filtered_data.closetime.to_string(), // close_time + filtered_data.current_price.to_string(), // suggested_price + filtered_data.current_price.to_string(), // current_price + filtered_data.stoploss.to_string(), // stoploss + filtered_data.target_price.to_string(), // target_price + get_server_epoch().await.to_string(), // registered_server_epoch 0.0.to_string(), // profit_percent 0.0.to_string(), // minimum_profit_percent 0.0.to_string(), // maximum_profit_percent @@ -606,12 +590,12 @@ pub async fn insert_pre_suggested_coins( .await?; // insert record without duplicate(registerer, closetime, symbol) into [pre_suggested_coin_list] - for filtered_coin in filtered_coins { + for (symbol, filtered_data) in filtered_coins { let mut is_dupe = false; // initialize for list_element in &pre_suggested_coin_list { - if (filtered_coin.symbol == list_element.symbol) - && (filtered_coin.closetime == list_element.close_time) + if (*symbol == list_element.symbol) + && (filtered_data.closetime == list_element.close_time) { is_dupe = true; break; @@ -620,13 +604,13 @@ pub async fn insert_pre_suggested_coins( if is_dupe == false { let mut insert_values = vec![ - filtered_coin.symbol.clone(), // symbol - filtered_coin.closetime.to_string(), // close_time - filtered_coin.current_price.to_string(), // suggested_price - filtered_coin.current_price.to_string(), // current_price - filtered_coin.stoploss.to_string(), // stoploss - filtered_coin.target_price.to_string(), // target_price - server_epoch().await.to_string(), // registered_server_epoch + symbol.clone(), // symbol + filtered_data.closetime.to_string(), // close_time + filtered_data.current_price.to_string(), // suggested_price + filtered_data.current_price.to_string(), // current_price + filtered_data.stoploss.to_string(), // stoploss + filtered_data.target_price.to_string(), // target_price + get_server_epoch().await.to_string(), // registered_server_epoch 0.0.to_string(), // profit_percent 0.0.to_string(), // minimum_profit_percent 0.0.to_string(), // maximum_profit_percent @@ -643,15 +627,15 @@ pub async fn insert_pre_suggested_coins( } } } else { - for filtered_coin in filtered_coins { + for (symbol, filtered_data) in filtered_coins { let mut insert_values = vec![ - filtered_coin.symbol.clone(), // symbol - filtered_coin.closetime.to_string(), // close_time - filtered_coin.current_price.to_string(), // suggested_price - filtered_coin.current_price.to_string(), // current_price - filtered_coin.stoploss.to_string(), // stoploss - filtered_coin.target_price.to_string(), // target_price - server_epoch().await.to_string(), // registered_server_epoch + symbol.clone(), // symbol + filtered_data.closetime.to_string(), // close_time + filtered_data.current_price.to_string(), // suggested_price + filtered_data.current_price.to_string(), // current_price + filtered_data.stoploss.to_string(), // stoploss + filtered_data.target_price.to_string(), // target_price + get_server_epoch().await.to_string(), // registered_server_epoch 0.0.to_string(), // profit_percent 0.0.to_string(), // minimum_profit_percent 0.0.to_string(), // maximum_profit_percent diff --git a/src/value_estimation_team/datapoints/price_data.rs b/src/value_estimation_team/datapoints/price_data.rs index 7421a8d..a2ac3dc 100644 --- a/src/value_estimation_team/datapoints/price_data.rs +++ b/src/value_estimation_team/datapoints/price_data.rs @@ -2,7 +2,6 @@ #![allow(warnings)] use crate::coin_health_check_team::request_candles::CandleData; -use crate::coin_health_check_team::request_others::CoinPriceData; use crate::database_control::*; use csv::{DeserializeRecordsIter, StringRecord}; use rust_decimal::{prelude::ToPrimitive, Decimal}; @@ -10,6 +9,10 @@ use serde::Deserialize; use sqlx::FromRow; use std::sync::Arc; use tokio::{fs::*, sync::Mutex, time::*}; +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone, PartialEq)] +pub enum CandleType { UP, DOWN } #[derive(Debug, Clone, PartialEq)] pub struct RealtimePriceData { @@ -20,7 +23,7 @@ pub struct RealtimePriceData { pub low_price: f64, pub close_time: i64, pub quote_asset_volume: f64, - pub candle_type: String, + pub candle_type: CandleType, } impl RealtimePriceData { @@ -33,7 +36,7 @@ impl RealtimePriceData { low_price: 0.0, close_time: 0, quote_asset_volume: 0.0, - candle_type: String::new(), + candle_type: CandleType::DOWN, }; data } @@ -41,194 +44,170 @@ impl RealtimePriceData { pub async fn update_realtime_price_data( interval: &String, - read_candle_for_opclo: &Vec<(String, Vec)>, - read_candle_for_rt: &Vec<(String, Vec)>, - write_rt_data: &mut Vec<(String, Vec)>, - read_price: &Vec, - read_symbol: &Vec, + read_candle: &HashMap>, + read_candle_for_rt: &HashMap>, + write_rt_data: &mut HashMap>, + read_price: &HashMap, + read_symbol: &HashSet, ) -> Result<(), Box> { let instant = Instant::now(); let mut rt_price_vec: Vec = Vec::new(); - let mut rt_data_vec: Vec<(String, Vec)> = Vec::new(); + let mut rt_data_map: HashMap> = HashMap::new(); for element in read_symbol { - let candle_search_result = read_candle_for_opclo.iter().position(|x| x.0 == *element); - - match candle_search_result { - Some(T) => { - for element in &read_candle_for_opclo[T].1 { - let mut realtime_price_data_builder = RealtimePriceData::new(); - realtime_price_data_builder.opclo_price = - (element.open_price + element.close_price) / 2.0; - realtime_price_data_builder.open_price = element.open_price; - realtime_price_data_builder.close_price = element.close_price; - realtime_price_data_builder.high_price = element.high_price; - realtime_price_data_builder.low_price = element.low_price; - realtime_price_data_builder.close_time = element.close_time; - realtime_price_data_builder.quote_asset_volume = element.quote_asset_volume; - if element.open_price < element.close_price { - realtime_price_data_builder.candle_type = String::from("UP"); - } else { - realtime_price_data_builder.candle_type = String::from("DOWN"); - } - rt_price_vec.push(realtime_price_data_builder); - } - // reflect realtime data to the last element in rt_price_vec - if interval.contains("1m") { - let price_search_result = read_price.iter().position(|x| x.symbol == *element); - if price_search_result.is_some() { - // update close_price - rt_price_vec.last_mut().unwrap().close_price = - read_price[price_search_result.unwrap()].current_price; - // update opclo_price - rt_price_vec.last_mut().unwrap().opclo_price = - (rt_price_vec.last_mut().unwrap().open_price - + rt_price_vec.last_mut().unwrap().close_price) - / 2.0; - // update candle_type - if rt_price_vec.last_mut().unwrap().close_price - >= rt_price_vec.last_mut().unwrap().open_price - { - rt_price_vec.last_mut().unwrap().candle_type = String::from("UP"); - } else { - rt_price_vec.last_mut().unwrap().candle_type = String::from("DOWN"); - } - // update high_price - if rt_price_vec.last_mut().unwrap().high_price - < read_price[price_search_result.unwrap()].current_price - { - rt_price_vec.last_mut().unwrap().high_price = - read_price[price_search_result.unwrap()].current_price; - } - // update low_price - if rt_price_vec.last_mut().unwrap().low_price - > read_price[price_search_result.unwrap()].current_price - { - rt_price_vec.last_mut().unwrap().low_price = - read_price[price_search_result.unwrap()].current_price; - } - } + if let Some(candle_data) = read_candle.get(element) { + for element in candle_data { + let mut realtime_price_data_builder = RealtimePriceData::new(); + realtime_price_data_builder.opclo_price = + (element.open_price + element.close_price) / 2.0; + realtime_price_data_builder.open_price = element.open_price; + realtime_price_data_builder.close_price = element.close_price; + realtime_price_data_builder.high_price = element.high_price; + realtime_price_data_builder.low_price = element.low_price; + realtime_price_data_builder.close_time = element.close_time; + realtime_price_data_builder.quote_asset_volume = element.quote_asset_volume; + if element.open_price < element.close_price { + realtime_price_data_builder.candle_type = CandleType::UP; } else { - // for 30m, uses 1m candle - // for 1d, uses 30m candle - // for 1w, uses 1d candle - // for 1mon, uses 1w candle + realtime_price_data_builder.candle_type = CandleType::DOWN; + } + rt_price_vec.push(realtime_price_data_builder); + } + // reflect realtime data to the last element in rt_price_vec + if interval.contains("1m") { + if let Some(current_price) = read_price.get(element) { + // update close_price + rt_price_vec.last_mut().unwrap().close_price = *current_price; + // update opclo_price + rt_price_vec.last_mut().unwrap().opclo_price = + (rt_price_vec.last_mut().unwrap().open_price + + rt_price_vec.last_mut().unwrap().close_price) + / 2.0; + // update candle_type + if rt_price_vec.last_mut().unwrap().close_price + >= rt_price_vec.last_mut().unwrap().open_price + { + rt_price_vec.last_mut().unwrap().candle_type = CandleType::UP; + } else { + rt_price_vec.last_mut().unwrap().candle_type = CandleType::DOWN; + } + // update high_price + if rt_price_vec.last_mut().unwrap().high_price + < *current_price + { + rt_price_vec.last_mut().unwrap().high_price = *current_price; + } + // update low_price + if rt_price_vec.last_mut().unwrap().low_price + > *current_price + { + rt_price_vec.last_mut().unwrap().low_price = *current_price; + } + } + } else { + // for 30m, uses 1m candle + // for 1d, uses 30m candle + // for 1w, uses 1d candle + // for 1mon, uses 1w candle + if let Some(rt_vec) = read_candle_for_rt.get(element) { + if rt_vec.len() >= 2 { + let previous_close_time = rt_price_vec[rt_price_vec.len()-2].close_time; + // update realtime information for the latest candle + let mut update_closeprice = 0.0; + let mut update_highprice = 0.0; + let mut update_lowprice = 0.0; + let mut update_quote_asset_volume = 0.0; - // search symbol - let candle_search_result = - read_candle_for_rt.iter().position(|x| x.0 == *element); - match candle_search_result { - Some(T) => { - if read_candle_for_rt[T].1.len() >= 2 { - let mut candle_vec_clone = read_candle_for_rt[T].1.clone(); - let mut rt_price_vec_clone = rt_price_vec.clone(); - rt_price_vec_clone.reverse(); - rt_price_vec_clone.truncate(2); - rt_price_vec_clone.reverse(); - - // update realtime information for the latest candle - let mut update_closeprice = 0.0; - let mut update_highprice = 0.0; - let mut update_lowprice = 0.0; - let mut update_quote_asset_volume = 0.0; - - // search close time - let prev_closetime_result = candle_vec_clone.binary_search_by_key( - &rt_price_vec_clone.first().unwrap().close_time, - |RealtimePriceData { - opclo_price, - open_price, - close_price, - high_price, - low_price, - close_time, - quote_asset_volume, - candle_type, - }| *close_time, - ); - if prev_closetime_result.is_ok() { - let result = - candle_vec_clone.get(prev_closetime_result.unwrap() + 1..); - if result.is_some() { - let update_highprice_result = - result.unwrap().iter().max_by(|x, y| { - x.high_price.partial_cmp(&y.high_price).unwrap() - }); - if update_highprice_result.is_some() { - update_highprice = - update_highprice_result.unwrap().high_price; - } - - let update_lowprice_result = - result.unwrap().iter().min_by(|x, y| { - x.low_price.partial_cmp(&y.low_price).unwrap() - }); - if update_lowprice_result.is_some() { - update_lowprice = - update_lowprice_result.unwrap().low_price; - } - - for element in result.unwrap() { - update_quote_asset_volume += element.quote_asset_volume; - } - } + // search close time + let prev_closetime_result = rt_vec.binary_search_by_key( + &previous_close_time, + |RealtimePriceData { + opclo_price, + open_price, + close_price, + high_price, + low_price, + close_time, + quote_asset_volume, + candle_type, + }| *close_time, + ); + if prev_closetime_result.is_ok() { + let result = + rt_vec.get(prev_closetime_result.unwrap() + 1..); + if result.is_some() { + let update_highprice_result = + result.unwrap().iter().max_by(|x, y| { + x.high_price.partial_cmp(&y.high_price).unwrap() + }); + if update_highprice_result.is_some() { + update_highprice = + update_highprice_result.unwrap().high_price; } - let price_search_result = - read_price.iter().position(|x| x.symbol == *element); - if price_search_result.is_some() { - update_closeprice = - read_price[price_search_result.unwrap()].current_price; + let update_lowprice_result = + result.unwrap().iter().min_by(|x, y| { + x.low_price.partial_cmp(&y.low_price).unwrap() + }); + if update_lowprice_result.is_some() { + update_lowprice = + update_lowprice_result.unwrap().low_price; } - // update the latest candle with values - if update_highprice != 0.0 - && !update_highprice.is_nan() - && update_highprice.is_finite() - { - rt_price_vec.last_mut().unwrap().high_price = update_highprice; - } - if update_lowprice != 0.0 - && !update_lowprice.is_nan() - && update_lowprice.is_finite() - { - rt_price_vec.last_mut().unwrap().low_price = update_lowprice; - } - if update_quote_asset_volume != 0.0 - && !update_quote_asset_volume.is_nan() - && update_quote_asset_volume.is_finite() - { - rt_price_vec.last_mut().unwrap().quote_asset_volume = - update_quote_asset_volume; - } - if update_closeprice != 0.0 - && !update_closeprice.is_nan() - && update_closeprice.is_finite() - { - rt_price_vec.last_mut().unwrap().close_price = - update_closeprice; - rt_price_vec.last_mut().unwrap().opclo_price = - (update_closeprice - + rt_price_vec.last_mut().unwrap().open_price) - / 2.0; + for element in result.unwrap() { + update_quote_asset_volume += element.quote_asset_volume; } } } - None => {} + + if let Some(current_price) = read_price.get(element) { + update_closeprice = *current_price; + } + + // update the latest candle with values + if update_highprice != 0.0 + && !update_highprice.is_nan() + && update_highprice.is_finite() + { + rt_price_vec.last_mut().unwrap().high_price = update_highprice; + } + if update_lowprice != 0.0 + && !update_lowprice.is_nan() + && update_lowprice.is_finite() + { + rt_price_vec.last_mut().unwrap().low_price = update_lowprice; + } + if update_quote_asset_volume != 0.0 + && !update_quote_asset_volume.is_nan() + && update_quote_asset_volume.is_finite() + { + rt_price_vec.last_mut().unwrap().quote_asset_volume = + update_quote_asset_volume; + } + if update_closeprice != 0.0 + && !update_closeprice.is_nan() + && update_closeprice.is_finite() + { + rt_price_vec.last_mut().unwrap().close_price = + update_closeprice; + rt_price_vec.last_mut().unwrap().opclo_price = + (update_closeprice + + rt_price_vec.last_mut().unwrap().open_price) + / 2.0; + } } } - - rt_data_vec.push((element.clone(), rt_price_vec.clone())); - rt_price_vec.clear(); } - None => {} + + if let Some(price_vec) = write_rt_data.get_mut(element) { + *price_vec = rt_price_vec.clone(); + } else { + write_rt_data.insert(element.clone(), rt_price_vec.clone()); + } + rt_price_vec.clear(); } } - - *write_rt_data = rt_data_vec; - - // println!(" datapoints/price_{} 완료 elapsed:{:.2}s", interval, instant.elapsed().as_secs_f32()); Ok(()) } diff --git a/src/value_estimation_team/indicators/adx.rs b/src/value_estimation_team/indicators/adx.rs index 9ecdce4..ddb0944 100644 --- a/src/value_estimation_team/indicators/adx.rs +++ b/src/value_estimation_team/indicators/adx.rs @@ -1,4 +1,4 @@ -use super::{FilteredData, RealtimePriceData, try_join_all, Arc, Mutex}; +use super::{FilteredDataValue, RealtimePriceData, try_join_all, Arc, Mutex, HashMap}; #[derive(Clone, Debug)] pub struct AdxData { @@ -19,109 +19,109 @@ struct DiData { close_time: i64, } -pub async fn adx(adx_len: usize, di_len: usize, input_rt_data: &Vec<(String, Vec)>, -filtered_symbols: &Vec,) -> Result)>, Box> { +pub async fn adx(adx_len: usize, di_len: usize, input_rt_data: &HashMap>, +filtered_symbols: &HashMap,) -> Result>, Box> { if filtered_symbols.is_empty() { Err(("Err"))?; } - let mut adx_vec: Vec<(String, Vec)> = Vec::new(); + let mut adx_vec: HashMap> = HashMap::new(); let mut adx_vec_arc = Arc::new(Mutex::new(adx_vec)); let mut task_vec = Vec::new(); - for element in filtered_symbols { - let mut rt_data_vec = input_rt_data.clone(); + for (filtered_symbol, filtered_data) in filtered_symbols { + let mut rt_data_map = input_rt_data.clone(); let adx_vec_arc_c = Arc::clone(&adx_vec_arc); - let symbol = element.symbol.clone(); - task_vec.push(tokio::spawn(async move { - let idx_result = rt_data_vec.iter().position(|a| a.0 == symbol); - - if idx_result.is_some() { - // step 1: calculate +DM, -DM, TR - let windows = rt_data_vec[idx_result.unwrap()].1.windows(2); - let mut basic_data_vec: Vec = Vec::new(); - for window in windows { - let prev_rt_data = window.first().unwrap(); - let current_rt_data = window.last().unwrap(); - let mut up = current_rt_data.high_price - prev_rt_data.high_price; - let mut down = prev_rt_data.low_price - current_rt_data.low_price; - let basic_data = BasicData { - dm_plus: if up > down && up > 0.0 { up } else { 0.0 }, - dm_minus: if down > up && down > 0.0 { down } else { 0.0 }, - true_range: f64::max(f64::max(current_rt_data.high_price - current_rt_data.low_price, - current_rt_data.high_price - prev_rt_data.close_price), - current_rt_data.low_price - prev_rt_data.close_price), - close_time: current_rt_data.close_time, - }; - basic_data_vec.push(basic_data); - } - // step 2: smoothing +DM, -DM, TR - let alpha: f64 = 1.0/(di_len as f64); - let mut smoothed_basic_data_vec: Vec = Vec::new(); - - let partial_vec1 = basic_data_vec.get(..di_len).unwrap(); // for calculation of initial value - let partial_vec2 = basic_data_vec.get(di_len..).unwrap(); // for calculation of the rest + let symbol = filtered_symbol.clone(); + task_vec.push(tokio::spawn(async move { + if let Some(rt_price_data) = rt_data_map.get(&symbol) { + if rt_price_data.len()-1 > adx_len && rt_price_data.len() > di_len { + // step 1: calculate +DM, -DM, TR + let windows = rt_price_data.windows(2); + let mut basic_data_vec: Vec = Vec::new(); + for window in windows { + let prev_rt_data = window.first().unwrap(); + let current_rt_data = window.last().unwrap(); + let mut up = current_rt_data.high_price - prev_rt_data.high_price; + let mut down = prev_rt_data.low_price - current_rt_data.low_price; + let basic_data = BasicData { + dm_plus: if up > down && up > 0.0 { up } else { 0.0 }, + dm_minus: if down > up && down > 0.0 { down } else { 0.0 }, + true_range: f64::max(f64::max(current_rt_data.high_price - current_rt_data.low_price, + current_rt_data.high_price - prev_rt_data.close_price), + current_rt_data.low_price - prev_rt_data.close_price), + close_time: current_rt_data.close_time, + }; + basic_data_vec.push(basic_data); + } + // step 2: smoothing +DM, -DM, TR + let alpha: f64 = 1.0/(di_len as f64); + let mut smoothed_basic_data_vec: Vec = Vec::new(); + + let partial_vec1 = basic_data_vec.get(..di_len).unwrap(); // for calculation of initial value + let partial_vec2 = basic_data_vec.get(di_len..).unwrap(); // for calculation of the rest - let mut dm_plus_calculated = 0.0; - let mut dm_minus_calculated = 0.0; - let mut tr_calculated = 0.0; - for element in partial_vec1 { - dm_plus_calculated += element.dm_plus; - dm_minus_calculated += element.dm_minus; - tr_calculated += element.true_range; - } - dm_plus_calculated /= di_len as f64; - dm_minus_calculated /= di_len as f64; - tr_calculated /= di_len as f64; - - let basic_data = BasicData { dm_plus: dm_plus_calculated, dm_minus: dm_minus_calculated, true_range: tr_calculated, close_time: partial_vec1.last().unwrap().close_time }; - smoothed_basic_data_vec.push(basic_data); - - for element in partial_vec2 { - dm_plus_calculated = alpha * element.dm_plus + (1.0 - alpha) * dm_plus_calculated; - dm_minus_calculated = alpha * element.dm_minus + (1.0 - alpha) * dm_minus_calculated; - tr_calculated = alpha * element.true_range + (1.0 - alpha) * tr_calculated; - let basic_data = BasicData { dm_plus: dm_plus_calculated, dm_minus: dm_minus_calculated, true_range: tr_calculated, close_time: element.close_time }; + let mut dm_plus_calculated = 0.0; + let mut dm_minus_calculated = 0.0; + let mut tr_calculated = 0.0; + for element in partial_vec1 { + dm_plus_calculated += element.dm_plus; + dm_minus_calculated += element.dm_minus; + tr_calculated += element.true_range; + } + dm_plus_calculated /= di_len as f64; + dm_minus_calculated /= di_len as f64; + tr_calculated /= di_len as f64; + + let basic_data = BasicData { dm_plus: dm_plus_calculated, dm_minus: dm_minus_calculated, true_range: tr_calculated, close_time: partial_vec1.last().unwrap().close_time }; smoothed_basic_data_vec.push(basic_data); - } - // step 3: calculate DI - let mut di_data_vec: Vec = Vec::new(); - for basic_data in smoothed_basic_data_vec { - let di_data = DiData { di_plus: (100.0 * basic_data.dm_plus) / basic_data.true_range, di_minus: (100.0 * basic_data.dm_minus) / basic_data.true_range, close_time: basic_data.close_time}; - di_data_vec.push(di_data); - } + for element in partial_vec2 { + dm_plus_calculated = alpha * element.dm_plus + (1.0 - alpha) * dm_plus_calculated; + dm_minus_calculated = alpha * element.dm_minus + (1.0 - alpha) * dm_minus_calculated; + tr_calculated = alpha * element.true_range + (1.0 - alpha) * tr_calculated; + let basic_data = BasicData { dm_plus: dm_plus_calculated, dm_minus: dm_minus_calculated, true_range: tr_calculated, close_time: element.close_time }; + smoothed_basic_data_vec.push(basic_data); + } - // step 4: calculate ADX - let mut initial_adx_vec: Vec = Vec::new(); - for di_data in di_data_vec { - let sum = di_data.di_plus + di_data.di_minus; - let difference = (di_data.di_plus - di_data.di_minus).abs(); - let divisor = if sum <= 0.00000001 { 1.0 } else { sum }; - let adx_data = AdxData { adx: difference.abs()/divisor, close_time: di_data.close_time }; - initial_adx_vec.push(adx_data); - } - let partial_vec1 = initial_adx_vec.get(..adx_len).unwrap(); // for calculation of initial value - let partial_vec2 = initial_adx_vec.get(adx_len..).unwrap(); // for calculation of the rest + // step 3: calculate DI + let mut di_data_vec: Vec = Vec::new(); + for basic_data in smoothed_basic_data_vec { + let di_data = DiData { di_plus: (100.0 * basic_data.dm_plus) / basic_data.true_range, di_minus: (100.0 * basic_data.dm_minus) / basic_data.true_range, close_time: basic_data.close_time}; + di_data_vec.push(di_data); + } - let mut smoothed_adx_vec: Vec = Vec::new(); - let mut adx_calculated = 0.0; - for element in partial_vec1 { - adx_calculated += element.adx; - } - adx_calculated /= adx_len as f64; - - let adx_data = AdxData { adx: adx_calculated, close_time: partial_vec1.last().unwrap().close_time }; - smoothed_adx_vec.push(adx_data); - - let alpha: f64 = 1.0 /(adx_len as f64); - for element in partial_vec2 { - adx_calculated = alpha * element.adx + (1.0 - alpha) * adx_calculated; - let adx_data = AdxData { adx: 100.0 * adx_calculated, close_time: element.close_time }; + // step 4: calculate ADX + let mut initial_adx_vec: Vec = Vec::new(); + for di_data in di_data_vec { + let sum = di_data.di_plus + di_data.di_minus; + let difference = (di_data.di_plus - di_data.di_minus).abs(); + let divisor = if sum <= 0.00000001 { 1.0 } else { sum }; + let adx_data = AdxData { adx: difference.abs()/divisor, close_time: di_data.close_time }; + initial_adx_vec.push(adx_data); + } + let partial_vec1 = initial_adx_vec.get(..adx_len).unwrap(); // for calculation of initial value + let partial_vec2 = initial_adx_vec.get(adx_len..).unwrap(); // for calculation of the rest + + let mut smoothed_adx_vec: Vec = Vec::new(); + let mut adx_calculated = 0.0; + for element in partial_vec1 { + adx_calculated += element.adx; + } + adx_calculated /= adx_len as f64; + + let adx_data = AdxData { adx: adx_calculated, close_time: partial_vec1.last().unwrap().close_time }; smoothed_adx_vec.push(adx_data); - } + + let alpha: f64 = 1.0 /(adx_len as f64); + for element in partial_vec2 { + adx_calculated = alpha * element.adx + (1.0 - alpha) * adx_calculated; + let adx_data = AdxData { adx: 100.0 * adx_calculated, close_time: element.close_time }; + smoothed_adx_vec.push(adx_data); + } - let mut adx_vec_arc_lock = adx_vec_arc_c.lock().await; - adx_vec_arc_lock.push((symbol.clone(), smoothed_adx_vec.clone())); + let mut adx_vec_arc_lock = adx_vec_arc_c.lock().await; + adx_vec_arc_lock.insert(symbol.clone(), smoothed_adx_vec.clone()); + } } })); } diff --git a/src/value_estimation_team/indicators/bollingerband.rs b/src/value_estimation_team/indicators/bollingerband.rs index 9cbc19d..4a10356 100644 --- a/src/value_estimation_team/indicators/bollingerband.rs +++ b/src/value_estimation_team/indicators/bollingerband.rs @@ -4,12 +4,13 @@ use crate::database_control::*; use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; use crate::value_estimation_team::indicators::sma::{SmaData, sma}; -use crate::strategy_team::FilteredData; +use crate::strategy_team::FilteredDataValue; use futures::future::try_join_all; use serde::Deserialize; use sqlx::FromRow; use std::sync::Arc; use tokio::{fs::*, io::AsyncWriteExt, sync::Mutex, time::*}; +use super::{HashMap}; #[derive(Clone, Debug)] pub struct BollingerBandData { @@ -35,35 +36,31 @@ impl BollingerBandData { pub async fn bollingerband( period: usize, sd_factor: f64, - input_rt_data: &Vec<(String, Vec)>, - filtered_symbols: &Vec, -) -> Result)>, Box> { + input_rt_data: &HashMap>, + filtered_symbols: &HashMap, +) -> Result>, Box> { if filtered_symbols.is_empty() { Err(("Err"))?; } - let mut sma_data_vec: Vec<(String, Vec)> = sma(period, input_rt_data, filtered_symbols).await?; - - let mut bb_data_wrapper: Vec<(String, Vec)> = Vec::new(); + let mut sma_data_map: HashMap> = sma(period, input_rt_data, filtered_symbols).await?; + let mut bb_data_wrapper: HashMap> = HashMap::new(); let mut bb_data_wrapper_arc = Arc::new(Mutex::new(bb_data_wrapper)); let mut task_vec = Vec::new(); - for filtered_symbol in filtered_symbols { - let filtered_symbol_c = filtered_symbol.clone(); - let rt_data_vec_c: Vec<(String, Vec)> = input_rt_data.clone(); - let sma_data_vec_c = sma_data_vec.clone(); - let bb_data_wrapper_arc_c = Arc::clone(&bb_data_wrapper_arc); - task_vec.push(tokio::spawn(async move { - let search_result1 = rt_data_vec_c.clone().iter().position(|x| x.0 == *filtered_symbol_c.symbol); - let search_result2 = sma_data_vec_c.iter().position(|x| x.0 == *filtered_symbol_c.symbol); - - if search_result1.is_some() && search_result2.is_some() { + for (symbol, filtered_data) in filtered_symbols { + if input_rt_data.contains_key(symbol) && sma_data_map.contains_key(symbol) { + let symbol_c = symbol.clone(); + let rt_data_vec_c = input_rt_data.get(symbol).unwrap().clone(); + let sma_data_vec_c = sma_data_map.get(symbol).unwrap().clone(); + let bb_data_wrapper_arc_c = Arc::clone(&bb_data_wrapper_arc); + task_vec.push(tokio::spawn(async move { let mut bb_data = BollingerBandData::new(); let mut bb_data_vec: Vec = Vec::new(); // if the data has shorter than buffer - if sma_data_vec_c[search_result2.unwrap()].1.len() > period { - let result = rt_data_vec_c[search_result1.unwrap()].1.binary_search_by_key( - &sma_data_vec_c[search_result2.unwrap()].1.first().unwrap().close_time, + if sma_data_vec_c.len() > period { + let result = rt_data_vec_c.binary_search_by_key( + &sma_data_vec_c.first().unwrap().close_time, |RealtimePriceData { opclo_price, open_price, @@ -75,17 +72,17 @@ pub async fn bollingerband( candle_type, }| *close_time, ); - + match result { Ok(T) => { if T <= period - 1 { let mut read_data_iter = - sma_data_vec_c[search_result2.unwrap()].1.iter(); + sma_data_vec_c.iter(); for _ in T..period - 1 { read_data_iter.next(); } let window_iter = - rt_data_vec_c[search_result1.unwrap()].1.windows(period); + rt_data_vec_c.windows(period); for buffer in window_iter { let mut sd_mean = 0.0; let mut standard_deviation = 0.0; @@ -99,7 +96,7 @@ pub async fn bollingerband( } standard_deviation = sd_factor * ((standard_deviation / period as f64).sqrt()); - + match read_data_iter.next() { Some(T) => { bb_data.sma = T.sma_value; @@ -119,10 +116,10 @@ pub async fn bollingerband( } let mut bb_data_wrapper_lock = bb_data_wrapper_arc_c.lock().await; bb_data_wrapper_lock - .push((filtered_symbol_c.symbol.clone(), bb_data_vec.clone())); + .insert(symbol_c, bb_data_vec.clone()); } - } - })); + })); + } } try_join_all(task_vec).await?; let a = bb_data_wrapper_arc.lock().await.to_owned(); diff --git a/src/value_estimation_team/indicators/ema.rs b/src/value_estimation_team/indicators/ema.rs index cd5e2e2..b4bfeec 100644 --- a/src/value_estimation_team/indicators/ema.rs +++ b/src/value_estimation_team/indicators/ema.rs @@ -7,7 +7,8 @@ use serde::Deserialize; use sqlx::FromRow; use std::sync::Arc; use tokio::{fs::*, io::AsyncWriteExt, sync::Mutex, time::*}; -use super::FilteredData; +use super::FilteredDataValue; +use std::collections::HashMap; #[derive(Clone, Debug)] pub struct EmaData { @@ -27,9 +28,9 @@ impl EmaData { // Binance EMA (closeprice) pub async fn ema( moving_number: usize, - input_rt_data: &Vec<(String, Vec)>, - filtered_symbols: &Vec, -) -> Result)>, Box> { + input_rt_data: &HashMap>, + filtered_symbols: &HashMap, +) -> Result>, Box> { if filtered_symbols.is_empty() { Err("Err")?; } @@ -38,55 +39,50 @@ pub async fn ema( let mut ema_t: f64 = 0.0; let mut ema_prev: f64 = 0.0; - let mut ema_data_wrapper: Vec<(String, Vec)> = Vec::new(); + let mut ema_data_wrapper: HashMap> = HashMap::new(); let mut ema_data_wrapper_arc = Arc::new(Mutex::new(ema_data_wrapper)); let mut task_vec = Vec::new(); - for filtered_elem in filtered_symbols { - let symbol_search_result = input_rt_data.iter().position(|x| x.0 == *filtered_elem.symbol); + for (symbol, filtered_data) in filtered_symbols { + if let Some(rt_data_vec) = input_rt_data.get(symbol) { + let ema_data_wrapper_arc_c = Arc::clone(&ema_data_wrapper_arc); + let mut ema_data = EmaData::new(); + let mut ema_data_vec: Vec = Vec::new(); + let symbol_c = symbol.clone(); + let rt_data_vec_c = rt_data_vec.clone(); + task_vec.push(tokio::spawn(async move { + if rt_data_vec_c.len() < moving_number { + ema_data.ema_value = 0.0; + ema_data.close_time = 0; + ema_data_vec.push(ema_data.clone()); + } else { + let partial_vec1 = rt_data_vec_c.get(..moving_number).unwrap(); + let partial_vec2 = rt_data_vec_c.get(moving_number..).unwrap(); - match symbol_search_result { - Some(T) => { - let ema_data_wrapper_arc_c = Arc::clone(&ema_data_wrapper_arc); - let filtered_elem_c = filtered_elem.clone(); - let mut ema_data = EmaData::new(); - let mut ema_data_vec: Vec = Vec::new(); - let input_rt_data_c = input_rt_data.clone(); - task_vec.push(tokio::spawn(async move { - if input_rt_data_c[T].1.len() < moving_number { - ema_data.ema_value = 0.0; - ema_data.close_time = 0; - ema_data_vec.push(ema_data.clone()); - } else { - let partial_vec1 = input_rt_data_c[T].1.get(..moving_number).unwrap(); - let partial_vec2 = input_rt_data_c[T].1.get(moving_number..).unwrap(); - - let mut sma_for_initial_value = 0.0; - for element in partial_vec1 { - sma_for_initial_value += element.close_price; - } - sma_for_initial_value /= moving_number as f64; - - ema_data.ema_value = sma_for_initial_value; - ema_data.close_time = partial_vec1.last().unwrap().close_time; - ema_data_vec.push(ema_data.clone()); - - ema_prev = sma_for_initial_value; - - for element in partial_vec2 { - ema_t = (1.0 - alpha) * ema_prev + alpha * element.close_price; - - ema_data.ema_value = ema_t; - ema_data.close_time = element.close_time; - ema_data_vec.push(ema_data.clone()); - - ema_prev = ema_t; - } + let mut sma_for_initial_value = 0.0; + for element in partial_vec1 { + sma_for_initial_value += element.close_price; } - let mut ema_data_wrapper_lock = ema_data_wrapper_arc_c.lock().await; - ema_data_wrapper_lock.push((filtered_elem_c.symbol.clone(), ema_data_vec.clone())); - })); - } - None => {} + sma_for_initial_value /= moving_number as f64; + + ema_data.ema_value = sma_for_initial_value; + ema_data.close_time = partial_vec1.last().unwrap().close_time; + ema_data_vec.push(ema_data.clone()); + + ema_prev = sma_for_initial_value; + + for element in partial_vec2 { + ema_t = (1.0 - alpha) * ema_prev + alpha * element.close_price; + + ema_data.ema_value = ema_t; + ema_data.close_time = element.close_time; + ema_data_vec.push(ema_data.clone()); + + ema_prev = ema_t; + } + } + let mut ema_data_wrapper_lock = ema_data_wrapper_arc_c.lock().await; + ema_data_wrapper_lock.insert(symbol_c, ema_data_vec.clone()); + })); } } try_join_all(task_vec).await?; @@ -97,9 +93,9 @@ pub async fn ema( // Binance EMA ((open+close)/2) pub async fn ema_opclo( moving_number: usize, - input_rt_data: &Vec<(String, Vec)>, - filtered_symbols: &Vec, -) -> Result)>, Box> { + input_rt_data: &HashMap>, + filtered_symbols: &HashMap, +) -> Result>, Box> { if filtered_symbols.is_empty() { Err("Err")?; } @@ -108,58 +104,53 @@ pub async fn ema_opclo( let mut ema_t: f64 = 0.0; let mut ema_prev: f64 = 0.0; - let mut ema_data_wrapper: Vec<(String, Vec)> = Vec::new(); + let mut ema_data_wrapper: HashMap> = HashMap::new(); let mut ema_data_wrapper_arc = Arc::new(Mutex::new(ema_data_wrapper)); let mut task_vec = Vec::new(); - for filtered_elem in filtered_symbols { - let symbol_search_result = input_rt_data.iter().position(|x| x.0 == *filtered_elem.symbol); + for (symbol, filtered_data) in filtered_symbols { + if let Some(rt_data_vec) = input_rt_data.get(symbol) { + let ema_data_wrapper_arc_c = Arc::clone(&ema_data_wrapper_arc); + let mut ema_data = EmaData::new(); + let mut ema_data_vec: Vec = Vec::new(); + let symbol_c = symbol.clone(); + let rt_data_vec_c = rt_data_vec.clone(); + task_vec.push(tokio::spawn(async move { + if rt_data_vec_c.len() < moving_number { + ema_data.ema_value = 0.0; + ema_data.close_time = 0; + ema_data_vec.push(ema_data.clone()); + } else { + let partial_vec1 = rt_data_vec_c.get(..moving_number).unwrap(); + let partial_vec2 = rt_data_vec_c.get(moving_number..).unwrap(); - match symbol_search_result { - Some(T) => { - let ema_data_wrapper_arc_c = Arc::clone(&ema_data_wrapper_arc); - let filtered_elem_c = filtered_elem.clone(); - let mut ema_data = EmaData::new(); - let mut ema_data_vec: Vec = Vec::new(); - let input_rt_data_c = input_rt_data.clone(); - task_vec.push(tokio::spawn(async move { - if input_rt_data_c[T].1.len() < moving_number { - ema_data.ema_value = 0.0; - ema_data.close_time = 0; - ema_data_vec.push(ema_data.clone()); - } else { - let partial_vec1 = input_rt_data_c[T].1.get(..moving_number).unwrap(); - let partial_vec2 = input_rt_data_c[T].1.get(moving_number..).unwrap(); - - let mut sma_for_initial_value = 0.0; - for element in partial_vec1 { - sma_for_initial_value += element.opclo_price; - } - sma_for_initial_value /= moving_number as f64; - - ema_data.ema_value = sma_for_initial_value; - ema_data.close_time = partial_vec1.last().unwrap().close_time; - ema_data_vec.push(ema_data.clone()); - - ema_prev = sma_for_initial_value; - - for element in partial_vec2 { - ema_t = (1.0 - alpha) * ema_prev + alpha * element.opclo_price; - - ema_data.ema_value = ema_t; - ema_data.close_time = element.close_time; - ema_data_vec.push(ema_data.clone()); - - ema_prev = ema_t; - } + let mut sma_for_initial_value = 0.0; + for element in partial_vec1 { + sma_for_initial_value += element.opclo_price; } - let mut ema_data_wrapper_lock = ema_data_wrapper_arc_c.lock().await; - ema_data_wrapper_lock.push((filtered_elem_c.symbol.clone(), ema_data_vec.clone())); - })); - } - None => {} + sma_for_initial_value /= moving_number as f64; + + ema_data.ema_value = sma_for_initial_value; + ema_data.close_time = partial_vec1.last().unwrap().close_time; + ema_data_vec.push(ema_data.clone()); + + ema_prev = sma_for_initial_value; + + for element in partial_vec2 { + ema_t = (1.0 - alpha) * ema_prev + alpha * element.close_price; + + ema_data.ema_value = ema_t; + ema_data.close_time = element.close_time; + ema_data_vec.push(ema_data.clone()); + + ema_prev = ema_t; + } + } + let mut ema_data_wrapper_lock = ema_data_wrapper_arc_c.lock().await; + ema_data_wrapper_lock.insert(symbol_c, ema_data_vec.clone()); + })); } } try_join_all(task_vec).await?; let a = ema_data_wrapper_arc.lock().await.to_owned(); Ok(a) -} \ No newline at end of file +} diff --git a/src/value_estimation_team/indicators/heatmap_volume.rs b/src/value_estimation_team/indicators/heatmap_volume.rs index 57690fe..f2de9fd 100644 --- a/src/value_estimation_team/indicators/heatmap_volume.rs +++ b/src/value_estimation_team/indicators/heatmap_volume.rs @@ -1,8 +1,9 @@ use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; -use super::FilteredData; +use super::FilteredDataValue; use std::sync::Arc; -use tokio::{fs::*, io::AsyncWriteExt, sync::Mutex, time::*}; +use tokio::sync::Mutex; use futures::future::try_join_all; +use super::HashMap; #[derive(Debug, Clone, PartialEq)] pub enum HeatMapLevel { @@ -28,148 +29,150 @@ pub async fn heatmap_volume( high_thold: f64, medium_thold: f64, normal_thold: f64, - filtered_symbols: &Vec, - input_rt_data: &Vec<(String, Vec)>, -) -> Result)>, Box> { - let mut heatmap_data_wrapper: Vec<(String, Vec)> = Vec::new(); + filtered_symbols: &HashMap, + input_rt_data: &HashMap>, +) -> Result>, Box> { + let mut heatmap_data_wrapper: HashMap> = HashMap::new(); let mut heatmap_data_wrapper_arc = Arc::new(Mutex::new(heatmap_data_wrapper)); let mut task_vec = Vec::new(); - for element in filtered_symbols { - let heatmap_data_wrapper_arc_c = Arc::clone(&heatmap_data_wrapper_arc); - let element_c = element.clone(); - let input_rt_data_c = input_rt_data.clone(); - task_vec.push(tokio::spawn(async move { - let search_result = input_rt_data_c.iter().position(|x| x.0 == element_c.symbol); - if search_result.is_some_and(|a| input_rt_data_c[a].1.len() >= ma_len && input_rt_data_c[a].1.len() >= std_len) { - // calc mean - #[derive(Debug, Clone)] - struct MeanData { - mean_value: f64, - close_time: i64, - } - - let mut mean_vec: Vec = Vec::new(); - let mut mean_data = MeanData { - mean_value: 0.0, - close_time: 0, - }; - let window = input_rt_data_c[search_result.unwrap()].1.windows(ma_len); - for buffer in window { - // calculate SMA of volume - mean_data.mean_value = 0.0; - mean_data.close_time = 0; - for element in buffer { - mean_data.mean_value += element.quote_asset_volume; + for (symbol, filtered_data) in filtered_symbols { + if let Some(rt_price_vec) = input_rt_data.get(symbol) { + let heatmap_data_wrapper_arc_c = Arc::clone(&heatmap_data_wrapper_arc); + let symbol_c = symbol.clone(); + let rt_price_vec_c = rt_price_vec.clone(); + task_vec.push(tokio::spawn(async move { + if rt_price_vec_c.len() >= ma_len && rt_price_vec_c.len() >= std_len { + // calc mean + #[derive(Debug, Clone)] + struct MeanData { + mean_value: f64, + close_time: i64, } - mean_data.mean_value /= ma_len as f64; - mean_data.close_time = buffer.last().unwrap().close_time; - - mean_vec.push(mean_data.clone()); - } - - // calc pstdev - #[derive(Debug, Clone)] - struct PstdevData { - pstdev_value: f64, // population standard deviation value - close_time: i64, - } - let mut pstdev_vec: Vec = Vec::new(); - let mut pstdev_data = PstdevData { - pstdev_value: 0.0, - close_time: 0, - }; - let mut mean = 0.0; - let mut summation = 0.0; - let mut sample_minus_mean = 0.0; - let window = input_rt_data_c[search_result.unwrap()].1.windows(std_len); - for buffer in window { - pstdev_data.pstdev_value = 0.0; - pstdev_data.close_time = 0; - mean = 0.0; - summation = 0.0; - sample_minus_mean = 0.0; - for element in buffer { - mean += element.quote_asset_volume; - } - mean /= std_len as f64; - - for element in buffer { - sample_minus_mean = element.quote_asset_volume - mean; - summation += sample_minus_mean.powi(2); - } - - pstdev_data.pstdev_value = (summation / std_len as f64).sqrt(); - pstdev_data.close_time = buffer.last().unwrap().close_time; - - pstdev_vec.push(pstdev_data.clone()); - } - - // calc stdbar and heatmap volume - let mut heatmap_vol_vec: Vec = Vec::new(); - let mut heatmap_vol_data = HeatmapVolumeData { - heatmap_value: 0.0, - heatmap_level: HeatMapLevel::Normal, - close_time: 0, - }; - - if ma_len == std_len { - let mut rt_data_trunc_vec = - input_rt_data_c[search_result.unwrap()].1.get(ma_len - 1..).unwrap().iter(); - - let zipped = mean_vec.iter().zip(pstdev_vec.iter()); - for element in zipped { - heatmap_vol_data.heatmap_value = - (rt_data_trunc_vec.next().unwrap().quote_asset_volume - - element.0.mean_value) - / element.1.pstdev_value; - heatmap_vol_data.close_time = element.0.close_time; - if heatmap_vol_data.heatmap_value >= extra_high_thold { - heatmap_vol_data.heatmap_level = HeatMapLevel::ExtraHigh; - } else if heatmap_vol_data.heatmap_value > high_thold { - heatmap_vol_data.heatmap_level = HeatMapLevel::High; - } else if heatmap_vol_data.heatmap_value > medium_thold { - heatmap_vol_data.heatmap_level = HeatMapLevel::Medium; - } else if heatmap_vol_data.heatmap_value > normal_thold { - heatmap_vol_data.heatmap_level = HeatMapLevel::Normal; - } else { - heatmap_vol_data.heatmap_level = HeatMapLevel::Low; + + let mut mean_vec: Vec = Vec::new(); + let mut mean_data = MeanData { + mean_value: 0.0, + close_time: 0, + }; + let window = rt_price_vec_c.windows(ma_len); + for buffer in window { + // calculate SMA of volume + mean_data.mean_value = 0.0; + mean_data.close_time = 0; + for element in buffer { + mean_data.mean_value += element.quote_asset_volume; } - heatmap_vol_vec.push(heatmap_vol_data.clone()); + mean_data.mean_value /= ma_len as f64; + mean_data.close_time = buffer.last().unwrap().close_time; + + mean_vec.push(mean_data.clone()); } - } else if ma_len > std_len { - let mut rt_data_trunc_vec = - input_rt_data_c[search_result.unwrap()].1.get(std_len - 1..).unwrap().iter(); - let mut mean_trunc_vec = - mean_vec.get(mean_vec.len() - std_len..).unwrap().iter(); - let zipped = mean_trunc_vec.zip(pstdev_vec.iter()); - for element in zipped { - heatmap_vol_data.heatmap_value = - (rt_data_trunc_vec.next().unwrap().quote_asset_volume - - element.0.mean_value) - / element.1.pstdev_value; - heatmap_vol_data.close_time = element.0.close_time; - heatmap_vol_vec.push(heatmap_vol_data.clone()); + + // calc pstdev + #[derive(Debug, Clone)] + struct PstdevData { + pstdev_value: f64, // population standard deviation value + close_time: i64, } - } else { - let mut rt_data_trunc_vec = - input_rt_data_c[search_result.unwrap()].1.get(ma_len - 1..).unwrap().iter(); - let mut pstdev_trunc_vec = - pstdev_vec.get(pstdev_vec.len() - ma_len..).unwrap().iter(); - let zipped = mean_vec.iter().zip(pstdev_trunc_vec); - for element in zipped { - heatmap_vol_data.heatmap_value = - (rt_data_trunc_vec.next().unwrap().quote_asset_volume - - element.0.mean_value) - / element.1.pstdev_value; - heatmap_vol_data.close_time = element.0.close_time; - heatmap_vol_vec.push(heatmap_vol_data.clone()); - } // level 구현 + let mut pstdev_vec: Vec = Vec::new(); + let mut pstdev_data = PstdevData { + pstdev_value: 0.0, + close_time: 0, + }; + let mut mean = 0.0; + let mut summation = 0.0; + let mut sample_minus_mean = 0.0; + let window = rt_price_vec_c.windows(std_len); + for buffer in window { + pstdev_data.pstdev_value = 0.0; + pstdev_data.close_time = 0; + mean = 0.0; + summation = 0.0; + sample_minus_mean = 0.0; + for element in buffer { + mean += element.quote_asset_volume; + } + mean /= std_len as f64; + + for element in buffer { + sample_minus_mean = element.quote_asset_volume - mean; + summation += sample_minus_mean.powi(2); + } + + pstdev_data.pstdev_value = (summation / std_len as f64).sqrt(); + pstdev_data.close_time = buffer.last().unwrap().close_time; + + pstdev_vec.push(pstdev_data.clone()); + } + + // calc stdbar and heatmap volume + let mut heatmap_vol_vec: Vec = Vec::new(); + let mut heatmap_vol_data = HeatmapVolumeData { + heatmap_value: 0.0, + heatmap_level: HeatMapLevel::Normal, + close_time: 0, + }; + + if ma_len == std_len { + let mut rt_data_trunc_vec = + rt_price_vec_c.get(ma_len - 1..).unwrap().iter(); + + let zipped = mean_vec.iter().zip(pstdev_vec.iter()); + for element in zipped { + heatmap_vol_data.heatmap_value = + (rt_data_trunc_vec.next().unwrap().quote_asset_volume + - element.0.mean_value) + / element.1.pstdev_value; + heatmap_vol_data.close_time = element.0.close_time; + if heatmap_vol_data.heatmap_value >= extra_high_thold { + heatmap_vol_data.heatmap_level = HeatMapLevel::ExtraHigh; + } else if heatmap_vol_data.heatmap_value > high_thold { + heatmap_vol_data.heatmap_level = HeatMapLevel::High; + } else if heatmap_vol_data.heatmap_value > medium_thold { + heatmap_vol_data.heatmap_level = HeatMapLevel::Medium; + } else if heatmap_vol_data.heatmap_value > normal_thold { + heatmap_vol_data.heatmap_level = HeatMapLevel::Normal; + } else { + heatmap_vol_data.heatmap_level = HeatMapLevel::Low; + } + heatmap_vol_vec.push(heatmap_vol_data.clone()); + } + } else if ma_len > std_len { + let mut rt_data_trunc_vec = + rt_price_vec_c.get(std_len - 1..).unwrap().iter(); + let mut mean_trunc_vec = + mean_vec.get(mean_vec.len() - std_len..).unwrap().iter(); + let zipped = mean_trunc_vec.zip(pstdev_vec.iter()); + for element in zipped { + heatmap_vol_data.heatmap_value = + (rt_data_trunc_vec.next().unwrap().quote_asset_volume + - element.0.mean_value) + / element.1.pstdev_value; + heatmap_vol_data.close_time = element.0.close_time; + heatmap_vol_vec.push(heatmap_vol_data.clone()); + } + } else { + let mut rt_data_trunc_vec = + rt_price_vec_c.get(ma_len - 1..).unwrap().iter(); + let mut pstdev_trunc_vec = + pstdev_vec.get(pstdev_vec.len() - ma_len..).unwrap().iter(); + let zipped = mean_vec.iter().zip(pstdev_trunc_vec); + for element in zipped { + heatmap_vol_data.heatmap_value = + (rt_data_trunc_vec.next().unwrap().quote_asset_volume + - element.0.mean_value) + / element.1.pstdev_value; + heatmap_vol_data.close_time = element.0.close_time; + heatmap_vol_vec.push(heatmap_vol_data.clone()); + } // level 구현 + } + let mut heatmap_data_wrapper_lock = heatmap_data_wrapper_arc_c.lock().await; + heatmap_data_wrapper_lock.insert(symbol_c, heatmap_vol_vec.clone()); } - let mut heatmap_data_wrapper_lock = heatmap_data_wrapper_arc_c.lock().await; - heatmap_data_wrapper_lock.push((element_c.symbol.clone(), heatmap_vol_vec.clone())); - } - })); + })); + } + } try_join_all(task_vec).await?; let a = heatmap_data_wrapper_arc.lock().await.to_owned(); diff --git a/src/value_estimation_team/indicators/macd.rs b/src/value_estimation_team/indicators/macd.rs index e164ede..e7ef55d 100644 --- a/src/value_estimation_team/indicators/macd.rs +++ b/src/value_estimation_team/indicators/macd.rs @@ -1,6 +1,6 @@ use crate::value_estimation_team::indicators::ema::{EmaData, ema}; use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; -use super::FilteredData; +use super::{FilteredDataValue, HashMap}; use std::sync::Arc; use tokio::{fs::*, io::AsyncWriteExt, sync::Mutex, time::*}; use futures::future::try_join_all; @@ -43,116 +43,114 @@ pub async fn ema_macd( fast_len: usize, slow_len: usize, signal_smoothing: usize, - input_rt_data: &Vec<(String, Vec)>, - filtered_symbols: &Vec -) -> Result)>, Box> { - let mut macd_oscil_vec: Vec<(String, Vec)> = Vec::new(); + input_rt_data: &HashMap>, + filtered_symbols: &HashMap +) -> Result>, Box> { + let mut macd_oscil_vec: HashMap> = HashMap::new(); let fast_emas = ema(fast_len, input_rt_data, filtered_symbols).await?; let slow_emas = ema(slow_len, input_rt_data, filtered_symbols).await?; - let mut macd_data_wrapper: Vec<(String, Vec)> = Vec::new(); + let mut macd_data_wrapper: HashMap> = HashMap::new(); let mut macd_data_wrapper_arc = Arc::new(Mutex::new(macd_data_wrapper)); let mut task_vec = Vec::new(); - for filtered_elem in filtered_symbols { + for (symbol, filtered_data) in filtered_symbols { let fast_emas_c = fast_emas.clone(); let slow_emas_c = slow_emas.clone(); - let filtered_elem_c = filtered_elem.clone(); - let macd_data_wrapper_arc_c = Arc::clone(&macd_data_wrapper_arc); - task_vec.push(tokio::spawn(async move { - let fast_search_result = fast_emas_c.iter().position(|x| x.0 == filtered_elem_c.symbol); - let slow_search_result = slow_emas_c.iter().position(|x| x.0 == filtered_elem_c.symbol); + if let (Some(fast_ema_vec), Some(slow_ema_vec)) = (fast_emas.get(symbol), slow_emas_c.get(symbol)) { + let symbol_c = symbol.clone(); + let fast_ema_vec_c = fast_ema_vec.clone(); + let slow_ema_vec_c = slow_ema_vec.clone(); + let macd_data_wrapper_arc_c = Arc::clone(&macd_data_wrapper_arc); + task_vec.push(tokio::spawn(async move { + if fast_ema_vec_c.len() >= signal_smoothing && slow_ema_vec_c.len() >= signal_smoothing { + let result = fast_ema_vec_c.binary_search_by_key( + &slow_ema_vec_c.first().unwrap().close_time, + |&EmaData { + ema_value, + close_time, + }| close_time, + ); + if result.is_ok() { + // making MACD + let temp_vec = fast_ema_vec_c.get(result.unwrap()..).unwrap(); + let zipped = temp_vec.iter().zip(slow_ema_vec_c); + let mut macd = TempData::new(); + let mut macd_vec: Vec = Vec::new(); + for element in zipped { + macd.value = element.0.ema_value - element.1.ema_value; + macd.close_time = element.0.close_time; + macd_vec.push(macd.clone()); + } + + // making signal (smoothed MACD) + // TODO: this should be calculated in EMA (currently, SMA) + // let macd_vec_window = macd_vec.windows(signal_smoothing); + // let mut macd_signal_vec: Vec = Vec::new(); + // for window in macd_vec_window { + // let mut sum_value = 0.0; + // for element in window { + // sum_value += element.value; + // } + // macd.value = sum_value / signal_smoothing as f64; + // macd.close_time = window.last().unwrap().close_time; + // macd_signal_vec.push(macd.clone()); + // } + let mut macd_signal = TempData::new(); + let mut macd_signal_vec: Vec = Vec::new(); + let partial_vec1 = macd_vec.get(..signal_smoothing).unwrap(); + let partial_vec2 = macd_vec.get(signal_smoothing..).unwrap(); - if fast_search_result.is_some_and(|a| fast_emas_c[a].1.len() >= signal_smoothing) && - slow_search_result.is_some_and(|a| slow_emas_c[a].1.len() >= signal_smoothing) { - let fast_ema = fast_emas_c[fast_search_result.unwrap()].1.clone(); - let slow_ema = slow_emas_c[fast_search_result.unwrap()].1.clone(); - let result = fast_ema.binary_search_by_key( - &slow_ema.first().unwrap().close_time, - |&EmaData { - ema_value, - close_time, - }| close_time, - ); - if result.is_ok() { - // making MACD - let temp_vec = fast_ema.get(result.unwrap()..).unwrap(); - let zipped = temp_vec.iter().zip(slow_ema); - let mut macd = TempData::new(); - let mut macd_vec: Vec = Vec::new(); - for element in zipped { - macd.value = element.0.ema_value - element.1.ema_value; - macd.close_time = element.0.close_time; - macd_vec.push(macd.clone()); - } - - // making signal (smoothed MACD) - // TODO: this should be calculated in EMA (currently, SMA) - // let macd_vec_window = macd_vec.windows(signal_smoothing); - // let mut macd_signal_vec: Vec = Vec::new(); - // for window in macd_vec_window { - // let mut sum_value = 0.0; - // for element in window { - // sum_value += element.value; - // } - // macd.value = sum_value / signal_smoothing as f64; - // macd.close_time = window.last().unwrap().close_time; - // macd_signal_vec.push(macd.clone()); - // } - let mut macd_signal = TempData::new(); - let mut macd_signal_vec: Vec = Vec::new(); - let partial_vec1 = macd_vec.get(..signal_smoothing).unwrap(); - let partial_vec2 = macd_vec.get(signal_smoothing..).unwrap(); + let mut sma_for_initial_value = 0.0; + for element in partial_vec1 { + sma_for_initial_value += element.value; + } + sma_for_initial_value /= signal_smoothing as f64; - let mut sma_for_initial_value = 0.0; - for element in partial_vec1 { - sma_for_initial_value += element.value; - } - sma_for_initial_value /= signal_smoothing as f64; - - macd_signal.value = sma_for_initial_value; - macd_signal.close_time = partial_vec1.last().unwrap().close_time; - macd_signal_vec.push(macd_signal.clone()); - - let alpha: f64 = 2.0 / (signal_smoothing as f64 + 1.0); - let mut ema_prev = sma_for_initial_value; - for element in partial_vec2 { - let ema_t = (1.0 - alpha) * ema_prev + alpha * element.value; - - macd_signal.value = ema_t; - macd_signal.close_time = element.close_time; + macd_signal.value = sma_for_initial_value; + macd_signal.close_time = partial_vec1.last().unwrap().close_time; macd_signal_vec.push(macd_signal.clone()); - ema_prev = ema_t; - } - - let result = macd_vec.binary_search_by_key( - &macd_signal_vec.first().unwrap().close_time, - |&TempData { - value, - close_time, - }| close_time, - ); + let alpha: f64 = 2.0 / (signal_smoothing as f64 + 1.0); + let mut ema_prev = sma_for_initial_value; + for element in partial_vec2 { + let ema_t = (1.0 - alpha) * ema_prev + alpha * element.value; - if result.is_ok() { - let result = macd_vec.get(result.unwrap()..); - if result.is_some() { - let zipped = result.unwrap().iter().zip(macd_signal_vec); - - let mut macd_vec: Vec = Vec::new(); - for element in zipped { - let mut macd = MacdData::new(); - macd.macd_value = element.0.value; - macd.signal_value = element.1.value; - macd.close_time = element.0.close_time; - macd_vec.push(macd); + macd_signal.value = ema_t; + macd_signal.close_time = element.close_time; + macd_signal_vec.push(macd_signal.clone()); + + ema_prev = ema_t; + } + + let result = macd_vec.binary_search_by_key( + &macd_signal_vec.first().unwrap().close_time, + |&TempData { + value, + close_time, + }| close_time, + ); + + if result.is_ok() { + let result = macd_vec.get(result.unwrap()..); + if result.is_some() { + let zipped = result.unwrap().iter().zip(macd_signal_vec); + + let mut macd_vec: Vec = Vec::new(); + for element in zipped { + let mut macd = MacdData::new(); + macd.macd_value = element.0.value; + macd.signal_value = element.1.value; + macd.close_time = element.0.close_time; + macd_vec.push(macd); + } + let mut macd_data_wrapper_lock = macd_data_wrapper_arc_c.lock().await; + macd_data_wrapper_lock.insert(symbol_c, macd_vec.clone()); } - let mut macd_data_wrapper_lock = macd_data_wrapper_arc_c.lock().await; - macd_data_wrapper_lock.push((filtered_elem_c.symbol.clone(), macd_vec.clone())); } } } - } - })); + })); + } } try_join_all(task_vec).await?; let a = macd_data_wrapper_arc.lock().await.to_owned(); diff --git a/src/value_estimation_team/indicators/mod.rs b/src/value_estimation_team/indicators/mod.rs index a7172f3..b75a7d3 100644 --- a/src/value_estimation_team/indicators/mod.rs +++ b/src/value_estimation_team/indicators/mod.rs @@ -9,8 +9,9 @@ pub mod stoch_rsi; pub mod supertrend; pub mod tema; -use crate::strategy_team::FilteredData; +use crate::strategy_team::FilteredDataValue; use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; use futures::future::try_join_all; use std::sync::Arc; use tokio::{fs::*, io::AsyncWriteExt, sync::Mutex, time::*}; +use std::collections::HashMap; diff --git a/src/value_estimation_team/indicators/rsi.rs b/src/value_estimation_team/indicators/rsi.rs index 0004882..a81276c 100644 --- a/src/value_estimation_team/indicators/rsi.rs +++ b/src/value_estimation_team/indicators/rsi.rs @@ -3,13 +3,14 @@ use crate::database_control::*; use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; -use crate::strategy_team::FilteredData; +use crate::strategy_team::FilteredDataValue; use futures::future::try_join_all; use serde::Deserialize; use sqlx::FromRow; use std::f64::NAN; use std::sync::Arc; use tokio::{fs::*, io::AsyncWriteExt, sync::Mutex, time::*}; +use super::HashMap; #[derive(Clone, Debug)] pub struct RsiData { @@ -29,21 +30,20 @@ impl RsiData { // Binance RSI (EMA, Closeprice, Wilder's weight, 150 candles) pub async fn rsi( rsi_number: usize, - input_rt_data: &Vec<(String, Vec)>, - filtered_symbols: &Vec, -) -> Result)>, Box> { + input_rt_data: &HashMap>, + filtered_symbols: &HashMap, +) -> Result>, Box> { let instant = Instant::now(); - let mut rsi_data_wrapper: Vec<(String, Vec)> = Vec::new(); + let mut rsi_data_wrapper: HashMap> = HashMap::new(); let mut rsi_data_wrapper_arc = Arc::new(Mutex::new(rsi_data_wrapper)); let mut task_vec = Vec::new(); - for element in filtered_symbols { - let element_c = element.clone(); - let rsi_data_wrapper_arc_c = Arc::clone(&rsi_data_wrapper_arc); - let search_result = input_rt_data.iter().position(|x| x.0 == *element_c.symbol); - if search_result.is_some() { - let input_rt_data_c = input_rt_data.clone(); + for (symbol, filtered_data) in filtered_symbols { + if let Some(rt_price_vec) = input_rt_data.get(symbol) { + let rsi_data_wrapper_arc_c = Arc::clone(&rsi_data_wrapper_arc); + let symbol_c = symbol.clone(); + let mut input_rt_data_c = rt_price_vec.clone(); task_vec.push(tokio::spawn(async move { let mut rsi_data_vec: Vec = Vec::new(); let mut rsi_data = RsiData::new(); @@ -56,18 +56,17 @@ pub async fn rsi( let mut rsi: f64 = 0.0; let mut last_close_time = 0; - if input_rt_data_c[search_result.unwrap()].1.len() < rsi_number + 1 { + if input_rt_data_c.len() < rsi_number + 1 { rsi_data.rsi_value = 0.0; rsi_data.close_time = 0; rsi_data_vec.push(rsi_data.clone()); } else { - read_data_vec = input_rt_data_c[search_result.unwrap()].1.clone(); - if read_data_vec.len() >= (150 + rsi_number) as usize { - read_data_vec.reverse(); - read_data_vec.truncate((150 + rsi_number) as usize); - read_data_vec.reverse(); + if input_rt_data_c.len() >= (150 + rsi_number) as usize { + input_rt_data_c.reverse(); + input_rt_data_c.truncate((150 + rsi_number) as usize); + input_rt_data_c.reverse(); } - let window_iter = read_data_vec.windows(rsi_number + 1); + let window_iter = input_rt_data_c.windows(rsi_number + 1); let mut prev_avg_ups: Option = None; let mut prev_avg_downs: Option = None; @@ -143,7 +142,7 @@ pub async fn rsi( rsi_data_vec.push(rsi_data.clone()); } let mut rsi_data_wrapper_lock = rsi_data_wrapper_arc_c.lock().await; - rsi_data_wrapper_lock.push((element_c.symbol.clone(), rsi_data_vec.clone())); + rsi_data_wrapper_lock.insert(symbol_c, rsi_data_vec.clone()); } })); } diff --git a/src/value_estimation_team/indicators/sma.rs b/src/value_estimation_team/indicators/sma.rs index 33ed250..62cc3b3 100644 --- a/src/value_estimation_team/indicators/sma.rs +++ b/src/value_estimation_team/indicators/sma.rs @@ -3,12 +3,13 @@ use crate::database_control::*; use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; -use crate::strategy_team::FilteredData; +use crate::strategy_team::FilteredDataValue; use futures::future::try_join_all; use serde::Deserialize; use sqlx::FromRow; use std::sync::Arc; use tokio::{fs::*, io::AsyncWriteExt, sync::Mutex, time::*}; +use super::HashMap; #[derive(Clone, Debug)] pub struct SmaData { @@ -28,29 +29,28 @@ impl SmaData { // Binance MA (closeprice) pub async fn sma( moving_number: usize, - input_rt_data: &Vec<(String, Vec)>, - filtered_symbols: &Vec, -) -> Result)>, Box> { + input_rt_data: &HashMap>, + filtered_symbols: &HashMap, +) -> Result>, Box> { if filtered_symbols.is_empty() { Err("Err")?; } - let mut sma_data_wrapper: Vec<(String, Vec)> = Vec::new(); + let mut sma_data_wrapper: HashMap> = HashMap::new(); let mut sma_data_wrapper_arc = Arc::new(Mutex::new(sma_data_wrapper)); let mut task_vec = Vec::new(); - for filtered_symbol in filtered_symbols { - let filtered_symbol_c = filtered_symbol.clone(); - let sma_data_wrapper_arc_c = Arc::clone(&sma_data_wrapper_arc); - let input_rt_data_c = input_rt_data.clone(); - task_vec.push(tokio::spawn(async move { - let search_result = input_rt_data_c.iter().position(|x| x.0 == *filtered_symbol_c.symbol); - if search_result.is_some() { + for (symbol, filtered_data) in filtered_symbols { + if let Some(vec) = input_rt_data.get(symbol) { + let sma_data_wrapper_arc_c = Arc::clone(&sma_data_wrapper_arc); + let symbol_c = symbol.clone(); + let rt_price_data = vec.clone(); + task_vec.push(tokio::spawn(async move { let mut sma_data = SmaData::new(); let mut sma_data_vec: Vec = Vec::new(); - if input_rt_data_c[search_result.unwrap()].1.len() >= moving_number { - let mut iter = input_rt_data_c[search_result.unwrap()].1.windows(moving_number); + if rt_price_data.len() >= moving_number { + let mut iter = rt_price_data.windows(moving_number); for buffer in iter { let mut avg = 0.0; for element in buffer { @@ -64,9 +64,9 @@ pub async fn sma( } } let mut sma_data_wrapper_lock = sma_data_wrapper_arc_c.lock().await; - sma_data_wrapper_lock.push((filtered_symbol_c.symbol.clone(), sma_data_vec.clone())); - } - })); + sma_data_wrapper_lock.insert(symbol_c, sma_data_vec.clone()); + })); + } } try_join_all(task_vec).await?; let a = sma_data_wrapper_arc.lock().await.to_owned(); @@ -76,29 +76,28 @@ pub async fn sma( // Binance MA ((open+close)/2) pub async fn sma_opclo( moving_number: usize, - input_rt_data: &Vec<(String, Vec)>, - filtered_symbols: &Vec, -) -> Result)>, Box> { + input_rt_data: &HashMap>, + filtered_symbols: &HashMap, +) -> Result>, Box> { if filtered_symbols.is_empty() { Err("Err")?; } - let mut sma_data_wrapper: Vec<(String, Vec)> = Vec::new(); + let mut sma_data_wrapper: HashMap> = HashMap::new(); let mut sma_data_wrapper_arc = Arc::new(Mutex::new(sma_data_wrapper)); let mut task_vec = Vec::new(); - for filtered_symbol in filtered_symbols { - let filtered_symbol_c = filtered_symbol.clone(); - let sma_data_wrapper_arc_c = Arc::clone(&sma_data_wrapper_arc); - let input_rt_data_c = input_rt_data.clone(); - task_vec.push(tokio::spawn(async move { - let search_result = input_rt_data_c.iter().position(|x| x.0 == *filtered_symbol_c.symbol); - if search_result.is_some() { + for (symbol, filtered_data) in filtered_symbols { + if let Some(vec) = input_rt_data.get(symbol) { + let sma_data_wrapper_arc_c = Arc::clone(&sma_data_wrapper_arc); + let symbol_c = symbol.clone(); + let rt_price_data = vec.clone(); + task_vec.push(tokio::spawn(async move { let mut sma_data = SmaData::new(); let mut sma_data_vec: Vec = Vec::new(); - if input_rt_data_c[search_result.unwrap()].1.len() >= moving_number { - let mut iter = input_rt_data_c[search_result.unwrap()].1.windows(moving_number); + if rt_price_data.len() >= moving_number { + let mut iter = rt_price_data.windows(moving_number); for buffer in iter { let mut avg = 0.0; for element in buffer { @@ -112,9 +111,9 @@ pub async fn sma_opclo( } } let mut sma_data_wrapper_lock = sma_data_wrapper_arc_c.lock().await; - sma_data_wrapper_lock.push((filtered_symbol_c.symbol.clone(), sma_data_vec.clone())); - } - })); + sma_data_wrapper_lock.insert(symbol_c, sma_data_vec.clone()); + })); + } } try_join_all(task_vec).await?; let a = sma_data_wrapper_arc.lock().await.to_owned(); diff --git a/src/value_estimation_team/indicators/stoch_rsi.rs b/src/value_estimation_team/indicators/stoch_rsi.rs index 881f2ee..071b729 100644 --- a/src/value_estimation_team/indicators/stoch_rsi.rs +++ b/src/value_estimation_team/indicators/stoch_rsi.rs @@ -4,13 +4,14 @@ use crate::database_control::*; use crate::value_estimation_team::indicators::rsi::{RsiData, rsi}; use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; -use crate::strategy_team::FilteredData; +use crate::strategy_team::FilteredDataValue; use futures::{future::try_join_all, lock::Mutex}; use serde::Deserialize; use sqlx::FromRow; use std::f64::NAN; use std::sync::Arc; use tokio::{fs::*, io::AsyncWriteExt, time::*}; +use std::collections::{HashMap, HashSet}; #[derive(Clone, Debug)] pub struct StochRsiData { @@ -50,39 +51,31 @@ pub async fn stoch_rsi( stoch_rsi_length: usize, smooth_k: usize, smooth_d: usize, - input_rt_data: &Vec<(String, Vec)>, - filtered_symbols: &Vec -) -> Result)>, Box> { - let mut stoch_rsi_data_wrapper: Vec<(String, Vec)> = Vec::new(); + input_rt_data: &HashMap>, + filtered_symbols: &HashMap, +) -> Result>, Box> { + let mut stoch_rsi_data_wrapper: HashMap> = HashMap::new(); let mut stoch_rsi_data_wrapper_arc = Arc::new(Mutex::new(stoch_rsi_data_wrapper)); - let mut stoch_rsi_data_vec: Vec = Vec::new(); - if rsi_length == 0 || stoch_rsi_length == 0 || smooth_k == 0 || smooth_d == 0 { - panic!( - "rsi_length or stoch_rsi_length or smooth_k or smooth_d can't be 0! rsi_length: {}, stoch_rsi_length: {}, k:{}, d:{}", - rsi_length, stoch_rsi_length, smooth_k, smooth_d - ); - } - let rsi_data_vec = rsi(rsi_length, input_rt_data, filtered_symbols).await?; + let rsi_data_map = rsi(rsi_length, input_rt_data, filtered_symbols).await?; let mut task_vec = Vec::new(); - for element in rsi_data_vec { + for (symbol, rsi_vec) in rsi_data_map { let mut stoch_rsi_data = StochRsiData::new(); let mut stoch_rsi_k_data = StochRsiKData::new(); let stoch_rsi_data_wrapper_arc_c = Arc::clone(&stoch_rsi_data_wrapper_arc); - let element_c = element.clone(); + let symbol_c = symbol.clone(); task_vec.push(tokio::spawn(async move { let mut stoch_rsi_data_vec: Vec = Vec::new(); let mut stoch_rsi_k_data_vec: Vec = Vec::new(); let mut stoch_rsi_vec: Vec = Vec::new(); let mut stoch_rsi = RsiData::new(); - if element_c.1.len() >= stoch_rsi_length - && element_c.1.len() >= smooth_k - && element_c.1.len() >= smooth_d + if rsi_vec.len() >= stoch_rsi_length + && rsi_vec.len() >= smooth_k + && rsi_vec.len() >= smooth_d { - let mut read_data_vec = element_c.1; - let window_iter = read_data_vec.windows(stoch_rsi_length); + let window_iter = rsi_vec.windows(stoch_rsi_length); for buffer_window in window_iter { let max_value = buffer_window @@ -131,7 +124,7 @@ pub async fn stoch_rsi( stoch_rsi_data_vec.push(stoch_rsi_data.clone()); } let mut stoch_rsi_data_wrapper_lock = stoch_rsi_data_wrapper_arc_c.lock().await; - stoch_rsi_data_wrapper_lock.push((element_c.0.clone(), stoch_rsi_data_vec.clone())); + stoch_rsi_data_wrapper_lock.insert(symbol_c, stoch_rsi_data_vec.clone()); } })); } diff --git a/src/value_estimation_team/indicators/supertrend.rs b/src/value_estimation_team/indicators/supertrend.rs index f39cc3c..7911c78 100644 --- a/src/value_estimation_team/indicators/supertrend.rs +++ b/src/value_estimation_team/indicators/supertrend.rs @@ -1,11 +1,21 @@ use crate::value_estimation_team::datapoints::price_data::RealtimePriceData; +use super::{HashMap, FilteredDataValue}; +use std::sync::Arc; +use futures::future::try_join_all; +use tokio::sync::Mutex; + +#[derive(PartialEq, Clone, Debug)] +pub enum SuperTrendArea { UP, DOWN } + +#[derive(PartialEq, Clone, Debug)] +pub enum SuperTrendSignal { BUY, SELL } #[derive(Clone, Debug)] pub struct SupertrendData { pub band_value: f64, - pub signal: Option, // BUY or SELL - pub area: String, // UP or DOWN - pub close_time: i64, + pub signal: Option, // BUY or SELL + pub area: SuperTrendArea, // UP or DOWN + pub close_time: i64 } impl SupertrendData { @@ -13,8 +23,8 @@ impl SupertrendData { let a = SupertrendData { band_value: 0.0, signal: None, - area: String::new(), - close_time: 0, + area: SuperTrendArea::DOWN, + close_time: 0 }; a @@ -55,184 +65,193 @@ impl ATRData { } } +// TODO: should return type be Option? // Implementation from TradingView Script (SuperTrend by KivancOzbilgic, source price: closeprice) -pub async fn supertrend( - symbol: &String, - input_rt_data: &Vec<(String, Vec)>, +// return key: close_time +pub async fn supertrend( atr_period: usize, multiplier: f64, is_original_method: bool, -) -> Option> { - let symbol_search_result = input_rt_data.iter().position(|x| x.0 == *symbol); - - match symbol_search_result { - Some(T) => { - if input_rt_data[T].1.len() >= atr_period && atr_period >= 2 { - // making True Range - let mut true_range_vec: Vec = Vec::new(); - let mut tr_data = TrueRangeData::new(); - let mut tr_val_hl = 0.0; // High - Low - let mut tr_val_hcp = 0.0; // Abs(High - Close_previous) - let mut tr_val_lcp = 0.0; // Abs(Low - Close_previous) - - let window_vec = input_rt_data[T].1.windows(2); - - tr_data.tr_value = input_rt_data[T].1.first().unwrap().high_price - - input_rt_data[T].1.first().unwrap().low_price; - tr_data.close_time = input_rt_data[T].1.first().unwrap().close_time; - true_range_vec.push(tr_data.clone()); - - for buffer in window_vec { - tr_val_hl = buffer[1].high_price - buffer[1].low_price; - tr_val_hcp = (buffer[1].high_price - buffer[0].close_price).abs(); - tr_val_lcp = (buffer[1].low_price - buffer[0].close_price).abs(); - - tr_data.tr_value = tr_val_hl.max(tr_val_hcp.max(tr_val_lcp)); - tr_data.close_time = buffer[1].close_time; + input_rt_data: &HashMap>, + filtered_symbols: &HashMap, +) -> Result>, Box> { + let mut supertrend_data_wrapper: HashMap> = HashMap::new(); + let mut supertrend_data_wrapper_arc = Arc::new(Mutex::new(supertrend_data_wrapper)); + let mut task_vec = Vec::new(); + for (symbol, filtered_data) in filtered_symbols { + if let Some(rt_price_vec) = input_rt_data.get(symbol) { + if atr_period < rt_price_vec.len()-1 { + let symbol_c = symbol.clone(); + let supertrend_data_wrapper_arc_c = Arc::clone(&supertrend_data_wrapper_arc); + let rt_price_vec_c = rt_price_vec.clone(); + task_vec.push(tokio::spawn(async move { + // making True Range + let mut true_range_vec: Vec = Vec::new(); + let mut tr_data = TrueRangeData::new(); + let mut tr_val_hl = 0.0; // High - Low + let mut tr_val_hcp = 0.0; // Abs(High - Close_previous) + let mut tr_val_lcp = 0.0; // Abs(Low - Close_previous) + + let window_vec = rt_price_vec_c.windows(2); + + tr_data.tr_value = rt_price_vec_c.first().unwrap().high_price + - rt_price_vec_c.first().unwrap().low_price; + tr_data.close_time = rt_price_vec_c.first().unwrap().close_time; true_range_vec.push(tr_data.clone()); - } - - // making Average True Range - let mut average_true_range_vec: Vec = Vec::new(); - let mut atr_data = ATRData::new(); - - if is_original_method == true { - // original calculation of ATR - let mut first_value = 0.0; - let mut first_vec = &true_range_vec[0..atr_period]; - - for element in first_vec { - first_value += element.tr_value; + + for buffer in window_vec { + tr_val_hl = buffer[1].high_price - buffer[1].low_price; + tr_val_hcp = (buffer[1].high_price - buffer[0].close_price).abs(); + tr_val_lcp = (buffer[1].low_price - buffer[0].close_price).abs(); + + tr_data.tr_value = tr_val_hl.max(tr_val_hcp.max(tr_val_lcp)); + tr_data.close_time = buffer[1].close_time; + + true_range_vec.push(tr_data.clone()); } - first_value /= (atr_period) as f64; - atr_data.atr_value = first_value; - atr_data.close_time = first_vec.last().unwrap().close_time; - average_true_range_vec.push(atr_data.clone()); - - let mut temp_prev_atr_value = first_value; - for element in &true_range_vec[atr_period..] { - atr_data.atr_value = ((temp_prev_atr_value * ((atr_period - 1) as f64)) - + element.tr_value) - / atr_period as f64; - atr_data.close_time = element.close_time; - - average_true_range_vec.push(atr_data.clone()); - temp_prev_atr_value = atr_data.atr_value; - } - } else { - // Calculation of ATR from SMA True Range with atr_period - let window = true_range_vec.windows(atr_period); - - let mut sum = 0.0; - for buffer in window { - for element in buffer { - sum += element.tr_value; + + // making Average True Range + let mut average_true_range_vec: Vec = Vec::new(); + let mut atr_data = ATRData::new(); + + if is_original_method == true { + // original calculation of ATR + let mut first_value = 0.0; + let mut first_vec = &true_range_vec[0..atr_period]; + + for element in first_vec { + first_value += element.tr_value; } - - atr_data.atr_value = sum / atr_period as f64; - atr_data.close_time = buffer.last().unwrap().close_time; + first_value /= (atr_period) as f64; + atr_data.atr_value = first_value; + atr_data.close_time = first_vec.last().unwrap().close_time; average_true_range_vec.push(atr_data.clone()); - - sum = 0.0; - } - } - - // making Supertrend - #[derive(Clone)] - struct BandData { - basic_upperband: f64, - basic_lowerband: f64, - close_time: i64, - } - - let mut supertrend_vec: Vec = Vec::new(); - let mut supertrend_data = SupertrendData::new(); - let mut band_vec: Vec = Vec::new(); - let mut band_data = BandData { - basic_upperband: 0.0, - basic_lowerband: 0.0, - close_time: 0, - }; - - let closetime_search_result = input_rt_data[T].1.iter().position(|x| { - x.close_time == average_true_range_vec.first().unwrap().close_time - }); - let mut rt_data = &input_rt_data[T].1[closetime_search_result.unwrap()..]; - let zipped = rt_data.iter().zip(average_true_range_vec); - - for element in zipped { - band_data.basic_upperband = ((element.0.high_price + element.0.low_price) - / 2.0) - - (multiplier * element.1.atr_value); - band_data.basic_lowerband = ((element.0.high_price + element.0.low_price) - / 2.0) - + (multiplier * element.1.atr_value); - band_data.close_time = element.1.close_time; - band_vec.push(band_data.clone()); - } - - let mut zipped = rt_data.iter().zip(band_vec); - let first_element = zipped.next().unwrap(); - let mut prev_close_price = first_element.0.close_price; - let mut final_upperband = 0.0; - let mut final_lowerband = 0.0; - let mut prev_final_upperband = first_element.1.basic_upperband; - let mut prev_final_lowerband = first_element.1.basic_lowerband; - let mut trend = 1; // 1 means up trend, -1 means down trend - let mut prev_trend = 1; - for element in zipped { - // set final upperband - if prev_close_price > prev_final_upperband { - final_upperband = prev_final_upperband.max(element.1.basic_upperband); + + let mut temp_prev_atr_value = first_value; + for element in &true_range_vec[atr_period..] { + atr_data.atr_value = ((temp_prev_atr_value * ((atr_period - 1) as f64)) + + element.tr_value) + / atr_period as f64; + atr_data.close_time = element.close_time; + + average_true_range_vec.push(atr_data.clone()); + temp_prev_atr_value = atr_data.atr_value; + } } else { - final_upperband = element.1.basic_upperband; + // Calculation of ATR from SMA True Range with atr_period + let window = true_range_vec.windows(atr_period); + + let mut sum = 0.0; + for buffer in window { + for element in buffer { + sum += element.tr_value; + } + + atr_data.atr_value = sum / atr_period as f64; + atr_data.close_time = buffer.last().unwrap().close_time; + average_true_range_vec.push(atr_data.clone()); + + sum = 0.0; + } } - - // set final lowerband - if prev_close_price < prev_final_lowerband { - final_lowerband = prev_final_lowerband.min(element.1.basic_lowerband); - } else { - final_lowerband = element.1.basic_lowerband; + + // making Supertrend + #[derive(Clone)] + struct BandData { + basic_upperband: f64, + basic_lowerband: f64, + close_time: i64, } - - // set supertrend - if trend == -1 && element.0.close_price > prev_final_lowerband { - supertrend_data.area = String::from("UP"); - trend = 1; - } else if trend == 1 && element.0.close_price < prev_final_upperband { - supertrend_data.area = String::from("DOWN"); - trend = -1; + + let mut supertrend_vec: Vec = Vec::new(); + let mut supertrend_data = SupertrendData::new(); + let mut band_vec: Vec = Vec::new(); + let mut band_data = BandData { + basic_upperband: 0.0, + basic_lowerband: 0.0, + close_time: 0, + }; + + let closetime_search_result = rt_price_vec_c.iter().position(|x| { + x.close_time == average_true_range_vec.first().unwrap().close_time + }); + let mut rt_data = &rt_price_vec_c[closetime_search_result.unwrap()..]; + let zipped = rt_data.iter().zip(average_true_range_vec); + + for element in zipped { + band_data.basic_upperband = ((element.0.high_price + element.0.low_price) + / 2.0) + - (multiplier * element.1.atr_value); + band_data.basic_lowerband = ((element.0.high_price + element.0.low_price) + / 2.0) + + (multiplier * element.1.atr_value); + band_data.close_time = element.1.close_time; + band_vec.push(band_data.clone()); } - - if supertrend_data.area.contains("UP") { - supertrend_data.band_value = final_upperband; - } else { - supertrend_data.band_value = final_lowerband; + + let mut zipped = rt_data.iter().zip(band_vec); + let first_element = zipped.next().unwrap(); + let mut prev_close_price = first_element.0.close_price; + let mut final_upperband = 0.0; + let mut final_lowerband = 0.0; + let mut prev_final_upperband = first_element.1.basic_upperband; + let mut prev_final_lowerband = first_element.1.basic_lowerband; + let mut trend = 1; // 1 means up trend, -1 means down trend + let mut prev_trend = 1; + for element in zipped { + // set final upperband + if prev_close_price > prev_final_upperband { + final_upperband = prev_final_upperband.max(element.1.basic_upperband); + } else { + final_upperband = element.1.basic_upperband; + } + + // set final lowerband + if prev_close_price < prev_final_lowerband { + final_lowerband = prev_final_lowerband.min(element.1.basic_lowerband); + } else { + final_lowerband = element.1.basic_lowerband; + } + + // set supertrend + if trend == -1 && element.0.close_price > prev_final_lowerband { + supertrend_data.area = SuperTrendArea::UP; + trend = 1; + } else if trend == 1 && element.0.close_price < prev_final_upperband { + supertrend_data.area = SuperTrendArea::DOWN; + trend = -1; + } + + if supertrend_data.area == SuperTrendArea::UP { + supertrend_data.band_value = final_upperband; + } else { + supertrend_data.band_value = final_lowerband; + } + + if trend == 1 && prev_trend == -1 { + supertrend_data.signal = Some(SuperTrendSignal::BUY); + } else if trend == -1 && prev_trend == 1 { + supertrend_data.signal = Some(SuperTrendSignal::SELL); + } else { + supertrend_data.signal = None; + } + + supertrend_data.close_time = element.1.close_time; + supertrend_vec.push(supertrend_data.clone()); + + prev_close_price = element.0.close_price; + prev_final_upperband = final_upperband; + prev_final_lowerband = final_lowerband; + prev_trend = trend; } - - if trend == 1 && prev_trend == -1 { - supertrend_data.signal = Some(String::from("BUY")); - } else if trend == -1 && prev_trend == 1 { - supertrend_data.signal = Some(String::from("SELL")); - } else { - supertrend_data.signal = None; - } - - supertrend_data.close_time = element.1.close_time; - supertrend_vec.push(supertrend_data.clone()); - - prev_close_price = element.0.close_price; - prev_final_upperband = final_upperband; - prev_final_lowerband = final_lowerband; - prev_trend = trend; - } - - Some(supertrend_vec) - } else { - None + let mut supertrend_data_wrapper_lock = supertrend_data_wrapper_arc_c.lock().await; + supertrend_data_wrapper_lock.insert(symbol_c, supertrend_vec.clone()); + })); } } - None => None, } + try_join_all(task_vec).await?; + let a = supertrend_data_wrapper_arc.lock().await.to_owned(); + Ok(a) }