Browse Source

1.整理添加bybit导出
2.bybit 新增
3.修改回撤计算
4.添加双模导出

gepangpang 1 year ago
parent
commit
4152fb6aea

+ 3 - 1
config_balance.toml.sample

@@ -4,12 +4,14 @@ is_export = true
 proxy_address = "127.0.0.1:7890"
 # 账号列表(account_name: 导出展示名字)
 account_list = [["account_name", "access_key", "secret_key", "pass_key"]]
-# 配置交易所 目前支持["gate"]
+# 配置交易所 目前支持["gate","bybit"]
 exchanges = ["gate"]
 # 配置查询时间(格式:2023-12-6 15:00:00)
 range_time = ["2023-12-20 18:00:00", "2023-12-26 10:00:00"]
 # 配置查询时间(格式:2023-12-6 15:00:00)
 count_range_time = ["2023-12-25 10:00:00", "2023-12-26 10:00:00"]
+# 配置导出表格模式(1:按小时导出,2:按余额变动导出)
+export_mode = 1
 # 导出路径(不填则为默认路径"./")
 export_path = ""
 # 导出文件名字(不填则为默认名字"export")

+ 1 - 1
src/export_analyze.rs

@@ -44,7 +44,7 @@ pub async fn export_ticker(symbol: &str, exchange: &str, range_interval: &str) {
 
 pub fn parse_range_interval (range_interval: &str) -> RangeInterval {
     let interval_text;
-    let mut interval_time = 0;
+    let interval_time;
     let range_interval_len = range_interval.len();
     let time = range_interval[0..range_interval_len - 1].parse::<i64>().unwrap();
     let mut unit = range_interval[range_interval_len - 1..range_interval_len].to_string();

+ 174 - 91
src/export_balance.rs

@@ -3,8 +3,9 @@ use std::str::FromStr;
 use chrono::NaiveDateTime;
 use rust_decimal::Decimal;
 use rust_decimal_macros::dec;
-use tracing::info;
+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;
 
@@ -18,96 +19,169 @@ pub async fn export_balance(config_info: BalanceConfigInfo) {
     //获取不同账号的数据
     let mut all_account_balance_info: BTreeMap<String, Vec<Vec<String>>> = BTreeMap::new();
     let mut account_name_list: Vec<String> = vec![];
-    loop {
-        if config_info_clone.account_list.len() == 0 {
-            info!("没有账号信息");
-            return;
-        }
+    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<String, String> = 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());
 
-        for account in config_info_clone.account_list.clone() {
-            let mut account_info: BTreeMap<String, String> = 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()).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<String>> = vec![];
-                    let mut times = 0;
-
-                    for item in array {
-                        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" => { "转入转出" }
-                            "pnl" => { "减仓盈亏" }
-                            "fee" => {
-                                "交易手续费";
-                                continue;
-                            }
-                            "refr" => {
-                                "推荐人返佣";
-                                continue;
-                            }
-                            "fund" => {
-                                "资金费用";
-                                continue;
-                            }
-                            "point_dnw" => {
-                                "点卡转入转出";
-                                continue;
-                            }
-                            "point_fee" => {
-                                "点卡交易手续费";
-                                continue;
-                            }
-                            "point_refr" => {
-                                "点卡推荐人返佣";
-                                continue;
-                            }
-                            _ => {
-                                "未知-变更类型";
-                                continue;
+                    let mut gate_exc = GateSwapRest::new(false, account_info.clone());
+                    let data = gate_exc.account_book("usdt".to_string()).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<String>> = 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" => { "转入转出" }
+                                    "pnl" => { "减仓盈亏" }
+                                    "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<String> = 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<String, String> = 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().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<String>> = 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 text = item["text"].as_str().unwrap();
-                        let contract = item["contract"].as_str().unwrap();
-                        let trade_id = item["trade_id"].as_str().unwrap();
+                                    let time = transaction_time.parse::<i64>().unwrap() / 1000;
 
-                        let mut name_data_array: Vec<String> = 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());
+                                    let mut name_data_array: Vec<String> = 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());
+                                    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;
+                        }
                     }
-                    all_account_balance_info.insert(account_name.clone(), name_data_all.clone());
-                } else {
-                    info!("不是数组 检查数据");
-                }
+                    // break;
+                };
             }
-            // break;
-        }
-        break;//这里是为了 代码收纳,用了loop来放置代码
+            _ => {
+                error!("交易所输入错误!");
+                panic!("交易所输入错误!")
+            }
+        };
     }
 
     // 根据账户变动填充
@@ -128,18 +202,23 @@ pub async fn export_balance(config_info: BalanceConfigInfo) {
 
     // 根据小时填充
     let mut last_timestamp: i64 = -1;
-    let mut export_time_list: Vec<i64> = vec![];
+    let mut hour_time_list: Vec<i64> = vec![];
     for item in start_time..end_time {
         if item / 3600 != last_timestamp / 3600 {
             last_timestamp = item;
-            export_time_list.push(last_timestamp);
+            hour_time_list.push(last_timestamp);
         }
     }
-    export_time_list.push(end_time);
-    let all_balance_info: Vec<Vec<String>> = supply_balance(all_account_balance_info.clone(), export_time_list.clone());
+    hour_time_list.push(end_time);
+    let all_balance_info: Vec<Vec<String>> = supply_balance(all_account_balance_info.clone(), hour_time_list.clone());
 
     account_name_list.push("total_balance".to_string());
-    export_template::template_balance::export_html(config_info_clone.clone(), &account_name_list, &export_time_list, &all_balance_info, statistic_result);
+    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!("导出模式错误,请检查导出模式!")
+    };
 }
 
 
@@ -172,8 +251,8 @@ pub fn statistic_balance(balance_info: Vec<Vec<String>>, count_start_time: i64,
             total_income = (((past / present) - Decimal::ONE) * dec!(100)).round_dp(2);
         }
     }
-    let start_time = NaiveDateTime::from_timestamp_millis((count_start_time + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d日%H点").to_string();
-    let end_time = NaiveDateTime::from_timestamp_millis((end_start_time + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d日%H点").to_string();
+    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)
 }
 
@@ -211,7 +290,11 @@ pub fn supply_balance(source_balance_info: BTreeMap<String, Vec<Vec<String>>>, t
                     let balance_info_filter: Vec<Vec<String>> = balance_info.iter().filter(|item| {
                         item[0].parse::<i64>().unwrap() < time
                     }).cloned().collect();
-                    let mut last_balance_info = balance_info_filter[balance_info_filter.len() - 1].clone();
+                    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);

+ 0 - 8
src/export_template/template_balance.rs

@@ -16,11 +16,6 @@ pub fn export_html(config: BalanceConfigInfo, acc_name_all: &Vec<String>, x_time
     // 创建 Handlebars 实例
     let mut handlebars = Handlebars::new();
 
-    // 数据组装
-    // info!("账号昵称数组:{:?}",acc_name_all);
-    // info!("X_时间片:{:?}",x_time);
-    // info!("X_时间片:{:?}",x_time);
-    // info!("组装数据原数组:{:?}",data_list);
     //时间片数据组装
     let mut time_str_x = vec![];
     for time in x_time {
@@ -48,9 +43,7 @@ pub fn export_html(config: BalanceConfigInfo, acc_name_all: &Vec<String>, x_time
 
     let mut series_all = Vec::new();
     for (key, val) in map {
-        // info!("{:?}=---{:?}",key.to_string(),val.len());
         let series = json!({"name": key.to_string(), "type": "line", "smooth": true, "data": val});
-        // info!("数据:{:?}",series);
         series_all.push(series);
     }
     let json = json!({
@@ -58,7 +51,6 @@ pub fn export_html(config: BalanceConfigInfo, acc_name_all: &Vec<String>, x_time
     });
 
     let series_str = format!("{}", json["data"]);
-    // info!("时间片:{:?}----{:?}",time_str_x.len(),time_str_x[time_str_x.len()-1]);
 
     let data = serde_json::json!({
         "chart_title": format!("账户合约余额变更历史"),

+ 1 - 0
src/main.rs

@@ -5,6 +5,7 @@ use crate::utils::utils::{AnalyzeConfigInfo, BalanceConfigInfo, TickerConfigInfo
 pub mod swap_binance;
 pub mod swap_gate;
 pub mod swap_okx;
+pub mod swap_bybit;
 pub mod robot_data;
 pub mod utils;
 pub mod http;

+ 26 - 0
src/swap_bybit/bybit_swap_rest.rs

@@ -0,0 +1,26 @@
+use crate::http::request::{get, Response};
+
+pub async fn get_history_trades(symbol: &str, start_at: &str, end_at: &str) -> Response {
+    let url = "https://www.okx.com/api/v5/market/history-trades";
+    let mut params = serde_json::json!({
+        "instId":symbol,
+        "type":"2"
+    });
+    if end_at.len() > 0 {
+        params["after"] = serde_json::Value::from(end_at);
+    }
+    if start_at.len() > 0 {
+        params["before"] = serde_json::Value::from(start_at);
+    }
+    get(url, params.to_string(), None).await
+}
+
+
+//查阅所有交易币对的 基础信息
+pub async fn get_symbol_details_all() -> Response {
+    let url = "https://www.okx.com/api/v5/public/instruments";
+    let params = serde_json::json!({
+        "instType":"SWAP"
+    });
+    get(url, params.to_string(), None).await
+}

+ 391 - 0
src/swap_bybit/bybit_swap_rest_utils.rs

@@ -0,0 +1,391 @@
+use std::collections::BTreeMap;
+
+use reqwest::Client;
+use reqwest::header::HeaderMap;
+use ring::hmac;
+use rust_decimal::Decimal;
+use rust_decimal::prelude::FromPrimitive;
+use rust_decimal_macros::dec;
+use toml::Value;
+use tracing::{info, trace};
+use crate::http::request::Response;
+use crate::utils::utils::parse_params_to_str;
+
+
+#[derive(Clone, Debug)]
+pub struct BybitSwapRest {
+    pub label: String,
+    base_url: String,
+    client: reqwest::Client,
+    /*******参数*/
+    //登陆所需参数
+    login_param: BTreeMap<String, String>,
+    delays: Vec<i64>,
+    max_delay: i64,
+    avg_delay: Decimal,
+}
+
+impl BybitSwapRest {
+    /*******************************************************************************************************/
+    /*****************************************获取一个对象****************************************************/
+    /*******************************************************************************************************/
+
+    pub fn new(is_colo: bool, login_param: BTreeMap<String, String>) -> BybitSwapRest
+    {
+        return BybitSwapRest::new_label("default-BybitSwapRest".to_string(), is_colo, login_param);
+    }
+    pub fn new_label(label: String, is_colo: bool, login_param: BTreeMap<String, String>) -> BybitSwapRest {
+        let base_url = if is_colo {
+            "https://api.bytick.com".to_string()
+        } else {
+            "https://api.bytick.com".to_string()
+        };
+
+        if is_colo {
+            info!("开启高速(未配置,走普通:{})通道",base_url);
+        } else {
+            info!("走普通通道:{}",base_url);
+        }
+        /*****返回结构体*******/
+        BybitSwapRest {
+            label,
+            base_url,
+            client: Client::new(),
+            login_param,
+            delays: vec![],
+            max_delay: 0,
+            avg_delay: dec!(0.0),
+        }
+    }
+
+    /*******************************************************************************************************/
+    /*****************************************rest请求函数********************************************************/
+    /*******************************************************************************************************/
+    //服務器時間
+    pub async fn get_server_time(&mut self) -> Response {
+        let params = serde_json::json!({
+         });
+        let data = self.request("GET".to_string(),
+                                "/v5".to_string(),
+                                "/market/time".to_string(),
+                                false,
+                                params.to_string(),
+        ).await;
+        data
+    }
+    //查詢可交易產品的規格信息
+    pub async fn get_instruments_info(&mut self, symbol: String) -> Response {
+        let params = serde_json::json!({
+            "category":"linear",
+            "symbol":symbol
+         });
+        let data = self.request("GET".to_string(),
+                                "/v5".to_string(),
+                                "/market/instruments-info".to_string(),
+                                false,
+                                params.to_string(),
+        ).await;
+        data
+    }
+    //賬戶的交易日誌。
+    pub async fn get_account_transaction_log(&mut self) -> Response {
+        let mut params = serde_json::json!({
+            "accountType":"UNIFIED",
+            "category":"linear",
+            "currency":"USDT",
+         });
+        params["startTime"] = serde_json::json!(1703124000000 as i64);
+        params["endTime"] = serde_json::json!(1703664000000  as i64);
+        let data = self.request("GET".to_string(),
+                                "/v5".to_string(),
+                                "/account/transaction-log".to_string(),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+
+    //查詢平倉盈虧
+    pub async fn get_position_closed_pnl(&mut self, symbol: String) -> Response {
+        let mut params = serde_json::json!({
+            "symbol":symbol,
+            "limit":"200"
+         });
+        params["startTime"] = serde_json::json!(1703124000000 as i64);
+        params["endTime"] = serde_json::json!(1703664000000  as i64);
+        let data = self.request("GET".to_string(),
+                                "/contract/v3".to_string(),
+                                "/private/position/closed-pnl".to_string(),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+
+    //查詢錢包資金紀錄
+    pub async fn get_account_wallet_fund_records(&mut self, start_time: i64, end_time: i64) -> Response {
+        let mut params = serde_json::json!({
+            "coin":"USDT",
+            "walletFundType":"RealisedPNL",
+            "limit":"50",
+         });
+        params["startTime"] = serde_json::json!(start_time);
+        params["endTime"] = serde_json::json!(end_time);
+
+        let data = self.request("GET".to_string(),
+                                "/contract/v3".to_string(),
+                                "/private/account/wallet/fund-records".to_string(),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+
+
+    /*******************************************************************************************************/
+    /*****************************************工具函数********************************************************/
+    /*******************************************************************************************************/
+    pub fn get_delays(&self) -> Vec<i64> {
+        self.delays.clone()
+    }
+    pub fn get_avg_delay(&self) -> Decimal {
+        self.avg_delay.clone()
+    }
+    pub fn get_max_delay(&self) -> i64 {
+        self.max_delay.clone()
+    }
+    fn get_delay_info(&mut self) {
+        let last_100 = if self.delays.len() > 100 {
+            self.delays[self.delays.len() - 100..].to_vec()
+        } else {
+            self.delays.clone()
+        };
+
+        let max_value = last_100.iter().max().unwrap();
+        if max_value.clone().to_owned() > self.max_delay {
+            self.max_delay = max_value.clone().to_owned();
+        }
+
+        let sum: i64 = last_100.iter().sum();
+        let sum_v = Decimal::from_i64(sum).unwrap();
+        let len_v = Decimal::from_u64(last_100.len() as u64).unwrap();
+        self.avg_delay = (sum_v / len_v).round_dp(1);
+        self.delays = last_100.clone().into_iter().collect();
+    }
+    //调用请求
+    pub async fn request(&mut self,
+                         method: String,
+                         prefix_url: String,
+                         request_url: String,
+                         is_login: bool,
+                         params: String) -> Response
+    {
+        trace!("login_param:{:?}", self.login_param);
+        //解析账号信息
+        let mut access_key = "".to_string();
+        let mut secret_key = "".to_string();
+        // let mut passphrase = "".to_string();
+        if self.login_param.contains_key("access_key") {
+            access_key = self.login_param.get("access_key").unwrap().to_string();
+        }
+        if self.login_param.contains_key("secret_key") {
+            secret_key = self.login_param.get("secret_key").unwrap().to_string();
+        }
+        // if self.login_param.contains_key("pass_key") {
+        //     passphrase = self.login_param.get("pass_key").unwrap().to_string();
+        // }
+        let mut is_login_param = true;
+        if access_key == "" || secret_key == "" {
+            is_login_param = false
+        }
+
+
+        //请求头配置-如果需要登陆则存在额外配置
+        let mut body = "".to_string();
+        let timestamp = Self::get_timestamp();
+        let mut headers = HeaderMap::new();
+        headers.insert("Content-Type", "application/json; charset=utf-8".parse().unwrap());
+        headers.insert("X-BAPI-RECV-WINDOW", "5000".parse().unwrap());
+
+        if method == "POST" {
+            body = params.clone();
+        }
+
+
+        //是否需要登陆-- 组装sing
+        if is_login {
+            if !is_login_param {
+                let mut res = Response::new();
+                res.code = "-1".to_string();
+                res.msg = "登陆参数错误".to_string();
+                return res;
+            } else {
+                //需要登陆-且登陆参数齐全
+                trace!("param:{}", params);
+                trace!("body:{}", body);
+                //组装sing
+                let sing = Self::sign(
+                    access_key.clone(),
+                    secret_key.clone(),
+                    method.clone(),
+                    params.clone(),
+                    timestamp.clone(),
+                );
+                //组装header
+                headers.extend(Self::headers(sing, timestamp, access_key));
+            }
+        }
+
+
+        // trace!("headers:{:?}", headers);
+        let base_url = format!("{}{}", prefix_url.clone(), request_url.clone());
+        let start_time = chrono::Utc::now().timestamp_millis();
+        let get_response = self.http_toll(
+            format!("{}{}", prefix_url.clone(), request_url.clone()),
+            method.to_string(),
+            params.clone(),
+            headers,
+        ).await;
+        let time_array = chrono::Utc::now().timestamp_millis() - start_time;
+        self.delays.push(time_array);
+        self.get_delay_info();
+        let res_data = Self::res_data_analysis(get_response, base_url, params);
+        res_data
+    }
+
+    pub fn headers(sign: String, timestamp: String, access_key: String) -> HeaderMap {
+        let mut headers = HeaderMap::new();
+        headers.insert("X-BAPI-SIGN-TYPE", "2".parse().unwrap());
+        headers.insert("X-BAPI-API-KEY", access_key.parse().unwrap());
+        headers.insert("X-BAPI-TIMESTAMP", timestamp.parse().unwrap());
+        headers.insert("X-BAPI-SIGN", sign.parse().unwrap());
+        // headers.insert("X-Referer", passphrase.parse().unwrap());
+        headers
+    }
+    pub fn sign(access_key: String,
+                secret_key: String,
+                method: String,
+                params: String, timestamp: String) -> String
+    {
+        /*签名生成*/
+        let url_param_str = parse_params_to_str(params.clone());
+        let parameters = if method == "GET" {
+            url_param_str
+        } else {
+            params
+        };
+
+        let message = format!("{}{}5000{}", timestamp, access_key, parameters);
+        trace!("message:{}",message);
+
+        // 做签名
+        let hmac_key = ring::hmac::Key::new(hmac::HMAC_SHA256, secret_key.as_bytes());
+        let result = ring::hmac::sign(&hmac_key, &message.as_bytes());
+        let sign = hex::encode(result.as_ref());
+        sign
+    }
+
+    async fn http_toll(&mut self, request_path: String, request_type: String, params: String, headers: HeaderMap) -> Result<Response, reqwest::Error> {
+        /****请求接口与 地址*/
+        let url = format!("{}{}", self.base_url.to_string(), request_path);
+        let request_type = request_type.clone().to_uppercase();
+        let addrs_url = format!("{}?{}", url.clone(), parse_params_to_str(params.clone()));
+        // let addrs_url: String = if  parse_params_to_str(params.clone()) == "" {
+        //     url.clone()
+        // } else {
+        //     format!("{}?{}", url.clone(), RestTool::parse_params_to_str(params.clone()))
+        // };
+
+        trace!("url:{}", url);
+        trace!("addrs_url:{}", addrs_url);
+        let params_json: serde_json::Value = serde_json::from_str(&params).unwrap();
+        trace!("params_json:{}",params_json);
+        trace!("headers:{:?}",headers);
+
+
+        let req = match request_type.as_str() {
+            "GET" => self.client.get(addrs_url.clone()).headers(headers),
+            "POST" => self.client.post(url.clone()).body(params).headers(headers),
+            "DELETE" => self.client.delete(addrs_url.clone()).headers(headers),
+            // "PUT" => self.client.put(url.clone()).json(&params),
+            // _ => return Ok(ResponseData::error(self.label.clone(), format!("错误的请求类型:{}", request_type.clone()))), // 处理未知请求类型
+            _ => {
+                let mut res = Response::new();
+                res.code = "-1".to_string();
+                res.msg = "请求类型错误".to_string();
+                res.data = "".to_string();
+                return Ok(res);
+            }
+        };
+
+        let response = req.send().await?;
+        let mut res = Response::new();
+        if response.status().is_success() {
+            // 读取响应的内容
+            let body = response.text().await?;
+            trace!("ok-----{}", body);
+            // res_data = ResponseData::new(self.label.clone(), "200".to_string(), "success".to_string(), body);
+            res.code = "200".to_string();
+            res.msg = "success".to_string();
+            res.data = body;
+        } else {
+            let body = response.text().await?;
+            trace!("error-----{}", body);
+            // res_data = ResponseData::error(self.label.clone(), body.to_string())
+            res.code = "-1".to_string();
+            res.msg = body;
+            res.data = "".to_string();
+        }
+
+        Ok(res)
+    }
+
+
+    //res_data 解析
+    pub fn res_data_analysis(result: Result<Response, reqwest::Error>, base_url: String, params: String) -> Response {
+        let res = Response::new();
+        trace!("原始数据:{:?}",result);
+        match result {
+            Ok(res_data) => {
+                if res_data.code != "200" {
+                    // res_data
+                    trace!("不等于200");
+                    let msg = res_data.msg;
+                    let body: String = res_data.data.as_str().parse().unwrap();
+                    let json_value = serde_json::from_str::<Value>(&body);
+
+                    match json_value {
+                        Ok(_data) => {
+                            let mut error = Response::new();
+                            error.code = "-1".to_string();
+                            error.msg = format!("请求错误{:?}", res.msg);
+                            error.data = format!("请求地址:{},请求参数:{}", base_url, params);
+                            error
+                        }
+                        Err(e) => {
+                            let mut error = Response::new();
+                            error.code = "-1".to_string();
+                            error.msg = format!("json 解析失败:{},相关参数:{}", e, msg);
+                            error.data = "".to_string();
+                            error
+                        }
+                    }
+                } else {
+                    res_data
+                }
+            }
+            Err(err) => {
+                let mut error = Response::new();
+                error.code = "-1".to_string();
+                error.msg = format!("json 解析失败:{},相关参数:{}", err, params);
+                error.data = "".to_string();
+                error
+            }
+        }
+    }
+
+    fn get_timestamp() -> String {
+        chrono::Utc::now().timestamp_millis()
+            .to_string()
+    }
+}

+ 184 - 0
src/swap_bybit/bybit_swap_standard.rs

@@ -0,0 +1,184 @@
+use std::io::{Error, ErrorKind};
+use rust_decimal::Decimal;
+
+use serde::{Deserialize, Serialize};
+
+use crate::swap_okx::okx_swap_rest::{get_history_trades, get_symbol_details_all};
+use crate::struct_standard::Trades;
+
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SwapTrades {
+    inst_id: String,
+    //	产品ID
+    trade_id: String,
+    //	成交ID
+    px: String,
+    //	成交价格
+    sz: Decimal,
+    //成交数量
+    side: String,
+    //成交方向buy:,//买sell:,//卖
+    ts: String,//成交时间,Unix时间戳的毫秒数格式, 如1597026383085
+}
+
+#[allow(dead_code)]
+pub(crate) async fn standard_history_candles(symbol: &str, start_at: &str, end_at: &str, ct_val: Decimal) -> Result<Vec<Trades>, Error> {
+    let mut symbol_fmt = format!("{}-SWAP", symbol.replace("_", "-").clone());
+    symbol_fmt = symbol_fmt.to_uppercase();
+
+
+    let res_data = get_history_trades(&symbol_fmt, &start_at, &end_at).await;
+    if res_data.code == "200" {
+        let res_data_str = res_data.data;
+        let json_value: serde_json::Value = serde_json::from_str(&res_data_str).unwrap();
+
+        let result = if json_value.get("code").is_some() && json_value["code"] == "0" {
+            let data = json_value.get("data").unwrap();
+
+            // info!("data:{:?}",data.to_string().as_str());
+
+            let ticker_list: Vec<SwapTrades> = serde_json::from_str(data.to_string().as_str()).unwrap();
+            ticker_list.iter().map(|item| {
+                let mut size_i = item.sz;
+                if item.side.as_str() == "sell" {
+                    size_i = -size_i;
+                } else if item.side.as_str() == "buy" {
+                    size_i = size_i;
+                }
+
+                Trades {
+                    id: item.trade_id.to_string(),
+                    data_type: "ticker_okx".to_string(),
+                    symbol: symbol.to_string(),
+                    create_time: item.ts.to_string(),
+                    size: (size_i * ct_val).to_string(),
+                    price: item.px.clone(),
+                    side: if size_i > Decimal::ZERO { "BUY".to_string() } else { "SELL".to_string() },
+                    is_effect: true,
+                }
+            }).collect()
+        } else {
+            vec![]
+        };
+        Ok(result)
+    } else {
+        Err(Error::new(ErrorKind::Other, res_data.to_string()))
+    }
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+#[allow(non_snake_case)]
+pub struct SwapSymbolDateils {
+    instType: String,
+    //    产品类型
+    instId: String,
+    //    产品id, 如 BTC-USD-SWAP
+    uly: String,
+    //    标的指数,如 BTC-USD,仅适用于交割/永续/期权
+    instFamily: String,
+    //    交易品种,如 BTC-USD,仅适用于交割/永续/期权
+    category: String,
+    //    币种类别(已废弃)
+    baseCcy: String,
+    //   交易货币币种,如 BTC-USDT 中的 BTC ,仅适用于币币/币币杠杆
+    quoteCcy: String,
+    //   计价货币币种,如 BTC-USDT 中的USDT ,仅适用于币币/币币杠杆
+    settleCcy: String,
+    //   盈亏结算和保证金币种,如 BTC 仅适用于交割/永续/期权
+    ctVal: Decimal,
+    //  合约面值,仅适用于交割/永续/期权
+    ctMult: String,
+    //   合约乘数,仅适用于交割/永续/期权
+    ctValCcy: String,
+    //  合约面值计价币种,仅适用于交割/永续/期权
+    optType: String,
+    //   期权类型,C或P 仅适用于期权
+    stk: String,
+    //  行权价格,仅适用于期权
+    listTime: String,
+    //  上线时间   Unix时间戳的毫秒数格式,如 1597026383085
+    expTime: String,
+    //   产品下线时间   适用于币币/杠杆/交割/永续/期权,对于 交割/期权,为交割/行权日期;亦可以为产品下线时间,有变动就会推送。
+    lever: String,
+    //   该instId支持的最大杠杆倍数,不适用于币币、期权
+    tickSz: String,
+    //   下单价格精度,如 0.0001     对于期权来说,是梯度中的最小下单价格精度,如果想要获取期权价格梯度,请使用"获取期权价格梯度"接口
+    lotSz: String,
+    //   下单数量精度,如 BTC-USDT-SWAP:1
+    minSz: String,
+    //   最小下单数量,    合约的数量单位是“张”,现货的数量单位是“交易货币”
+    ctType: String,
+    //  linear:正向合约 inverse:反向合约   仅适用于交割/永续
+    alias: String,
+    //    合约日期别名
+    // this_week:本周
+    // next_week:次周
+    // this_month:本月
+    // next_month:次月
+    // quarter:季度
+    // next_quarter:次季度
+    // 仅适用于交割
+    // 不建议使用,用户应通过 expTime 字段获取合约的交割日期
+    state: String,
+    //     产品状态
+    // live:交易中
+    // suspend:暂停中
+    // preopen:预上线,如:交割和期权的新合约在 live 之前,会有 preopen 状态
+    // test:测试中(测试产品,不可交易)
+    maxLmtSz: String,
+    //    合约或现货限价单的单笔最大委托数量,   合约的数量单位是“张”,现货的数量单位是“交易货币”
+    maxMktSz: String,
+    //    合约或现货市价单的单笔最大委托数量,   合约的数量单位是“张”,现货的数量单位是“USDT”
+    maxLmtAmt: String,
+    //    限价单的单笔最大USD价值
+    maxMktAmt: String,
+    //    市价单的单笔最大USD价值    仅适用于币币/币币杠杆
+    maxTwapSz: String,
+    //    合约或现货时间加权单的单笔最大委托数量,  合约的数量单位是“张”,现货的数量单位是“交易货币”
+    maxIcebergSz: String,
+    //    合约或现货冰山委托的单笔最大委托数量,  合约的数量单位是“张”,现货的数量单位是“交易货币”
+    maxTriggerSz: String,
+    //     合约或现货计划委托委托的单笔最大委托数量,   合约的数量单位是“张”,现货的数量单位是“交易货币”
+    maxStopSz: String,//     合约或现货止盈止损市价委托的单笔最大委托数量,   合约的数量单位是“张”,现货的数量单位是“USDT”
+}
+
+pub(crate) async fn standard_symbol_details(symbol: &str) -> Result<Option<SwapSymbolDateils>, Error> {
+    let mut symbol_fmt = symbol.replace("_", "-").clone();
+    symbol_fmt = symbol_fmt.to_uppercase();
+    let ct_val_ccy_array: Vec<&str> = symbol_fmt.split("-").collect();
+    let symbol_name = ct_val_ccy_array[0];
+
+
+    let res_data = get_symbol_details_all().await;
+    if res_data.code == "200" {
+        let res_data_str = res_data.data;
+        let json_value: serde_json::Value = serde_json::from_str(&res_data_str).unwrap();
+
+        if json_value.get("code").is_some() && json_value["code"] == "0" {
+            let data = json_value.get("data").unwrap();
+            // info!("data:{:?}",data.to_string().as_str());
+            let symbol_dateils_all: Vec<SwapSymbolDateils> = serde_json::from_str(data.to_string().as_str()).unwrap();
+
+            for symbol_dateils in symbol_dateils_all {
+                if symbol_name == symbol_dateils.ctValCcy.as_str() {
+                    return Ok(Option::from(symbol_dateils));
+                }
+            }
+        }
+        Ok(None)
+    } else {
+        Err(Error::new(ErrorKind::Other, res_data.to_string()))
+    }
+}
+
+pub async fn standard_ct_val(symbol: &str) -> Decimal {
+    match standard_symbol_details(symbol).await.unwrap() {
+        None => Decimal::ONE,
+        Some(d) => d.ctVal
+    }
+}
+
+
+

+ 3 - 0
src/swap_bybit/mod.rs

@@ -0,0 +1,3 @@
+pub mod bybit_swap_rest;
+pub mod bybit_swap_standard;
+pub mod bybit_swap_rest_utils;

+ 2 - 0
src/utils/utils.rs

@@ -72,6 +72,7 @@ pub struct BalanceConfigInfo {
     pub exchanges: Vec<String>,
     pub range_time: Vec<String>,
     pub count_range_time: Vec<String>,
+    pub export_mode: u64,
     pub export_path: String,
     pub export_name: String,
 }
@@ -85,6 +86,7 @@ impl BalanceConfigInfo {
             exchanges: vec![],
             range_time: vec![],
             count_range_time: vec![],
+            export_mode: 0,
             export_path: "".to_string(),
             export_name: "".to_string(),
         }