use std::collections::{BTreeMap}; use std::str::FromStr; use chrono::NaiveDateTime; use rust_decimal::Decimal; // use rust_decimal::prelude::ToPrimitive; use rust_decimal_macros::dec; use tracing::{error, info}; use crate::{export_template}; use crate::swap_bybit::bybit_swap_rest_utils::BybitSwapRest; use crate::swap_gate::gate_swap_rest_utils::GateSwapRest; use crate::utils::utils::BalanceConfigInfo; pub async fn export_balance(config_info: BalanceConfigInfo) { let config_info_clone = config_info.clone(); let start_time = NaiveDateTime::parse_from_str(&config_info.range_time[0].clone(), "%Y-%m-%d %H:%M:%S").unwrap().timestamp() - 8 * 3600; let end_time = NaiveDateTime::parse_from_str(&config_info.range_time[1].clone(), "%Y-%m-%d %H:%M:%S").unwrap().timestamp() - 8 * 3600; let count_start_time = NaiveDateTime::parse_from_str(&config_info.count_range_time[0].clone(), "%Y-%m-%d %H:%M:%S").unwrap().timestamp() - 8 * 3600; let count_end_time = NaiveDateTime::parse_from_str(&config_info.count_range_time[1].clone(), "%Y-%m-%d %H:%M:%S").unwrap().timestamp() - 8 * 3600; //获取不同账号的数据 let mut all_account_balance_info: BTreeMap>> = BTreeMap::new(); let mut account_name_list: Vec = vec![]; if config_info_clone.account_list.len() == 0 { info!("没有账号信息"); return; } for exchange in config_info.exchanges.clone() { let exchange_up = exchange.to_uppercase(); match exchange_up.as_str() { "GATE" => { for account in config_info_clone.account_list.clone() { let mut account_info: BTreeMap = BTreeMap::new(); account_info.insert("account_name".to_string(), account[0].clone()); account_info.insert("access_key".to_string(), account[1].clone()); account_info.insert("secret_key".to_string(), account[2].clone()); let mut gate_exc = GateSwapRest::new(false, account_info.clone()); let data = gate_exc.account_book("usdt".to_string(), start_time, end_time).await; // info!("请求完成{:?}",data.clone()); if data.code.as_str() == "200" { let account_name = account_info.get("account_name").unwrap(); account_name_list.push(account_name.clone()); //账号 let json_value: serde_json::Value = serde_json::from_str(&data.data).unwrap(); if let serde_json::Value::Array(array) = json_value { let mut name_data_all: Vec> = vec![]; let mut times = 0; for item in array { // info!("数据{:?}",item.clone().to_string()); let time = item["time"].as_i64().unwrap();//秒级 if time == times { continue; } times = time; let change = item["change"].as_str().unwrap(); let balance = item["balance"].as_str().unwrap(); let type_str = match item["type"].as_str().unwrap() { "dnw" => { "transfer" } "pnl" => { "profit" } "fee" => { "交易手续费"; continue; } "refr" => { "推荐人返佣"; continue; } "fund" => { "资金费用"; continue; } "point_dnw" => { "点卡转入转出"; continue; } "point_fee" => { "点卡交易手续费"; continue; } "point_refr" => { "点卡推荐人返佣"; continue; } _ => { "未知-变更类型"; continue; } }; let text = item["text"].as_str().unwrap(); let contract = item["contract"].as_str().unwrap(); let trade_id = item["trade_id"].as_str().unwrap(); let mut name_data_array: Vec = vec![]; name_data_array.push(time.to_string()); name_data_array.push(trade_id.to_string()); name_data_array.push(change.to_string()); name_data_array.push(balance.to_string()); name_data_array.push(type_str.to_string()); name_data_array.push(contract.to_string()); name_data_array.push(text.to_string()); name_data_all.push(name_data_array.clone()); } all_account_balance_info.insert(account_name.clone(), name_data_all.clone()); } else { info!("不是数组 检查数据"); } } // break; }; } "BYBIT" => { for account in config_info_clone.account_list.clone() { let mut account_info: BTreeMap = BTreeMap::new(); account_info.insert("account_name".to_string(), account[0].clone()); account_info.insert("access_key".to_string(), account[1].clone()); account_info.insert("secret_key".to_string(), account[2].clone()); let mut bybit_exc = BybitSwapRest::new(false, account_info.clone()); let data = bybit_exc.get_account_transaction_log(start_time * 1000, end_time * 1000).await; // info!("请求完成{:?}",data.clone()); if data.code.as_str() == "200" { let account_name = account_info.get("account_name").unwrap(); account_name_list.push(account_name.clone()); //解析 let json_value: serde_json::Value = serde_json::from_str(&data.data).unwrap(); let ret_code = json_value.get("retCode").unwrap().as_i64().unwrap(); let ret_msg = json_value.get("retMsg").unwrap().as_str().unwrap(); if ret_code == 0 && ret_msg == "OK" { let result = json_value.get("result").unwrap(); let list = result.get("list").unwrap(); if let serde_json::Value::Array(array) = list { let mut name_data_all: Vec> = vec![]; for item in array { let id = item.get("id").unwrap().as_str().unwrap(); let type_str = item.get("type").unwrap().as_str().unwrap(); let change = item.get("change").unwrap().as_str().unwrap(); let cash_balance = item.get("cashBalance").unwrap().as_str().unwrap(); let transaction_time = item.get("transactionTime").unwrap().as_str().unwrap(); let type_str = match type_str { "TRADE" => { "交易" } _ => { "其他-变更类型"; continue; } }; let time = transaction_time.parse::().unwrap() / 1000; let mut name_data_array: Vec = vec![]; name_data_array.push(time.to_string()); name_data_array.push(id.to_string()); name_data_array.push(change.to_string()); name_data_array.push(cash_balance.to_string()); name_data_array.push(type_str.to_string()); name_data_array.push("".to_string()); name_data_array.push("".to_string()); name_data_all.push(name_data_array.clone()); } all_account_balance_info.insert(account_name.clone(), name_data_all.clone()); } } else { info!("解析失败:{:?}",json_value); continue; } } // break; }; } _ => { error!("交易所输入错误!"); panic!("交易所输入错误!") } }; } // 根据账户变动填充 let mut count_time_list = vec![]; for (_account_name, balance_info) in all_account_balance_info.clone() { for value in balance_info { let time = value[0].clone().parse::().unwrap(); if !count_time_list.contains(&time) && time > count_start_time && time < count_end_time { count_time_list.push(time) } } }; count_time_list.sort_by(|a, b| a.cmp(&b)); count_time_list.insert(0, count_start_time); count_time_list.push(count_end_time); let count_balance_info = supply_balance(all_account_balance_info.clone(), count_time_list.clone()); let statistic_result = statistic_balance(count_balance_info.clone(), count_start_time.clone(), count_end_time.clone(), config_info.clone()); // 根据小时填充 let mut last_timestamp: i64 = -1; let mut hour_time_list: Vec = vec![]; for item in start_time..end_time { if item / 3600 != last_timestamp / 3600 { last_timestamp = item; hour_time_list.push(last_timestamp); } } hour_time_list.push(end_time); let all_balance_info: Vec> = supply_balance(all_account_balance_info.clone(), hour_time_list.clone()); account_name_list.push("total_balance".to_string()); match config_info_clone.export_mode { 1 => export_template::template_balance::export_html(config_info, &account_name_list, &hour_time_list, &all_balance_info, statistic_result), 2 => export_template::template_balance::export_html(config_info, &account_name_list, &count_time_list, &count_balance_info, statistic_result), _ => error!("导出模式错误,请检查导出模式!") }; } pub fn statistic_balance(balance_info: Vec>, count_start_time: i64, end_start_time: i64, _config: BalanceConfigInfo) -> String { let mut max_withdrawal = dec!(0); let mut max_total_withdrawal = dec!(0); let mut total_income = dec!(0); let mut balance_info_map: BTreeMap>> = BTreeMap::new(); for info in balance_info.clone() { let mut default_info = balance_info_map.get(&info[0]).unwrap_or(&vec![]).clone(); default_info.push(info.clone()); balance_info_map.insert(info[0].clone(), default_info); } for (account_name, balance_info) in balance_info_map.clone() { let mut max_price = dec!(0); let mut min_price = dec!(0); let mut withdrawal = dec!(0); for (index, info) in balance_info.iter().enumerate() { let present_price = Decimal::from_str(&info[4]).unwrap(); if index == 0 || max_price < present_price { max_price = present_price; min_price = present_price; continue; } let present_withdrawal = ((max_price - min_price) / max_price * dec!(100)).round_dp(2); if present_withdrawal > withdrawal { withdrawal = present_withdrawal } if max_price > present_price && present_price < min_price { min_price = present_price; } } if account_name != "total_balance" { if max_withdrawal < withdrawal { max_withdrawal = withdrawal } } else { max_total_withdrawal = withdrawal; let present = Decimal::from_str(&balance_info[0][4]).unwrap(); let past = Decimal::from_str(&balance_info[balance_info.len() - 1][4]).unwrap(); total_income = ((past - present) / present * dec!(100)).round_dp(2); } } let start_time = NaiveDateTime::from_timestamp_millis((count_start_time + 8 * 3600) * 1000).unwrap().format("%d日%H点").to_string(); let end_time = NaiveDateTime::from_timestamp_millis((end_start_time + 8 * 3600) * 1000).unwrap().format("%d日%H点").to_string(); format!("{}到{},总收益约为{}%,最大回撤约为{}%,其中单个账号最大回撤约为{}%。", start_time, end_time, total_income, max_total_withdrawal, max_withdrawal) } pub fn supply_balance(source_balance_info: BTreeMap>>, time_slicer: Vec) -> Vec> { let mut balance_info_list: Vec> = vec![]; for time in time_slicer.clone() { // 根据时间片去拿数据 ,如果有添加没有,手动添加 let mut total_price = Decimal::ZERO; for (key, value) in source_balance_info.clone() { let mut balance_info = value.clone(); balance_info.sort_by(|a, b| a[0].cmp(&b[0])); let mut new_info: Option> = None; for mut info in balance_info.clone() { let info_time = info[0].parse::().unwrap(); if info_time == time { match new_info { None => { info.insert(0, key.clone()); new_info = Option::from(info);//记录当前实际 } Some(ref value) => { if value[1] < info[1] { info.insert(0, key.clone()); new_info = Option::from(info);//记录当前实际 } } } break; } } match new_info { None => { let balance_info_filter: Vec> = balance_info.iter().filter(|item| { item[0].parse::().unwrap() < time }).cloned().collect(); let mut last_balance_info = if balance_info_filter.len() == 0 { balance_info[0].clone() } else { balance_info_filter[balance_info_filter.len() - 1].clone() }; last_balance_info.insert(0, key.clone()); total_price += Decimal::from_str(&last_balance_info[4]).unwrap(); balance_info_list.push(last_balance_info); } Some(ref info) => { total_price += Decimal::from_str(&info[4]).unwrap(); balance_info_list.push(info.clone()); } } } let sum_row: Vec = vec!["total_balance".to_string(), time.to_string(), "".to_string(), "0".to_string(), total_price.round_dp(2).to_string(), "".to_string(), "".to_string(), "".to_string()]; balance_info_list.push(sum_row.clone()); }; balance_info_list }