Implement entry positioning

This commit is contained in:
Sik Yoon 2024-05-20 03:16:21 +09:00
parent d8592f65f6
commit 0eca6971db
9 changed files with 2081 additions and 457 deletions

223
src/future/mod.rs Normal file
View File

@ -0,0 +1,223 @@
pub mod order;
pub mod table_mgmt;
use hmac_sha256::HMAC;
use crate::RunningMode::*;
use crate::{API_KEY, API_KEY_TESTNET, RUNNING_MODE, SECRET_KEY, SECRET_KEY_TESTNET, FUTURES_URL, FUTURES_URL_TEST};
use crate::strategy_team::{AllData, TimeData};
use crate::database_control::*;
use rust_decimal::Decimal;
use sqlx::FromRow;
use hex::ToHex;
use crate::future::table_mgmt::select_listuped_positions;
#[derive(Debug, PartialEq, Clone, sqlx::Type)]
pub enum Position {
#[sqlx(rename = "long")]
Long,
#[sqlx(rename = "short")]
Short
}
#[derive(Debug, PartialEq, Clone, sqlx::Type)]
pub enum Status {
#[sqlx(rename = "listup")]
Listup,
#[sqlx(rename = "filled")]
Filled,
#[sqlx(rename = "partially_filled")]
PartiallyFilled
}
impl std::fmt::Display for Position {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
pub struct EntryCoinInfo {
pub symbol: String,
pub close_time: i64,
pub registered_server_epoch: i64,
}
impl EntryCoinInfo {
fn new() -> EntryCoinInfo {
let a = EntryCoinInfo {
symbol: String::new(),
close_time: 0,
registered_server_epoch: 0
};
a
}
}
#[derive(Debug, Clone)]
pub enum ContractType {
Perpetual, // this is the target
CurrentMonth,
NextMonth,
CurrentQuarter,
NextQuarter,
PerpetualDelivering,
}
#[derive(Debug, Clone)]
pub enum ContractStatus {
PendingTrading, // this is the target
Trading,
PreDelivering,
Delivering,
Delivered,
PreSettle,
Settling,
Close,
}
#[derive(Debug, FromRow, Clone)]
pub struct FuturesExchangeInfo {
pub stepsize: Decimal,
pub ticksize: Decimal,
pub contract_type: ContractType,
pub contract_status: ContractStatus,
pub base_asset_precision: u32,
pub quote_precision: u32,
pub notional: Decimal
}
impl FuturesExchangeInfo {
fn new() -> FuturesExchangeInfo {
let a = FuturesExchangeInfo {
stepsize: Decimal::new(0, 8),
ticksize: Decimal::new(0, 8),
contract_type: ContractType::Perpetual,
contract_status: ContractStatus::Trading,
base_asset_precision: 0,
quote_precision: 0,
notional: Decimal::new(0, 8),
};
a
}
}
#[derive(Clone, Debug)]
pub struct FuturesTradeFee {
user_level: Option<i32>,
taker_fee_percent: Decimal,
maker_fee_percent: Decimal,
}
impl FuturesTradeFee {
pub fn new() -> FuturesTradeFee {
let a = FuturesTradeFee {
user_level: None,
taker_fee_percent: Decimal::new(0, 8),
maker_fee_percent: Decimal::new(0, 8),
};
a
}
}
#[derive(Debug, FromRow, Clone)]
pub struct PositionCoinList {
pub id: u64,
pub order_type: String,
pub status: String,
pub symbol: String,
pub order_id: u64,
pub position: Position,
pub registered_server_epoch: i64,
pub transact_time: i64,
pub close_time: i64,
pub used_usdt: Decimal,
pub expected_get_usdt: f64,
pub expected_usdt_profit: f64,
pub entry_price: Decimal,
pub current_price: Decimal,
pub base_qty_ordered: Decimal,
pub base_qty_fee_adjusted: Decimal,
pub pure_profit_percent: f64,
pub minimum_profit_percent: f64,
pub maximum_profit_percent: f64,
}
impl PositionCoinList {
fn new() -> PositionCoinList {
let a = PositionCoinList{
id: 0,
order_type: String::new(),
status: String::new(),
symbol: String::new(),
order_id: 0,
position: Position::Short,
registered_server_epoch: 0,
transact_time: 0,
close_time: 0,
used_usdt: Decimal::new(0, 8),
expected_get_usdt: 0.0,
expected_usdt_profit: 0.0,
entry_price: Decimal::new(0, 8),
current_price: Decimal::new(0, 8),
base_qty_ordered: Decimal::new(0, 8),
base_qty_fee_adjusted: Decimal::new(0, 8),
pure_profit_percent: 0.0,
minimum_profit_percent: 0.0,
maximum_profit_percent: 0.0,
};
a
}
}
async fn hmac_signature(query: &mut String) {
// fetch time information from [time] table
let table_name = String::from("time");
let columns = String::from("*");
let condition = None;
let mut time_info = TimeData {
server_epoch: 0,
local_epoch: 0,
epoch_difference: 0,
server_ymdhs: String::new(),
local_ymdhs: String::new(),
last_server_epoch: 0,
last_server_ymdhs: String::new(),
};
let select_result = select_record(&table_name, &columns, &condition, &time_info)
.await
.unwrap();
let difference_epoch = select_result.first().unwrap().epoch_difference;
let server_epoch = select_result.first().unwrap().server_epoch;
// build query message
// local 시간이 server 시간보다 너무 앞서거나 뒤쳐지면 USER_DATA를 요청할 수 없으므로 local과 server 의 시간 차이만큼을 local에서 빼어 보정한 뒤
// 이를 timestamp로 사용한다.
let mut timestamp;
if difference_epoch >= 0 {
timestamp = (server_epoch as u128 + difference_epoch as u128).to_string();
} else if difference_epoch < 0 {
timestamp = (server_epoch as u128 + (difference_epoch * -1) as u128).to_string();
} else {
timestamp = server_epoch.to_string();
}
let recv_window_size = "5000".to_string(); // default: 5,000ms, Max: 60,000ms
let mut query_build = String::from("&timestamp=");
query_build.push_str(&timestamp);
query_build.push_str("&recvWindow=");
query_build.push_str(&recv_window_size);
let mut secret_key = String::new();
unsafe {
if RUNNING_MODE == TEST {
secret_key.push_str(SECRET_KEY_TESTNET);
} else {
secret_key.push_str(SECRET_KEY);
}
}
query.push_str(&query_build);
let signature = HMAC::mac(&query.as_bytes(), secret_key.as_bytes());
query.push_str("&signature=");
query.push_str(signature.encode_hex::<String>().as_str());
}

1027
src/future/order.rs Normal file

File diff suppressed because it is too large Load Diff

314
src/future/table_mgmt.rs Normal file
View File

@ -0,0 +1,314 @@
use super::{hmac_signature, REAL, SIMUL, TEST, RUNNING_MODE, FUTURES_URL, FUTURES_URL_TEST, API_KEY, API_KEY_TESTNET, Position, FuturesExchangeInfo, PositionCoinList, FuturesTradeFee};
use std::collections::HashMap;
use rust_decimal::{Decimal, RoundingStrategy};
use crate::database_control::*;
use crate::decimal_funcs::*;
use rust_decimal_macros::dec;
use rust_decimal::prelude::ToPrimitive;
use serde_json::Value;
use reqwest::{Client, ClientBuilder};
pub async fn get_tradefee_balance(future_trade_fee: &mut FuturesTradeFee, client: &Client) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// building URL and API-keys
let mut url = String::new();
let mut api_key = String::new();
unsafe {
if RUNNING_MODE == TEST {
url.push_str(FUTURES_URL_TEST);
api_key = API_KEY_TESTNET.to_string();
} else {
url.push_str(FUTURES_URL);
api_key = API_KEY.to_string();
}
}
let endpoint_url = "/fapi/v2/account?";
url.push_str(endpoint_url);
let mut url_build = String::new();
hmac_signature(&mut url_build).await;
url.push_str(&url_build);
let res = client
.get(&url)
.header("X-MBX-APIKEY", api_key)
.send()
.await?;
let body = res.text_with_charset("utf-8").await?;
// deserialize JSON
let v = serde_json::from_str::<Value>(body.as_str());
match v {
Ok(T) => {
if let (Some(fee_tier), Some(available_balance)) = (T.get("feeTier"), T.get("availableBalance")) {
let update_table_name = String::from("future_available_balance");
let update_values = vec![
(String::from("available_usdt"), available_balance.as_str().unwrap().to_string()),
];
let update_condition = vec![(String::from("id"), String::from("1"))];
update_record2(&update_table_name, &update_values, &update_condition)
.await
.unwrap();
match fee_tier.as_number().unwrap().as_i64() {
Some(0) => { // Regular User
future_trade_fee.user_level = Some(0);
future_trade_fee.maker_fee_percent = dec!(0.0200);
future_trade_fee.taker_fee_percent = dec!(0.0500);
},
Some(1) => { // VIP1
future_trade_fee.user_level = Some(1);
future_trade_fee.maker_fee_percent = dec!(0.0160);
future_trade_fee.taker_fee_percent = dec!(0.0400);
},
Some(2) => {// VIP 2
future_trade_fee.user_level = Some(2);
future_trade_fee.maker_fee_percent = dec!(0.0140);
future_trade_fee.taker_fee_percent = dec!(0.0350);
},
Some(3) => {// VIP 3
future_trade_fee.user_level = Some(3);
future_trade_fee.maker_fee_percent = dec!(0.0120);
future_trade_fee.taker_fee_percent = dec!(0.0320);
},
Some(4) => {// VIP 4
future_trade_fee.user_level = Some(4);
future_trade_fee.maker_fee_percent = dec!(0.0100);
future_trade_fee.taker_fee_percent = dec!(0.0300);
},
Some(5) => {// VIP 5
future_trade_fee.user_level = Some(5);
future_trade_fee.maker_fee_percent = dec!(0.0080);
future_trade_fee.taker_fee_percent = dec!(0.0270);
},
Some(6) => {// VIP 6
future_trade_fee.user_level = Some(6);
future_trade_fee.maker_fee_percent = dec!(0.0060);
future_trade_fee.taker_fee_percent = dec!(0.0250);
},
Some(7) => {// VIP 7
future_trade_fee.user_level = Some(7);
future_trade_fee.maker_fee_percent = dec!(0.0040);
future_trade_fee.taker_fee_percent = dec!(0.0220);
},
Some(8) => {// VIP 8
future_trade_fee.user_level = Some(8);
future_trade_fee.maker_fee_percent = dec!(0.0020);
future_trade_fee.taker_fee_percent = dec!(0.0200);
},
Some(9) => {// VIP 9
future_trade_fee.user_level = Some(9);
future_trade_fee.maker_fee_percent = dec!(0.0000);
future_trade_fee.taker_fee_percent = dec!(0.0170);
},
Some(_) => {},
None => {}
}
log::info!("fee_tier: {:?}. available_balance: {:?}", fee_tier, available_balance);
} else {
log::error!("Endpoint(/fapi/v1/leverage?) output changed!");
return Err("Err")?;
}
}
Err(e) => {
log::warn!("account information failed!: {}, {}", body, e);
}
}
Ok(())
}
pub async fn update_price_of_filled_positions(
coin_price_map: &HashMap<String, f64>,
exchange_info_map: &HashMap<String, FuturesExchangeInfo>,
futures_trade_fee: &FuturesTradeFee,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let filled_buy_orders = select_filled_positions().await?;
if !filled_buy_orders.is_empty() {
// 심볼들을 5개씩 청스로 나누어 테스크를 생성하고 각 태스크 별로 병렬로 처리한다.
// update real-time current price to each record through chunks
let chunks: std::slice::Chunks<'_, PositionCoinList> = filled_buy_orders.chunks(5);
let mut task_vec = Vec::new();
for chunk in chunks {
let chunk_vec = chunk.to_vec();
let coin_price_vec_c = coin_price_map.clone();
let exchange_info_vec_c = exchange_info_map.clone();
let futures_trade_fee_map_c = futures_trade_fee.clone();
task_vec.push(tokio::spawn(async move {
update_repeat_task(
chunk_vec,
&coin_price_vec_c,
&exchange_info_vec_c,
&futures_trade_fee_map_c,
)
.await;
}));
}
}
Ok(())
}
async fn update_repeat_task(
buy_ordered_coin_vec: Vec<PositionCoinList>,
coin_price_map: &HashMap<String, f64>,
exchange_info_map: &HashMap<String, FuturesExchangeInfo>,
futures_trade_fee: &FuturesTradeFee,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let update_table_name = String::from("future_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 update_colums = vec![
"current_price",
"expected_get_usdt",
"expected_usdt_profit",
"pure_profit_percent",
"minimum_profit_percent",
"maximum_profit_percent",
];
let mut update_record_build: Vec<String> = Vec::new();
let mut update_record: Vec<Vec<String>> = Vec::new();
// update current_price, expected__get_usdt, expected_usdt_profit, pure_profit_percent, minimum_profit_percent, maximum_profit_percent
for element in buy_ordered_coin_vec {
// build update values
update_record_build.clear();
if coin_price_map.contains_key(&element.symbol)
&& exchange_info_map.contains_key(&element.symbol)
&& futures_trade_fee.user_level.is_some()
{
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 = futures_trade_fee.maker_fee_percent;
let lot_step_size = exchange_info_map.get(&element.symbol).unwrap().stepsize;
let quote_precision = exchange_info_map
.get(&element.symbol)
.unwrap()
.quote_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_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 mut pure_profit_percent;
if element.position == Position::Long {
pure_profit_percent = ((expected_get_usdt.to_f64().unwrap()
/ element.used_usdt.to_f64().unwrap())
- 1.0)
* 100.0;
} else {
pure_profit_percent = ((expected_get_usdt.to_f64().unwrap()
/ element.used_usdt.to_f64().unwrap())
- 1.0)
* -100.0;
}
pure_profit_percent = (pure_profit_percent * 100.0).round() / 100.0; // Rounding
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());
// 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
}
update_record.push(update_record_build.clone());
}
}
}
update_records(&update_table_name, &update_record, &update_colums).await;
Ok(())
}
pub async fn select_filled_positions() -> Result<Vec<PositionCoinList>, Box<dyn std::error::Error + Send + Sync>> {
let select_table_name = String::from("future_ordered_coin_list");
let select_columns = String::from("*");
let mut select_condition_build = String::from("WHERE (status = 'FILLED' or status = 'SIMUL')");
let select_condition = Some(select_condition_build);
let data_struct = PositionCoinList::new();
let select_result = try_select_record(
&select_table_name,
&select_columns,
&select_condition,
&data_struct,
)
.await;
if select_result.is_ok() {
Ok(select_result.unwrap())
} else {
eprint!("select_filled_positions() error!");
Err("error")?
}
}
pub async fn select_listuped_positions() -> Result<Vec<PositionCoinList>, Box<dyn std::error::Error + Send + Sync>> {
let select_table_name = String::from("future_ordered_coin_list");
let select_columns = String::from("*");
let mut select_condition_build = String::from("WHERE status = 'LISTUP'");
let select_condition = Some(select_condition_build);
let data_struct = PositionCoinList::new();
let select_result = try_select_record(
&select_table_name,
&select_columns,
&select_condition,
&data_struct,
)
.await;
if select_result.is_ok() {
Ok(select_result.unwrap())
} else {
eprint!("select_filled_positions() error!");
Err("error")?
}
}

View File

@ -14,12 +14,16 @@ use rust_decimal_macros::dec;
use sqlx::FromRow;
use std::{io, io::Write, path::Path, process::Stdio};
use tokio::{fs::*, io::ErrorKind, process::Command, task::JoinHandle, time::*};
use crate::future::order::*;
use crate::future::FuturesExchangeInfo;
use std::collections::HashMap;
const STRATEGIST_NUMBER: u32 = 16;
pub async fn initialization() {
println!("- - - initialization start - - -");
// initialize_webdriver().await;
future_setup().await;
initialize_database().await;
println!("- - - initialization done - - -");
}
@ -65,6 +69,27 @@ async fn initialize_webdriver() {
println!("Ok");
}
async fn future_setup() {
print!(">>> Check future setting...");
let client = ClientBuilder::new()
.timeout(tokio::time::Duration::from_millis(700))
.build()
.unwrap();
let mut future_exchange_info_map: HashMap<String, FuturesExchangeInfo> = HashMap::new();
request_future_exchange_infomation(&client, &mut future_exchange_info_map).await;
for (symbol, futures_exchange_info) in future_exchange_info_map {
set_margin_type(&symbol, MarginType::Isolated, &client).await; // Default: Isolated
set_initial_leverage(&symbol, 1, &client).await; // Default: x1
}
set_position_mode(&client).await; // Default: One-way Mode
set_asset_mode(&client).await; // Default: Single-Asset Mode
print!("Ok");
io::stdout().flush();
}
async fn initialize_database() {
let client = ClientBuilder::new()
.timeout(tokio::time::Duration::from_millis(700))
@ -1226,6 +1251,90 @@ async fn initialize_database() {
println!("Ok");
}
{
// future_available_balance
print!("table 'future_available_balance'...");
io::stdout().flush();
let table_name = String::from("future_available_balance");
let exists_result = exists_table(&table_name).await;
let initial_table = vec![
("id", "integer", Some("PK, AI, UN")),
("available_usdt", "decimal(16,8)", None),
];
let table_condition = None;
if exists_result == false {
let mut result = new_table(&table_name, &initial_table, &table_condition).await;
if result.is_err() {
loop {
result = new_table(&table_name, &initial_table, &table_condition).await;
if result.is_ok() {
break;
}
sleep(Duration::from_millis(10)).await;
}
}
} else {
delete_all_rows(&table_name)
.await
.expect("Failed to delete rows!");
}
let initial_columns = vec!["available_usdt"];
let initial_values = vec![String::from("0.0")];
insert_one_record(&table_name, &initial_columns, &initial_values)
.await
.expect("Failed to insert initial record!");
println!("Ok");
}
{
// future_ordered_coin_list
print!("table 'future_ordered_coin_list'...");
io::stdout().flush();
let table_name = String::from("future_ordered_coin_list");
let exists_result = exists_table(&table_name).await;
let initial_table = vec![
("id", "integer", Some("PK, AI, UN")),
("order_type", "char(20)", None), // POSITIONING, CLOSING
("status", "char(20)", None), // LISTUP, FILLED, PARTIALLY_FILLED
("symbol", "char(20)", None),
("order_id", "bigint", Some("UN")),
("position", "char(20)", None),
("registered_server_epoch", "bigint", None),
("transact_time", "bigint", None),
("close_time", "bigint", None),
("used_usdt", "decimal(16,8)", None),
("expected_get_usdt", "double", None),
("expected_usdt_profit", "double", None),
("entry_price", "decimal(16,8)", None),
("current_price", "decimal(16,8)", None),
("base_qty_ordered", "decimal(16,8)", None),
("base_qty_fee_adjusted", "decimal(16,8)", None),
("pure_profit_percent", "double", None),
("minimum_profit_percent", "double", None),
("maximum_profit_percent", "double", None),
];
let table_condition = None;
if exists_result == false {
let mut result = new_table(&table_name, &initial_table, &table_condition).await;
if result.is_err() {
loop {
result = new_table(&table_name, &initial_table, &table_condition).await;
if result.is_ok() {
break;
}
sleep(Duration::from_millis(10)).await;
}
}
}
println!("Ok");
}
{
// buy_ordered_coin_list
print!("table 'buy_ordered_coin_list'...");

View File

@ -14,8 +14,8 @@ pub const SECRET_KEY_TESTNET: &str =
// URL
pub const URL_TEST: &str = "https://testnet.binance.vision";
pub const URL: &str = "https://api1.binance.com";
pub const FUTURE_URL_TEST: &str = "https://testnet.binancefuture.com";
pub const FUTURE_URL: &str = "https://fapi.binance.com";
pub const FUTURES_URL_TEST: &str = "https://testnet.binancefuture.com";
pub const FUTURES_URL: &str = "https://fapi.binance.com";
// Select program mode
#[derive(PartialEq)]

View File

@ -14,6 +14,7 @@ use reqwest::{Client, ClientBuilder};
use rust_decimal::Decimal;
use simple_logger::set_up_color_terminal;
use sqlx::{mysql::*, Connection, Executor, FromRow, Row};
use tradingbot::future::{FuturesExchangeInfo, FuturesTradeFee};
use std::collections::{HashMap, HashSet};
use std::{
io::{self, Write},
@ -24,6 +25,23 @@ use tradingbot::{RunningMode::*, *};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// future last price data
let mut future_price_map: HashMap<String, f64> = HashMap::new(); // <symbol, price>
let (tx_future_price_map, mut rx_future_price_map) = watch::channel(future_price_map);
let mut rx2_future_price_map = rx_future_price_map.clone();
let mut futures_exchange_info_map: HashMap<String, FuturesExchangeInfo> = HashMap::new();
let (tx_futures_exchange_info, mut rx_futures_exchange_info) = watch::channel(futures_exchange_info_map);
let mut rx2_futures_exchange_info = rx_futures_exchange_info.clone();
let mut rx3_futures_exchange_info = rx_futures_exchange_info.clone();
let mut futures_trade_fee = FuturesTradeFee::new();
let (tx_futures_trade_fee, mut rx_futures_trade_fee) = watch::channel(futures_trade_fee);
let mut rx2_futures_trade_fee = rx_futures_trade_fee.clone();
let mut rx3_futures_trade_fee = rx_futures_trade_fee.clone();
// parse argument and set program preference
program_setting();
@ -116,6 +134,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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_map: HashMap<String, Vec<CandleData>> = HashMap::new(); // <symbol, Vec<CandleData>>
let (tx_candle_1m_map, mut rx_candle_1m_map) = watch::channel(candle_1m_map);
@ -921,8 +941,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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();
// future exchange info
let futures_exchange_info = rx3_futures_exchange_info.borrow().clone();
let result =
strategy_team::strategy_manager::execute_list_up_for_buy(&all_data).await;
strategy_team::strategy_manager::execute_list_up_for_buy(&all_data, &futures_exchange_info).await;
match result {
Ok(T) => {
@ -1354,6 +1376,198 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
});
// Futures Section
// Task#XX: get future last price
tokio::task::spawn(async move {
sleep(Duration::from_secs(20)).await;
let mut elapsed_time = 0;
loop {
let instant = Instant::now();
let client = ClientBuilder::new()
.timeout(tokio::time::Duration::from_millis(1000))
.build()
.unwrap();
let mut future_price_map_temp: HashMap<String, f64> = HashMap::new();
future::order::get_last_price(&client, &mut future_price_map_temp).await;
if future_price_map_temp.len() != 0 {
tx_future_price_map.send_modify(|vec| *vec = future_price_map_temp);
}
}
if 333 > elapsed_time {
sleep(Duration::from_millis((333 - elapsed_time) as u64)).await;
}
});
// Task#XX: get future exchange information
tokio::task::spawn(async move {
sleep(Duration::from_secs(20)).await;
let client = ClientBuilder::new()
.timeout(Duration::from_millis(1000))
.build()
.unwrap();
let mut elapsed_time = 0;
loop {
let instant = Instant::now();
let mut futures_exchange_info_map_temp: HashMap<String, FuturesExchangeInfo> = HashMap::new();
let result = future::order::request_future_exchange_infomation(
&client,
&mut futures_exchange_info_map_temp,
)
.await;
if tx_futures_exchange_info.is_closed() {
log::error!("tx_futures_exchange_info has been closed!");
} else {
if futures_exchange_info_map_temp.len() != 0 {
tx_futures_exchange_info.send_modify(|map| *map = futures_exchange_info_map_temp);
}
}
// send Task#0 a message to notify running on
match result {
Ok(T) => {
}
Err(E) => {}
}
// sleep as much as the loop recurs per 10 second if all operation finished within 10 second.
elapsed_time = instant.elapsed().as_millis();
if 10000 > elapsed_time {
sleep(Duration::from_millis((10000 - elapsed_time) as u64)).await;
}
}
});
// Task#XX: get futures trade fee and available balance(USDT)
tokio::task::spawn(async move {
sleep(Duration::from_secs(3)).await;
let mut elapsed_time = 0;
loop {
let instant = Instant::now();
let client = ClientBuilder::new()
.timeout(tokio::time::Duration::from_millis(1000))
.build()
.unwrap();
let mut futures_trade_fee_temp = FuturesTradeFee::new();
future::table_mgmt::get_tradefee_balance(&mut futures_trade_fee_temp, &client).await;
tx_futures_trade_fee.send_modify(|vec| *vec = futures_trade_fee_temp);
}
if 2000 > elapsed_time {
sleep(Duration::from_millis((2000 - elapsed_time) as u64)).await;
}
});
// Task#XX: update price of filled positions
tokio::task::spawn(async move {
sleep(Duration::from_secs(20)).await;
let mut elapsed_time = 0;
loop {
let instant = Instant::now();
let coin_price_map = rx_future_price_map.borrow().clone();
let futures_exchange_info_map = rx_futures_exchange_info.borrow().clone();
let future_trade_fee = rx_futures_trade_fee.borrow().clone();
let result = future::table_mgmt::update_price_of_filled_positions(
&coin_price_map,
&futures_exchange_info_map,
&future_trade_fee,
)
.await;
// send Task#0 a message to notify running on
match result {
Ok(T) => {
}
Err(E) => {}
}
// sleep as much as the loop recurs per 1 second if all operation finished within 1 second.
elapsed_time = instant.elapsed().as_millis();
if 100 > elapsed_time {
sleep(Duration::from_millis((100 - elapsed_time) as u64)).await;
}
}
});
// Task#XX: monitoring ordered positions
tokio::task::spawn(async move {
sleep(Duration::from_secs(20)).await;
let mut elapsed_time = 0;
loop {
let instant = Instant::now();
let client = ClientBuilder::new()
.timeout(tokio::time::Duration::from_millis(1000))
.build()
.unwrap();
let future_trade_fee = rx2_futures_trade_fee.borrow().clone();
let result = future::order::monitoring_unfilled_order(
&client,
&future_trade_fee,
)
.await;
// send Task#0 a message to notify running on
match result {
Ok(T) => {
}
Err(E) => {}
}
// sleep as much as the loop recurs per 1 second if all operation finished within 1 second.
elapsed_time = instant.elapsed().as_millis();
if 100 > elapsed_time {
sleep(Duration::from_millis((100 - elapsed_time) as u64)).await;
}
}
});
// Task#XX: monitoring ordered positions
tokio::task::spawn(async move {
sleep(Duration::from_secs(20)).await;
let mut elapsed_time = 0;
loop {
let instant = Instant::now();
let coin_price_map = rx2_future_price_map.borrow().clone();
let futures_exchange_info_map = rx2_futures_exchange_info.borrow().clone();
let future_trade_fee = rx3_futures_trade_fee.borrow().clone();
let result = future::order::entry_position(
&coin_price_map,
&futures_exchange_info_map,
&future_trade_fee,
)
.await;
// send Task#0 a message to notify running on
match result {
Ok(T) => {
}
Err(E) => {}
}
// sleep as much as the loop recurs per 1 second if all operation finished within 1 second.
elapsed_time = instant.elapsed().as_millis();
if 50 > elapsed_time {
sleep(Duration::from_millis((50 - elapsed_time) as u64)).await;
}
}
});
loop {}
Ok(())

View File

@ -1,4 +1,4 @@
use crate::value_estimation_team::indicators::wiliams_percent_r;
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,
@ -8,12 +8,16 @@ use super::{
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
SupertrendData, TemaData, ToPrimitive, TradeFee, WiliamsPercentR, future_duplicate_filter, insert_future_coins
};
use crate::future::{Position, FuturesExchangeInfo};
use crate::future::table_mgmt::select_filled_positions;
// BUY conditions
pub async fn list_up_for_buy(
alldata: &AllData,
future_exchange_info_map: &HashMap<String, FuturesExchangeInfo>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// print rt_price for debugging
// let a = alldata.rt_price_30m_vec.iter().position(|a| a.0 == "BTCUSDT");
@ -25,481 +29,113 @@ pub async fn list_up_for_buy(
filtered_data.insert(symbol.clone(), FilteredDataValue::new());
}
// current Tema(3) > current Tema(10)
// let mut keys_to_remove: HashSet<String> = HashSet::new();
// let tema_3 = tema(3, &alldata.rt_price_1d_vec, &filtered_data).await?;
// let tema_10 = tema(10, &alldata.rt_price_1d_vec, &filtered_data).await?;
// let server_epoch = get_server_epoch().await;
// for (symbol, values) in &mut filtered_data {
// if let (Some(tema5_vec), Some(tema10_vec)) = (tema_3.get(symbol), tema_10.get(symbol)) {
// if tema5_vec.len() > 2
// && tema10_vec.len() > 2
// && tema5_vec.last().unwrap().close_time == tema10_vec.last().unwrap().close_time
// && tema5_vec.last().unwrap().close_time > server_epoch
// && tema10_vec.last().unwrap().close_time > server_epoch
// {
// if tema5_vec.last().unwrap().tema_value > tema10_vec.last().unwrap().tema_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;
// Wiliams %R(200) > -20.0
// Wiliams %R(30) > -20.0
let mut keys_to_remove: HashSet<String> = HashSet::new();
let mut wprs200 = wiliams_percent_r(200, &alldata.rt_price_30m_vec, &filtered_data).await?;
let mut wprs30 = wiliams_percent_r(30, &alldata.rt_price_30m_vec, &filtered_data).await?;
let server_epoch = get_server_epoch().await;
for (symbol, values) in &mut filtered_data {
if let (Some(wpr200_vec), Some(wpr30_vec)) = (wprs200.get(symbol), wprs30.get(symbol)) {
if wpr200_vec.len() > 15
&& wpr30_vec.len() > 15
&& wpr200_vec.last().unwrap().close_time > server_epoch
&& wpr200_vec.last().unwrap().r_value > -20.0
&& wpr30_vec.last().unwrap().close_time > server_epoch
&& wpr30_vec.last().unwrap().r_value > -20.0
{
} else {
keys_to_remove.insert(symbol.clone());
}
} else {
keys_to_remove.insert(symbol.clone());
}
}
remove_keys(&mut filtered_data, keys_to_remove).await;
// current Tema(300) > current Tema(200) > current Tema(100)
let mut keys_to_remove: HashSet<String> = HashSet::new();
let tema_100 = tema(100, &alldata.rt_price_30m_vec, &filtered_data).await?;
let tema_200 = tema(200, &alldata.rt_price_30m_vec, &filtered_data).await?;
let tema_300 = tema(300, &alldata.rt_price_30m_vec, &filtered_data).await?;
let server_epoch = get_server_epoch().await;
for (symbol, values) in &mut filtered_data {
if let (Some(tema100_vec), Some(tema200_vec), Some(tema300_vec)) = (
tema_100.get(symbol),
tema_200.get(symbol),
tema_300.get(symbol),
) {
if (tema100_vec.len() > 15 && tema200_vec.len() > 15 && tema300_vec.len() > 15)
&& tema100_vec.last().unwrap().close_time == tema200_vec.last().unwrap().close_time
&& tema200_vec.last().unwrap().close_time == tema300_vec.last().unwrap().close_time
&& tema100_vec.last().unwrap().close_time > server_epoch
{
if tema100_vec.last().unwrap().tema_value > tema300_vec.last().unwrap().tema_value
&& tema200_vec.last().unwrap().tema_value
< tema300_vec.last().unwrap().tema_value
&& tema200_vec.last().unwrap().tema_value
> tema100_vec.last().unwrap().tema_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;
// supertrend(ATR period 10, multiplier: 3.0, 30m close price)
// let mut keys_to_remove: HashSet<String> = HashSet::new();
// let server_epoch = get_server_epoch().await;
// let supertrend_30m_map =
// supertrend(10, 3.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 stoploss, target_price
// let band_value: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(
// supertrend_vec.last().unwrap().band_value,
// )
// .unwrap();
// let open_price: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(
// rt_price_vec.last().unwrap().open_price,
// )
// .unwrap();
// let current_price: Decimal = rust_decimal::prelude::FromPrimitive::from_f64(
// rt_price_vec.last().unwrap().close_price,
// )
// .unwrap();
// if supertrend_vec.last().unwrap().area == SuperTrendArea::UP
// && band_value < current_price
// && band_value < open_price
// {
// values.current_price = current_price;
// values.closetime = rt_price_vec.last().unwrap().close_time;
// values.stoploss = band_value;
// values.target_price = decimal_add(
// decimal_mul(decimal_sub(current_price, values.stoploss), dec!(10.0)),
// current_price,
// );
// } else if supertrend_vec.last().unwrap().area == SuperTrendArea::DOWN
// && band_value > current_price
// && band_value > open_price
// {
// values.current_price = current_price;
// values.closetime = rt_price_vec.last().unwrap().close_time;
// values.stoploss = decimal_sub(open_price, decimal_sub(band_value, open_price));
// values.target_price = decimal_add(
// decimal_mul(decimal_sub(open_price, values.stoploss), dec!(10.0)),
// current_price,
// );
// } 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;
// current ADX(15, 15) < 25
// let mut keys_to_remove: HashSet<String> = HashSet::new();
// let adx_vec = adx(15, 15, &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.len() > 10 &&
// adx_vec[last_idx].adx < 25.0 {
// } 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;
// StochRSI (RSI_len: 200, StochRSI_len: 200, K: 3, D: 3) K_current < 70, K_prev < 70, K_prev_1 < 70
// let mut keys_to_remove: HashSet<String> = HashSet::new();
// let stoch_rsis = stoch_rsi(200, 200, 3, 3, &alldata.rt_price_30m_vec, &filtered_data).await?;
// for (symbol, values) in &mut filtered_data {
// if 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 stoch_rsi_vec.len() > 10
// && search_result.is_some_and(|a| {
// stoch_rsi_vec[a].k < 70.0
// && stoch_rsi_vec[a - 1].k < 70.0
// && stoch_rsi_vec[a - 2].k < 70.0
// })
// {
// } else {
// keys_to_remove.insert(symbol.clone());
// }
// } else {
// keys_to_remove.insert(symbol.clone());
// }
// }
// remove_keys(&mut filtered_data, keys_to_remove).await;
// Heatmap volume: filtering close price with Extra High is over the previous candle from 30 previous candles
let mut keys_to_remove: HashSet<String> = HashSet::new();
let heatmap_volumes = heatmap_volume(
30,
30,
60,
60,
4.0,
2.5,
1.0,
-0.5,
&filtered_data,
&alldata.rt_price_30m_vec,
&alldata.rt_price_1m_vec,
)
.await?;
for (symbol, values) in &mut filtered_data {
if let Some(heatmap_volume_vec) = heatmap_volumes.get(symbol) {
if heatmap_volume_vec.len() > 50 {
let heatmap_volume_trunc = heatmap_volume_vec
.get(heatmap_volume_vec.len() - 31..heatmap_volume_vec.len() - 1)
.unwrap();
let windows = heatmap_volume_trunc.windows(2);
for slice in windows {
if slice[1].heatmap_level == HeatMapLevel::ExtraHigh {
if let (prev_candle_idx, current_candle_idx) = (
(&alldata
.rt_price_30m_vec
.get(symbol)
.unwrap()
.iter()
.position(|x| x.close_time == slice[0].close_time))
.unwrap(),
(&alldata
.rt_price_30m_vec
.get(symbol)
.unwrap()
.iter()
.position(|x| x.close_time == slice[1].close_time))
.unwrap(),
) {
let prev_candle =
&alldata.rt_price_30m_vec.get(symbol).unwrap()[prev_candle_idx];
let current_candle =
&alldata.rt_price_30m_vec.get(symbol).unwrap()[current_candle_idx];
if current_candle.close_price < prev_candle.close_price
|| current_candle.close_price < prev_candle.open_price
|| current_candle.close_price < prev_candle.high_price
|| current_candle.close_price < prev_candle.low_price
{
keys_to_remove.insert(symbol.clone());
}
}
}
}
}
}
}
remove_keys(&mut filtered_data, keys_to_remove).await;
// limit buy price: 0.25 * abs(이전 5 개 중 최대값 제거 한 opclo 값 평균 - 현재 open 값) + 현재 open 값 > current_price
let mut keys_to_remove: HashSet<String> = 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_30m_vec.get(symbol) {
if rt_price_vec.last().unwrap().close_time > server_epoch && rt_price_vec.len() >= 6 {
let mut opclo_vec: Vec<f64> = 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 + (0.5 * difference) {
} else {
keys_to_remove.insert(symbol.clone());
}
} else {
keys_to_remove.insert(symbol.clone());
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;
}
} else {
}
if do_buy == false {
keys_to_remove.insert(symbol.clone());
}
}
remove_keys(&mut filtered_data, keys_to_remove).await;
if filtered_data.keys().len() != 0 {
let date_now = chrono::Local::now().to_rfc2822();
println!("{} future coins: {:?}", date_now, filtered_data.keys());
}
// let final_filtered_data = duplicate_filter(8, &filtered_data).await?;
// insert_pre_suggested_coins(8, false, &final_filtered_data, &alldata).await;
let final_filtered_data = future_duplicate_filter(&filtered_data, &future_exchange_info_map).await?;
insert_future_coins(Position::Short, server_epoch, &final_filtered_data).await?;
Ok(())
}
pub async fn list_up_for_sell(
all_data: &AllData,
exchange_info_map: &HashMap<String, ExchangeInfo>,
trade_fee_map: &HashMap<String, TradeFee>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let filled_buy_orders = select_filled_buy_orders(8).await?;
// pub async fn list_up_for_sell(
// all_data: &AllData,
// exchange_info_map: &HashMap<String, ExchangeInfo>,
// trade_fee_map: &HashMap<String, TradeFee>,
// ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// let filled_positions = select_filled_positions().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<SupertrendData> = Vec::new();
let server_epoch = get_server_epoch().await;
let mut filtered_symbols: HashMap<String, FilteredDataValue> = HashMap::new();
for element in &filled_buy_orders {
filtered_symbols.insert(element.symbol.clone(), FilteredDataValue::new());
}
let supertrend_30m =
supertrend(10, 2.0, true, &all_data.rt_price_30m_vec, &filtered_symbols).await?;
let tema_300 = tema(300, &all_data.rt_price_30m_vec, &filtered_symbols).await?;
let tema_200 = tema(200, &all_data.rt_price_30m_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(tema300_vec), Some(tema200_vec), Some(tema100_vec)) = (
tema_300.get(&element.symbol),
tema_200.get(&element.symbol),
tema_100.get(&element.symbol),
) {
if tema200_vec.len() > 2
&& tema100_vec.len() > 2
&& tema300_vec.len() > 2
&& tema200_vec.last().unwrap().close_time
== tema100_vec.last().unwrap().close_time
&& tema300_vec.last().unwrap().close_time
== tema100_vec.last().unwrap().close_time
&& tema300_vec.last().unwrap().close_time > server_epoch
&& tema200_vec.last().unwrap().close_time > server_epoch
&& tema100_vec.last().unwrap().close_time > server_epoch
&& ((tema200_vec.last().unwrap().tema_value
> tema100_vec.last().unwrap().tema_value
&& tema200_vec[tema200_vec.len() - 2].tema_value
< tema100_vec[tema100_vec.len() - 2].tema_value)
|| (tema200_vec.last().unwrap().tema_value
> tema300_vec.last().unwrap().tema_value
&& tema200_vec[tema200_vec.len() - 2].tema_value
< tema300_vec[tema300_vec.len() - 2].tema_value))
{
is_overturned = true;
}
}
// let client = ClientBuilder::new()
// .timeout(tokio::time::Duration::from_millis(5000))
// .build()
// .unwrap();
// let server_epoch = get_server_epoch().await;
if let (Some(exchange_info), Some(tradefee), Some(supertrend_vec)) = (
exchange_info_map.get(&element.symbol),
trade_fee_map.get(&element.symbol),
supertrend_30m.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();
}
// for element in filled_positions {
// let mut is_sell = false;
let lot_step_size = exchange_info.stepsize;
let quote_commission_precision = exchange_info.quote_commission_precision;
// if element.used_usdt >= dec!(10.0) {
// if let (Some(exchange_info), Some(tradefee)) = (
// exchange_info_map.get(&element.symbol),
// trade_fee_map.get(&element.symbol),
// ) {
// let lot_step_size = exchange_info.stepsize;
// let quote_commission_precision = exchange_info.quote_commission_precision;
// TODO: BNB 코인이 있으면
// let base_qty_to_be_ordered =
// element.base_qty_ordered.round_dp_with_strategy(
// lot_step_size.normalize().scale(),
// RoundingStrategy::ToZero,
// );
// TODO: BNB 코인이 없으면
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.current_price <= element.stoploss {
is_sell = true;
} else if element.current_price >= element.target_price {
is_sell = true;
} else if server_epoch - element.transact_time >= (1_800_000) * 5
&& is_overturned == true
{
is_sell = true;
}
// // 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 minimum_candles = 5;
let maximum_candles = 240;
for count_candles in minimum_candles..=maximum_candles {
if count_candles < maximum_candles
&& server_epoch - element.transact_time
> (1_800_000) * count_candles
&& (target_profit_percent != 0.0
&& target_profit_percent.is_sign_positive()
&& target_profit_percent
* ((maximum_candles - count_candles) as f64
/ (maximum_candles - minimum_candles + 1) as f64)
<= element.pure_profit_percent)
{
is_sell = true;
break;
} else {
break;
}
}
// if !element.current_price.is_zero() {
// if element.pure_profit_percent >= 1.0 {
// is_sell = true;
// } else if element.pure_profit_percent <= -0.8 {
// is_sell = true;
// } else if server_epoch - element.transact_time >= (1_800_000) * 1 {
// // time up selling
// is_sell = true;
// }
// if is_sell == true {
// limit_order_sell(
// &element,
// element.current_price,
// base_qty_to_be_ordered,
// &client,
// &exchange_info_map,
// &trade_fee_map,
// )
// .await;
// }
// }
// }
// }
// }
if server_epoch - element.transact_time >= (1_800_000) * maximum_candles {
// 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(())
}
// Ok(())
// }

View File

@ -42,8 +42,9 @@ use rust_decimal_macros::dec;
use sqlx::FromRow;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use strategy_manager::insert_pre_suggested_coins;
use strategy_manager::{insert_pre_suggested_coins, insert_future_coins};
use tokio::sync::Mutex;
use crate::future::FuturesExchangeInfo;
#[derive(Debug, Clone)]
pub struct AllData {
@ -154,6 +155,48 @@ pub async fn duplicate_filter(
Ok(filtered_data_c)
}
pub async fn future_duplicate_filter(
original_filtered_data: &HashMap<String, FilteredDataValue>,
future_exchange_info_map: &HashMap<String, FuturesExchangeInfo>
) -> Result<HashMap<String, FilteredDataValue>, Box<dyn std::error::Error + Send + Sync>> {
let inspect_table_name_1 = String::from("future_ordered_coin_list");
let mut filtered_data: HashMap<String, FilteredDataValue> = HashMap::new();
let mut filtered_data_arc: Arc<Mutex<HashMap<String, FilteredDataValue>>> =
Arc::new(Mutex::new(filtered_data));
let mut task_vec = Vec::new();
for (symbol, filtered_data) in original_filtered_data {
let mut exists_condition_build = String::from("symbol=\'");
exists_condition_build.push_str(symbol.as_str());
exists_condition_build.push_str("\' AND close_time=");
exists_condition_build.push_str(filtered_data.closetime.to_string().as_str());
let exists_condition = Some(exists_condition_build);
let exists_condition_c = exists_condition.clone();
let inspect_table_name_1_c = inspect_table_name_1.clone();
let future_exchange_info_map_c = future_exchange_info_map.clone();
let symbol_c = symbol.clone();
let filtered_data_c = filtered_data.clone();
let filtered_data_arc_c = Arc::clone(&filtered_data_arc);
task_vec.push(tokio::spawn(async move {
if future_exchange_info_map_c.contains_key(&symbol_c) {
let inspect_result_1 =
exists_record(&inspect_table_name_1_c, &exists_condition_c).await;
if inspect_result_1 == false
{
let mut filtered_data_lock = filtered_data_arc_c.lock().await;
filtered_data_lock.insert(symbol_c, filtered_data_c);
}
}
}));
}
try_join_all(task_vec).await?;
let filtered_data_c = filtered_data_arc.lock().await.clone();
Ok(filtered_data_c)
}
pub async fn remove_keys(
filtered_data: &mut HashMap<String, FilteredDataValue>,
keys_to_remove: HashSet<String>,

View File

@ -1,9 +1,11 @@
use crate::coex::exchange_team::*;
use crate::coex::order_team::*;
use crate::future::Position;
use csv::{DeserializeRecordsIter, StringRecord};
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use serde::Deserialize;
use crate::future::FuturesExchangeInfo;
// use super::strategy_test;
use super::{
@ -30,6 +32,7 @@ struct Record {
pub async fn execute_list_up_for_buy(
all_data: &AllData,
future_exchange_info_map: &HashMap<String, FuturesExchangeInfo>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// crate::strategy_team::strategy_001::list_up_for_buy(all_data).await;
// crate::strategy_team::strategy_002::list_up_for_buy(all_data).await;
@ -40,7 +43,7 @@ pub async fn execute_list_up_for_buy(
// crate::strategy_team::strategy_007::list_up_for_buy(all_data).await;
crate::strategy_team::strategy_008::list_up_for_buy(all_data).await;
crate::strategy_team::strategy_009::list_up_for_buy(all_data).await;
crate::strategy_team::future_strategy::list_up_for_buy(all_data).await;
crate::strategy_team::future_strategy::list_up_for_buy(all_data, &future_exchange_info_map).await;
Ok(())
}
@ -131,3 +134,58 @@ pub async fn insert_pre_suggested_coins(
Ok(())
}
pub async fn insert_future_coins(
position: Position,
server_epoch: i64,
filtered_coins: &HashMap<String, FilteredDataValue>
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut insert_table_name = String::from("future_ordered_coin_list");
let insert_columns = vec![
"order_type",
"status",
"symbol",
"order_id",
"position",
"registered_server_epoch",
"transact_time",
"close_time",
"used_usdt",
"expected_get_usdt",
"expected_usdt_profit",
"entry_price",
"current_price",
"base_qty_ordered",
"base_qty_fee_adjusted",
"pure_profit_percent",
"minimum_profit_percent",
"maximum_profit_percent",
];
for (symbol, filtered_data) in filtered_coins {
let mut insert_values = vec![
String::from("POSITIONING"), // order_type
String::from("LISTUP"), // status
symbol.clone(), // symbol
0.to_string(), // order_id
position.to_string(), // position
server_epoch.to_string(), // registered_server_epoch
0.to_string(), // transact_time
filtered_data.closetime.to_string(), // close_time
0.0.to_string(), // used_usdt
0.0.to_string(), // expected_get_usdt
0.0.to_string(), // expected_usdt_profit
0.0.to_string(), // entry_price
0.0.to_string(), // current_price
0.0.to_string(), // base_qty_ordered
0.0.to_string(), // base_qty_fee_adjusted
0.0.to_string(), // pure_profit_percent
0.0.to_string(), // minimum_profit_percent
0.0.to_string(), // maximum_profit_percent
];
insert_one_record(&insert_table_name, &insert_columns, &insert_values).await;
}
Ok(())
}