Эх сурвалжийг харах

完成火币标准层数据

gepangpang 1 жил өмнө
parent
commit
1982a128b9

+ 12 - 0
global/src/account_info.rs

@@ -19,6 +19,12 @@ pub struct AccountInfo {
     pub bitget_access_key: String,
     pub bitget_secret_key: String,
     pub bitget_pass: String,
+    pub bybit_access_key: String,
+    pub bybit_secret_key: String,
+    pub bybit_pass: String,
+    pub htx_access_key: String,
+    pub htx_secret_key: String,
+    pub htx_pass: String,
 }
 
 impl AccountInfo {
@@ -37,6 +43,12 @@ impl AccountInfo {
             bitget_access_key: "".to_string(),
             bitget_secret_key: "".to_string(),
             bitget_pass: "".to_string(),
+            bybit_access_key: "".to_string(),
+            bybit_secret_key: "".to_string(),
+            bybit_pass: "".to_string(),
+            htx_access_key: "".to_string(),
+            htx_secret_key: "".to_string(),
+            htx_pass: "".to_string(),
         }
     }
 }

+ 1 - 1
standard/Cargo.toml

@@ -12,7 +12,7 @@ tokio = { version = "1.31.0", features = ["full"] }
 async-trait = "0.1.73"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0.104"
-rust_decimal = "1.32.0"
+rust_decimal = { version = "1.32.0", features = ["maths"] }
 rust_decimal_macros = "1.32.0"
 chrono = "0.4.30"
 futures = "0.3"

+ 6 - 1
standard/src/exchange.rs

@@ -13,6 +13,7 @@ use crate::bitget_swap::BitgetSwap;
 use crate::coinex_swap::CoinexSwap;
 // use crate::kucoin_spot::KucoinSpot;
 // use crate::okx_swap::OkxSwap;
+use crate::htx_swap::HtxSwap;
 
 /// 交易所交易模式枚举
 /// - `BinanceSwap`: Binance交易所期货;
@@ -32,7 +33,8 @@ pub enum ExchangeEnum {
     // BitgetSpot,
     CoinexSwap,
     BitgetSwap,
-    BybitSwap
+    BybitSwap,
+    HtxSwap
 }
 
 /// Exchange结构体
@@ -107,6 +109,9 @@ impl Exchange {
             ExchangeEnum::CoinexSwap => {
                 Box::new(CoinexSwap::new(symbol, is_colo, params, order_sender, error_sender).await)
             }
+            ExchangeEnum::HtxSwap => {
+                Box::new(HtxSwap::new(symbol, is_colo, params, order_sender, error_sender).await)
+            }
         }
     }
 }

+ 30 - 8
standard/src/handle_info.rs

@@ -7,17 +7,18 @@ use tracing::{error, info};
 use exchanges::response_base::ResponseData;
 use global::public_params;
 use crate::exchange::ExchangeEnum;
-use crate::{binance_swap_handle, gate_swap_handle, bybit_swap_handle, bitget_swap_handle, kucoin_handle, coinex_swap_handle};
+use crate::{binance_swap_handle, gate_swap_handle, bybit_swap_handle, bitget_swap_handle, kucoin_handle, coinex_swap_handle, htx_swap_handle};
 use crate::{Account, MarketOrder, Position, SpecialDepth, SpecialOrder, SpecialTicker};
 
 #[allow(dead_code)]
 pub struct HandleSwapInfo;
 
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct DepthParam {
     pub depth_asks: Vec<MarketOrder>,
     pub depth_bids: Vec<MarketOrder>,
     pub t: Decimal,
-    pub create_at: i64
+    pub create_at: i64,
 }
 
 #[allow(dead_code)]
@@ -46,13 +47,16 @@ impl HandleSwapInfo {
             // },
             ExchangeEnum::BitgetSwap => {
                 bitget_swap_handle::handle_account_info(res_data, symbol)
-            },
+            }
             ExchangeEnum::BybitSwap => {
                 bybit_swap_handle::handle_account_info(res_data, symbol)
             }
             ExchangeEnum::CoinexSwap => {
                 coinex_swap_handle::handle_account_info(res_data, symbol)
             }
+            ExchangeEnum::HtxSwap => {
+                htx_swap_handle::handle_account_info(res_data, symbol)
+            }
             _ => {
                 error!("未找到该交易所!handle_account_info: {:?}",exchange);
                 panic!("未找到该交易所!handle_account_info: {:?}", exchange);
@@ -90,13 +94,17 @@ impl HandleSwapInfo {
                 info!(?res_data);
                 panic!("BitgetSwap 85 未实现格式化");
                 // bitget_swap_handle::handle_special_ticker(res_data)
-            },
+            }
             ExchangeEnum::BybitSwap => {
                 bybit_swap_handle::handle_ticker(res_data)
             }
             ExchangeEnum::CoinexSwap => {
                 coinex_swap_handle::handle_ticker(res_data)
             }
+            ExchangeEnum::HtxSwap => {
+                SpecialDepth::new()
+                // htx_swap_handle::handle_ticker(res_data)
+            }
         }
     }
     // 处理position信息
@@ -125,13 +133,16 @@ impl HandleSwapInfo {
             // },
             ExchangeEnum::BitgetSwap => {
                 bitget_swap_handle::handle_position(res_data, ct_val)
-            },
+            }
             ExchangeEnum::BybitSwap => {
                 bybit_swap_handle::handle_position(res_data, ct_val)
             }
             ExchangeEnum::CoinexSwap => {
                 coinex_swap_handle::handle_position(res_data, ct_val)
             }
+            ExchangeEnum::HtxSwap => {
+                htx_swap_handle::handle_position(res_data, ct_val)
+            }
         }
     }
     // 处理订单信息
@@ -158,13 +169,16 @@ impl HandleSwapInfo {
             // },
             ExchangeEnum::BitgetSwap => {
                 bitget_swap_handle::handle_order(res_data, ct_val)
-            },
+            }
             ExchangeEnum::BybitSwap => {
                 bybit_swap_handle::handle_order(res_data, ct_val)
             }
             ExchangeEnum::CoinexSwap => {
                 coinex_swap_handle::handle_order(res_data, ct_val)
             }
+            ExchangeEnum::HtxSwap => {
+                htx_swap_handle::handle_order(res_data, ct_val)
+            }
         }
     }
 
@@ -304,7 +318,7 @@ pub fn format_depth(exchange: ExchangeEnum, res_data: &ResponseData) -> DepthPar
             depth_bids = bybit_swap_handle::format_depth_items(res_data.data["b"].clone());
             t = Decimal::from_i64(res_data.reach_time).unwrap();
             create_at = res_data.reach_time * 1000;
-        },
+        }
         ExchangeEnum::CoinexSwap => {
             let depth = &res_data.data["depth"];
             depth_asks = coinex_swap_handle::format_depth_items(&depth["asks"].clone());
@@ -313,13 +327,21 @@ pub fn format_depth(exchange: ExchangeEnum, res_data: &ResponseData) -> DepthPar
             t = Decimal::from_i64(time).unwrap();
             create_at = time * 1000;
         }
+        ExchangeEnum::HtxSwap => {
+            let depth = &res_data.data;
+            depth_asks = htx_swap_handle::format_depth_items(depth["asks"].clone());
+            depth_bids = htx_swap_handle::format_depth_items(depth["bids"].clone());
+            let time = depth["ts"].to_string().parse::<i64>().unwrap();
+            t = Decimal::from_i64(time).unwrap();
+            create_at = time * 1000;
+        }
     }
 
     DepthParam {
         depth_asks,
         depth_bids,
         t,
-        create_at
+        create_at,
     }
 }
 

+ 637 - 0
standard/src/htx_swap.rs

@@ -0,0 +1,637 @@
+use std::collections::{BTreeMap};
+use exchanges::htx_swap_rest::HtxSwapRest;
+use std::io::{Error, ErrorKind};
+use tokio::sync::mpsc::Sender;
+use std::str::FromStr;
+use async_trait::async_trait;
+use futures::stream::FuturesUnordered;
+use futures::TryStreamExt;
+use rust_decimal::Decimal;
+use rust_decimal::prelude::{FromPrimitive};
+use serde_json::json;
+use serde_json::Value::Null;
+use tokio::spawn;
+use tokio::time::Instant;
+use tracing::{error, info};
+use global::trace_stack::TraceStack;
+use crate::exchange::ExchangeEnum;
+use crate::{Account, Market, Order, OrderCommand, Platform, Position, PositionModeEnum, Ticker, utils};
+
+#[allow(dead_code)]
+#[derive(Clone)]
+pub struct HtxSwap {
+    exchange: ExchangeEnum,
+    symbol: String,
+    is_colo: bool,
+    params: BTreeMap<String, String>,
+    request: HtxSwapRest,
+    market: Market,
+    order_sender: Sender<Order>,
+    error_sender: Sender<Error>,
+}
+
+impl HtxSwap {
+    pub async fn new(symbol: String, is_colo: bool, params: BTreeMap<String, String>, order_sender: Sender<Order>, error_sender: Sender<Error>) -> HtxSwap {
+        let market = Market::new();
+        let mut htx_swap = HtxSwap {
+            exchange: ExchangeEnum::HtxSwap,
+            symbol: symbol.to_uppercase(),
+            is_colo,
+            params: params.clone(),
+            request: HtxSwapRest::new(is_colo, params.clone()),
+            market,
+            order_sender,
+            error_sender,
+        };
+        htx_swap.market = HtxSwap::get_market(&mut htx_swap).await.unwrap();
+        // 修改持仓模式
+        let mode_result = htx_swap.set_dual_mode("", true).await;
+        match mode_result {
+            Ok(ok) => {
+                info!("HtxSwap:设置持仓模式成功!{:?}", ok);
+            }
+            Err(error) => {
+                error!("HtxSwap:设置持仓模式失败!{:?}", error)
+            }
+        }
+        // 设置持仓杠杆
+        // let lever_rate_result = htx_swap.set_dual_leverage("10").await;
+        // match lever_rate_result {
+        //     Ok(ok) => {
+        //         info!("HtxSwap:设置持仓杠杆成功!{:?}", ok);
+        //     }
+        //     Err(error) => {
+        //         error!("HtxSwap:设置持仓杠杆失败!{:?}", error)
+        //     }
+        // }
+
+        return htx_swap;
+    }
+}
+
+#[async_trait]
+impl Platform for HtxSwap {
+    fn clone_box(&self) -> Box<dyn Platform + Send + Sync> { Box::new(self.clone()) }
+
+    fn get_self_exchange(&self) -> ExchangeEnum { ExchangeEnum::HtxSwap }
+
+    fn get_self_symbol(&self) -> String { self.symbol.clone() }
+
+    fn get_self_is_colo(&self) -> bool { self.is_colo }
+
+    fn get_self_params(&self) -> BTreeMap<String, String> { self.params.clone() }
+
+    fn get_self_market(&self) -> Market { self.market.clone() }
+
+    fn get_request_delays(&self) -> Vec<i64> {
+        // self.request.get_delays()
+        vec![]
+    }
+
+    fn get_request_avg_delay(&self) -> Decimal {
+        // self.request.get_avg_delay()
+        Decimal::ZERO
+    }
+
+    fn get_request_max_delay(&self) -> i64 { 0 }
+
+    async fn get_server_time(&mut self) -> Result<String, Error> {
+        let response = self.request.get_server_time().await;
+        if response.code != 200 {
+            return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取服务器时间异常{:?}", response).to_string()));
+        }
+
+        let res_data_json = response.data;
+        let result = res_data_json["ts"].to_string();
+        Ok(result)
+    }
+
+    async fn get_account(&mut self) -> Result<Account, Error> {
+        let params = json!({
+            "margin_account": "USDT"
+        });
+        let response = self.request.get_account(params).await;
+        if response.code != 200 {
+            return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取账户信息异常{:?}", response).to_string()));
+        }
+
+        let res_data_json = response.data;
+        let mut account = Account::new();
+        for data in res_data_json.as_array().unwrap() {
+            let margin_position = Decimal::from_str(&data["margin_position"].to_string()).unwrap();
+
+            let balance = Decimal::from_str(&data["margin_balance"].to_string()).unwrap();
+            let frozen_balance = Decimal::from_str(&data["margin_frozen"].to_string()).unwrap();
+            let available_balance = balance - margin_position - frozen_balance;
+            // 格式化account信息
+            account = Account {
+                coin: data["margin_asset"].as_str().unwrap().to_string(),
+                balance,
+                available_balance,
+                frozen_balance,
+                stocks: Default::default(),
+                available_stocks: Default::default(),
+                frozen_stocks: Default::default(),
+            };
+        }
+        return Ok(account);
+    }
+
+    async fn get_spot_account(&mut self) -> Result<Vec<Account>, Error> {
+        Err(Error::new(ErrorKind::NotFound, "htx_swap get_spot_account:该交易所方法未实现".to_string()))
+    }
+
+    async fn get_position(&mut self) -> Result<Vec<Position>, Error> {
+        let symbol_format = utils::format_symbol(self.symbol.clone(), "-");
+        let ct_val = self.market.ct_val;
+        let params = json!({
+            "contract_type": "swap",
+            "contract_code": symbol_format
+        });
+        let response = self.request.get_user_position(params).await;
+        if response.code != 200 {
+            return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取仓位异常{:?}", response).to_string()));
+        }
+
+        let res_data_json = response.data;
+        let positions_info = res_data_json.as_array().unwrap();
+        let result = positions_info.iter().map(|item| { format_position_item(item, &ct_val) }).collect();
+        Ok(result)
+    }
+
+    async fn get_positions(&mut self) -> Result<Vec<Position>, Error> {
+        let ct_val = self.market.ct_val;
+        let params = json!({
+            "contract_type": "swap"
+        });
+        let response = self.request.get_user_position(params).await;
+        info!(?response);
+
+        if response.code != 200 {
+            return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取仓位异常{:?}", response).to_string()));
+        }
+
+        let res_data_json = response.data;
+        let positions_info = res_data_json.as_array().unwrap();
+        let result = positions_info.iter().map(|item| { format_position_item(item, &ct_val) }).collect();
+        Ok(result)
+    }
+
+    async fn get_ticker(&mut self) -> Result<Ticker, Error> {
+        return self.get_ticker_symbol(self.symbol.clone()).await;
+    }
+
+    async fn get_ticker_symbol(&mut self, symbol: String) -> Result<Ticker, Error> {
+        let symbol_format = utils::format_symbol(symbol.clone(), "-");
+        let params = json!({
+            "contract_code": symbol_format,
+        });
+        let response = self.request.get_ticker(params).await;
+        if response.code != 200 {
+            return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取行情信息异常{:?}", response).to_string()));
+        }
+
+        let res_data_json = response.data;
+        let ticker_info = res_data_json["tick"].clone();
+        let time = ticker_info["ts"].as_i64().unwrap();
+        let result = Ticker {
+            time,
+            high: Decimal::from_str(ticker_info["high"].as_str().unwrap()).unwrap(),
+            low: Decimal::from_str(ticker_info["low"].as_str().unwrap()).unwrap(),
+            sell: Decimal::from_f64(ticker_info["bid"][0].as_f64().unwrap()).unwrap(),
+            buy: Decimal::from_f64(ticker_info["ask"][0].as_f64().unwrap()).unwrap(),
+            last: Decimal::from_str(ticker_info["close"].as_str().unwrap()).unwrap(),
+            volume: Decimal::from_str(ticker_info["amount"].as_str().unwrap()).unwrap(),
+        };
+        Ok(result)
+    }
+
+    async fn get_market(&mut self) -> Result<Market, Error> {
+        self.get_market_symbol(self.symbol.clone()).await
+    }
+
+    async fn get_market_symbol(&mut self, symbol: String) -> Result<Market, Error> {
+        let symbol_format = utils::format_symbol(symbol.clone(), "-");
+        let symbol_array: Vec<&str> = self.symbol.split("_").collect();
+
+        let params = json!({
+            "pair": symbol_format,
+            "contract_type":"swap"
+        });
+        let response = self.request.get_market(params).await;
+        if response.code != 200 {
+            return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取市场信息异常{:?}", response).to_string()));
+        }
+        let res_data_json = response.data.as_array().unwrap();
+        let market_info = res_data_json[0].clone();
+
+        if !market_info["pair"].as_str().unwrap().to_string().eq(&symbol_format) {
+            return Err(Error::new(ErrorKind::NotFound, format!("符号未找到:symbol={}, response={:?}", symbol_format, response))).unwrap();
+        }
+
+        let base_asset = market_info["symbol"].as_str().unwrap().to_string();
+        let quote_asset = symbol_array[1].to_string();
+
+        let tick_size = Decimal::from_f64(market_info["price_tick"].as_f64().unwrap()).unwrap();
+        let price_precision = Decimal::from_u32(tick_size.scale()).unwrap();
+        let amount_size = Decimal::ONE;
+        let amount_precision = Decimal::ZERO;
+        let min_qty = Decimal::NEGATIVE_ONE;
+        let max_qty = Decimal::NEGATIVE_ONE;
+        let ct_val = Decimal::from_f64(market_info["contract_size"].as_f64().unwrap()).unwrap();
+
+        let result = Market {
+            symbol: format!("{}_{}", base_asset, quote_asset),
+            base_asset,
+            quote_asset,
+            tick_size,
+            amount_size,
+            price_precision,
+            amount_precision,
+            min_qty,
+            max_qty,
+            min_notional: min_qty,
+            max_notional: max_qty,
+            ct_val,
+        };
+        Ok(result)
+    }
+
+    async fn get_order_detail(&mut self, order_id: &str, custom_id: &str) -> Result<Order, Error> {
+        let symbol_format = utils::format_symbol(self.symbol.clone(), "-");
+        let ct_val = self.market.ct_val;
+
+        let mut params = json!({
+            "pair": symbol_format
+        });
+        if order_id.eq("") { params["client_order_id"] = json!(custom_id) } else { params["order_id"] = json!(order_id) };
+        let response = self.request.get_order_details(params).await;
+        if response.code != 200 || response.data == Null {
+            return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取订单详情异常{:?}", response).to_string()));
+        }
+
+        let res_data_json = response.data;
+        let result = format_order_item(res_data_json, ct_val);
+        Ok(result)
+    }
+
+    async fn get_orders_list(&mut self, _status: &str) -> Result<Vec<Order>, Error> {
+        Err(Error::new(ErrorKind::NotFound, "htx_swap get_orders_list:该交易所方法未实现".to_string()))
+    }
+
+    async fn take_order(&mut self, custom_id: &str, origin_side: &str, price: Decimal, amount: Decimal) -> Result<Order, Error> {
+        let ct_val = self.market.ct_val;
+
+        return self.take_order_symbol(self.symbol.clone(), ct_val, custom_id, origin_side, price, amount).await;
+    }
+
+    async fn take_order_symbol(&mut self, symbol: String, ct_val: Decimal, custom_id: &str, origin_side: &str, price: Decimal, amount: Decimal) -> Result<Order, Error> {
+        let symbol_format = utils::format_symbol(symbol, "-");
+        let final_size = amount / ct_val;
+        let mut params = json!({
+            "pair": symbol_format,
+            "client_order_id": custom_id,
+            "contract_type": "swap",
+            "volume": final_size.to_string(),
+            "lever_rate": 10
+        });
+        if price.eq(&Decimal::ZERO) {
+            params["order_price_type"] = json!("market");
+        } else {
+            params["price"] = json!(price.to_string());
+            params["order_price_type"] = json!("limit");
+        };
+        match origin_side {
+            "kd" => {
+                params["direction"] = json!("buy");
+                params["offset"] = json!("open");
+            }
+            "pd" => {
+                params["direction"] = json!("sell");
+                params["offset"] = json!("close");
+            }
+            "kk" => {
+                params["direction"] = json!("sell");
+                params["offset"] = json!("open");
+            }
+            "pk" => {
+                params["direction"] = json!("buy");
+                params["offset"] = json!("close");
+            }
+            _ => { panic!("htx_usdt_swap 下单参数错误"); }
+        };
+        let response = self.request.swap_order(params).await;
+        if response.code != 200 {
+            return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 下单异常{:?}", response).to_string()));
+        }
+
+        let res_data_json = response.data;
+        let result = Order {
+            id: res_data_json["order_id"].to_string(),
+            custom_id: res_data_json["client_order_id"].to_string(),
+            price: Decimal::ZERO,
+            amount: Decimal::ZERO,
+            deal_amount: Decimal::ZERO,
+            avg_price: Decimal::ZERO,
+            status: "NEW".to_string(),
+            order_type: "".to_string(),
+            trace_stack: TraceStack::new(0, Instant::now()).on_special("339 htx_swap".to_string()),
+        };
+        return Ok(result);
+    }
+
+    async fn cancel_order(&mut self, order_id: &str, custom_id: &str) -> Result<Order, Error> {
+        let symbol_format = utils::format_symbol(self.symbol.clone(), "-");
+        let mut params = json!({
+            "pair": symbol_format,
+            "contract_type": "swap"
+        });
+        if order_id.eq("") { params["client_order_id"] = json!(custom_id) } else { params["order_id"] = json!(order_id) };
+        let response = self.request.cancel_order(params).await;
+
+        // 取消失败,进行报错
+        if response.code != 200 || response.data["errors"].as_array().unwrap_or(&vec![]).len() > 0 {
+            return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 取消订单异常{:?}", response).to_string()));
+        }
+
+        let res_data_json = response.data;
+        let mut id = order_id.to_string();
+        let mut custom_id = custom_id.to_string();
+        if order_id.eq("") {
+            custom_id = res_data_json["successes"].as_str().unwrap().to_string()
+        } else {
+            id = res_data_json["successes"].as_str().unwrap().to_string()
+        };
+        let result = Order {
+            id,
+            custom_id,
+            price: Decimal::ZERO,
+            amount: Decimal::ZERO,
+            deal_amount: Decimal::ZERO,
+            avg_price: Decimal::ZERO,
+            status: "REMOVE".to_string(),
+            order_type: "".to_string(),
+            trace_stack: TraceStack::new(0, Instant::now()).on_special("374 htx_swap".to_string()),
+        };
+        Ok(result)
+    }
+
+    async fn cancel_orders(&mut self) -> Result<Vec<Order>, Error> {
+        Err(Error::new(ErrorKind::NotFound, "htx_swap cancel_orders:该交易所方法未实现".to_string()))
+    }
+
+    async fn cancel_orders_all(&mut self) -> Result<Vec<Order>, Error> {
+        let symbol_format = utils::format_symbol(self.symbol.clone(), "-");
+        let params = json!({
+            "pair": symbol_format,
+            "contract_type": "swap"
+        });
+        let response = self.request.cancel_price_order(params).await;
+        if response.code != 200 {
+            return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 撤销所有订单异常{:?}", response).to_string()));
+        }
+
+        let result = vec![Order::new()];
+        Ok(result)
+    }
+
+    async fn take_stop_loss_order(&mut self, _stop_price: Decimal, _price: Decimal, _side: &str) -> Result<serde_json::Value, Error> {
+        Err(Error::new(ErrorKind::NotFound, "htx_swap take_stop_loss_order:该交易所方法未实现".to_string()))
+    }
+
+    async fn cancel_stop_loss_order(&mut self, _order_id: &str) -> Result<serde_json::Value, Error> {
+        Err(Error::new(ErrorKind::NotFound, "htx_swap cancel_stop_loss_order:该交易所方法未实现".to_string()))
+    }
+
+    async fn set_dual_mode(&mut self, _coin: &str, is_dual_mode: bool) -> Result<String, Error> {
+        let pos_mode = if is_dual_mode { "dual_side" } else { "single_side" };
+        let params = json!({
+            "margin_account": "USDT",
+            "position_mode": pos_mode,
+        });
+        let response = self.request.setting_dual_mode(params).await;
+        if response.code != 200 {
+            return Err(Error::new(ErrorKind::Other, format!("设置持仓模式失败:{:?}", response).to_string()));
+        }
+
+        return Ok(response.data.to_string());
+    }
+
+    async fn set_dual_leverage(&mut self, leverage: &str) -> Result<String, Error> {
+        let symbol_format = utils::format_symbol(self.symbol.clone(), "-");
+        let params = json!({
+            "pair": symbol_format,
+            "contract_type": "swap",
+            "lever_rate": leverage
+        });
+        let response = self.request.setting_dual_leverage(params).await;
+
+        if response.code != 200 {
+            return Err(Error::new(ErrorKind::Other, format!("设置杠杆失败:{:?}", response).to_string()));
+        }
+
+        return Ok(response.data.to_string());
+    }
+
+    async fn set_auto_deposit_status(&mut self, _status: bool) -> Result<String, Error> {
+        Err(Error::new(ErrorKind::NotFound, "htx_swap set_auto_deposit_status:该交易所方法未实现".to_string()))
+    }
+
+    async fn wallet_transfers(&mut self, _coin: &str, _from: &str, _to: &str, _amount: Decimal) -> Result<String, Error> {
+        Err(Error::new(ErrorKind::NotFound, "htx_swap wallet_transfers:该交易所方法未实现".to_string()))
+    }
+
+    async fn command_order(&mut self, order_command: &mut OrderCommand, trace_stack: &TraceStack) {
+        let mut handles = vec![];
+
+        // 下单指令
+        for item in order_command.limits_open.keys() {
+            let mut ts = trace_stack.clone();
+
+            let amount = Decimal::from_str(&*order_command.limits_open[item].get(0).unwrap().clone()).unwrap();
+            let side = order_command.limits_open[item].get(1).unwrap().clone();
+            let price = Decimal::from_str(&*order_command.limits_open[item].get(2).unwrap().clone()).unwrap();
+            let cid = order_command.limits_open[item].get(3).unwrap().clone();
+
+            //  order_name: [数量,方向,价格,c_id]
+            let mut self_clone = self.clone();
+            let handle = spawn(async move {
+                // TraceStack::show_delay(&ts.ins);
+                ts.on_before_send();
+                let result = self_clone.take_order(cid.as_str(), side.as_str(), price, amount).await;
+                ts.on_after_send();
+
+                match result {
+                    Ok(mut result) => {
+                        result.trace_stack = ts;
+
+                        self_clone.order_sender.send(result).await.unwrap();
+                    }
+                    Err(error) => {
+                        info!(?error);
+                        let mut err_order = Order::new();
+                        err_order.custom_id = cid.clone();
+                        err_order.status = "REMOVE".to_string();
+
+                        self_clone.order_sender.send(err_order).await.unwrap();
+                        self_clone.error_sender.send(error).await.unwrap();
+                    }
+                }
+            });
+            handles.push(handle)
+        }
+        let futures = FuturesUnordered::from_iter(handles);
+        // 等待所有任务完成
+        let _: Result<Vec<_>, _> = futures.try_collect().await;
+
+        // 撤销订单
+        let mut cancel_handlers = vec![];
+        for item in order_command.cancel.keys() {
+            let order_id = order_command.cancel[item].get(1).unwrap().clone();
+            let custom_id = order_command.cancel[item].get(0).unwrap().clone();
+
+            let mut self_clone = self.clone();
+            let handle = spawn(async move {
+                let result = self_clone.cancel_order(&order_id, &custom_id).await;
+                match result {
+                    Ok(_) => {
+                        // result_sd.send(result).await.unwrap();
+                    }
+                    Err(error) => {
+                        // 取消失败去查订单。
+                        let query_rst = self_clone.get_order_detail(&order_id, &custom_id).await;
+                        match query_rst {
+                            Ok(order) => {
+                                self_clone.order_sender.send(order).await.unwrap();
+                            }
+                            Err(err) => {
+                                error!("撤单失败,而且查单也失败了,htx_swap,oid={}, cid={}, err={:?}。", order_id.clone(), custom_id.clone(), err);
+                            }
+                        }
+                        self_clone.error_sender.send(error).await.unwrap();
+                    }
+                }
+            });
+            cancel_handlers.push(handle)
+        }
+        let futures = FuturesUnordered::from_iter(cancel_handlers);
+        // 等待所有任务完成
+        let _: Result<Vec<_>, _> = futures.try_collect().await;
+
+        // 检查订单指令
+        let mut check_handlers = vec![];
+        for item in order_command.check.keys() {
+            let order_id = order_command.check[item].get(1).unwrap().clone();
+            let custom_id = order_command.check[item].get(0).unwrap().clone();
+
+            let mut self_clone = self.clone();
+            let handle = spawn(async move {
+                let result = self_clone.get_order_detail(order_id.as_str(), custom_id.as_str()).await;
+                match result {
+                    Ok(result) => {
+                        self_clone.order_sender.send(result).await.unwrap();
+                    }
+                    Err(error) => {
+                        self_clone.error_sender.send(error).await.unwrap();
+                    }
+                }
+            });
+            check_handlers.push(handle)
+        }
+
+        let futures = FuturesUnordered::from_iter(check_handlers);
+        // 等待所有任务完成
+        let _: Result<Vec<_>, _> = futures.try_collect().await;
+    }
+}
+
+// pub fn format_account_info(balance_data: Value) -> Account {
+//     let balance_coin = balance_data["coin"].as_str().unwrap().to_string().to_uppercase();
+//     let available_balance = Decimal::from_str(balance_data["available"].as_str().unwrap()).unwrap();
+//     let frozen_balance = Decimal::from_str(balance_data["frozen"].as_str().unwrap()).unwrap();
+//     let balance = available_balance + frozen_balance;
+//
+//     Account {
+//         coin: balance_coin,
+//         balance,
+//         available_balance,
+//         frozen_balance,
+//         stocks: Decimal::ZERO,
+//         available_stocks: Decimal::ZERO,
+//         frozen_stocks: Decimal::ZERO,
+//     }
+// }
+
+pub fn format_order_item(order: serde_json::Value, ct_val: Decimal) -> Order {
+    let id = order["order_id"].to_string();
+    let custom_id = order["client_order_id"].to_string();
+    let price = Decimal::from_str(&order["price"].to_string()).unwrap();
+    let amount = Decimal::from_str(&order["volume"].to_string()).unwrap() * ct_val;
+    let deal_amount = Decimal::from_str(&order["trade_volume"].to_string()).unwrap() * ct_val;
+    let avg_price = Decimal::from_f64(order["trade_avg_price"].as_f64().unwrap_or(0.0)).unwrap() * ct_val;
+
+    let status = order["status"].to_string();
+
+    let custom_status = if ["6", "7", "11"].contains(&&**&status) {
+        "REMOVE".to_string()
+    } else if ["1", "2", "3", "4", "5"].contains(&&**&status) {
+        "NEW".to_string()
+    } else {
+        "NULL".to_string()
+    };
+    Order {
+        id,
+        custom_id,
+        price,
+        amount,
+        deal_amount,
+        avg_price,
+        status: custom_status,
+        order_type: order["order_price_type"].as_str().unwrap().to_string(),
+        trace_stack: TraceStack::new(0, Instant::now()).on_special("630 htx_swap".to_string()),
+    }
+}
+
+pub fn format_position_item(position: &serde_json::Value, ct_val: &Decimal) -> Position {
+    let symbol = position["symbol"].as_str().unwrap().to_string();
+    let margin_level = Decimal::from_str(&position["lever_rate"].to_string()).unwrap();
+    let amount = Decimal::from_f64(position["volume"].as_f64().unwrap()).unwrap() * ct_val;
+
+    let frozen_amount = Decimal::from_f64(position["frozen"].as_f64().unwrap()).unwrap();
+    let price = Decimal::from_f64(position["cost_hold"].as_f64().unwrap()).unwrap();
+    let profit = Decimal::from_f64(position["profit_unreal"].as_f64().unwrap()).unwrap();
+    let position_mode = match position["position_mode"].as_str().unwrap() {
+        "dual_side" => {
+            match position["direction"].as_str().unwrap() {
+                "sell" => {
+                    PositionModeEnum::Short
+                }
+                "buy" => {
+                    PositionModeEnum::Long
+                }
+                _ => {
+                    panic!("htx_usdt_swap: 未知的持仓模式与持仓方向: {}, {}",
+                           position["direction"].as_str().unwrap(), position["direction"].as_str().unwrap())
+                }
+            }
+        }
+        "single_side" => {
+            PositionModeEnum::Both
+        }
+        _ => {
+            panic!("htx_usdt_swap: 未知的持仓模式: {}", position["position_mode"].as_str().unwrap())
+        }
+    };
+    let margin = Decimal::from_f64(position["position_margin"].as_f64().unwrap()).unwrap();
+
+    Position {
+        symbol,
+        margin_level,
+        amount,
+        frozen_amount,
+        price,
+        profit,
+        position_mode,
+        margin,
+    }
+}

+ 139 - 0
standard/src/htx_swap_handle.rs

@@ -0,0 +1,139 @@
+use std::str::FromStr;
+use rust_decimal::Decimal;
+use rust_decimal::prelude::FromPrimitive;
+use tokio::time::Instant;
+use exchanges::response_base::ResponseData;
+use global::trace_stack::TraceStack;
+use crate::{Account, MarketOrder, Order, Position, PositionModeEnum, SpecialOrder};
+
+// 处理账号信息
+pub fn handle_account_info(response: &ResponseData, _symbol: &String) -> Account {
+    let mut result = Account::new();
+    let account_infos = response.data.as_array().unwrap();
+    for data in account_infos {
+        if data["margin_asset"].as_str().unwrap() != "USDT" { continue; }
+        let margin_position = Decimal::from_str(&data["margin_position"].to_string()).unwrap();
+
+        let balance = Decimal::from_str(&data["margin_balance"].to_string()).unwrap();
+        let frozen_balance = Decimal::from_str(&data["margin_frozen"].to_string()).unwrap();
+        let available_balance = balance - margin_position - frozen_balance;
+        // 格式化account信息
+        let account = Account {
+            coin: data["margin_asset"].as_str().unwrap().to_string(),
+            balance,
+            available_balance,
+            frozen_balance,
+            stocks: Default::default(),
+            available_stocks: Default::default(),
+            frozen_stocks: Default::default(),
+        };
+        result = account
+    }
+    return result;
+}
+
+// 处理order信息
+pub fn handle_order(res_data: ResponseData, ct_val: Decimal) -> SpecialOrder {
+    let res_data_json = res_data.data;
+    let order_info = vec![format_order_item(res_data_json.clone(), ct_val)];
+    SpecialOrder {
+        name: res_data.label,
+        order: order_info,
+    }
+}
+
+// 处理订单信息
+pub fn format_order_item(order: serde_json::Value, ct_val: Decimal) -> Order {
+    println!("{}", order);
+    println!("{}", order["price"]);
+    let id = order["order_id"].to_string();
+    let custom_id = order["client_order_id"].to_string();
+    let price = Decimal::from_str(&order["price"].to_string()).unwrap();
+    let amount = Decimal::from_str(&order["volume"].to_string()).unwrap() * ct_val;
+    let deal_amount = Decimal::from_str(&order["trade_volume"].to_string()).unwrap() * ct_val;
+    let avg_price = Decimal::from_f64(order["trade_avg_price"].as_f64().unwrap_or(0.0)).unwrap() * ct_val;
+
+    let status = order["status"].to_string();
+
+    let custom_status = if ["6", "7", "11"].contains(&&**&status) {
+        "REMOVE".to_string()
+    } else if ["1", "2", "3", "4", "5"].contains(&&**&status) {
+        "NEW".to_string()
+    } else {
+        "NULL".to_string()
+    };
+    Order {
+        id,
+        custom_id,
+        price,
+        amount,
+        deal_amount,
+        avg_price,
+        status: custom_status,
+        order_type: order["order_price_type"].as_str().unwrap().to_string(),
+        trace_stack: TraceStack::new(0, Instant::now()).on_special("73 htx_swap_handle".to_string()),
+    }
+}
+
+// 格式化深度信息
+pub fn format_depth_items(value: serde_json::Value) -> Vec<MarketOrder> {
+    let mut depth_items: Vec<MarketOrder> = vec![];
+    println!("{}", value);
+    for value in value.as_array().unwrap() {
+        depth_items.push(MarketOrder {
+            price: Decimal::from_str(&value[0].to_string()).unwrap(),
+            amount: Decimal::from_str(&value[1].to_string()).unwrap(),
+        })
+    }
+    return depth_items;
+}
+
+// 处理position信息
+pub fn handle_position(res_data: &ResponseData, ct_val: &Decimal) -> Vec<Position> {
+    let res_data_json = res_data.data.as_array().unwrap();
+    res_data_json.iter().map(|item| { format_position_item(item, ct_val) }).collect()
+}
+
+pub fn format_position_item(position: &serde_json::Value, ct_val: &Decimal) -> Position {
+    let symbol = position["symbol"].as_str().unwrap().to_string();
+    let margin_level = Decimal::from_str(&position["lever_rate"].to_string()).unwrap();
+    let amount = Decimal::from_f64(position["volume"].as_f64().unwrap()).unwrap() * ct_val;
+
+    let frozen_amount = Decimal::from_f64(position["frozen"].as_f64().unwrap()).unwrap();
+    let price = Decimal::from_f64(position["cost_hold"].as_f64().unwrap()).unwrap();
+    let profit = Decimal::from_f64(position["profit_unreal"].as_f64().unwrap()).unwrap();
+    let position_mode = match position["position_mode"].as_str().unwrap() {
+        "dual_side" => {
+            match position["direction"].as_str().unwrap() {
+                "sell" => {
+                    PositionModeEnum::Short
+                }
+                "buy" => {
+                    PositionModeEnum::Long
+                }
+                _ => {
+                    panic!("htx_usdt_swap: 未知的持仓模式与持仓方向: {}, {}",
+                           position["direction"].as_str().unwrap(), position["direction"].as_str().unwrap())
+                }
+            }
+        }
+        "single_side" => {
+            PositionModeEnum::Both
+        }
+        _ => {
+            panic!("htx_usdt_swap: 未知的持仓模式: {}", position["position_mode"].as_str().unwrap())
+        }
+    };
+    let margin = Decimal::from_f64(position["position_margin"].as_f64().unwrap()).unwrap();
+
+    Position {
+        symbol,
+        margin_level,
+        amount,
+        frozen_amount,
+        price,
+        profit,
+        position_mode,
+        margin,
+    }
+}

+ 2 - 0
standard/src/lib.rs

@@ -38,6 +38,8 @@ mod bitget_swap;
 mod bitget_swap_handle;
 mod coinex_swap;
 mod coinex_swap_handle;
+mod htx_swap;
+pub mod htx_swap_handle;
 
 /// 持仓模式枚举
 /// - `Both`:单持仓方向

+ 424 - 328
standard/tests/exchange_test.rs

@@ -2,11 +2,9 @@ use std::collections::{BTreeMap};
 use std::io::{Error};
 use std::sync::Arc;
 use std::sync::atomic::AtomicBool;
-use futures::StreamExt;
 use rust_decimal_macros::dec;
 use tokio::sync::mpsc::{channel, Receiver, Sender};
 use tokio::sync::Mutex;
-use tokio::try_join;
 use tracing::{error, trace};
 // use exchanges::binance_spot_ws::{BinanceSpotLogin, BinanceSpotSubscribeType, BinanceSpotWs, BinanceSpotWsType};
 // use exchanges::binance_swap_ws::{BinanceSwapLogin, BinanceSwapSubscribeType, BinanceSwapWs, BinanceSwapWsType};
@@ -14,16 +12,18 @@ use tracing::{error, trace};
 // use exchanges::kucoin_spot_ws::{KucoinSpotLogin, KucoinSpotSubscribeType, KucoinSpotWs, KucoinSpotWsType};
 // use exchanges::gate_swap_ws::{GateSwapLogin, GateSwapSubscribeType, GateSwapWs, GateSwapWsType};
 // use exchanges::bitget_spot_ws::{BitgetSpotLogin, BitgetSpotSubscribeType, BitgetSpotWs, BitgetSpotWsType};
-use exchanges::okx_swap_ws::{OkxSwapLogin, OkxSwapSubscribeType, OkxSwapWs, OkxSwapWsType};
+// use exchanges::okx_swap_ws::{OkxSwapLogin, OkxSwapSubscribeType, OkxSwapWs, OkxSwapWsType};
+use exchanges::htx_swap_ws::{HtxSwapLogin, HtxSwapSubscribeType, HtxSwapWs, HtxSwapWsType};
 use exchanges::response_base::ResponseData;
 use standard::exchange::{Exchange, ExchangeEnum};
 // use standard::{binance_spot_handle, Order, Platform, utils};
 // use standard::{binance_handle, Order, Platform, utils};
 // use standard::{kucoin_handle, Order, Platform, utils};
 // use standard::{kucoin_spot_handle, Order, Platform, utils};
-// use standard::{gate_handle, Order, Platform, utils};
+// use standard::{gate_swap_handle, handle_info, Order, Platform, utils};
 // use standard::{bitget_spot_handle, Order, Platform, utils};
-use standard::{okx_handle, Order, Platform, utils};
+// use standard::{okx_handle, Order, Platform, utils};
+use standard::{htx_swap_handle, handle_info, Order, Platform, utils};
 
 // 创建实体
 #[allow(dead_code)]
@@ -42,14 +42,14 @@ pub async fn test_new_exchange(exchange: ExchangeEnum, symbol: &str) -> Box<dyn
             params.insert("secret_key".to_string(), secret_key);
             Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
         }
-        ExchangeEnum::BinanceSpot => {
-            let mut params: BTreeMap<String, String> = BTreeMap::new();
-            let access_key = account_info.binance_access_key;
-            let secret_key = account_info.binance_secret_key;
-            params.insert("access_key".to_string(), access_key);
-            params.insert("secret_key".to_string(), secret_key);
-            Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
-        }
+        // ExchangeEnum::BinanceSpot => {
+        //     let mut params: BTreeMap<String, String> = BTreeMap::new();
+        //     let access_key = account_info.binance_access_key;
+        //     let secret_key = account_info.binance_secret_key;
+        //     params.insert("access_key".to_string(), access_key);
+        //     params.insert("secret_key".to_string(), secret_key);
+        //     Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
+        // }
         ExchangeEnum::GateSwap => {
             let mut params: BTreeMap<String, String> = BTreeMap::new();
             let access_key = account_info.gate_access_key;
@@ -58,14 +58,14 @@ pub async fn test_new_exchange(exchange: ExchangeEnum, symbol: &str) -> Box<dyn
             params.insert("secret_key".to_string(), secret_key);
             Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
         }
-        ExchangeEnum::GateSpot => {
-            let mut params: BTreeMap<String, String> = BTreeMap::new();
-            let access_key = account_info.gate_access_key;
-            let secret_key = account_info.gate_secret_key;
-            params.insert("access_key".to_string(), access_key);
-            params.insert("secret_key".to_string(), secret_key);
-            Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
-        }
+        // ExchangeEnum::GateSpot => {
+        //     let mut params: BTreeMap<String, String> = BTreeMap::new();
+        //     let access_key = account_info.gate_access_key;
+        //     let secret_key = account_info.gate_secret_key;
+        //     params.insert("access_key".to_string(), access_key);
+        //     params.insert("secret_key".to_string(), secret_key);
+        //     Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
+        // }
         ExchangeEnum::KucoinSwap => {
             let mut params: BTreeMap<String, String> = BTreeMap::new();
             let access_key = account_info.kucoin_access_key;
@@ -76,99 +76,132 @@ pub async fn test_new_exchange(exchange: ExchangeEnum, symbol: &str) -> Box<dyn
             params.insert("pass_key".to_string(), pass_key);
             Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
         }
-        ExchangeEnum::KucoinSpot => {
+        // ExchangeEnum::KucoinSpot => {
+        //     let mut params: BTreeMap<String, String> = BTreeMap::new();
+        //     let access_key = account_info.kucoin_access_key;
+        //     let secret_key = account_info.kucoin_secret_key;
+        //     let pass_key = account_info.kucoin_pass;
+        //     params.insert("access_key".to_string(), access_key);
+        //     params.insert("secret_key".to_string(), secret_key);
+        //     params.insert("pass_key".to_string(), pass_key);
+        //     Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
+        // }
+        // ExchangeEnum::OkxSwap => {
+        //     let mut params: BTreeMap<String, String> = BTreeMap::new();
+        //     let access_key = account_info.okx_access_key;
+        //     let secret_key = account_info.okx_secret_key;
+        //     let pass_key = account_info.okx_pass;
+        //     params.insert("access_key".to_string(), access_key);
+        //     params.insert("secret_key".to_string(), secret_key);
+        //     params.insert("pass_key".to_string(), pass_key);
+        //     Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
+        // }
+        // ExchangeEnum::BitgetSpot => {
+        //     let mut params: BTreeMap<String, String> = BTreeMap::new();
+        //     let access_key = account_info.bitget_access_key;
+        //     let secret_key = account_info.bitget_secret_key;
+        //     let pass_key = account_info.bitget_pass;
+        //     params.insert("access_key".to_string(), access_key);
+        //     params.insert("secret_key".to_string(), secret_key);
+        //     params.insert("pass_key".to_string(), pass_key);
+        //     Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
+        // }
+        ExchangeEnum::BitgetSwap => {
             let mut params: BTreeMap<String, String> = BTreeMap::new();
-            let access_key = account_info.kucoin_access_key;
-            let secret_key = account_info.kucoin_secret_key;
-            let pass_key = account_info.kucoin_pass;
+            let access_key = account_info.bitget_access_key;
+            let secret_key = account_info.bitget_secret_key;
+            let pass_key = account_info.bitget_pass;
             params.insert("access_key".to_string(), access_key);
             params.insert("secret_key".to_string(), secret_key);
             params.insert("pass_key".to_string(), pass_key);
             Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
         }
-        ExchangeEnum::OkxSwap => {
+        ExchangeEnum::BybitSwap => {
             let mut params: BTreeMap<String, String> = BTreeMap::new();
-            let access_key = account_info.okx_access_key;
-            let secret_key = account_info.okx_secret_key;
-            let pass_key = account_info.okx_pass;
+            let access_key = account_info.bybit_access_key;
+            let secret_key = account_info.bybit_secret_key;
+            let pass_key = account_info.bybit_pass;
             params.insert("access_key".to_string(), access_key);
             params.insert("secret_key".to_string(), secret_key);
             params.insert("pass_key".to_string(), pass_key);
             Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
         }
-        ExchangeEnum::BitgetSpot => {
+        ExchangeEnum::HtxSwap => {
             let mut params: BTreeMap<String, String> = BTreeMap::new();
-            let access_key = account_info.bitget_access_key;
-            let secret_key = account_info.bitget_secret_key;
-            let pass_key = account_info.bitget_pass;
+            let access_key = account_info.htx_access_key;
+            let secret_key = account_info.htx_secret_key;
+            let pass_key = account_info.htx_pass;
             params.insert("access_key".to_string(), access_key);
             params.insert("secret_key".to_string(), secret_key);
             params.insert("pass_key".to_string(), pass_key);
             Exchange::new(exchange, symbol.to_string(), false, params, order_sender, error_sender).await
         }
+        _ => {
+            panic!("该交易所未实现!")
+        }
     }
 }
 
 #[allow(dead_code)]
-pub async fn test_new_exchange_wss<T>(exchange: ExchangeEnum, symbol: &str, subscriber_type: T, mold: &str) where Vec<OkxSwapSubscribeType>: From<T> {
+pub async fn test_new_exchange_wss<T>(exchange: ExchangeEnum, symbol: &str, subscriber_type: T, mold: &str) where Vec<HtxSwapSubscribeType>: From<T> {
     utils::proxy_handle();
     let account_info = global::account_info::get_account_info("../test_account.toml");
     match exchange {
-        ExchangeEnum::BinanceSpot => {
-            // let symbol_format = utils::format_symbol(symbol.to_string(), "").to_uppercase();
-            // trace!(symbol_format);
-            // let name = format!("binance_spot@{}", symbol.to_string().to_lowercase());
-            // let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
-            // let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded::<ResponseData>();
-            // let write_tx_am = Arc::new(Mutex::new(write_tx));
-            // let is_shutdown_arc = Arc::new(AtomicBool::new(true));
-            //
-            // let params = BinanceSpotLogin {
-            //     api_key: account_info.binance_access_key,
-            //     api_secret: account_info.binance_secret_key,
-            // };
-            // let mut exchange_wss;
-            // exchange_wss = BinanceSpotWs::new_label(name, false, Option::from(params), BinanceSpotWsType::PublicAndPrivate);
-            // exchange_wss.set_symbols(vec![symbol_format]);
-            // exchange_wss.set_subscribe(subscriber_type.into());
-            //
-            //
-            // let mold_arc = Arc::new(mold.to_string());
-            // //读取
-            // tokio::spawn(async move {
-            //     let mold_clone = Arc::clone(&mold_arc);
-            //     loop {
-            //         if let Some(data) = read_rx.next().await {
-            //             trace!("原始数据 data:{:?}",data);
-            //             match mold_clone.as_str() {
-            //                 "depth" => {
-            //                     if data.data != "" {
-            //                         let result = binance_spot_handle::handle_special_depth(data);
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 "ticker" => {
-            //                     if data.data != "" {
-            //                         let result = binance_spot_handle::handle_special_ticker(data);
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 _ => {
-            //                     error!("没有该命令!mode={}", mold_clone);
-            //                     panic!("没有该命令!mode={}", mold_clone)
-            //                 }
-            //             }
-            //         }
-            //     };
-            // });
-            //
-            // let t1 = tokio::spawn(async move {
-            //     //链接
-            //     let bool_v3_clone = Arc::clone(&is_shutdown_arc);
-            //     exchange_wss.ws_connect_async(bool_v3_clone, &write_tx_am, write_rx, read_tx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
-            // });
-            // try_join!(t1).unwrap();
-        }
+        // ExchangeEnum::BinanceSpot => {
+        // let symbol_format = utils::format_symbol(symbol.to_string(), "").to_uppercase();
+        // trace!(symbol_format);
+        // let name = format!("binance_spot@{}", symbol.to_string().to_lowercase());
+        // let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
+        // let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded::<ResponseData>();
+        // let write_tx_am = Arc::new(Mutex::new(write_tx));
+        // let is_shutdown_arc = Arc::new(AtomicBool::new(true));
+        //
+        // let params = BinanceSpotLogin {
+        //     api_key: account_info.binance_access_key,
+        //     api_secret: account_info.binance_secret_key,
+        // };
+        // let mut exchange_wss;
+        // exchange_wss = BinanceSpotWs::new_label(name, false, Option::from(params), BinanceSpotWsType::PublicAndPrivate);
+        // exchange_wss.set_symbols(vec![symbol_format]);
+        // exchange_wss.set_subscribe(subscriber_type.into());
+        //
+        //
+        // let mold_arc = Arc::new(mold.to_string());
+        // //读取
+        // tokio::spawn(async move {
+        //     let mold_clone = Arc::clone(&mold_arc);
+        //     loop {
+        //         if let Some(data) = read_rx.next().await {
+        //             trace!("原始数据 data:{:?}",data);
+        //             match mold_clone.as_str() {
+        //                 "depth" => {
+        //                     if data.data != "" {
+        //                         let result = binance_spot_handle::handle_special_depth(data);
+        //                         trace!(?result)
+        //                     }
+        //                 }
+        //                 "ticker" => {
+        //                     if data.data != "" {
+        //                         let result = binance_spot_handle::handle_special_ticker(data);
+        //                         trace!(?result)
+        //                     }
+        //                 }
+        //                 _ => {
+        //                     error!("没有该命令!mode={}", mold_clone);
+        //                     panic!("没有该命令!mode={}", mold_clone)
+        //                 }
+        //             }
+        //         }
+        //     };
+        // });
+        //
+        // let t1 = tokio::spawn(async move {
+        //     //链接
+        //     let bool_v3_clone = Arc::clone(&is_shutdown_arc);
+        //     exchange_wss.ws_connect_async(bool_v3_clone, &write_tx_am, write_rx, read_tx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
+        // });
+        // try_join!(t1).unwrap();
+        // }
         ExchangeEnum::BinanceSwap => {
             // let symbol_format = utils::format_symbol(symbol.to_string(), "").to_uppercase();
             // trace!(symbol_format);
@@ -291,81 +324,80 @@ pub async fn test_new_exchange_wss<T>(exchange: ExchangeEnum, symbol: &str, subs
             // });
             // try_join!(t1).unwrap();
         }
-        ExchangeEnum::KucoinSpot => {
-            // let symbol_format = utils::format_symbol(symbol.to_string(), "-").to_uppercase();
-            // let symbol_back = utils::format_symbol(symbol.to_string(), "_");
-            // trace!(symbol_format);
-            // let name = format!("kucoin_spot@{}", symbol.to_string().to_lowercase());
-            // let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
-            // let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded::<ResponseData>();
-            // let write_tx_am = Arc::new(Mutex::new(write_tx));
-            // let is_shutdown_arc = Arc::new(AtomicBool::new(true));
-            //
-            // let params = KucoinSpotLogin {
-            //     access_key: account_info.kucoin_access_key,
-            //     secret_key: account_info.kucoin_secret_key,
-            //     pass_key: account_info.kucoin_pass,
-            // };
-            // let mut exchange_wss = if ["depth", "ticker"].contains(&mold) {
-            //     KucoinSpotWs::new_label(name, false, Option::from(params), KucoinSpotWsType::Public).await
-            // } else {
-            //     KucoinSpotWs::new_label(name, false, Option::from(params), KucoinSpotWsType::Private).await
-            // };
-            // exchange_wss.set_symbols(vec![symbol_format]);
-            // exchange_wss.set_subscribe(subscriber_type.into());
-            //
-            // let mold_arc = Arc::new(mold.to_string());
-            // tokio::spawn(async move {
-            //     let mold_clone = Arc::clone(&mold_arc);
-            //     loop {
-            //         if let Some(data) = read_rx.next().await {
-            //             trace!("原始数据 data:{:?}",data);
-            //             match mold_clone.as_str() {
-            //                 "depth" => {
-            //                     if data.data != "" {
-            //                         let result = kucoin_spot_handle::handle_special_depth(data);
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 "ticker" => {
-            //                     if data.data != "" {
-            //                         let result = kucoin_spot_handle::handle_special_ticker(data);
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 "account" => {
-            //                     if data.data != "" {
-            //                         let result = kucoin_spot_handle::handle_account_info(data, symbol_back.clone());
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 "orders" => {
-            //                     if data.data != "" {
-            //                         let result = kucoin_spot_handle::handle_order(data, dec!(1));
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 _ => {
-            //                     error!("没有该命令!mode={}", mold_clone);
-            //                     panic!("没有该命令!mode={}", mold_clone)
-            //                 }
-            //             }
-            //         }
-            //     }
-            // });
-            // let t1 = tokio::spawn(async move {
-            //     //链接
-            //     let bool_v3_clone = Arc::clone(&is_shutdown_arc);
-            //     exchange_wss.ws_connect_async(bool_v3_clone, &write_tx_am, write_rx, read_tx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
-            // });
-            // try_join!(t1).unwrap();
-        }
+        // ExchangeEnum::KucoinSpot => {
+        // let symbol_format = utils::format_symbol(symbol.to_string(), "-").to_uppercase();
+        // let symbol_back = utils::format_symbol(symbol.to_string(), "_");
+        // trace!(symbol_format);
+        // let name = format!("kucoin_spot@{}", symbol.to_string().to_lowercase());
+        // let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
+        // let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded::<ResponseData>();
+        // let write_tx_am = Arc::new(Mutex::new(write_tx));
+        // let is_shutdown_arc = Arc::new(AtomicBool::new(true));
+        //
+        // let params = KucoinSpotLogin {
+        //     access_key: account_info.kucoin_access_key,
+        //     secret_key: account_info.kucoin_secret_key,
+        //     pass_key: account_info.kucoin_pass,
+        // };
+        // let mut exchange_wss = if ["depth", "ticker"].contains(&mold) {
+        //     KucoinSpotWs::new_label(name, false, Option::from(params), KucoinSpotWsType::Public).await
+        // } else {
+        //     KucoinSpotWs::new_label(name, false, Option::from(params), KucoinSpotWsType::Private).await
+        // };
+        // exchange_wss.set_symbols(vec![symbol_format]);
+        // exchange_wss.set_subscribe(subscriber_type.into());
+        //
+        // let mold_arc = Arc::new(mold.to_string());
+        // tokio::spawn(async move {
+        //     let mold_clone = Arc::clone(&mold_arc);
+        //     loop {
+        //         if let Some(data) = read_rx.next().await {
+        //             trace!("原始数据 data:{:?}",data);
+        //             match mold_clone.as_str() {
+        //                 "depth" => {
+        //                     if data.data != "" {
+        //                         let result = kucoin_spot_handle::handle_special_depth(data);
+        //                         trace!(?result)
+        //                     }
+        //                 }
+        //                 "ticker" => {
+        //                     if data.data != "" {
+        //                         let result = kucoin_spot_handle::handle_special_ticker(data);
+        //                         trace!(?result)
+        //                     }
+        //                 }
+        //                 "account" => {
+        //                     if data.data != "" {
+        //                         let result = kucoin_spot_handle::handle_account_info(data, symbol_back.clone());
+        //                         trace!(?result)
+        //                     }
+        //                 }
+        //                 "orders" => {
+        //                     if data.data != "" {
+        //                         let result = kucoin_spot_handle::handle_order(data, dec!(1));
+        //                         trace!(?result)
+        //                     }
+        //                 }
+        //                 _ => {
+        //                     error!("没有该命令!mode={}", mold_clone);
+        //                     panic!("没有该命令!mode={}", mold_clone)
+        //                 }
+        //             }
+        //         }
+        //     }
+        // });
+        // let t1 = tokio::spawn(async move {
+        //     //链接
+        //     let bool_v3_clone = Arc::clone(&is_shutdown_arc);
+        //     exchange_wss.ws_connect_async(bool_v3_clone, &write_tx_am, write_rx, read_tx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
+        // });
+        // try_join!(t1).unwrap();
+        // }
         ExchangeEnum::GateSwap => {
             // let symbol_format = utils::format_symbol(symbol.to_string(), "_").to_uppercase();
             // trace!(symbol_format);
             // let name = format!("gate_swap@{}", symbol.to_string().to_lowercase());
             // let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
-            // let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded::<ResponseData>();
             // let write_tx_am = Arc::new(Mutex::new(write_tx));
             // let is_shutdown_arc = Arc::new(AtomicBool::new(true));
             //
@@ -377,204 +409,268 @@ pub async fn test_new_exchange_wss<T>(exchange: ExchangeEnum, symbol: &str, subs
             // exchange_wss.set_symbols(vec![symbol_format.clone()]);
             // exchange_wss.set_subscribe(subscriber_type.into());
             //
-            // let mold_arc = Arc::new(mold.to_string());
-            // tokio::spawn(async move {
-            //     let mold_clone = Arc::clone(&mold_arc);
-            //     loop {
-            //         if let Some(data) = read_rx.next().await {
-            //             trace!("原始数据 data:{:?}",data);
-            //             match mold_clone.as_str() {
-            //                 "depth" => {
-            //                     if data.data != "" {
-            //                         let result = gate_handle::handle_special_depth(data);
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 "ticker" => {
-            //                     if data.data != "" {
-            //                         let result = gate_handle::handle_special_ticker(data);
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 "account" => {
-            //                     if data.data != "" {
-            //                         let result = gate_handle::handle_account_info(data, symbol_format.clone());
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 "orders" => {
-            //                     if data.data != "" {
-            //                         let result = gate_handle::handle_order(data, dec!(1));
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 _ => {
-            //                     error!("没有该命令!mode={}", mold_clone);
-            //                     panic!("没有该命令!mode={}", mold_clone)
-            //                 }
-            //             }
-            //         }
-            //     }
-            // });
-            // let t1 = tokio::spawn(async move {
-            //     //链接
-            //     let bool_v3_clone = Arc::clone(&is_shutdown_arc);
-            //     exchange_wss.ws_connect_async(bool_v3_clone, &write_tx_am, write_rx, read_tx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
-            // });
-            // try_join!(t1).unwrap();
-        }
-        ExchangeEnum::BitgetSpot => {
-            // let symbol_format = utils::format_symbol(symbol.to_string(), "-").to_uppercase();
-            // let symbol_back = utils::format_symbol(symbol.to_string(), "_");
-            // trace!(symbol_format);
-            // let name = format!("bitget_spot@{}", symbol.to_string().to_lowercase());
-            // let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
-            // let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded::<ResponseData>();
-            // let write_tx_am = Arc::new(Mutex::new(write_tx));
-            // let is_shutdown_arc = Arc::new(AtomicBool::new(true));
-            //
-            // let params = BitgetSpotLogin {
-            //     api_key: account_info.bitget_access_key,
-            //     secret_key: account_info.bitget_secret_key,
-            //     passphrase_key: account_info.bitget_pass,
-            // };
-            //
-            // let mut exchange_wss = if ["depth", "ticker"].contains(&mold) {
-            //     BitgetSpotWs::new_label(name, false, Option::from(params), BitgetSpotWsType::Public)
-            // } else {
-            //     BitgetSpotWs::new_label(name, false, Option::from(params), BitgetSpotWsType::Private)
-            // };
-            // exchange_wss.set_symbols(vec![symbol_format]);
-            // exchange_wss.set_subscribe(subscriber_type.into());
+            // let bool_v3_clone = Arc::clone(&is_shutdown_arc);
+            // let mold_clone = mold.to_string().clone();
+            // let fun = move |data: ResponseData| {
+            //     let symbol_format_c = symbol_format.clone();
+            //     let mold_cc = mold_clone.clone();
             //
-            // let mold_arc = Arc::new(mold.to_string());
-            // //读取
-            // tokio::spawn(async move {
-            //     loop {
-            //         let mold_clone = Arc::clone(&mold_arc);
-            //         if let Some(data) = read_rx.next().await {
-            //             trace!("原始数据 data:{:?}",data);
-            //             match mold_clone.as_str() {
-            //                 "depth" => {
-            //                     if data.data != "" {
-            //                         let result = bitget_spot_handle::handle_special_depth(data);
-            //                         trace!(?result)
-            //                     }
-            //                 }
-            //                 "ticker" => {
-            //                     if data.data != "" {
-            //                         let result = bitget_spot_handle::handle_special_ticker(data);
-            //                         trace!(?result)
-            //                     }
+            //     async move {
+            //         trace!("原始数据 data:{:?}",data);
+            //         match mold_cc.as_str() {
+            //             "depth" => {
+            //                 if data.data != "" {
+            //                     let result = handle_info::format_depth(ExchangeEnum::GateSwap, &data);
+            //                     trace!(?result)
             //                 }
-            //                 "account" => {
-            //                     if data.data != "" {
-            //                         let result = bitget_spot_handle::handle_account_info(data, symbol_back.clone());
-            //                         trace!(?result)
-            //                     }
+            //             }
+            //             "ticker" => {
+            //                 if data.data != "" {
+            //                     let result = gate_swap_handle::handle_book_ticker(&data);
+            //                     trace!(?result)
             //                 }
-            //                 "orders" => {
-            //                     if data.data != "" {
-            //                         let result = bitget_spot_handle::handle_order(data, dec!(1));
-            //                         trace!(?result)
-            //                     }
+            //             }
+            //             "account" => {
+            //                 if data.data != "" {
+            //                     let result = gate_swap_handle::handle_account_info(&data, &symbol_format_c);
+            //                     trace!(?result)
             //                 }
-            //                 _ => {
-            //                     error!("没有该命令!mode={}", mold_clone);
-            //                     panic!("没有该命令!mode={}", mold_clone)
+            //             }
+            //             "orders" => {
+            //                 if data.data != "" {
+            //                     let result = gate_swap_handle::handle_order(data, dec!(1));
+            //                     trace!(?result)
             //                 }
             //             }
-            //         }
+            //             _ => {
+            //                 error!("没有该命令!mode={}", mold_cc);
+            //                 panic!("没有该命令!mode={}", mold_cc)
+            //             }
+            //         };
             //     }
-            // });
-            // let t1 = tokio::spawn(async move {
-            //     //链接
-            //     let bool_v3_clone = Arc::clone(&is_shutdown_arc);
-            //     exchange_wss.ws_connect_async(bool_v3_clone, &write_tx_am, write_rx, read_tx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
-            // });
-            // try_join!(t1).unwrap();
+            // };
+            // exchange_wss.ws_connect_async(bool_v3_clone, fun, &write_tx_am, write_rx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
         }
-        ExchangeEnum::OkxSwap => {
+        // ExchangeEnum::BitgetSpot => {
+        // let symbol_format = utils::format_symbol(symbol.to_string(), "-").to_uppercase();
+        // let symbol_back = utils::format_symbol(symbol.to_string(), "_");
+        // trace!(symbol_format);
+        // let name = format!("bitget_spot@{}", symbol.to_string().to_lowercase());
+        // let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
+        // let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded::<ResponseData>();
+        // let write_tx_am = Arc::new(Mutex::new(write_tx));
+        // let is_shutdown_arc = Arc::new(AtomicBool::new(true));
+        //
+        // let params = BitgetSpotLogin {
+        //     api_key: account_info.bitget_access_key,
+        //     secret_key: account_info.bitget_secret_key,
+        //     passphrase_key: account_info.bitget_pass,
+        // };
+        //
+        // let mut exchange_wss = if ["depth", "ticker"].contains(&mold) {
+        //     BitgetSpotWs::new_label(name, false, Option::from(params), BitgetSpotWsType::Public)
+        // } else {
+        //     BitgetSpotWs::new_label(name, false, Option::from(params), BitgetSpotWsType::Private)
+        // };
+        // exchange_wss.set_symbols(vec![symbol_format]);
+        // exchange_wss.set_subscribe(subscriber_type.into());
+        //
+        // let mold_arc = Arc::new(mold.to_string());
+        // //读取
+        // tokio::spawn(async move {
+        //     loop {
+        //         let mold_clone = Arc::clone(&mold_arc);
+        //         if let Some(data) = read_rx.next().await {
+        //             trace!("原始数据 data:{:?}",data);
+        //             match mold_clone.as_str() {
+        //                 "depth" => {
+        //                     if data.data != "" {
+        //                         let result = bitget_spot_handle::handle_special_depth(data);
+        //                         trace!(?result)
+        //                     }
+        //                 }
+        //                 "ticker" => {
+        //                     if data.data != "" {
+        //                         let result = bitget_spot_handle::handle_special_ticker(data);
+        //                         trace!(?result)
+        //                     }
+        //                 }
+        //                 "account" => {
+        //                     if data.data != "" {
+        //                         let result = bitget_spot_handle::handle_account_info(data, symbol_back.clone());
+        //                         trace!(?result)
+        //                     }
+        //                 }
+        //                 "orders" => {
+        //                     if data.data != "" {
+        //                         let result = bitget_spot_handle::handle_order(data, dec!(1));
+        //                         trace!(?result)
+        //                     }
+        //                 }
+        //                 _ => {
+        //                     error!("没有该命令!mode={}", mold_clone);
+        //                     panic!("没有该命令!mode={}", mold_clone)
+        //                 }
+        //             }
+        //         }
+        //     }
+        // });
+        // let t1 = tokio::spawn(async move {
+        //     //链接
+        //     let bool_v3_clone = Arc::clone(&is_shutdown_arc);
+        //     exchange_wss.ws_connect_async(bool_v3_clone, &write_tx_am, write_rx, read_tx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
+        // });
+        // try_join!(t1).unwrap();
+        // }
+        // ExchangeEnum::OkxSwap => {
+        //     let symbol_format = utils::format_symbol(symbol.to_string(), "-").to_uppercase();
+        //     trace!(symbol_format);
+        //     let name = format!("okx_swap@{}", symbol.to_string().to_lowercase());
+        //     let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
+        //     let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded::<ResponseData>();
+        //     let write_tx_am = Arc::new(Mutex::new(write_tx));
+        //     let is_shutdown_arc = Arc::new(AtomicBool::new(true));
+        //
+        //     let params = OkxSwapLogin {
+        //         api_key: account_info.okx_access_key,
+        //         secret_key: account_info.okx_secret_key,
+        //         passphrase: account_info.okx_pass,
+        //     };
+        //
+        //     let mut exchange_wss = if ["depth", "ticker"].contains(&mold) {
+        //         OkxSwapWs::new_label(name, false, Option::from(params), OkxSwapWsType::Public)
+        //     } else if ["account", "orders", "position"].contains(&mold) {
+        //         OkxSwapWs::new_label(name, false, Option::from(params), OkxSwapWsType::Private)
+        //     } else {
+        //         OkxSwapWs::new_label(name, false, Option::from(params), OkxSwapWsType::Business)
+        //     };
+        //
+        //     exchange_wss.set_symbols(vec![symbol_format.clone()]);
+        //     exchange_wss.set_subscribe(subscriber_type.into());
+        //
+        //     let mold_arc = Arc::new(mold.to_string());
+        //     tokio::spawn(async move {
+        //         let mold_clone = Arc::clone(&mold_arc);
+        //         loop {
+        //             if let Some(data) = read_rx.next().await {
+        //                 trace!("原始数据 data:{:?}",data);
+        //                 match mold_clone.as_str() {
+        //                     "depth" => {
+        //                         if data.data != "" {
+        //                             let result = okx_handle::handle_special_depth(data);
+        //                             trace!(?result)
+        //                         }
+        //                     }
+        //                     "ticker" => {
+        //                         if data.data != "" {
+        //                             let result = okx_handle::handle_special_ticker(data);
+        //                             trace!(?result)
+        //                         }
+        //                     }
+        //                     "account" => {
+        //                         if data.data != "" {
+        //                             let result = okx_handle::handle_account_info(data, symbol_format.clone());
+        //                             trace!(?result)
+        //                         }
+        //                     }
+        //                     "position" => {
+        //                         if data.data != "" {
+        //                             let result = okx_handle::handle_position(data, dec!(10));
+        //                             trace!(?result)
+        //                         }
+        //                     }
+        //                     "orders" => {
+        //                         if data.data != "" {
+        //                             let result = okx_handle::handle_order(data, dec!(10));
+        //                             trace!(?result)
+        //                         }
+        //                     }
+        //                     _ => {
+        //                         error!("没有该命令!mode={}", mold_clone);
+        //                         panic!("没有该命令!mode={}", mold_clone)
+        //                     }
+        //                 }
+        //             }
+        //         }
+        //     });
+        //
+        //     let t1 = tokio::spawn(async move {
+        //         //链接
+        //         let bool_v3_clone = Arc::clone(&is_shutdown_arc);
+        //         exchange_wss.ws_connect_async(bool_v3_clone, &write_tx_am, write_rx, read_tx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
+        //     });
+        //     try_join!(t1).unwrap();
+        // }
+        ExchangeEnum::HtxSwap => {
             let symbol_format = utils::format_symbol(symbol.to_string(), "-").to_uppercase();
             trace!(symbol_format);
-            let name = format!("okx_swap@{}", symbol.to_string().to_lowercase());
+            let name = format!("htx_swap@{}", symbol.to_string().to_lowercase());
             let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
-            let (read_tx, mut read_rx) = futures_channel::mpsc::unbounded::<ResponseData>();
             let write_tx_am = Arc::new(Mutex::new(write_tx));
             let is_shutdown_arc = Arc::new(AtomicBool::new(true));
 
-            let params = OkxSwapLogin {
-                api_key: account_info.okx_access_key,
-                secret_key: account_info.okx_secret_key,
-                passphrase: account_info.okx_pass,
+            let params = HtxSwapLogin {
+                api_key: account_info.htx_access_key,
+                secret: account_info.htx_secret_key,
             };
 
-            let mut exchange_wss = if ["depth", "ticker"].contains(&mold) {
-                OkxSwapWs::new_label(name, false, Option::from(params), OkxSwapWsType::Public)
-            } else if ["account", "orders", "position"].contains(&mold) {
-                OkxSwapWs::new_label(name, false, Option::from(params), OkxSwapWsType::Private)
-            } else {
-                OkxSwapWs::new_label(name, false, Option::from(params), OkxSwapWsType::Business)
+            let htx_wss_type = match mold.to_string().clone().as_str() {
+                "depth" => HtxSwapWsType::Public,
+                _ => HtxSwapWsType::Private
             };
 
+            let mut exchange_wss = HtxSwapWs::new_label(name, Option::from(params), htx_wss_type);
             exchange_wss.set_symbols(vec![symbol_format.clone()]);
             exchange_wss.set_subscribe(subscriber_type.into());
+            let bool_v3_clone = Arc::clone(&is_shutdown_arc);
+            let mold_clone = mold.to_string().clone();
+            let fun = move |data: ResponseData| {
+                let symbol_format_c = symbol_format.clone();
+                let mold_cc = mold_clone.clone();
 
-            let mold_arc = Arc::new(mold.to_string());
-            tokio::spawn(async move {
-                let mold_clone = Arc::clone(&mold_arc);
-                loop {
-                    if let Some(data) = read_rx.next().await {
-                        trace!("原始数据 data:{:?}",data);
-                        match mold_clone.as_str() {
-                            "depth" => {
-                                if data.data != "" {
-                                    let result = okx_handle::handle_special_depth(data);
-                                    trace!(?result)
-                                }
-                            }
-                            "ticker" => {
-                                if data.data != "" {
-                                    let result = okx_handle::handle_special_ticker(data);
-                                    trace!(?result)
-                                }
+                async move {
+                    trace!("原始数据 data:{:?}",data);
+                    match mold_cc.as_str() {
+                        "depth" => {
+                            if data.data != "" {
+                                let result = handle_info::format_depth(ExchangeEnum::HtxSwap, &data);
+                                trace!("-------------------------------");
+                                trace!(?result)
                             }
-                            "account" => {
-                                if data.data != "" {
-                                    let result = okx_handle::handle_account_info(data, symbol_format.clone());
-                                    trace!(?result)
-                                }
-                            }
-                            "position" => {
-                                if data.data != "" {
-                                    let result = okx_handle::handle_position(data, dec!(10));
-                                    trace!(?result)
-                                }
+                        }
+                        "position" => {
+                            if data.data != "" {
+                                let result = htx_swap_handle::handle_position(&data, &dec!(10));
+                                trace!("-------------------------------");
+                                trace!(?result)
                             }
-                            "orders" => {
-                                if data.data != "" {
-                                    let result = okx_handle::handle_order(data, dec!(10));
-                                    trace!(?result)
-                                }
+                        }
+                        "account" => {
+                            if data.data != "" {
+                                let result = htx_swap_handle::handle_account_info(&data, &symbol_format_c);
+                                trace!("-------------------------------");
+                                trace!(?result)
                             }
-                            _ => {
-                                error!("没有该命令!mode={}", mold_clone);
-                                panic!("没有该命令!mode={}", mold_clone)
+                        }
+                        "orders" => {
+                            println!("{:?}", data);
+                            if data.data != "" {
+                                let result = htx_swap_handle::handle_order(data, dec!(10));
+                                trace!("-------------------------------");
+                                trace!(?result)
                             }
                         }
-                    }
+                        _ => {
+                            error!("没有该命令!mode={}", mold_cc);
+                            panic!("没有该命令!mode={}", mold_cc)
+                        }
+                    };
                 }
-            });
-
-            let t1 = tokio::spawn(async move {
-                //链接
-                let bool_v3_clone = Arc::clone(&is_shutdown_arc);
-                exchange_wss.ws_connect_async(bool_v3_clone, &write_tx_am, write_rx, read_tx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
-            });
-            try_join!(t1).unwrap();
+            };
+            exchange_wss.ws_connect_async(bool_v3_clone, fun, &write_tx_am, write_rx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
         }
         _ => {
-            error!("该交易所不支持!test_new_exchange_wss:{:?}",exchange);
+            error!("该交易所不支持!test_new_exchange_wss:{:?}", exchange);
             panic!("该交易所不支持!test_new_exchange_wss:{:?}", exchange)
         }
     }

+ 4 - 1
standard/tests/gate_swap_test.rs

@@ -3,9 +3,12 @@ mod exchange_test;
 use std::collections::BTreeMap;
 use std::env;
 use std::io::Error;
+use chrono::Utc;
 use rust_decimal_macros::dec;
 use tokio::sync::mpsc;
+use tokio::time::Instant;
 use tracing::{instrument, trace};
+use global::trace_stack::TraceStack;
 use standard::exchange::{Exchange, ExchangeEnum};
 use standard::{Order, OrderCommand, Platform, utils};
 use crate::exchange_test::{test_new_exchange};
@@ -266,7 +269,7 @@ async fn test_command_order() {
     command.limits_open.insert("888888".to_string(), vec!["100".to_string(), "kd".to_string(), "0.18".to_string(), "888888".to_string()]);
     command.limits_close.insert("999999".to_string(), vec!["100".to_string(), "kk".to_string(), "0.25".to_string(), "999999".to_string()]);
     command.check.insert("888888".to_string(), vec!["999999".to_string(), "".to_string()]);
-    gate_swap_exchange.command_order(command, Default::default()).await;
+    gate_swap_exchange.command_order(&mut command, &TraceStack::new(Utc::now().timestamp_micros(), Instant::now())).await;
 
     loop {
         if let Ok(order) = order_receiver.try_recv() {

+ 56 - 0
standard/tests/htx_handle_test.rs

@@ -0,0 +1,56 @@
+mod exchange_test;
+
+use tracing::{instrument};
+use exchanges::htx_swap_ws::HtxSwapSubscribeType;
+use standard::exchange::ExchangeEnum;
+use crate::exchange_test::{test_new_exchange_wss};
+
+const SYMBOL: &str = "USTC_USDT";
+
+// 测试订阅深度订阅
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+#[instrument(level = "TRACE")]
+async fn test_get_wss_depth() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_subscribe_type = vec![
+        HtxSwapSubscribeType::PuFuturesDepth
+    ];
+    test_new_exchange_wss(ExchangeEnum::HtxSwap, SYMBOL, htx_subscribe_type, "depth").await;
+}
+
+// 测试订阅Account信息
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+#[instrument(level = "TRACE")]
+async fn test_get_wss_account() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_subscribe_type = vec![
+        HtxSwapSubscribeType::PrFuturesBalances
+    ];
+    test_new_exchange_wss(ExchangeEnum::HtxSwap, SYMBOL, htx_subscribe_type, "account").await;
+}
+
+// 测试订阅Position信息
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+#[instrument(level = "TRACE")]
+async fn test_get_wss_position() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_subscribe_type = vec![
+        HtxSwapSubscribeType::PrFuturesPositions
+    ];
+    test_new_exchange_wss(ExchangeEnum::HtxSwap, SYMBOL, htx_subscribe_type, "position").await;
+}
+
+// 测试订阅Orders信息
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+#[instrument(level = "TRACE")]
+async fn test_get_wss_orders() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_subscribe_type = vec![
+        HtxSwapSubscribeType::PrFuturesOrders
+    ];
+    test_new_exchange_wss(ExchangeEnum::HtxSwap, SYMBOL, htx_subscribe_type, "orders").await;
+}

+ 282 - 0
standard/tests/htx_swap_test.rs

@@ -0,0 +1,282 @@
+mod exchange_test;
+
+use std::collections::BTreeMap;
+use std::env;
+use std::io::Error;
+use chrono::Utc;
+use rust_decimal_macros::dec;
+use tokio::sync::mpsc;
+use tokio::time::Instant;
+use tracing::{instrument, trace};
+use global::trace_stack::TraceStack;
+use standard::exchange::{Exchange, ExchangeEnum};
+use standard::{Order, OrderCommand, Platform, utils};
+use crate::exchange_test::{test_new_exchange};
+
+const SYMBOL: &str = "USTC_USDT";
+
+// 测试获取Exchange实体
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_self_exchange() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_self_exchange = htx_swap_exchange.get_self_exchange();
+    trace!(?htx_get_self_exchange);
+}
+
+// 测试获取交易对信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_self_symbol() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_self_symbol = htx_swap_exchange.get_self_symbol();
+    trace!(?htx_get_self_symbol);
+}
+
+// 测试获取是否使用高速通道
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_self_is_colo() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_self_is_colo = htx_swap_exchange.get_self_is_colo();
+    trace!(?htx_get_self_is_colo);
+}
+
+// 测试获取登录params信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_self_params() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_self_params = htx_swap_exchange.get_self_params();
+    trace!("htx_get_self_params={:?}",htx_get_self_params);
+}
+
+// 测试获取Market信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_self_market() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_self_market = htx_swap_exchange.get_self_market();
+    trace!(?htx_get_self_market);
+}
+
+// 测试获取请求时间信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_request_delays() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_request_delays = htx_swap_exchange.get_request_delays();
+    trace!(?htx_get_request_delays);
+}
+
+// 测试获取请求平均时间信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_request_avg_delay() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_request_avg_delay = htx_swap_exchange.get_request_avg_delay();
+    trace!(?htx_get_request_avg_delay);
+}
+
+// 测试获取最大请求时间信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_request_max_delay() {
+    global::log_utils::init_log_with_trace();
+
+    let htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_request_max_delay = htx_swap_exchange.get_request_max_delay();
+    trace!(?htx_get_request_max_delay);
+}
+
+// 测试获取服务器时间
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_server_time() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_server_time = htx_swap_exchange.get_server_time().await;
+    trace!(?htx_get_server_time);
+}
+
+// 测试获取账号信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_account() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_account = htx_swap_exchange.get_account().await;
+    trace!(?htx_get_account);
+}
+
+// 测试获取持仓信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_position() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_position = htx_swap_exchange.get_position().await;
+    trace!(?htx_get_position);
+}
+
+// 测试获取所有持仓信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_positions() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_positions = htx_swap_exchange.get_positions().await;
+    trace!(?htx_get_positions);
+}
+
+// 测试获取Ticker信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_ticker() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_ticker = htx_swap_exchange.get_ticker().await;
+    trace!(?htx_get_ticker);
+}
+
+// 测试获取Market信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_market() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_market = htx_swap_exchange.get_market().await;
+    trace!(?htx_get_market);
+}
+
+// 测试获取Order详情信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_order_detail() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_order_detail = htx_swap_exchange.get_order_detail("1234925996822429696", "9999992").await;
+    trace!(?htx_get_order_detail);
+}
+
+// 测试获取Order列表信息
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_get_orders_list() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_get_orders_list = htx_swap_exchange.get_orders_list("finished").await;
+    trace!(?htx_get_orders_list);
+}
+
+// 测试下单
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_take_order() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_take_order = htx_swap_exchange.take_order("999999914", "kd", dec!(0.02320), dec!(20)).await;
+    trace!(?htx_take_order);
+}
+
+// 测试撤销订单
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_cancel_order() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_cancel_order = htx_swap_exchange.cancel_order("", "999999900").await;
+    trace!(?htx_cancel_order);
+}
+
+// 测试批量撤销订单
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_cancel_orders() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_cancel_orders = htx_swap_exchange.cancel_orders_all().await;
+    trace!(?htx_cancel_orders);
+}
+
+// 测试设置持仓模式
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_set_dual_mode() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_set_dual_mode = htx_swap_exchange.set_dual_mode("usdt", true).await;
+    trace!(?htx_set_dual_mode);
+}
+
+// 测试设置杠杆
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_set_dual_leverage() {
+    global::log_utils::init_log_with_trace();
+
+    let mut htx_swap_exchange: Box<dyn Platform> = test_new_exchange(ExchangeEnum::HtxSwap, SYMBOL).await;
+    let htx_set_dual_leverage = htx_swap_exchange.set_dual_leverage("10").await;
+    trace!(?htx_set_dual_leverage);
+}
+
+// 测试指令下单
+#[tokio::test]
+#[instrument(level = "TRACE")]
+async fn test_command_order() {
+    global::log_utils::init_log_with_trace();
+    utils::proxy_handle();
+
+    let (order_sender, mut order_receiver): (mpsc::Sender<Order>, mpsc::Receiver<Order>) = mpsc::channel(1024);
+    let (error_sender, mut error_receiver): (mpsc::Sender<Error>, mpsc::Receiver<Error>) = mpsc::channel(1024);
+
+    let mut params: BTreeMap<String, String> = BTreeMap::new();
+    let access_key = env::var("htx_access_key").unwrap_or("".to_string());
+    let secret_key = env::var("htx_secret_key").unwrap_or("".to_string());
+    params.insert("access_key".to_string(), access_key);
+    params.insert("secret_key".to_string(), secret_key);
+
+    let mut htx_swap_exchange: Box<dyn Platform> = Exchange::new(ExchangeEnum::HtxSwap, SYMBOL.to_string(), false, params, order_sender, error_sender).await;
+
+    let mut command = OrderCommand::new();
+    command.cancel.insert("888888".to_string(), vec!["888888".to_string(), "".to_string()]);
+    command.limits_open.insert("888888".to_string(), vec!["100".to_string(), "kd".to_string(), "0.18".to_string(), "888888".to_string()]);
+    command.limits_close.insert("999999".to_string(), vec!["100".to_string(), "kk".to_string(), "0.25".to_string(), "999999".to_string()]);
+    command.check.insert("888888".to_string(), vec!["999999".to_string(), "".to_string()]);
+    htx_swap_exchange.command_order(&mut command, &TraceStack::new(Utc::now().timestamp_micros(), Instant::now())).await;
+
+    loop {
+        if let Ok(order) = order_receiver.try_recv() {
+            trace!(?order);
+        }
+        if let Ok(error) = error_receiver.try_recv() {
+            trace!(?error);
+        }
+    }
+}

+ 11 - 1
test_account.toml.sample

@@ -19,4 +19,14 @@ okx_pass = ""
 # bitget账号密码
 bitget_access_key = ""
 bitget_secret_key = ""
-bitget_pass = ""
+bitget_pass = ""
+
+# bitget账号密码
+bybit_access_key = ""
+bybit_secret_key = ""
+bybit_pass = ""
+
+# htx账号密码
+htx_access_key = ""
+htx_secret_key = ""
+htx_pass = ""