Ver Fonte

清退策略完成

skyfffire há 2 semanas atrás
pai
commit
f75ecdd17c
3 ficheiros alterados com 216 adições e 10 exclusões
  1. 13 0
      src/exchange/extended_rest_client.rs
  2. 17 6
      src/main.rs
  3. 186 4
      src/strategy.rs

+ 13 - 0
src/exchange/extended_rest_client.rs

@@ -1,3 +1,4 @@
+use std::ops::Mul;
 use std::str::FromStr;
 use chrono::{Duration, Timelike, Utc};
 use anyhow::{anyhow, bail, Context, Result};
@@ -161,6 +162,17 @@ impl ExtendedRestClient {
         ).await
     }
 
+    pub async fn get_positions(&mut self) -> Response {
+        let params = json!({});
+
+        self.request("GET",
+                     "/api/v1",
+                     "/user/positions",
+                     true,
+                     params,
+        ).await
+    }
+
     pub async fn get_order(&mut self, id: &str) -> Response {
         let params = json!({});
 
@@ -258,6 +270,7 @@ impl ExtendedRestClient {
         let price = Decimal::from_str(price)?;
         let synthetic_amount = Decimal::from_str(qty)?;
         let collateral_amount = synthetic_amount.checked_mul(price).ok_or_else(|| anyhow!("Collateral amount multiplication overflowed"))?;
+        // collateral_amount = collateral_amount.mul(dec!(0.999));//千分之一的滑点试一下
         let total_fee_rate = taker_fee_rate.checked_add(builder_fee_rate).ok_or_else(|| anyhow!("Total fee addition overflowed"))?;
         let fee_amount = total_fee_rate.checked_mul(collateral_amount).ok_or_else(|| anyhow!("Fee amount multiplication overflowed"))?;
 

+ 17 - 6
src/main.rs

@@ -88,12 +88,12 @@ async fn main() {
 
     // ---- 清理和关闭 ----
     // 等待订阅任务结束(如果它设计为可结束的话)
-    info!("等待订阅任务完成...");
+    info!("等待清退任务完成...");
     // 可以给 subscription_handle 设置一个超时等待
-    match tokio::time::timeout(tokio::time::Duration::from_secs(10), subscription_handle).await {
-        Ok(Ok(_)) => info!("订阅任务正常结束。"),
-        Ok(Err(e)) => error!("订阅任务返回错误: {:?}", e),
-        Err(_) => warn!("等待订阅任务超时。"),
+    match tokio::time::timeout(tokio::time::Duration::from_secs(30), subscription_handle).await {
+        Ok(Ok(_)) => info!("清退任务正常结束。"),
+        Ok(Err(e)) => error!("清退任务返回错误: {:?}", e),
+        Err(_) => warn!("等待清退任务超时。"),
     }
 
     info!("应用程序已关闭。");
@@ -147,7 +147,7 @@ pub async fn run_extended_subscriptions(running: Arc<AtomicBool>, config: &Confi
 
             let dm_clone = Arc::clone(&dm);
             let sm_clone = Arc::clone(&sm);
-            async move {                
+            async move {
                 let mut dm_guard = dm_clone.lock().await;
 
                 // 记录消息延迟
@@ -178,5 +178,16 @@ pub async fn run_extended_subscriptions(running: Arc<AtomicBool>, config: &Confi
         });
     }
 
+    // 等待系统退出,并执行后续程序
+    while running.load(Ordering::Relaxed) {
+        // 可以添加一些周期性检查或任务,但主要是等待
+        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
+    }
+
+    info!("执行后续清退策略……");
+    let mut s = strategy_am.lock().await;
+    let dm = data_manager_am.lock().await;
+    s.shutdown(market, &dm).await?;
+
     Ok(())
 }

+ 186 - 4
src/strategy.rs

@@ -160,7 +160,7 @@ impl Strategy {
         match self.check_order_filled_status(&order_id, dm).await {
             Ok(status) => {
                 let has_filled = matches!(status.as_str(), "FILLED");
-                
+
                 if has_filled {
                     info!("限价单已成交,准备执行市价单");
                     self.state = StrategyState::ExecutingMarketOrder { };
@@ -277,7 +277,7 @@ impl Strategy {
                    }
                    "CANCELLED" => {
                        info!("市价单已被取消,重新下单");
-                       
+
                        self.state = StrategyState::ExecutingMarketOrder { };
 
                        Ok(())
@@ -389,12 +389,12 @@ impl Strategy {
             .and_then(|v| Decimal::from_str(v)
                 .map_err(|e| anyhow!("查单-解析 'data.filledQty' 为 Decimal 失败: {}, 值: {}", e, v))
             )?;
-    
+
         self.filled_quantity = filled_qty.normalize();
         if self.filled_quantity > Decimal::ZERO {
             info!("订单 {} ,成交数量: {}", order_id, self.filled_quantity);
         }
-        
+
         Ok(status.to_string())
     }
 
@@ -457,4 +457,186 @@ impl Strategy {
             }
         }
     }
+
+    /// 策略清退:取消所有订单并平掉所有仓位
+    pub async fn shutdown(&mut self, market: &str, dm: &DataManager) -> Result<()> {
+        info!("========================================");
+        info!("开始执行策略清退流程");
+        info!("========================================");
+
+        // 第一步:取消所有订单
+        self.cancel_all_orders(market).await?;
+
+        // 等待一段时间确保订单取消完成
+        sleep(Duration::from_millis(2000)).await;
+
+        // 第二步:平掉所有仓位
+        self.close_all_positions(dm).await?;
+
+        info!("========================================");
+        info!("策略清退流程完成");
+        info!("========================================");
+
+        Ok(())
+    }
+
+    /// 取消指定市场的所有订单
+    async fn cancel_all_orders(&self, market: &str) -> Result<()> {
+        info!("步骤 1/2: 取消所有订单 (市场: {})", market);
+
+        let response = {
+            let mut client = self.rest_client.lock().await;
+            client.mass_cancel_by_market(market).await
+        };
+
+        let value = &response.data;
+
+        // 预先捕获整个 Value 的字符串表示,用于错误报告
+        let value_str = serde_json::to_string(&value)
+            .unwrap_or_else(|_| "无法序列化 JSON Value".to_string());
+
+        // 获取status
+        let status = value.get("status")
+            .and_then(|v| v.as_str())
+            .ok_or_else(|| anyhow!("批量撤单-获取 'status' 失败,原始JSON:{}", value_str))?;
+
+        // 判定status
+        if status != "OK" {
+            bail!("批量撤单失败,状态不为OK,原始JSON:{}", value_str)
+        }
+
+        info!("批量撤单成功,详情: {:?}", value_str);
+
+        Ok(())
+    }
+
+    /// 平掉所有仓位
+    async fn close_all_positions(&self, dm: &DataManager) -> Result<()> {
+        info!("步骤 2/2: 平掉所有仓位");
+
+        let response = {
+            let mut client = self.rest_client.lock().await;
+            client.get_positions().await
+        };
+
+        let value = &response.data;
+
+        // 预先捕获整个 Value 的字符串表示,用于错误报告
+        let value_str = serde_json::to_string(&value)
+            .unwrap_or_else(|_| "无法序列化 JSON Value".to_string());
+
+        // 获取status
+        let status = value.get("status")
+            .and_then(|v| v.as_str())
+            .ok_or_else(|| anyhow!("获取仓位-获取 'status' 失败,原始JSON:{}", value_str))?;
+
+        // 判定status
+        if status != "OK" {
+            bail!("获取仓位失败,状态不为OK,原始JSON:{}", value_str)
+        }
+
+        // 获取 data 字段(仓位数组)
+        let positions = value.get("data")
+            .and_then(|v| v.as_array())
+            .ok_or_else(|| anyhow!("获取仓位-获取 'data' 字段失败,原始 JSON: {}", value_str))?;
+
+        if positions.is_empty() {
+            info!("当前无持仓,无需平仓");
+            return Ok(());
+        }
+
+        info!("发现 {} 个持仓,准备平仓", positions.len());
+
+        // 遍历所有仓位并平仓
+        for (index, position) in positions.iter().enumerate() {
+            if let Err(e) = self.close_single_position(position, index + 1, dm).await {
+                warn!("平仓失败 (仓位 {}/{}): {}", index + 1, positions.len(), e);
+                // 继续处理其他仓位,不中断
+            }
+        }
+
+        Ok(())
+    }
+
+    /// 平掉单个仓位
+    async fn close_single_position(&self, position: &Value, position_number: usize, dm: &DataManager) -> Result<()> {
+        let position_str = serde_json::to_string(position)
+            .unwrap_or_else(|_| "无法序列化 JSON Value".to_string());
+
+        // 解析仓位信息
+        let market = position.get("market")
+            .and_then(|v| v.as_str())
+            .ok_or_else(|| anyhow!("解析仓位-获取 'market' 失败,原始JSON:{}", position_str))?;
+
+        let side = position.get("side")
+            .and_then(|v| v.as_str())
+            .ok_or_else(|| anyhow!("解析仓位-获取 'side' 失败,原始JSON:{}", position_str))?;
+
+        let size_str = position.get("size")
+            .and_then(|v| v.as_str())
+            .ok_or_else(|| anyhow!("解析仓位-获取 'size' 失败,原始JSON:{}", position_str))?;
+
+        let size = Decimal::from_str(size_str)
+            .map_err(|e| anyhow!("解析仓位-解析 'size' 为 Decimal 失败: {}, 值: {}", e, size_str))?;
+
+        // 获取当前标记价格(用于日志显示)
+        let mark_price = position.get("markPrice")
+            .and_then(|v| v.as_str())
+            .unwrap_or("N/A");
+
+        info!("----------------------------------------");
+        info!("平仓 ({}/总数未知)", position_number);
+        info!("  市场: {}", market);
+        info!("  方向: {}", side);
+        info!("  数量: {}", size);
+        info!("  标记价格: {}", mark_price);
+
+        // 确定平仓方向(与持仓方向相反)
+        let close_side = match side {
+            "LONG" => "SELL",   // 多头平仓用卖单
+            "SHORT" => "BUY",   // 空头平仓用买单
+            _ => bail!("未知的持仓方向: {}", side),
+        };
+
+        info!("  平仓方向: {}", close_side);
+
+
+        let price = match close_side {
+            "BUY" => dm.best_ask,       // 用卖一价平仓
+            "SELL" => dm.best_bid,      // 用买一价平仓
+            _ =>  bail!("未知的平仓方向: {}", side),
+        };
+
+        // 下市价单平仓
+        let create_result = {
+            let mut client = self.rest_client.lock().await;
+            client.post_order(
+                "MARKET",
+                close_side,
+                size.normalize().to_string().as_str(),
+                price.to_string().as_str(),  // 市价单价格传 0 或当前价格
+                false,  // postOnly = false
+                true,   // reduceOnly = true (只减仓)
+            ).await
+        };
+
+        // 解析平仓结果
+        match self.match_create_order_result(&create_result) {
+            Ok(order_id) => {
+                info!("✅ 平仓订单提交成功");
+                info!("  订单ID: {}", order_id);
+                info!("  市场: {}", market);
+                info!("  方向: {}", close_side);
+                info!("  数量: {}", size);
+                Ok(())
+            }
+            Err(e) => {
+                warn!("❌ 平仓订单提交失败");
+                warn!("  错误: {}", e);
+                warn!("  市场: {}", market);
+                warn!("  原始仓位数据: {}", position_str);
+                Err(e)
+            }
+        }
+    }
 }