Selaa lähdekoodia

Merge branch 'dev' into test

skyffire 1 vuosi sitten
vanhempi
commit
8ea942152f

+ 2 - 2
Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "as-rust"
-version = "0.1.0"
+version = "1.4.4"
 edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -14,7 +14,7 @@ tokio = { version = "1.31.0", features = ["full"] }
 chrono = "0.4.26"
 tracing = "0.1"
 tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
-tracing-appender = "0.2.2"
+tracing-appender-timezone = { git = "https://github.com/skyfffire/tracing-appender-timezone.git" }
 serde = { version = "1.0.188", features = ["derive"] }
 actix-rt = "2.5.0"
 actix-web = "4.0.0-beta.12"

+ 2 - 1
exchanges/Cargo.toml

@@ -42,4 +42,5 @@ tracing = "0.1"
 tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
 log = "0.4.20"
 
-##
+##生成 xlsx
+rust_xlsxwriter = "0.58.0"

+ 462 - 0
exchanges/src/bybit_swap_rest.rs

@@ -0,0 +1,462 @@
+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 tracing::{info, trace};
+
+use crate::http_tool::RestTool;
+use crate::response_base::ResponseData;
+
+#[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) -> ResponseData {
+        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_announcements(&mut self) -> ResponseData {
+        let  params = serde_json::json!({
+            "locale":"zh-TW"
+         });
+        let data = self.request("GET".to_string(),
+                                "/v5".to_string(),
+                                "/announcements/index".to_string(),
+                                false,
+                                params.to_string(),
+        ).await;
+        data
+    }
+    //查詢可交易產品的規格信息
+    pub async fn get_instruments_info(&mut self, symbol: String) -> ResponseData {
+        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_positions(&mut self, symbol: String) -> ResponseData {
+        let mut params = serde_json::json!({
+            "category":"linear",
+         });
+        if symbol.len() > 0 {
+            params.as_object_mut().unwrap().insert("symbol".parse().unwrap(), serde_json::Value::from(symbol));
+        }
+        let data = self.request("GET".to_string(),
+                                "/v5".to_string(),
+                                "/position/list".to_string(),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+    //设置持仓模式
+    pub async fn set_position_mode(&mut self, symbol: String, mode: i64) -> ResponseData {
+        let params = serde_json::json!({
+             "category": "linear",
+             "symbol": symbol,
+             "mode": mode,
+         });
+        let data = self.request("POST".to_string(),
+                                "/v5".to_string(),
+                                "/position/switch-mode".to_string(),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+
+    //設置槓桿
+    pub async fn set_leverage(&mut self, symbol: String,
+                              lever: String) -> ResponseData {
+        let params = serde_json::json!({
+             "category": "linear",
+             "symbol": symbol,
+             "buyLeverage": lever,
+             "sellLeverage": lever,
+         });
+        let data = self.request("POST".to_string(),
+                                "/v5".to_string(),
+                                "/position/set-leverage".to_string(),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+
+
+    //查詢錢包餘額
+    pub async fn get_account_balance(&mut self) -> ResponseData {
+        let  params = serde_json::json!({
+            "accountType":"UNIFIED"
+         });
+        let data = self.request("GET".to_string(),
+                                "/v5".to_string(),
+                                "/account/wallet-balance".to_string(),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+
+    //創建委託單
+    pub async fn swap_order(&mut self, params: serde_json::Value) -> ResponseData {
+        let data = self.request("POST".to_string(),
+                                "/v5".to_string(),
+                                "/order/create".to_string(),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+    //查詢實時委託單
+    pub async fn get_order(&mut self, symbol: String, order_id: String, order_link_id: String) -> ResponseData {
+        let mut params = serde_json::json!({
+            "category":"linear",
+            "symbol":symbol,
+         });
+        if order_id.len() > 0 {
+            params.as_object_mut().unwrap().insert("orderId".parse().unwrap(), serde_json::Value::from(order_id));
+        }
+        if order_link_id.len() > 0 {
+            params.as_object_mut().unwrap().insert("orderLinkId".parse().unwrap(), serde_json::Value::from(order_link_id));
+        }
+        let data = self.request("GET".to_string(),
+                                "/v5".to_string(),
+                                "/order/realtime".to_string(),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+
+    //撤单
+    pub async fn cancel_order(&mut self, symbol: String, order_id: String, order_link_id: String) -> ResponseData {
+        let mut params = serde_json::json!({
+             "category": "linear",
+             "symbol": symbol,
+         });
+        if order_id.len() > 0 {
+            params.as_object_mut().unwrap().insert("orderId".parse().unwrap(), serde_json::Value::from(order_id));
+        }
+        if order_link_id.len() > 0 {
+            params.as_object_mut().unwrap().insert("orderLinkId".parse().unwrap(), serde_json::Value::from(order_link_id));
+        }
+        let data = self.request("POST".to_string(),
+                                "/v5".to_string(),
+                                "/order/cancel".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) -> ResponseData
+    {
+        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 e = ResponseData::error(self.label.clone(), "登陆参数错误!".to_string());
+                return e;
+            } 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 = RestTool::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<ResponseData, reqwest::Error> {
+        let res_data: ResponseData;
+        /****请求接口与 地址*/
+        let url = format!("{}{}", self.base_url.to_string(), request_path);
+        let request_type = request_type.clone().to_uppercase();
+        let addrs_url: String = if RestTool::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 response = req.send().await?;
+        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);
+        } else {
+            let body = response.text().await?;
+            // trace!("error-----{}", body);
+            res_data = ResponseData::error(self.label.clone(), body.to_string())
+        }
+
+        Ok(res_data)
+    }
+
+
+    //res_data 解析
+    pub fn res_data_analysis(result: Result<ResponseData, reqwest::Error>, base_url: String, params: String) -> ResponseData {
+        // trace!("原始数据:{:?}",result);
+        match result {
+            Ok(res_data) => {
+                if res_data.code != "200" {
+                    // trace!("不等于200");
+                    let message: String = res_data.message;
+                    let json_value: serde_json::Value = serde_json::from_str(&message).unwrap();
+                    let code = json_value["code"].as_str().unwrap();
+                    let msg = json_value["msg"].as_str().unwrap();
+                    let error = ResponseData::new("".to_string(),
+                                                  format!("{}", code),
+                                                  format!("{}", msg),
+                                                  format!("请求地址:{},请求参数:{}", base_url, params));
+                    error
+                } else {
+                    let body: String = res_data.data.as_str().parse().unwrap();
+                    let json_value: serde_json::Value = serde_json::from_str(&body).unwrap();
+                    // trace!("json_value:{:?}",json_value.to_string().as_str());
+                    let code: i64 = if json_value["retCode"].as_i64().is_some() {
+                        json_value["retCode"].as_i64().unwrap()
+                    } else if json_value["ret_code"].as_i64().is_some() {
+                        json_value["ret_code"].as_i64().unwrap()
+                    } else {
+                        -1
+                    };
+
+                    let msg: &str = if json_value["retMsg"].as_str().is_some() {
+                        json_value["retMsg"].as_str().unwrap()
+                    } else if json_value["ret_msg"].as_str().is_some() {
+                        json_value["ret_msg"].as_str().unwrap()
+                    } else {
+                        ""
+                    };
+
+
+                    if code == 0 {
+                        let data = serde_json::to_string(&json_value["result"]).unwrap();
+                        let mut success = ResponseData::new("".to_string(), "200".to_string(), "success".to_string(), data.parse().unwrap());
+                        success.time = json_value["time"].as_i64().unwrap();
+                        success
+                    } else {
+                        let error = ResponseData::new("".to_string(),
+                                                      format!("{}", code),
+                                                      format!("{}", msg),
+                                                      format!("请求地址:{},请求参数:{}", base_url, params));
+                        error
+                    }
+                }
+            }
+            Err(err) => {
+                let error = ResponseData::error("".to_string(), format!("json 解析失败:{}", err));
+                error
+            }
+        }
+    }
+    fn get_timestamp() -> String {
+        chrono::Utc::now().timestamp_millis()
+            .to_string()
+    }
+}

+ 76 - 58
exchanges/src/crypto_spot_ws.rs

@@ -20,7 +20,10 @@ pub enum CryptoSpotWsType {
 //订阅频道
 #[derive(Clone)]
 pub enum CryptoSpotSubscribeType {
-    PuGetInstruments,
+    PuBook,
+    PuTicker,
+    PuTrade,
+    PuCandlestick,
 }
 
 //账号信息
@@ -60,7 +63,7 @@ impl CryptoSpotWs {
         let address_url = match ws_type {
             CryptoSpotWsType::Public => {
                 "wss://stream.crypto.com/exchange/v1/market".to_string()
-            },
+            }
             CryptoSpotWsType::Private => {
                 "wss://stream.crypto.com/exchange/v1/user".to_string()
             }
@@ -92,7 +95,7 @@ impl CryptoSpotWs {
     //手动添加币对
     pub fn set_symbols(&mut self, mut b_array: Vec<String>) {
         for symbol in b_array.iter_mut() {
-            // 
+            // 
             *symbol = symbol.to_uppercase();
             // 字符串替换
             *symbol = symbol.replace("_", "");
@@ -104,7 +107,11 @@ impl CryptoSpotWs {
     fn contains_pr(&self) -> bool {
         for t in self.subscribe_types.clone() {
             if match t {
-                CryptoSpotSubscribeType::PuGetInstruments => false,
+                CryptoSpotSubscribeType::PuBook => false,
+                CryptoSpotSubscribeType::PuTicker => false,
+                CryptoSpotSubscribeType::PuTrade => false,
+                CryptoSpotSubscribeType::PuCandlestick => false,
+
             } {
                 return true;
             }
@@ -117,8 +124,17 @@ impl CryptoSpotWs {
     //订阅枚举解析
     pub fn enum_to_string(symbol: String, subscribe_type: CryptoSpotSubscribeType) -> String {
         match subscribe_type {
-            CryptoSpotSubscribeType::PuGetInstruments => {
-                format!("{}@aggTrade", symbol)
+            CryptoSpotSubscribeType::PuBook => {
+                format!("book.{}-PERP", symbol)
+            }
+            CryptoSpotSubscribeType::PuTicker => {
+                format!("ticker.{}-PERP", symbol)
+            }
+            CryptoSpotSubscribeType::PuTrade => {
+                format!("trade.{}-PERP", symbol)
+            }
+            CryptoSpotSubscribeType::PuCandlestick => {
+                format!("candlestick.M1.{}-PERP", symbol)
             }
         }
     }
@@ -132,17 +148,21 @@ impl CryptoSpotWs {
             }
         }
 
-       let nonce =  Utc::now().timestamp_millis();
+        let nonce = Utc::now().timestamp_millis();
         let str = json!({
-          "id": 1,
-          "method": "subscribe",
-          "params": {
-            "channels":params
-          },
-          "nonce": nonce
-        });
+                  "id": 1,
+                  "method": "subscribe",
+                  "params": {
+                    "channels":params
+                  },
+                  "nonce": nonce
+                });
 
-        str.to_string()
+        if params.len() > 0 {
+            str.to_string()
+        } else {
+            "".to_string()
+        }
     }
     /*******************************************************************************************************/
     /*****************************************socket基本*****************************************************/
@@ -155,7 +175,7 @@ impl CryptoSpotWs {
                                   read_tx: UnboundedSender<ResponseData>) -> Result<(), Error>
     {
         let login_is = self.contains_pr();
-        // let subscription = self.get_subscription();
+        let subscription = self.get_subscription();
         let address_url = self.address_url.clone();
         let label = self.label.clone();
         // let heartbeat_time = self.heartbeat_time.clone();
@@ -170,11 +190,11 @@ impl CryptoSpotWs {
         // });
 
         //设置订阅
-        let  subscribe_array = vec![];
+        let mut subscribe_array = vec![];
         if login_is {
             //登录相关
         }
-        // subscribe_array.push(subscription.to_string());
+        subscribe_array.push(subscription.to_string());
 
         //链接
         let t2 = tokio::spawn(async move {
@@ -198,7 +218,7 @@ impl CryptoSpotWs {
     /*******************************************************************************************************/
     //数据解析-Text
     pub fn message_text(text: String) -> Option<ResponseData> {
-        let  response_data = Self::ok_text(text);
+        let response_data = Self::ok_text(text);
         Option::from(response_data)
     }
     //数据解析-ping
@@ -211,56 +231,54 @@ impl CryptoSpotWs {
     }
     //数据解析
     pub fn ok_text(text: String) -> ResponseData {
-        trace!("原始数据");
-        trace!(?text);
+        // trace!("原始数据");
+        // trace!(?text);
+
         let mut res_data = ResponseData::new("".to_string(), "".to_string(), "success".to_string(), "".to_string());
         let json_value: serde_json::Value = serde_json::from_str(&text).unwrap();
 
-        if json_value.get("id").is_some()  && json_value.get("method").is_some()&& json_value.get("code").is_some()  {
-            //服务器心跳,需要做响应
+        if json_value.get("id").is_some() && json_value.get("method").is_some() && json_value.get("code").is_some() {
             let id = json_value["id"].as_i64().unwrap();
             let method = json_value["method"].as_str().unwrap();
             let code = json_value["code"].as_i64().unwrap();
-            if code == 0 && method == "public/heartbeat"{
-                res_data.code = "-302".to_string();
-                let str = json!({
+
+            if method == "public/heartbeat" {
+                if code == 0 {
+                    res_data.code = "-302".to_string();
+                    let str = json!({
                   "id": id,
                   "method": "public/respond-heartbeat"
                 });
-                res_data.message = "服务器主动心跳检测,客户端回应~!".to_string();
-                res_data.data = str .to_string();
+                    res_data.message = "服务器主动心跳检测,客户端回应~!".to_string();
+                    res_data.data = str.to_string();
+                } else {
+                    res_data.message = "心跳异常~!".to_string();
+                }
+            } else if method == "subscribe" && json_value.get("channel").is_some() {
+                //订阅反馈
+                if code == 0 {
+                    res_data.code = "-201".to_string();
+                    res_data.channel = json_value["channel"].as_str().unwrap().to_string();
+                } else {
+                    res_data.message = "订阅失败~!".to_string();
+                    res_data.data = json_value["data"].to_string()
+                }
+            } else if method == "subscribe" && json_value.get("result").is_some() {
+                if code == 0 {
+                    let subscription = json_value["result"]["subscription"].as_str().unwrap();
+                    res_data.channel = subscription.to_string();
+                    res_data.code = "200".to_string();
+                    res_data.data = json_value["result"]["data"].to_string()
+                } else {
+                    res_data.message = "推送数据异常~!".to_string();
+                    res_data.data = json_value["result"]["data"].to_string()
+                }
+            } else {
+                res_data.message = "未知解析!!".to_string();
             }
-        } else{
-            res_data.message = "未知解析!!".to_string();
+        } else {
+            res_data.message = "错误解析!!".to_string();
         }
-
-        // if json_value.get("result").is_some() && json_value.get("id").is_some() &&
-        //     json_value.get("id").unwrap() == 1
-        // {
-        //     res_data.code = "-201".to_string();
-        //     res_data.message = "订阅成功".to_string();
-        // } else if json_value.get("error").is_some() {//订阅返回
-        //     res_data.code = json_value["error"]["code"].to_string();
-        //     res_data.message = json_value["error"]["msg"].to_string();
-        // } else if json_value.get("stream").is_some() {//订阅返回
-        //     res_data.data = format!("{}", json_value.get("data").as_ref().unwrap());
-        //     res_data.code = "200".to_string();
-        //
-        //     let channel = format!("{}", json_value.get("stream").as_ref().unwrap());
-        //     if channel.contains("@aggTrade") {
-        //         res_data.channel = "aggTrade".to_string();
-        //     } else if channel.contains("@depth20@100ms") {
-        //         res_data.channel = "depth".to_string();
-        //     } else if channel.contains("@bookTicker") {
-        //         res_data.channel = "bookTicker".to_string();
-        //     } else {
-        //         res_data.channel = "未知的频道".to_string();
-        //     }
-        // } else {
-        //     res_data.data = text
-        // }
-
-
         res_data
     }
 }

+ 1 - 0
exchanges/src/gate_swap_rest.rs

@@ -351,6 +351,7 @@ impl GateSwapRest {
         ).await;
         data
     }
+
     //查询个人成交记录
     pub async fn my_trades(&mut self, settle: String, contract: String, limit: i64) -> ResponseData {
         let mut params = serde_json::json!({

+ 2 - 2
exchanges/src/http_tool.rs

@@ -57,8 +57,8 @@ impl RestTool {
                     serde_json::Value::String(s) => s.clone(),
                     _ => value.to_string()
                 };
-                trace!("Key: {}", key);
-                trace!("Value: {}", formatted_value);
+                // trace!("Key: {}", key);
+                // trace!("Value: {}", formatted_value);
                 // let formatted_value = match value {
                 //     Value::String(s) => s.clone(),
                 //     _ => value.to_string()

+ 2 - 0
exchanges/src/lib.rs

@@ -20,4 +20,6 @@ pub mod bitget_spot_rest;
 pub mod kucoin_spot_ws;
 pub mod kucoin_spot_rest;
 pub mod crypto_spot_ws;
+pub mod bybit_swap_rest;
+pub mod xlsx_utils;
 

+ 1 - 1
exchanges/src/socket_tool.rs

@@ -105,7 +105,7 @@ impl AbstractWsMode {
                                 }
                             }
                             "-200" => {
-                                //订阅成功
+                                //登录成功
                                 trace!("登录成功:{:?}", data);
                             }
                             "-201" => {

+ 1 - 0
exchanges/src/utils.rs

@@ -6,3 +6,4 @@
 //     total_micros
 // }
 //
+

+ 92 - 0
exchanges/src/xlsx_utils.rs

@@ -0,0 +1,92 @@
+use std::collections::BTreeMap;
+use rust_xlsxwriter::*;
+
+pub fn creation_xlsx(one_row_name: &Vec<&str>, data_rows: &BTreeMap<String, Vec<Vec<String>>>, file_name: String) -> Result<(), XlsxError> {
+
+    // 创建一个新的Excel文件对象。
+    let mut workbook = Workbook::new();
+    for (key, value) in data_rows {
+        // 向工作簿中添加工作表。
+        let  worksheet = workbook.add_worksheet();
+        worksheet.set_name(key).unwrap();
+
+        // 第一行 列,明
+        let mut row_index: usize = 0;
+        let mut i: usize = 0;
+        let bold_format = Format::new().set_bold();
+        while i <= one_row_name.len() - 1 {
+            worksheet.write_with_format(row_index as RowNum, i as ColNum, one_row_name[i], &bold_format)?;
+            i = i + 1;
+        }
+
+
+        //后续 数据写入
+        row_index = 1;
+        while row_index <= value.len()  {
+            let row = value.get(row_index - 1).unwrap();
+
+            i = 0;
+            for str in row {
+                worksheet.write(row_index as RowNum, i as ColNum, str)?;
+                i = i + 1;
+            }
+            row_index = row_index + 1;
+        }
+    }
+
+
+    //
+    // for row in one_row_name {}
+    // // 写一个不带格式的字符串。
+    // worksheet.write(0, 0, "Hello")?;
+    //
+    //
+    // // 创建一些要在工作表中使用的格式。
+    // let bold_format = Format::new().set_bold();
+    // let decimal_format = Format::new().set_num_format("0.000");
+    // let date_format = Format::new().set_num_format("yyyy-mm-dd");
+    // let merge_format = Format::new()
+    //     .set_border(FormatBorder::Thin)
+    //     .set_align(FormatAlign::Center);
+    //
+    //
+    // // 为清晰设置列宽度。
+    // worksheet.set_column_width(0, 22)?;
+    //
+    // // 写一个不带格式的字符串。
+    // worksheet.write(0, 0, "Hello")?;
+    //
+    // // 用上面定义的粗体格式编写一个字符串。
+    // worksheet.write_with_format(1, 0, "World", &bold_format)?;
+    //
+    // //
+    // worksheet.write(2, 0, 1)?;
+    // worksheet.write(3, 0, 2.34)?;
+    //
+    // // 用格式写一个数字。
+    // worksheet.write_with_format(4, 0, 3.00, &decimal_format)?;
+    //
+    // // 写一个公式。
+    // worksheet.write(5, 0, Formula::new("=SIN(PI()/4)"))?;
+    //
+    // // 写一个日期。
+    // let date = ExcelDateTime::from_ymd(2023, 1, 25)?;
+    // worksheet.write_with_format(6, 0, &date, &date_format)?;
+    //
+    // //写一些链接。
+    // worksheet.write(7, 0, Url::new("https://www.rust-lang.org"))?;
+    // worksheet.write(8, 0, Url::new("https://www.rust-lang.org").set_text("Rust"))?;
+
+    // 写入一些合并的单元格。
+    // worksheet.merge_range(9, 0, 9, 1, "Merged cells", &merge_format)?;
+
+    // Insert an image.
+    // let image = Image::new("examples/rust_logo.png")?;
+    // worksheet.insert_image(1, 2, &image)?;
+
+    // 生成文件
+    let path = format!("./{}.xlsx", file_name);
+    workbook.save(path)?;
+
+    Ok(())
+}

+ 220 - 0
exchanges/tests/bybit_swap_test.rs

@@ -0,0 +1,220 @@
+use std::collections::BTreeMap;
+
+use exchanges::bybit_swap_rest::BybitSwapRest;
+
+const ACCESS_KEY: &str = "";
+const SECRET_KEY: &str = "";
+
+
+//ws-订阅公共频道信息
+// #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
+// async fn ws_custom_subscribe_pu() {
+//     global::log_utils::init_log_with_trace();
+//
+//
+//     let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
+//     let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded();
+//
+//
+//     let mut ws = get_ws(None, BybitSwapWsType::Public).await;
+//     ws.set_symbols(vec!["BTC_USDT".to_string()]);
+//     ws.set_subscribe(vec![
+//         // BybitSwapSubscribeType::PuBooks5,
+//         // BybitSwapSubscribeType::Putrades,
+//         BybitSwapSubscribeType::PuBooks50L2tbt,
+//         // BybitSwapSubscribeType::PuIndexTickers,
+//     ]);
+//
+//
+//     let write_tx_am = Arc::new(Mutex::new(write_tx));
+//     let bool_v1 = Arc::new(AtomicBool::new(true));
+//
+//     //读取
+//     let _bool_v1_clone = Arc::clone(&bool_v1);
+//     let _tr = tokio::spawn(async move {
+//         trace!("线程-数据读取-开启");
+//         loop {
+//             if let Some(data) = read_rx.next().await {
+//                 trace!("读取数据data:{:?}",data)
+//             }
+//         }
+//         // trace!("线程-数据读取-结束");
+//     });
+//
+//     //写数据
+//     // let bool_v2_clone = Arc::clone(&bool_v1);
+//     // let write_tx_clone = Arc::clone(&write_tx_am);
+//     // let su = ws.get_subscription();
+//     // let tw = tokio::spawn(async move {
+//     //     trace!("线程-数据写入-开始");
+//     //     loop {
+//     //         tokio::time::sleep(Duration::from_millis(20 * 1000)).await;
+//     //         // let close_frame = CloseFrame {
+//     //         //     code: CloseCode::Normal,
+//     //         //     reason: Cow::Borrowed("Bye bye"),
+//     //         // };
+//     //         // let message = Message::Close(Some(close_frame));
+//     //
+//     //
+//     //         let message = Message::Text(su.clone());
+//     //         AbstractWsMode::send_subscribe(write_tx_clone.clone(), message.clone()).await;
+//     //         trace!("发送指令成功");
+//     //     }
+//     //     trace!("线程-数据写入-结束");
+//     // });
+//
+//     let t1 = tokio::spawn(async move {
+//         //链接
+//         let bool_v3_clone = Arc::clone(&bool_v1);
+//         ws.ws_connect_async(bool_v3_clone, &write_tx_am, write_rx, read_tx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
+//         trace!("test 唯一线程结束--");
+//     });
+//     tokio::try_join!(t1).unwrap();
+//     trace!("当此结束");
+//     trace!("重启!");
+//     trace!("参考交易所关闭");
+//     return;
+// }
+//
+
+//rest-服務器時間
+#[tokio::test]
+async fn rest_get_server_time_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let req_data = ret.get_server_time().await;
+    println!("Bybit--服務器時間--{:?}", req_data);
+}
+
+//rest-查詢公告
+#[tokio::test]
+async fn rest_get_announcements_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let req_data = ret.get_announcements().await;
+    println!("Bybit--查詢公告--{:?}", req_data);
+}
+
+//rest-查詢可交易產品的規格信息
+#[tokio::test]
+async fn rest_get_instruments_info_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let req_data = ret.get_instruments_info("BTCUSDT".to_string()).await;
+    println!("Bybit--查詢可交易產品的規格信息--{:?}", req_data);
+}
+
+
+//rest-查詢錢包餘額
+#[tokio::test]
+async fn rest_get_account_balance_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let req_data = ret.get_account_balance().await;
+    println!("Bybit--查詢錢包餘額--{:?}", req_data);
+}
+
+//rest-查看持仓信息
+#[tokio::test]
+async fn rest_get_positions_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let req_data = ret.get_positions("DOGEUSDT".to_string()).await;
+    println!("Bybit--查看持仓信息--{:?}", req_data);
+}
+
+//rest-设置持仓模式
+#[tokio::test]
+async fn rest_set_position_mode_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let req_data = ret.set_position_mode("DOGEUSDT".to_string(), 3).await;
+    println!("Bybit--设置持仓模式--{:?}", req_data);
+}
+
+//rest-設置槓桿
+#[tokio::test]
+async fn rest_set_leverage_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let req_data = ret.set_leverage(
+        "DOGEUSDT".to_string(), "1".to_string()).await;
+    println!("Bybit--設置槓桿--{:?}", req_data);
+}
+
+
+//rest-創建委託單
+#[tokio::test]
+async fn rest_swap_order_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let params = serde_json::json!({
+            "category":"linear",
+            "symbol":"DOGEUSDT",
+            "orderType":"Limit",
+            "side":"Buy",
+            "qty":"1",
+            "price":"0.085",
+         });
+    let req_data = ret.swap_order(params).await;
+    println!("Bybit--創建委託單--{:?}", req_data);
+}
+
+
+//rest-查詢實時委託單
+#[tokio::test]
+async fn rest_get_order_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let req_data = ret.get_order("DOGEUSDT".to_string(),
+                                 "".to_string(), "".to_string()).await;
+    println!("Bybit--查詢實時委託單--{:?}", req_data);
+}
+
+
+//rest-撤单
+#[tokio::test]
+async fn rest_cancel_order_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let req_data = ret.cancel_order("DOGEUSDT".to_string(),
+                                    "1d3ea16f-cf1c-4dab-9a79-d441a2dea549".to_string(), "".to_string()).await;
+    println!("Bybit--撤单--{:?}", req_data);
+}
+
+
+// //rest-查詢實時委託單
+// #[tokio::test]
+// async fn rest_get_order_test() {
+//     global::log_utils::init_log_with_trace();
+//
+//     let mut ret = get_rest();
+//     let req_data = ret.get_order("BTCUSDT".to_string(),
+//                                  "".to_string(), "".to_string()).await;
+//     println!("Bybit--查詢可交易產品的規格信息--{:?}", req_data);
+// }
+
+
+// async fn get_ws(btree_map: Option<BybitSwapLogin>, type_v: BybitSwapWsType) -> BybitSwapWs {
+//     let ku_ws = BybitSwapWs::new(false, btree_map, type_v);
+//     ku_ws
+// }
+
+fn get_rest() -> BybitSwapRest {
+    let mut btree_map: BTreeMap<String, String> = BTreeMap::new();
+    btree_map.insert("access_key".to_string(), ACCESS_KEY.to_string());
+    btree_map.insert("secret_key".to_string(), SECRET_KEY.to_string());
+
+    let bybit_exc = BybitSwapRest::new(false, btree_map.clone());
+    bybit_exc
+}

+ 9 - 9
exchanges/tests/crypto_spot_test.rs

@@ -5,7 +5,7 @@ use futures_util::StreamExt;
 use tokio::sync::Mutex;
 use tracing::trace;
 
-use exchanges::crypto_spot_ws::{CryptoSpotLogin, CryptoSpotWs, CryptoSpotWsType};
+use exchanges::crypto_spot_ws::{CryptoSpotLogin, CryptoSpotSubscribeType, CryptoSpotWs, CryptoSpotWsType};
 
 const ACCESS_KEY: &str = "";
 const SECRET_KEY: &str = "";
@@ -19,12 +19,13 @@ async fn ws_pu() {
     let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
     let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded();
 
-    let mut ws = get_ws(None,CryptoSpotWsType::Public);
-    // ws.set_symbols(vec!["BTC_USDT".to_string()]);
+    let mut ws = get_ws(None, CryptoSpotWsType::Public);
+    ws.set_symbols(vec!["BTC_USD".to_string()]);
     ws.set_subscribe(vec![
-        // CryptoSpotSubscribeType::PuBookTicker,
-        // BinanceSwapSubscribeType::PuAggTrade,
-        // BinanceSwapSubscribeType::PuDepth20levels100ms,
+        // CryptoSpotSubscribeType::PuBook,
+        // CryptoSpotSubscribeType::PuTicker,
+        // CryptoSpotSubscribeType::PuTrade,
+        CryptoSpotSubscribeType::PuCandlestick,
     ]);
 
     let write_tx_am = Arc::new(Mutex::new(write_tx));
@@ -75,12 +76,11 @@ async fn ws_pu() {
     trace!("重启!");
     trace!("参考交易所关闭");
     return;
-
 }
 
-fn get_ws(btree_map: Option< CryptoSpotLogin>,ws_type:CryptoSpotWsType) -> CryptoSpotWs {
+fn get_ws(btree_map: Option<CryptoSpotLogin>, ws_type: CryptoSpotWsType) -> CryptoSpotWs {
     let binance_ws = CryptoSpotWs::new(false,
-                                        btree_map,
+                                       btree_map,
                                        ws_type);
     binance_ws
 }

+ 18 - 6
exchanges/tests/gate_swap_test.rs

@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
 use tracing::trace;
 
 use exchanges::gate_swap_rest::GateSwapRest;
-use exchanges::gate_swap_ws::{GateSwapLogin, GateSwapSubscribeType, GateSwapWs,  GateSwapWsType};
+use exchanges::gate_swap_ws::{GateSwapLogin, GateSwapSubscribeType, GateSwapWs, GateSwapWsType};
 
 const ACCESS_KEY: &str = "";
 const SECRET_KEY: &str = "";
@@ -20,7 +20,7 @@ async fn ws_custom_subscribe() {
     let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
     let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded();
 
-    let param  = GateSwapLogin{
+    let param = GateSwapLogin {
         api_key: ACCESS_KEY.to_string(),
         secret: SECRET_KEY.to_string(),
     };
@@ -99,18 +99,30 @@ async fn rest_cancel_order_all_test() {
     println!("okx--设置持仓模式--{:?}", req_data);
 }
 
+
+//rest-查询合约账户变更历史
+#[tokio::test]
+async fn rest_account_book_test() {
+    global::log_utils::init_log_with_trace();
+
+    let mut ret = get_rest();
+    let req_data = ret.account_book("usdt".to_string()).await;
+    println!("okx--查询合约账户变更历史--{:?}", req_data);
+}
+
 fn get_ws(btree_map: Option<GateSwapLogin>) -> GateSwapWs {
-    let  binance_ws = GateSwapWs::new(false,
-                                         btree_map,
-                                         GateSwapWsType::PublicAndPrivate("usdt".to_string()));
+    let binance_ws = GateSwapWs::new(false,
+                                     btree_map,
+                                     GateSwapWsType::PublicAndPrivate("usdt".to_string()));
     binance_ws
 }
 
+
 fn get_rest() -> GateSwapRest {
     let mut btree_map: BTreeMap<String, String> = BTreeMap::new();
     btree_map.insert("access_key".to_string(), ACCESS_KEY.to_string());
     btree_map.insert("secret_key".to_string(), SECRET_KEY.to_string());
 
-    let  ba_exc = GateSwapRest::new(false, btree_map);
+    let ba_exc = GateSwapRest::new(false, btree_map);
     ba_exc
 }

+ 52 - 56
exchanges/tests/test.rs

@@ -2,9 +2,9 @@ use exchanges::gate_swap_rest::GateSwapRest;
 use std::collections::BTreeMap;
 use tokio::io::{AsyncReadExt};
 use exchanges::kucoin_swap_rest::KucoinSwapRest;
-use exchanges::kucoin_swap_ws::{KucoinSubscribeType, KucoinSwapWs, KucoinWsType};
+use exchanges::kucoin_swap_ws::{KucoinSwapSubscribeType, KucoinSwapWs, KucoinSwapWsType};
 use exchanges::{proxy};
-use exchanges::okx_swap_ws::{OkxSubscribeType, OkxSwapWs, OkxWsType};
+use exchanges::okx_swap_ws::{OkxSwapSubscribeType, OkxSwapWs, OkxSwapWsType};
 
 use std::io::{Read, Write};
 use std::sync::Arc;
@@ -13,9 +13,8 @@ use tokio::sync::mpsc::channel;
 use tokio::try_join;
 use tracing::{trace};
 use tracing::instrument::WithSubscriber;
-use tungstenite::{connect, Message};
 use exchanges::binance_swap_rest::BinanceSwapRest;
-use exchanges::binance_swap_ws::{BinanceSubscribeType, BinanceSwapWs, BinanceWsType};
+use exchanges::binance_swap_ws::{BinanceSwapSubscribeType, BinanceSwapWs, BinanceSwapWsType};
 use exchanges::okx_swap_rest::OkxSwapRest;
 
 #[tokio::test]
@@ -46,7 +45,7 @@ async fn test_import() {
 
 
     //kucoin_rest -账户信息
-    demo_rest_kucoin().await;
+    // demo_rest_kucoin().await;
     //Kucoin-ws--公共频道
     // demo_ws_kucoin_pu().await;
     //Kucoin-ws--私有频道
@@ -186,9 +185,7 @@ async fn demo_okx_rest() {
     // trace!("okx_rest-rest - get_account- {:?}", res_data);
 }
 
-async fn demo_ws_gate() {
-
-}
+async fn demo_ws_gate() {}
 
 
 fn demo_so() {
@@ -283,54 +280,54 @@ async fn demo_ws_okx_bu() {
 }
 
 async fn demo_ws_kucoin_pr() {
-    let mut bool_v1 = Arc::new(AtomicBool::new(true));
-    let mut btree_map: BTreeMap<String, String> = BTreeMap::new();
-    btree_map.insert("access_key".to_string(), "".to_string());
-    btree_map.insert("secret_key".to_string(), "".to_string());
-    btree_map.insert("pass_key".to_string(), "".to_string());
-    trace!("----------------------btree_map{:?}", btree_map.clone());
-    let (tx, mut rx) = channel(1024);
-    let mut ku_ws = KucoinSwapWs::new(false, btree_map.clone(),
-                                      KucoinWsType::Private, tx).await;
-    ku_ws.set_subscribe(vec![KucoinSubscribeType::PrContractMarketTradeOrdersSys]);
-
-    let t1 = tokio::spawn(async move {
-        ku_ws.custom_subscribe(bool_v1,vec!["ACHUSDTM".to_string(), "ROSEUSDTM".to_string()]).await;
-    });
-
-    let t2 = tokio::spawn(async move {
-        loop {
-            if let Ok(received) = rx.try_recv() {
-                trace!( "age: {:?}", received);
-            }
-        }
-    });
-
-    try_join!(t1,t2).unwrap();
+    // let mut bool_v1 = Arc::new(AtomicBool::new(true));
+    // let mut btree_map: BTreeMap<String, String> = BTreeMap::new();
+    // btree_map.insert("access_key".to_string(), "".to_string());
+    // btree_map.insert("secret_key".to_string(), "".to_string());
+    // btree_map.insert("pass_key".to_string(), "".to_string());
+    // trace!("----------------------btree_map{:?}", btree_map.clone());
+    // let (tx, mut rx) = channel(1024);
+    // let mut ku_ws = KucoinSwapWs::new(false, btree_map.clone(),
+    //                                   KucoinWsType::Private, tx).await;
+    // ku_ws.set_subscribe(vec![KucoinSubscribeType::PrContractMarketTradeOrdersSys]);
+    //
+    // let t1 = tokio::spawn(async move {
+    //     ku_ws.custom_subscribe(bool_v1, vec!["ACHUSDTM".to_string(), "ROSEUSDTM".to_string()]).await;
+    // });
+    //
+    // let t2 = tokio::spawn(async move {
+    //     loop {
+    //         if let Ok(received) = rx.try_recv() {
+    //             trace!( "age: {:?}", received);
+    //         }
+    //     }
+    // });
+    //
+    // try_join!(t1,t2).unwrap();
 }
 
 async fn demo_ws_kucoin_pu() {
-    let mut bool_v1 = Arc::new(AtomicBool::new(true));
-    let btree_map: BTreeMap<String, String> = BTreeMap::new();
-    let (tx, mut rx) = channel(1024);
-    let mut ku_ws = KucoinSwapWs::new(false, btree_map, KucoinWsType::Public, tx).await;
-    ku_ws.set_subscribe(vec![
-        KucoinSubscribeType::PuContractMarketLevel2Depth50,
-        KucoinSubscribeType::PuContractMarkettickerV2,
-    ]);
-
-    let t1 = tokio::spawn(async move {
-        ku_ws.custom_subscribe(bool_v1,vec!["ACHUSDTM".to_string(), "ROSEUSDTM".to_string()]).await;
-    });
-    let t2 = tokio::spawn(async move {
-        loop {
-            if let Ok(received) = rx.try_recv() {
-                trace!("age: {:?}", received);
-            }
-        }
-    });
-
-    try_join!(t1,t2).unwrap();
+    // let mut bool_v1 = Arc::new(AtomicBool::new(true));
+    // let btree_map: BTreeMap<String, String> = BTreeMap::new();
+    // let (tx, mut rx) = channel(1024);
+    // let mut ku_ws = KucoinSwapWs::new(false, btree_map, KucoinWsType::Public, tx).await;
+    // ku_ws.set_subscribe(vec![
+    //     KucoinSubscribeType::PuContractMarketLevel2Depth50,
+    //     KucoinSubscribeType::PuContractMarkettickerV2,
+    // ]);
+    //
+    // let t1 = tokio::spawn(async move {
+    //     ku_ws.custom_subscribe(bool_v1, vec!["ACHUSDTM".to_string(), "ROSEUSDTM".to_string()]).await;
+    // });
+    // let t2 = tokio::spawn(async move {
+    //     loop {
+    //         if let Ok(received) = rx.try_recv() {
+    //             trace!("age: {:?}", received);
+    //         }
+    //     }
+    // });
+    //
+    // try_join!(t1,t2).unwrap();
 }
 
 async fn demo_rest_kucoin() {
@@ -437,11 +434,10 @@ async fn demo_rest_gate() {
     // trace!("gate-rest -order{:?}", res_data);
 
 
-    let res_data = gate_exc.my_trades("usdt".to_string()).await;
-    trace!("gate-rest -my_trades{:?}", res_data);
+    // let res_data = gate_exc.my_trades("usdt".to_string()).await;
+    // trace!("gate-rest -my_trades{:?}", res_data);
     let res_data = gate_exc.account_book("usdt".to_string()).await;
     trace!("gate-rest -account_book{:?}", res_data);
-
 }
 
 async fn demo_rest_ba() {

+ 229 - 0
exchanges/tests/xlsx_test.rs

@@ -0,0 +1,229 @@
+use std::collections::{BTreeMap, HashSet};
+use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
+
+use tracing::{trace};
+
+use exchanges::gate_swap_rest::GateSwapRest;
+use exchanges::xlsx_utils::creation_xlsx;
+
+//ws-订阅公共频道信息
+#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
+async fn test_gate_creation_xlsx() {
+    global::log_utils::init_log_with_trace();
+
+    //获取不同账号的数据
+    let mut acc_array: Vec<BTreeMap<String, String>> = vec![];
+    let mut acc_all_data: BTreeMap<String, Vec<Vec<String>>> = BTreeMap::new();
+    let mut acc_all_data_clone: BTreeMap<String, Vec<Vec<String>>> = BTreeMap::new();
+    let mut data_array_all: Vec<Vec<String>> = vec![];
+    let mut time_all: Vec<i64> = vec![];
+    loop {
+        let mut gate60: BTreeMap<String, String> = BTreeMap::new();
+        gate60.insert("acc_name".to_string(), String::from("gate60"));
+        gate60.insert("access_key".to_string(), String::from("61e3a1b12c44c8ccfce1f32782b9922f"));
+        gate60.insert("secret_key".to_string(), String::from("f3f533fa685cbae44b3017f73b71e90eaa9aa8d4922a39426744721e4824a527"));
+        acc_array.push(gate60);
+
+        let mut gate59: BTreeMap<String, String> = BTreeMap::new();
+        gate59.insert("acc_name".to_string(), String::from("gate59"));
+        gate59.insert("access_key".to_string(), String::from("c691d4fcc5a5a98af459f993a3a0c653"));
+        gate59.insert("secret_key".to_string(), String::from("05e7f8640bffeacc146b6f9f08512955d00d89bbdb051c9427f31adf96adeb2f"));
+        acc_array.push(gate59);
+
+        let mut gate58: BTreeMap<String, String> = BTreeMap::new();
+        gate58.insert("acc_name".to_string(), String::from("gate58"));
+        gate58.insert("access_key".to_string(), String::from("dfdc30687ac71daefa2fb39c706b8afa"));
+        gate58.insert("secret_key".to_string(), String::from("31e8999f85d38f5dd174f8919d9a1611c24d4e35b4d6319d6b160005871bf8b6"));
+        acc_array.push(gate58);
+
+        let mut gate57: BTreeMap<String, String> = BTreeMap::new();
+        gate57.insert("acc_name".to_string(), String::from("gate57"));
+        gate57.insert("access_key".to_string(), String::from("ba11ea511f343763db7c92c685f20c12"));
+        gate57.insert("secret_key".to_string(), String::from("272d1d38ac4f0af3e6ed96e6a9a2c59dd905c3d7ad730507008604bba14edb3d"));
+        acc_array.push(gate57);
+
+        let mut gate56: BTreeMap<String, String> = BTreeMap::new();
+        gate56.insert("acc_name".to_string(), String::from("gate56"));
+        gate56.insert("access_key".to_string(), String::from("72f0c351a83b04808baad625f6085599"));
+        gate56.insert("secret_key".to_string(), String::from("8a8d482bcc31f184cce350531cb40ca70564f9c466698d025e16d8943257dc0f"));
+        acc_array.push(gate56);
+
+        let mut gate55: BTreeMap<String, String> = BTreeMap::new();
+        gate55.insert("acc_name".to_string(), String::from("gate55"));
+        gate55.insert("access_key".to_string(), String::from("036408d5cbd8ddf1c6e0baab61ee641a"));
+        gate55.insert("secret_key".to_string(), String::from("940414a37711e59848011e99e223fa11b0e69f8badd35347301e3f8e41957a60"));
+        acc_array.push(gate55);
+
+        let mut gate54: BTreeMap<String, String> = BTreeMap::new();
+        gate54.insert("acc_name".to_string(), String::from("gate54"));
+        gate54.insert("access_key".to_string(), String::from("fbe564e8bd4efaa0c3c023ca8c057b36"));
+        gate54.insert("secret_key".to_string(), String::from("0be3c0223fd021fdacacc03f183f467e988ceee6eb1b0e09ca96bb1eebd45f39"));
+        acc_array.push(gate54);
+
+        let mut gate53: BTreeMap<String, String> = BTreeMap::new();
+        gate53.insert("acc_name".to_string(), String::from("gate53"));
+        gate53.insert("access_key".to_string(), String::from("16d91ba0a3d79a2925d16ea01a615fa1"));
+        gate53.insert("secret_key".to_string(), String::from("607b07cf240466656c00beb0c6fff252839467583fd3f8b14782eb007b3d99ce"));
+        acc_array.push(gate53);
+
+        let mut gate52: BTreeMap<String, String> = BTreeMap::new();
+        gate52.insert("acc_name".to_string(), String::from("gate52"));
+        gate52.insert("access_key".to_string(), String::from("31054006c457a80a027e961cf3e5e3a4"));
+        gate52.insert("secret_key".to_string(), String::from("a43f8f5672b49bfcc304d0731100610de1c55e7d2b6c00199b267993f0b189d1"));
+        acc_array.push(gate52);
+
+        let mut gate51: BTreeMap<String, String> = BTreeMap::new();
+        gate51.insert("acc_name".to_string(), String::from("gate51"));
+        gate51.insert("access_key".to_string(), String::from("edcedefe7830dd5722c2c37704ae700f"));
+        gate51.insert("secret_key".to_string(), String::from("41db86a8463ac7c66023a8505e5fddd3448e551a11a52141bf57ca8478e2149b"));
+        acc_array.push(gate51);
+
+
+        for acc in acc_array {
+            let mut gate_exc = GateSwapRest::new(false, acc.clone());
+            let data = gate_exc.account_book("usdt".to_string()).await;
+            // trace!("data???????:{:?}",data.clone());
+            if data.code.as_str() == "200" {
+                let acc_name = acc.get("acc_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 name_data_all_clone: Vec<Vec<String>> = vec![];
+                    for item in array {
+                        let time = item["time"].as_i64().unwrap();//秒级
+                        // 使用秒构建一个DateTime<Utc>对象
+                        // let datetime: DateTime<Utc> = Utc.timestamp(time, 0);
+                        //2023-12-10 00:00:00
+                        if 1702051200 > time {
+                            continue;
+                        }
+                        time_all.push(time);
+                        let time_str = NaiveDateTime::from_timestamp_millis((time + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
+
+                        trace!("数据时间解析:{:?}---{:?}",time,time_str);
+                        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" => { "交易手续费" }
+                            "refr" => { "推荐人返佣" }
+                            "fund" => { "资金费用" }
+                            "point_dnw" => { "点卡转入转出" }
+                            "point_fee" => { "点卡交易手续费" }
+                            "point_refr" => { "点卡推荐人返佣" }
+                            _ => {
+                                "未知-变更类型"
+                            }
+                        };
+                        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_str.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_clone: Vec<String> = vec![];
+                        name_data_array_clone.push(time.to_string());
+                        name_data_array_clone.push(trade_id.to_string());
+                        name_data_array_clone.push(change.to_string());
+                        name_data_array_clone.push(balance.to_string());
+                        name_data_array_clone.push(type_str.to_string());
+                        name_data_array_clone.push(contract.to_string());
+                        name_data_array_clone.push(text.to_string());
+
+                        name_data_all.push(name_data_array.clone());
+                        name_data_all_clone.push(name_data_array_clone.clone());
+                        let mut cp =  name_data_array.clone();
+                        cp.push(acc_name.clone().unwrap().to_string());
+                        data_array_all.push(cp);
+                    }
+                    acc_all_data.insert(acc_name.clone().unwrap().to_string(), name_data_all.clone());
+                    acc_all_data_clone.insert(acc_name.clone().unwrap().to_string(), name_data_all_clone.clone());
+                } else {
+                    trace!("不是数组 检查数据");
+                }
+            }
+            // break;
+        }
+        break;//这里是为了 代码收纳,用了loop来放置代码
+    }
+    trace!("数据如下:{:?}",acc_all_data);
+
+    //汇总
+    //1. 生成时间片
+    let mut unique = HashSet::new();
+    time_all.retain(|e| unique.insert(*e));
+    trace!("时间片:{:?}",time_all);
+
+    //2. 根据时间片 去求每个时间片的  总余额,
+    let mut sum_data_array_all: Vec<Vec<String>> = vec![];
+    for time in time_all.clone() {
+        let mut sum_data_array: Vec<String> = vec![];
+        let mut sum_balance: f64 = 0 as f64;
+        for (key, value) in acc_all_data_clone.clone() {
+            let acc_key = key;
+            trace!("读取value:{:?}",value);
+            let filter_arrya: Vec<Vec<String>> = value.clone().into_iter().filter(|s| {
+                trace!("读取time:{:?}",s);
+                let time_v = s[0].clone();
+                time_v < (time - 1).to_string()
+            }).collect();
+
+            let balance: f64 = if filter_arrya.len() > 0 {
+                let row = filter_arrya[filter_arrya.len() - 1].clone();
+                trace!("读取balance:{:?}",row);
+                let balance_clone = row[3].clone();
+                balance_clone.parse::<f64>().unwrap()
+            } else {
+                0 as f64
+            };
+            sum_balance = balance + sum_balance;
+        }
+
+        // 使用秒构建一个DateTime<Utc>对象
+        let time_str = NaiveDateTime::from_timestamp_millis((time + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
+
+        sum_data_array.push(time_str.to_string());
+        sum_data_array.push("".to_string());
+        sum_data_array.push("".to_string());
+        sum_data_array.push(sum_balance.to_string());
+        sum_data_array.push("".to_string());
+        sum_data_array.push("".to_string());
+        sum_data_array.push("".to_string());
+
+        sum_data_array_all.push(sum_data_array);
+    }
+    if sum_data_array_all.len() > 0 {
+        acc_all_data.insert("total".to_string(), sum_data_array_all);
+    }
+    if data_array_all.len() > 0{
+        acc_all_data.insert("gather".to_string(), data_array_all);
+    }
+
+    //数据组装.
+    let noe_row_name = vec!["时间", "成交Id", "变更金额", "变更后余额", "变更类型", "合约标识", "注释"];
+    //创建表格
+    //提示。涉及到 需要转换的数据,请提前自行转换,工具只负责写入生成
+    // after - 之后的,时间戳> after 才是有效数据
+    match creation_xlsx(&noe_row_name, &acc_all_data,
+                        "okx".to_string(),
+    ) {
+        Ok(d) => {
+            trace!("完成");
+        }
+        Err(z) => {
+            eprint!("{:?}", z);
+            trace!("失败");
+        }
+    }
+}

+ 7 - 2
global/Cargo.toml

@@ -9,8 +9,13 @@ edition = "2021"
 rust_decimal = "1.32.0"
 rust_decimal_macros = "1.32.0"
 tracing = "0.1"
-tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
-tracing-appender = "0.2.2"
+tracing-subscriber = { version = "0.3.17", features = [
+    "env-filter",
+    "time",
+    "local-time"
+] }
+time = { version = "0.3.7", features = ["macros"] }
+tracing-appender-timezone = { git = "https://github.com/skyfffire/tracing-appender-timezone.git" }
 toml = "0.5.11"
 serde = "1.0.183"
 serde_derive = "1.0"

+ 20 - 29
global/src/log_utils.rs

@@ -1,35 +1,14 @@
 use std::collections::HashMap;
 use std::fmt::Debug;
 use std::io;
-use chrono::{Datelike, FixedOffset, Timelike, Utc};
 use tracing::{Event, Subscriber, warn};
-use tracing_appender::non_blocking::WorkerGuard;
+use tracing_appender_timezone::non_blocking::WorkerGuard;
 use tracing_subscriber::{fmt, Layer};
-use tracing_subscriber::fmt::format::Writer;
-use tracing_subscriber::fmt::time::FormatTime;
 use tracing_subscriber::layer::{Context, SubscriberExt};
 use reqwest::{Client};
-use rust_decimal::prelude::ToPrimitive;
 use tracing::field::{Field, Visit};
+use tracing_appender_timezone::rolling::{RollingFileAppender, Rotation};
 
-// 用來格式化日誌的輸出時間格式
-struct LocalTimer;
-
-impl FormatTime for LocalTimer {
-    fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result {
-        let now = Utc::now().with_timezone(&FixedOffset::east_opt(8 * 3600).unwrap());
-        write!(
-            w,
-            "{:02}-{:02} {:02}:{:02}:{:02}.{:03}",
-            now.month(),
-            now.day(),
-            now.hour(),
-            now.minute(),
-            now.second(),
-            now.nanosecond() / 1e6.to_u32().unwrap()
-        )
-    }
-}
 
 struct ErrorMessageVisitor {
     message: String
@@ -105,19 +84,31 @@ pub fn final_init(level: &str, port: u32, account_name: String) -> WorkerGuard {
     path.push_str("./logs");
     path.push_str(port.to_string().as_str());
 
-    let file_appender = tracing_appender::rolling::daily(path, "as-debug.log");
-    let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
+    let file_appender = RollingFileAppender::builder()
+        .time_zone(8)
+        .rotation(Rotation::DAILY)
+        .filename_suffix("log")
+        .build(path)
+        .expect("initializing rolling file appender failed");
+    let (non_blocking, guard) = tracing_appender_timezone::non_blocking(file_appender);
+
+    use time::{macros::format_description, UtcOffset};
+    use tracing_subscriber::{fmt::time::OffsetTime};
+    let local_time = OffsetTime::new(
+        UtcOffset::from_hms(8, 0, 0).unwrap(),
+        format_description!("[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]"),
+    );
 
     let fmt_layer = fmt::layer()
-        .with_timer(LocalTimer)
-        .with_target(true)
+        .with_timer(local_time.clone())
+        .with_target(false)
         .with_level(true)
         .with_writer(io::stdout)
         .with_span_events(fmt::format::FmtSpan::FULL);
 
     let file_layer = fmt::layer()
-        .with_timer(LocalTimer)
-        .with_target(true)
+        .with_timer(local_time.clone())
+        .with_target(false)
         .with_ansi(false)
         .with_level(true)
         .with_writer(non_blocking.clone())

+ 4 - 12
src/main.rs

@@ -6,7 +6,7 @@ use std::sync::Arc;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::time::Duration;
 use tracing::{info, warn};
-use tracing_appender::non_blocking::WorkerGuard;
+use tracing_appender_timezone::non_blocking::WorkerGuard;
 use global::log_utils::send_remote_err_log;
 use global::params::Params;
 
@@ -75,16 +75,8 @@ async fn main() {
     ws_running.store(false, Ordering::Relaxed);
     tokio::time::sleep(Duration::from_secs(1)).await;
 
-    info!("等待其他线程后续处理完毕(再次按control c可以立马结束)……");
-    tokio::spawn(async move {
-        let mut quant = quant_arc.lock().await;
-        quant.exit(0).await;
-    });
-    let mut i = 5;
-    while i > 0 {
-        tokio::time::sleep(Duration::from_secs(1)).await;
-        info!("{}", i);
-        i = i - 1;
-    }
+    info!("等待清空仓位、订单(再次按control c可以立马结束)……");
+    let mut quant = quant_arc.lock().await;
+    quant.exit().await;
     info!("程序已退出!为以防万一,请再次检查仓位和订单!");
 }

+ 19 - 20
strategy/src/binance_spot.rs

@@ -10,7 +10,6 @@ use exchanges::response_base::ResponseData;
 use global::trace_stack::TraceStack;
 use standard::exchange::ExchangeEnum::BinanceSpot;
 use crate::exchange_disguise::on_special_depth;
-use crate::model::{OriginalTradeBa};
 use crate::quant::Quant;
 
 // 参考 币安 现货 启动
@@ -92,8 +91,8 @@ pub async fn reference_binance_spot_run(bool_v1 :Arc<AtomicBool>,
 
 async fn on_data(bot_arc_clone: Arc<Mutex<Quant>>,
                  update_flag_u: &mut Decimal,
-                 max_buy: &mut Decimal,
-                 min_sell: &mut Decimal,
+                 _max_buy: &mut Decimal,
+                 _min_sell: &mut Decimal,
                  data: ResponseData) {
     let mut trace_stack = TraceStack::default();
     trace_stack.on_after_network(chrono::Utc::now().timestamp_micros());
@@ -103,23 +102,23 @@ async fn on_data(bot_arc_clone: Arc<Mutex<Quant>>,
         return;
     }
     if data.channel == "aggTrade" {
-        let trade: OriginalTradeBa = serde_json::from_str(data.data.as_str()).unwrap();
-        let str = data.label.clone();
-        let mut quant = bot_arc_clone.lock().await;
-        if quant.is_update.contains_key(&data.label) && *quant.is_update.get(str.as_str()).unwrap() {
-            *max_buy = Decimal::ZERO;
-            *min_sell = Decimal::ZERO;
-            quant.is_update.remove(str.as_str());
-        }
-        if trade.p > *max_buy || *max_buy == Decimal::ZERO{
-            *max_buy = trade.p
-        }
-        if trade.p < *min_sell || *min_sell == Decimal::ZERO{
-            *min_sell = trade.p
-        }
-        {
-            quant.max_buy_min_sell_cache.insert(data.label, vec![*max_buy, *min_sell]);
-        }
+        // let trade: OriginalTradeBa = serde_json::from_str(data.data.as_str()).unwrap();
+        // let str = data.label.clone();
+        // let mut quant = bot_arc_clone.lock().await;
+        // if quant.is_update.contains_key(&data.label) && *quant.is_update.get(str.as_str()).unwrap() {
+        //     *max_buy = Decimal::ZERO;
+        //     *min_sell = Decimal::ZERO;
+        //     quant.is_update.remove(str.as_str());
+        // }
+        // if trade.p > *max_buy || *max_buy == Decimal::ZERO{
+        //     *max_buy = trade.p
+        // }
+        // if trade.p < *min_sell || *min_sell == Decimal::ZERO{
+        //     *min_sell = trade.p
+        // }
+        // {
+        //     quant.max_buy_min_sell_cache.insert(data.label, vec![*max_buy, *min_sell]);
+        // }
     } else if data.channel == "bookTicker" {
         trace_stack.on_before_format();
         let special_depth = standard::handle_info::HandleSwapInfo::handle_special_ticker(BinanceSpot, data.clone());

+ 1 - 1
strategy/src/binance_usdt_swap.rs

@@ -28,7 +28,7 @@ pub(crate) async fn reference_binance_swap_run(bool_v1 :Arc<AtomicBool>,
         ws.set_subscribe(vec![
             // BinanceSwapSubscribeType::PuDepth20levels100ms,
             BinanceSwapSubscribeType::PuBookTicker,
-            BinanceSwapSubscribeType::PuAggTrade
+            // BinanceSwapSubscribeType::PuAggTrade
         ]);
 
         //读取数据

+ 73 - 37
strategy/src/quant.rs

@@ -1107,35 +1107,59 @@ impl Quant {
     }
 
     #[instrument(skip(self, target_hold_coin), level="TRACE")]
-    pub async fn check_position(&mut self, target_hold_coin: Decimal) {
-        info!("清空挂单!");
+    pub async fn check_position(&mut self, target_hold_coin: Decimal) -> bool {
+        let mut is_clear = false;
+
+        info!("------------------------------------------------------------------------------------------------------------");
+        info!("步骤一:检查挂单:");
         match self.platform_rest.cancel_orders_all().await {
             Ok(val) => {
-                info!("清空所有挂单,{:?}", val);
+                let length = val.len();
+                is_clear = length == 0;
+
+                info!("已清空所有挂单({}条)", length);
+
+                for o in val {
+                    info!("    {:?}", o);
+                }
             }
             Err(err) => {
-                error!("取消所有订单异常: {}",err);
+                warn!("取消所有订单异常({}),启动备用方法。", err);
+
                 match self.platform_rest.cancel_orders().await {
                     Ok(val) => {
-                        info!("清空当前币对挂单,{:?}", val);
+                        let length = val.len();
+                        is_clear = length == 0;
+
+                        info!("清空所有挂单({}条):{:?}", length, val);
                     }
                     Err(exc) => {
-                        error!("清空当前币对订单异常: {}",exc);
+                        error!("清空当前币对订单异常: {}", exc);
                     }
                 }
             }
         }
+        info!("挂单检查完毕。");
+        info!("");
+
+        info!("步骤二:检查仓位:");
         if self.exchange.contains("spot") { // 现货
-            self.check_position_spot(target_hold_coin).await;
+            is_clear = is_clear && (self.check_position_spot(target_hold_coin.clone()).await == 0);
+            info!("检查遗漏仓位(现货),目标持仓:{}USDT", target_hold_coin);
         } else { // 合约
-            self.check_position_swap().await;
+            is_clear = is_clear && (self.check_position_swap().await == 0);
+            info!("遗漏仓位检查完毕(合约)!");
         }
-        info!("遗留仓位检测完毕");
+        info!("------------------------------------------------------------------------------------------------------------");
+        info!("");
+
+        return is_clear;
     }
 
     #[instrument(skip(self, target_hold_coin), level="TRACE")]
-    pub async fn check_position_spot(&mut self, target_hold_coin: Decimal) {
-        info!("---------------------------检查遗漏仓位(现货),目标持仓:{}USDT---------------------------", target_hold_coin);
+    pub async fn check_position_spot(&mut self, target_hold_coin: Decimal) -> usize {
+        let mut length = 0;
+
         match self.platform_rest.get_spot_account().await {
             Ok(mut val) => {
                 // 如果返回的数组里没有交易货币,则补充交易货币
@@ -1180,8 +1204,13 @@ impl Quant {
                                 // price = mp*0.999;
                                 amount = -diff / mp;
                             } else {
+                                // 不需要调整说明没有仓位了。
+
                                 continue;
                             }
+                            // 需要调整说明有仓位。
+                            length = 1;
+
                             info!(?ticker);
                             info!("需要调整现货仓位 {}USDT(目标:{}USDT) 共计{}{}。", diff, _hold_coin, amount, coin_name);
                             let mut ts = TraceStack::default();
@@ -1217,18 +1246,23 @@ impl Quant {
             }
         }
         info!("---------------------------遗漏仓位检查完毕(现货)!-----------------------------------");
+
+        return length;
     }
 
     #[instrument(skip(self), level="TRACE")]
-    pub async fn check_position_swap(&mut self) {
-        info!("---------------------------检查遗漏仓位(合约)!-----------------------------------");
+    pub async fn check_position_swap(&mut self) -> usize {
+        let mut length = 0;
         match self.platform_rest.get_positions().await {
             Ok(val) => {
+                info!("检查仓位信息({}条仓位信息,部分交易所会返回0持仓的):", length);
+
                 for position in val {
                     if position.amount.eq(&Decimal::ZERO) {
                         continue;
                     }
-                    info!("仓位获取到:{:?}", position);
+                    length = length + 1;
+                    info!("    仓位:{:?}", position);
                     match self.platform_rest.get_ticker_symbol(position.symbol.clone()).await {
                         Ok(ticker) => {
                             let ap = ticker.sell;
@@ -1243,7 +1277,7 @@ impl Quant {
                                     market_info = market;
                                 }
                                 Err(err) => {
-                                    error!("{} 获取当前market异常: {}", position.symbol.clone(), err);
+                                    error!("    {} 获取当前market异常: {}", position.symbol.clone(), err);
                                     continue;
                                 }
                             }
@@ -1260,7 +1294,7 @@ impl Quant {
                                     side = "pk";
                                 }
                                 _ => {
-                                    info!("仓位position_mode匹配失败,不做操作!");
+                                    error!("    仓位position_mode匹配失败,不做操作!");
                                     // 执行完当前币对  结束循环
                                     continue;
                                 }
@@ -1272,29 +1306,32 @@ impl Quant {
                             match self.platform_rest.take_order_symbol(position.symbol.clone(), Decimal::ONE, utils::generate_client_id(None).as_str(), side, price, position.amount.abs()).await {
                                 Ok(order) => {
                                     ts.on_after_send();
-                                    info!("{}仓位清除下单成功 {:?}, {}", position.symbol.clone(), order, ts.to_string());
+                                    info!("    {}仓位清除下单成功 {:?}, {}", position.symbol.clone(), order, ts.to_string());
                                     // 执行完当前币对  结束循环
                                     continue;
                                 }
                                 Err(error) => {
                                     ts.on_after_send();
-                                    error!("{}仓位清除下单异常 {}, {}", position.symbol.clone(), error, ts.to_string());
+                                    error!("    {}仓位清除下单异常 {}, {}", position.symbol.clone(), error, ts.to_string());
                                     // 执行完当前币对  结束循环
                                     continue;
                                 }
                             };
                         }
                         Err(err) => {
-                            error!("{} 获取当前ticker异常: {}", position.symbol.clone(), err)
+                            error!("    {} 获取当前ticker异常: {}", position.symbol.clone(), err)
                         }
                     }
                 }
             }
             Err(error) => {
+                length = 0;
+
                 error!("获取仓位信息异常: {}", error);
             }
         }
-        info!("---------------------------遗漏仓位检查完毕(合约)!-----------------------------------");
+
+        return length
     }
 
 
@@ -1323,24 +1360,23 @@ impl Quant {
         // info!("退出进程!");
     }
 
-    #[instrument(skip(self, delay), level="TRACE")]
-    pub async fn exit(&mut self, delay: i8) {
-        info!("--------------------------------------------------");
-        info!("预约退出操作 delay:{}", delay);
-        if delay > 0i8 {
-            sleep(Duration::from_secs(delay as u64)).await;
+    #[instrument(skip(self), level="TRACE")]
+    pub async fn exit(&mut self) {
+        info!("-------------------------启动退出流程----------------------------");
+        info!("");
+
+        // 循环清空仓位,如若彻底清空,才进行退出。
+        let mut clear_count = 1;
+        while !self.check_position(Decimal::ZERO).await {
+            sleep(Duration::from_secs(1)).await;
+
+            clear_count += 1;
+            info!("清理指令发送完毕,启动第{}次检查。", clear_count);
+            info!("");
         }
-        info!("开始退出操作");
-        info!("为避免api失效导致遗漏仓位 建议人工复查");
-        self.check_position(Decimal::ZERO).await;
-        sleep(Duration::from_secs(2)).await;
-        info!("双重检查遗漏仓位");
-        self.check_position(Decimal::ZERO).await;
-        info!("停机退出  停机原因: {}", self.exit_msg);
-        // 发送交易状态 await self._post_params()
-        // TODO: 向中控发送信号
-        self.running.store(false, Ordering::Relaxed);
-        info!("退出进程!");
+
+        info!("订单、仓位清除完毕,为避免api失效导致遗漏仓位,建议人工复查。");
+        info!("停机原因:{}。", self.exit_msg);
     }
 
     #[instrument(skip(self), level="TRACE")]