Browse Source

添加woox交易所数据收集

DESKTOP-NE65RNK\Citrus_limon 1 năm trước cách đây
mục cha
commit
da5c76474e

+ 4 - 3
exchanges/src/woo_swap_ws.rs

@@ -16,7 +16,8 @@ use tokio_tungstenite::tungstenite::{Error, Message};
 use tracing::{error, info, trace};
 
 use crate::response_base::ResponseData;
-use crate::socket_tool::{AbstractWsMode, HeartbeatType};
+use crate::socket_tool::{AbstractWsMode};
+// use crate::socket_tool::{AbstractWsMode, HeartbeatType};
 
 pub(crate) static LOGIN_DATA: Lazy<Mutex<(bool, bool)>> = Lazy::new(|| {
     println!("初始化...");
@@ -77,7 +78,7 @@ pub struct WooSwapWs {
     // 币对
     subscribe_types: Vec<WooSwapSubscribeType>,
     // 订阅
-    heartbeat_time: u64,                                        // 心跳间隔
+    _heartbeat_time: u64,                                        // 心跳间隔
 }
 
 
@@ -110,7 +111,7 @@ impl WooSwapWs {
             login_param,
             symbol_s: vec![],
             subscribe_types: vec![],
-            heartbeat_time: 1000 * 5,
+            _heartbeat_time: 1000 * 5,
         }
     }
 

+ 7 - 5
src/main.rs

@@ -14,6 +14,7 @@ mod bitmart_usdt_swap_data_listener;
 mod kucoin_usdt_swap_data_listener;
 mod coinsph_usdt_swap_data_listener;
 mod phemex_usdt_swap_data_listener;
+mod woo_usdt_swap_data_listener;
 
 
 use std::sync::Arc;
@@ -47,11 +48,12 @@ async fn main() {
     // bingx_usdt_swap_data_listener::run_listener(running.clone()).await;
     // coinsph_usdt_swap_data_listener::run_listener(running.clone()).await;
 
-    binance_usdt_swap_data_listener::run_listener(running.clone()).await;
-    gate_usdt_swap_data_listener::run_listener(running.clone()).await;
-    coinex_usdt_swap_data_listener::run_listener(running.clone()).await;
-    htx_usdt_swap_data_listener::run_listener(running.clone()).await;
-    phemex_usdt_swap_data_listener::run_listener(running.clone()).await;
+    // binance_usdt_swap_data_listener::run_listener(running.clone()).await;
+    // gate_usdt_swap_data_listener::run_listener(running.clone()).await;
+    // coinex_usdt_swap_data_listener::run_listener(running.clone()).await;
+    // htx_usdt_swap_data_listener::run_listener(running.clone()).await;
+    // phemex_usdt_swap_data_listener::run_listener(running.clone()).await;
+    woo_usdt_swap_data_listener::run_listener(running.clone()).await;
     // panic错误捕获,panic级别的错误直接退出
     // let panic_running = running.clone();
     std::panic::set_hook(Box::new(move |panic_info| {

+ 0 - 1
src/phemex_usdt_swap_data_listener.rs

@@ -101,7 +101,6 @@ pub async fn data_listener(response: ResponseData) {
         // k线数据
         "futures.candlesticks" => {
             let mut records = ExchangeStructHandler::records_handle(ExchangeEnum::PhemexSwap, &response);
-            println!("{:?}", records);
             let mul_map = MUL_MAP.lock().await;
             for record in records.iter_mut() {
                 // 真实交易量处理,因为phemex的量都是张数

+ 1 - 0
src/server.rs

@@ -180,6 +180,7 @@ async fn get_exchanges() -> impl Responder {
         "coinex_usdt_swap",
         "htx_usdt_swap",
         "phemex_usdt_swap"
+        "woox_usdt_swap"
     ];
     let response_data = json!(exchanges);
 

+ 120 - 0
src/woo_usdt_swap_data_listener.rs

@@ -0,0 +1,120 @@
+use std::collections::{BTreeMap, HashMap};
+use std::sync::{Arc};
+use std::sync::atomic::AtomicBool;
+use lazy_static::lazy_static;
+use rust_decimal::Decimal;
+use rust_decimal_macros::dec;
+use tokio::sync::{Mutex};
+use exchanges::woo_swap_rest::WooSwapRest;
+use exchanges::woo_swap_ws::{WooSwapSubscribeType, WooSwapWs, WooSwapWsType};
+use exchanges::response_base::ResponseData;
+use serde_json::{json};
+use standard::exchange::ExchangeEnum;
+use standard::exchange_struct_handler::ExchangeStructHandler;
+use standard::utils::symbol_out_mapper;
+use crate::listener_tools::{RecordMap, TradeMap, update_record, update_trade};
+
+const EXCHANGE_NAME: &str = "woo_usdt_swap";
+
+lazy_static! {
+    // static ref DEPTH_MAP: Mutex<DepthMap> = Mutex::new(HashMap::new());
+    static ref TRADES_MAP: Mutex<TradeMap> = Mutex::new(HashMap::new());
+    static ref RECORD_MAP: Mutex<RecordMap> = Mutex::new(HashMap::new());
+    static ref MUL_MAP: Mutex<HashMap<String, Decimal>> = Mutex::new(HashMap::new());
+}
+
+pub async fn run_listener(is_shutdown_arc: Arc<AtomicBool>) {
+    let name = "woo_usdt_swap_listener";
+    // 订阅所有币种
+    let login = BTreeMap::new();
+    let mut woo_rest = WooSwapRest::new(false, login);
+    let params = json!({});
+    let response = woo_rest.get_market(params).await;
+    let mut symbols = vec![];
+    if response.code == 200 {
+        let symbol_infos = response.data["rows"].as_array().unwrap();
+        let mut mul_map = MUL_MAP.lock().await;
+        for symbol_info in symbol_infos {
+            if !symbol_info["symbol"].as_str().unwrap().contains("PERP_") { continue; }
+            // quanto_multiplier是ct_val
+            let symbol = symbol_info["symbol"].as_str().unwrap().to_string().replace("PERP_", "");
+            let mul = Decimal::ONE;
+            mul_map.insert(symbol_out_mapper(ExchangeEnum::WooSwap, &symbol), mul);
+
+            symbols.push(symbol)
+        }
+    }
+    for chunk in symbols.chunks(20) {
+        let ws_name = name.to_string();
+        let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
+        let write_tx_am = Arc::new(Mutex::new(write_tx));
+        let symbols_chunk = chunk.iter().cloned().collect::<Vec<String>>();
+        let is_shutdown_clone = Arc::clone(&is_shutdown_arc);
+
+        tokio::spawn(async move {
+            let mut ws = WooSwapWs::new_with_tag(ws_name, false, None, WooSwapWsType::Public("4bd2d1a1-c033-43a1-b977-1aefa754e715".to_string())).clone();
+            ws.set_subscribe(vec![
+                WooSwapSubscribeType::PuFuturesTrades,
+                WooSwapSubscribeType::PuFuturesRecords,
+                // WooSwapSubscribeType::PuFuturesDepth
+            ]);
+
+            // 建立链接
+            ws.set_symbols(symbols_chunk);
+            ws.ws_connect_async(is_shutdown_clone, data_listener, &write_tx_am, write_rx).await.expect("链接失败(内部一个心跳线程应该已经关闭了)");
+        });
+    }
+}
+
+// 读取数据
+pub async fn data_listener(response: ResponseData) {
+    if response.code != 200 {
+        return;
+    }
+
+    match response.channel.as_str() {
+        // 深度数据
+        "futures.order_book" => {
+            // let depth = ExchangeStructHandler::order_book_handle(ExchangeEnum::WooSwap, &response);
+            //
+            // update_depth(&depth).await;
+        }
+        // 订单流数据
+        "futures.trades" => {
+            let mut trades = ExchangeStructHandler::trades_handle(ExchangeEnum::WooSwap, &response);
+            let mul_map = MUL_MAP.lock().await;
+
+            for trade in trades.iter_mut() {
+                // 真实交易量处理,因为woo的量都是张数
+                let mul = mul_map[trade.symbol.as_str()];
+                let mut real_size = trade.size * mul * trade.price;
+                real_size.rescale(2);
+                trade.size = real_size;
+
+                // 更新到本地数据库
+                let trades_map = TRADES_MAP.lock().await;
+                update_trade(trade, trades_map, EXCHANGE_NAME).await;
+            }
+        }
+        // k线数据
+        "futures.candlesticks" => {
+            let mut records = ExchangeStructHandler::records_handle(ExchangeEnum::WooSwap, &response);
+            let mul_map = MUL_MAP.lock().await;
+            for record in records.iter_mut() {
+                // 真实交易量处理,因为woo的量都是张数
+                let mul = mul_map[record.symbol.as_str()];
+                let mid_price = (record.high + record.low) * dec!(0.5);
+                let mut real_volume = record.volume * mul * mid_price;
+                real_volume.rescale(2);
+                record.volume = real_volume;
+
+                // 更新到本地数据库
+                let record_map = RECORD_MAP.lock().await;
+                update_record(record, record_map, EXCHANGE_NAME).await;
+            }
+        }
+        _ => {
+            // info!("48 未知的数据类型: {:?}", response)
+        }
+    }
+}

+ 6 - 1
standard/src/exchange.rs

@@ -15,6 +15,7 @@ use crate::mexc_swap::MexcSwap;
 use crate::bitmart_swap::BitMartSwap;
 use crate::coinsph_swap::CoinsphSwap;
 use crate::phemex_swap::PhemexSwap;
+use crate::woo_swap::WooSwap;
 
 
 /// 交易所交易模式枚举
@@ -41,7 +42,8 @@ pub enum ExchangeEnum {
     MexcSwap,
     BitMartSwap,
     CoinsphSwap,
-    PhemexSwap
+    PhemexSwap,
+    WooSwap
 }
 
 /// Exchange结构体
@@ -134,6 +136,9 @@ impl Exchange {
             ExchangeEnum::PhemexSwap => {
                 Box::new(PhemexSwap::new(symbol, is_colo, params, order_sender, error_sender).await)
             }
+            ExchangeEnum::WooSwap => {
+                Box::new(WooSwap::new(symbol, is_colo, params, order_sender, error_sender).await)
+            }
         }
     }
 }

+ 13 - 1
standard/src/exchange_struct_handler.rs

@@ -4,7 +4,7 @@ use rust_decimal::prelude::FromPrimitive;
 use tracing::{error};
 use exchanges::response_base::ResponseData;
 use crate::exchange::ExchangeEnum;
-use crate::{binance_swap_handle, gate_swap_handle, bybit_swap_handle, bitget_swap_handle, coinex_swap_handle, htx_swap_handle, bingx_swap_handle, mexc_swap_handle, okx_swap_handle, bitmart_swap_handle, kucoin_swap_handle, coinsph_swap_handle, phemex_swap_handle};
+use crate::{binance_swap_handle, gate_swap_handle, bybit_swap_handle, bitget_swap_handle, coinex_swap_handle, htx_swap_handle, bingx_swap_handle, mexc_swap_handle, okx_swap_handle, bitmart_swap_handle, kucoin_swap_handle, coinsph_swap_handle, phemex_swap_handle, woo_swap_handle};
 use crate::{Record, Ticker, Trade, Depth};
 use crate::{Account, OrderBook, Position, SpecialOrder};
 
@@ -63,6 +63,12 @@ impl ExchangeStructHandler {
                 depth_bids = phemex_swap_handle::format_depth_items(&res_data.data["orderbook_p"]["bids"].clone());
                 t = Decimal::from_i64(res_data.data["timestamp"].as_i64().unwrap() / 1000 / 1000).unwrap();
             }
+            ExchangeEnum::WooSwap => {
+                depth_asks = woo_swap_handle::format_depth_items(&res_data.data["asks"].clone());
+                depth_bids = woo_swap_handle::format_depth_items(&res_data.data["bids"].clone());
+                t = Decimal::from_i64(res_data.data["ts"].as_i64().unwrap()).unwrap();
+                symbol = res_data.data["data"]["symbol"].as_str().unwrap().replace("PERP_","")
+            }
             _ => {
                 error!("未找到该交易所!order_book_handle: {:?}", exchange);
                 panic!("未找到该交易所!order_book_handle: {:?}", exchange);
@@ -115,6 +121,9 @@ impl ExchangeStructHandler {
             ExchangeEnum::PhemexSwap => {
                 phemex_swap_handle::format_trade_items(&res_data)
             }
+            ExchangeEnum::WooSwap => {
+                woo_swap_handle::format_trade_items(&res_data)
+            }
             _ => {
                 error!("未找到该交易所!trades_handle: {:?}", exchange);
                 panic!("未找到该交易所!trades_handle: {:?}", exchange);
@@ -166,6 +175,9 @@ impl ExchangeStructHandler {
             ExchangeEnum::PhemexSwap => {
                 phemex_swap_handle::handle_records(&res_data.data)
             }
+            ExchangeEnum::WooSwap => {
+                woo_swap_handle::handle_records(&res_data.data)
+            }
             _ => {
                 error!("未找到该交易所!records_handle: {:?}", exchange);
                 panic!("未找到该交易所!records_handle: {:?}", exchange);

+ 2 - 0
standard/src/lib.rs

@@ -47,6 +47,8 @@ mod coinsph_swap;
 mod coinsph_swap_handle;
 mod phemex_swap;
 mod phemex_swap_handle;
+mod woo_swap;
+mod woo_swap_handle;
 
 /// 持仓模式枚举
 /// - `Both`:单持仓方向

+ 268 - 0
standard/src/woo_swap.rs

@@ -0,0 +1,268 @@
+use std::collections::{BTreeMap};
+use std::io::{Error, ErrorKind};
+use std::str::FromStr;
+use tokio::sync::mpsc::Sender;
+use async_trait::async_trait;
+use rust_decimal::{Decimal};
+use serde_json::{json, Value};
+use tracing::{error, info};
+use crate::{Platform, ExchangeEnum, Account, Position, Ticker, Market, Order};
+use exchanges::woo_swap_rest::WooSwapRest;
+use rust_decimal::prelude::FromPrimitive;
+
+#[allow(dead_code)]
+#[derive(Clone)]
+pub struct WooSwap {
+    exchange: ExchangeEnum,
+    symbol: String,
+    is_colo: bool,
+    params: BTreeMap<String, String>,
+    request: WooSwapRest,
+    market: Market,
+    order_sender: Sender<Order>,
+    error_sender: Sender<Error>,
+}
+
+impl WooSwap {
+    pub async fn new(symbol: String, is_colo: bool, params: BTreeMap<String, String>, order_sender: Sender<Order>, error_sender: Sender<Error>) -> WooSwap {
+        let market = Market::new();
+        let mut woo_swap = WooSwap {
+            exchange: ExchangeEnum::WooSwap,
+            symbol: symbol.to_uppercase(),
+            is_colo,
+            params: params.clone(),
+            request: WooSwapRest::new(is_colo, params.clone()),
+            market,
+            order_sender,
+            error_sender,
+        };
+
+        // 修改持仓模式
+        let symbol_array: Vec<&str> = symbol.split("_").collect();
+        let mode_result = woo_swap.set_dual_mode(symbol_array[1], true).await;
+        match mode_result {
+            Ok(ok) => {
+                info!("Woo:设置持仓模式成功!{:?}", ok);
+            }
+            Err(error) => {
+                error!("Woo:设置持仓模式失败!mode_result={}", error)
+            }
+        }
+        // 获取市场信息
+        woo_swap.market = WooSwap::get_market(&mut woo_swap).await.unwrap_or(woo_swap.market);
+        return woo_swap;
+    }
+}
+
+#[async_trait]
+impl Platform for WooSwap {
+    // 克隆方法
+    fn clone_box(&self) -> Box<dyn Platform + Send + Sync> { Box::new(self.clone()) }
+    // 获取交易所模式
+    fn get_self_exchange(&self) -> ExchangeEnum {
+        ExchangeEnum::WooSwap
+    }
+    // 获取交易对
+    fn get_self_symbol(&self) -> String { self.symbol.clone() }
+    // 获取是否使用高速通道
+    fn get_self_is_colo(&self) -> bool {
+        self.is_colo
+    }
+    // 获取params信息
+    fn get_self_params(&self) -> BTreeMap<String, String> {
+        self.params.clone()
+    }
+    // 获取market信息
+    fn get_self_market(&self) -> Market { self.market.clone() }
+    // 获取请求时间
+    fn get_request_delays(&self) -> Vec<i64> { self.request.get_delays() }
+    // 获取请求平均时间
+    fn get_request_avg_delay(&self) -> Decimal { self.request.get_avg_delay() }
+    // 获取请求最大时间
+    fn get_request_max_delay(&self) -> i64 { self.request.get_max_delay() }
+
+    // 获取服务器时间
+    async fn get_server_time(&mut self) -> Result<String, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+    // 获取账号信息
+    async fn get_account(&mut self) -> Result<Account, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    async fn get_spot_account(&mut self) -> Result<Vec<Account>, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    // 获取持仓信息
+    async fn get_position(&mut self) -> Result<Vec<Position>, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+    // 获取所有持仓
+    async fn get_positions(&mut self) -> Result<Vec<Position>, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+    // 获取市场行情
+    async fn get_ticker(&mut self) -> Result<Ticker, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    async fn get_ticker_symbol(&mut self, _symbol: String) -> Result<Ticker, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    async fn get_market(&mut self) -> Result<Market, Error> {
+        let symbol_format = format!("PERP_{}", self.symbol.clone().to_uppercase());
+        let params = json!({});
+        let res_data = self.request.get_market(params).await;
+        if res_data.code == 200 {
+            let res_data_json = res_data.data["rows"].as_array().unwrap();
+            let market_info = res_data_json.iter().find(|item| item["symbol"].as_str().unwrap() == symbol_format);
+            match market_info {
+                None => {
+                    error!("woo_swap:获取Market信息错误!\nget_market:res_data={:?}", res_data);
+                    Err(Error::new(ErrorKind::Other, res_data.to_string()))
+                }
+                Some(value) => {
+                    let symbol = value["symbol"].as_str().unwrap().to_string().replace("PERP_", "");
+                    let symbol_array: Vec<&str> = symbol.split("_").collect();
+                    let base_asset = symbol_array[0].to_string();
+                    let quote_asset = symbol_array[1].to_string();
+
+                    let tick_size = Decimal::from_str(value["quote_min"].as_str().unwrap()).unwrap();
+                    let amount_size = Decimal::from_str(&value["base_min"].as_str().unwrap()).unwrap();
+                    let price_precision =Decimal::from_u32(tick_size.scale()).unwrap();
+                    let amount_precision = Decimal::from_u32(amount_size.scale()).unwrap();
+                    let min_qty = Decimal::from_str(value["quote_min"].as_str().unwrap()).unwrap();
+                    let max_qty = Decimal::from_str(value["quote_max"].as_str().unwrap()).unwrap();
+                    let min_notional = Decimal::from_str(value["min_notional"].as_str().unwrap()).unwrap();
+                    let max_notional = max_qty;
+                    let ct_val = Decimal::ONE;
+
+                    let result = Market {
+                        symbol,
+                        base_asset,
+                        quote_asset,
+                        tick_size,
+                        amount_size,
+                        price_precision,
+                        amount_precision,
+                        min_qty,
+                        max_qty,
+                        min_notional,
+                        max_notional,
+                        ct_val,
+                    };
+                    Ok(result)
+                }
+            }
+        } else {
+            Err(Error::new(ErrorKind::Other, res_data.to_string()))
+        }
+    }
+
+    async fn get_market_symbol(&mut self, symbol: String) -> Result<Market, Error> {
+        let symbol_format = format!("PERP_{}", symbol.clone().to_uppercase());
+        let params = json!({});
+        let res_data = self.request.get_market(params).await;
+        if res_data.code == 200 {
+            let res_data_json = res_data.data["rows"].as_array().unwrap();
+            let market_info = res_data_json.iter().find(|item| item["symbol"].as_str().unwrap() == symbol_format);
+            match market_info {
+                None => {
+                    error!("woo_swap:获取Market信息错误!\nget_market:res_data={:?}", res_data);
+                    Err(Error::new(ErrorKind::Other, res_data.to_string()))
+                }
+                Some(value) => {
+                    let symbol = value["symbol"].as_str().unwrap().to_string().replace("PERP_", "");
+                    let symbol_array: Vec<&str> = symbol.split("_").collect();
+                    let base_asset = symbol_array[0].to_string();
+                    let quote_asset = symbol_array[1].to_string();
+
+                    let tick_size = Decimal::from_str(value["quote_min"].as_str().unwrap()).unwrap();
+                    let amount_size = Decimal::from_str(&value["base_min"].as_str().unwrap()).unwrap();
+                    let price_precision =Decimal::from_u32(tick_size.scale()).unwrap();
+                    let amount_precision = Decimal::from_u32(amount_size.scale()).unwrap();
+                    let min_qty = Decimal::from_str(value["quote_min"].as_str().unwrap()).unwrap();
+                    let max_qty = Decimal::from_str(value["quote_max"].as_str().unwrap()).unwrap();
+                    let min_notional = Decimal::from_str(value["min_notional"].as_str().unwrap()).unwrap();
+                    let max_notional = max_qty;
+                    let ct_val = Decimal::ONE;
+
+                    let result = Market {
+                        symbol,
+                        base_asset,
+                        quote_asset,
+                        tick_size,
+                        amount_size,
+                        price_precision,
+                        amount_precision,
+                        min_qty,
+                        max_qty,
+                        min_notional,
+                        max_notional,
+                        ct_val,
+                    };
+                    Ok(result)
+                }
+            }
+        } else {
+            Err(Error::new(ErrorKind::Other, res_data.to_string()))
+        }
+    }
+
+    // 获取订单详情
+    async fn get_order_detail(&mut self, _order_id: &str, _custom_id: &str) -> Result<Order, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+    // 获取订单列表
+    async fn get_orders_list(&mut self, _status: &str) -> Result<Vec<Order>, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+    // 下单接口
+    async fn take_order(&mut self, _custom_id: &str, _origin_side: &str, _price: Decimal, _amount: Decimal) -> Result<Order, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    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> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    // 撤销订单
+    async fn cancel_order(&mut self, _order_id: &str, _custom_id: &str) -> Result<Order, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+    // 批量撤销订单
+    async fn cancel_orders(&mut self) -> Result<Vec<Order>, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    async fn cancel_orders_all(&mut self) -> Result<Vec<Order>, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    async fn take_stop_loss_order(&mut self, _stop_price: Decimal, _price: Decimal, _side: &str) -> Result<Value, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    async fn cancel_stop_loss_order(&mut self, _order_id: &str) -> Result<Value, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    // 设置持仓模式
+    async fn set_dual_mode(&mut self, _coin: &str, _is_dual_mode: bool) -> Result<String, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    // 更新双持仓模式下杠杆
+    async fn set_dual_leverage(&mut self, _leverage: &str) -> Result<String, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+
+    async fn set_auto_deposit_status(&mut self, _status: bool) -> Result<String, Error> { Err(Error::new(ErrorKind::NotFound, "woo:该交易所方法未实现".to_string())) }
+
+    // 交易账户互转
+    async fn wallet_transfers(&mut self, _coin: &str, _from: &str, _to: &str, _amount: Decimal) -> Result<String, Error> {
+        Err(Error::new(ErrorKind::NotFound, "woo_swap:该交易所方法未实现".to_string()))
+    }
+}

+ 52 - 0
standard/src/woo_swap_handle.rs

@@ -0,0 +1,52 @@
+use std::str::FromStr;
+use rust_decimal::Decimal;
+use rust_decimal::prelude::FromPrimitive;
+use serde_json::Value;
+use exchanges::response_base::ResponseData;
+use crate::{OrderBook, Trade, Record};
+
+pub fn handle_records(value: &Value) -> Vec<Record> {
+    let mut records = vec![];
+    let record = value["data"].clone();
+    records.push(Record {
+        time: Decimal::from_i64(record["startTime"].as_i64().unwrap()).unwrap(),
+        open: Decimal::from_f64(record["open"].as_f64().unwrap()).unwrap(),
+        high: Decimal::from_f64(record["high"].as_f64().unwrap()).unwrap(),
+        low: Decimal::from_f64(record["low"].as_f64().unwrap()).unwrap(),
+        close: Decimal::from_f64(record["close"].as_f64().unwrap()).unwrap(),
+        volume: Decimal::from_f64(record["volume"].as_f64().unwrap()).unwrap(),
+        symbol: record["symbol"].as_str().unwrap().replace("PERP_", ""),
+    });
+
+    return records;
+}
+
+pub fn format_depth_items(value: &Value) -> Vec<OrderBook> {
+    let mut depth_items: Vec<OrderBook> = vec![];
+    for value in value.as_array().unwrap() {
+        depth_items.push(OrderBook {
+            price: Decimal::from_str(value[0].as_str().unwrap()).unwrap(),
+            amount: Decimal::from_str(value[1].as_str().unwrap()).unwrap(),
+        })
+    }
+    return depth_items;
+}
+
+pub fn format_trade_items(res_data: &ResponseData) -> Vec<Trade> {
+    let result = res_data.data["data"].as_array().unwrap();
+    let mut trades = vec![];
+
+    for item in result {
+        let side = item["side"] == "BUY";
+        let size = Decimal::from_f64(item["a"].as_f64().unwrap()).unwrap();
+        trades.push(Trade {
+            id: res_data.data["ts"].to_string(),
+            time: Decimal::from_i64(res_data.data["ts"].as_i64().unwrap()).unwrap(),
+            size: if side { size } else { -size },
+            price: Decimal::from_f64(item["p"].as_f64().unwrap()).unwrap(),
+            symbol: item["s"].as_str().unwrap().replace("PERP_", ""),
+        })
+    }
+
+    return trades;
+}