use crate::value_estimation_team::{datapoints::price_data::CandleType, indicators::wiliams_percent_r}; use super::{ adx, dec, decimal_add, decimal_div, decimal_mul, decimal_sub, dema, duplicate_filter, ema, ema_macd, exists_record, get_current_price_decimal, get_server_epoch, heatmap_volume, insert_pre_suggested_coins, limit_order_sell, remove_keys, rsi, select_filled_buy_orders, stoch_rsi, supertrend, tema, try_join_all, update_record3, wiliams_percent_r, AdxData, AllData, Arc, BollingerBandData, Client, ClientBuilder, Decimal, DemaData, EmaData, ExchangeInfo, FilteredDataValue, HashMap, HashSet, HeatMapLevel, HeatmapVolumeData, MacdData, Mutex, RealtimePriceData, RoundingStrategy, RsiData, StochRsiData, SuperTrendArea, SuperTrendSignal, SupertrendData, TemaData, ToPrimitive, TradeFee, WiliamsPercentR, }; // BUY conditions pub async fn list_up_for_buy( 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()); // basic filtering: filtering valid trade pair let mut filtered_data: HashMap = HashMap::new(); for symbol in &alldata.valid_symbol_vec { filtered_data.insert(symbol.clone(), FilteredDataValue::new()); } // Heatmap volume: filtering close price with Extra High is over the previous candle from 30 previous candles let mut keys_to_remove: HashSet = HashSet::new(); let heatmap_volumes = heatmap_volume( 60, 60, 4.0, 2.5, 1.0, -0.5, &filtered_data, &alldata.rt_price_1m_vec, ) .await?; let server_epoch = get_server_epoch().await; for (symbol, values) in &mut filtered_data { let mut do_buy = false; if let (Some(heatmap_volume_vec), Some(rt_price_vec), Some(rt_price_vec_30m)) = (heatmap_volumes.get(symbol), alldata.rt_price_1m_vec.get(symbol), alldata.rt_price_30m_vec.get(symbol)) { if heatmap_volume_vec.len() > 100 && heatmap_volume_vec.last().unwrap().close_time > server_epoch && rt_price_vec.last().unwrap().close_time == heatmap_volume_vec.last().unwrap().close_time && heatmap_volume_vec[heatmap_volume_vec.len()-2].heatmap_level == HeatMapLevel::ExtraHigh && rt_price_vec[rt_price_vec.len()-2].candle_type == CandleType::DOWN { let current_price: Decimal = rust_decimal::prelude::FromPrimitive::from_f64( rt_price_vec_30m.last().unwrap().close_price, ) .unwrap(); values.closetime = heatmap_volume_vec.last().unwrap().close_time; values.current_price = current_price; do_buy = true; } } if do_buy == false { keys_to_remove.insert(symbol.clone()); } } remove_keys(&mut filtered_data, keys_to_remove).await; // Wiliams %R(30) wpr_n-1 < -60.0 // let mut keys_to_remove: HashSet = HashSet::new(); // let mut wprs30 = wiliams_percent_r(30, &alldata.rt_price_1m_vec, &filtered_data).await?; // for (symbol, values) in &mut filtered_data { // let mut do_buy = false; // if let Some(wpr30_vec) = wprs30.get(symbol) { // if wpr30_vec.len() > 15 // && wpr30_vec.last().unwrap().close_time > server_epoch // && wpr30_vec[wpr30_vec.len()-2].r_value < -60.0 // { // do_buy = true; // } // } // if do_buy == false { // keys_to_remove.insert(symbol.clone()); // } // } // remove_keys(&mut filtered_data, keys_to_remove).await; let final_filtered_data = duplicate_filter(10, &filtered_data).await?; if !final_filtered_data.is_empty() { insert_pre_suggested_coins(10, false, &final_filtered_data).await; } Ok(()) } pub async fn list_up_for_sell( all_data: &AllData, exchange_info_map: &HashMap, trade_fee_map: &HashMap, ) -> Result<(), Box> { let filled_buy_orders = select_filled_buy_orders(10).await?; if !filled_buy_orders.is_empty() { let client = ClientBuilder::new() .timeout(tokio::time::Duration::from_millis(5000)) .build() .unwrap(); let mut supertrend_vec: Vec = Vec::new(); 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_30m = // supertrend(10, 3.0, true, &all_data.rt_price_30m_vec, &filtered_symbols).await?; // let dema_30 = dema(30, &all_data.rt_price_1m_vec, &filtered_symbols).await?; // let dema_120 = dema(120, &all_data.rt_price_1m_vec, &filtered_symbols).await?; // let tema_100 = tema(100, &all_data.rt_price_30m_vec, &filtered_symbols).await?; for element in filled_buy_orders { let mut is_sell = false; let mut is_overturned = false; if element.used_usdt >= dec!(10.0) { // if let (Some(dema30_vec), Some(dema120_vec)) = ( // dema_30.get(&element.symbol), // dema_120.get(&element.symbol) // ) { // if dema120_vec.len() > 2 // && dema30_vec.len() > 2 // && dema120_vec.last().unwrap().close_time // == dema30_vec.last().unwrap().close_time // && dema30_vec.last().unwrap().close_time > server_epoch // && dema120_vec.last().unwrap().close_time > server_epoch // && ((dema120_vec.last().unwrap().dema_value // > dema30_vec.last().unwrap().dema_value) // && dema120_vec[dema120_vec.len() - 2].dema_value // < dema30_vec[dema30_vec.len() - 2].dema_value) // { // is_overturned = true; // } // } if let (Some(exchange_info), Some(tradefee)) = ( exchange_info_map.get(&element.symbol), trade_fee_map.get(&element.symbol), ) { // update 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::UP // && band_value > element.stoploss // { // let update_table_name = String::from("buy_ordered_coin_list"); // let update_value = vec![(String::from("stoploss"), band_value.to_string())]; // let update_condition = vec![(String::from("id"), element.id.to_string())]; // update_record3(&update_table_name, &update_value, &update_condition) // .await // .unwrap(); // } let lot_step_size = exchange_info.stepsize; let quote_commission_precision = exchange_info.quote_commission_precision; // TODO: BNB 코인이 있으면 // let base_qty_to_be_ordered = // element.base_qty_ordered.round_dp_with_strategy( // lot_step_size.normalize().scale(), // RoundingStrategy::ToZero, // ); // TODO: BNB 코인이 없으면 let base_qty_to_be_ordered = element.base_qty_fee_adjusted.round_dp_with_strategy( lot_step_size.normalize().scale(), RoundingStrategy::ToZero, ); // let target_profit_percent = decimal_mul( // decimal_div( // decimal_sub(element.target_price, element.buy_price), // element.buy_price, // ), // dec!(100), // ) // .to_f64() // .unwrap(); if !element.current_price.is_zero() { if element.pure_profit_percent >= 1.0 { is_sell = true; } else if element.pure_profit_percent <= -0.8 { is_sell = true; } else if is_overturned == true { is_sell = true; } else if server_epoch - element.transact_time >= (1_800_000) * 1 { // time up selling is_sell = true; } // 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; // } if is_sell == true { limit_order_sell( &element, element.current_price, base_qty_to_be_ordered, &client, &exchange_info_map, &trade_fee_map, ) .await; } } } } } } Ok(()) }