From 0017064558c515665645c3da68e073ec4a4c7ddf Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Thu, 28 May 2026 16:56:22 +0800
Subject: [PATCH] feat(gateApi): 添加保证金比例限制和网格队列容量配置选项

---
 src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java |  304 ++++++++++++++++++++++++--------------------------
 1 files changed, 148 insertions(+), 156 deletions(-)

diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
index f953866..b3a4ea7 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -1,6 +1,8 @@
 package com.xcong.excoin.modules.gateApi;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.xcong.excoin.utils.dingtalk.DingTalkUtils;
 import io.gate.gateapi.ApiClient;
 import io.gate.gateapi.ApiException;
 import io.gate.gateapi.GateApiException;
@@ -377,8 +379,8 @@
         if (state != StrategyState.ACTIVE) {
             return;
         }
-//        processLongGrid(closePrice);
-//        processShortGrid(closePrice);
+        processLongGrid(closePrice);
+        processShortGrid(closePrice);
     }
 
     // ---- 仓位推送回调 ----
@@ -426,35 +428,8 @@
                     tryGenerateQueues();
                 }else {
                     longPositionSize = size;
-                    //取消多仓位线以上的开空仓挂单
-                    List<GridElement> allShortOrders = GridElement.findAllShortOrders(longEntryPrice);
-                    if (CollUtil.isNotEmpty(allShortOrders)){
-                        for (GridElement e : allShortOrders) {
-                            executor.cancelConditionalOrder(
-                                    e.getShortOrderId(),
-                                    orderId -> {
-                                        shortEntryTraderIdParam(
-                                                e,
-                                                null,
-                                                false
-                                        );
-                                    }
-                            );
-
-                            if (e.getShortTakeProfitOrderId() != null){
-                                executor.cancelConditionalOrder(
-                                        e.getShortTakeProfitOrderId(),
-                                        orderId -> {
-                                            shortTakeProfitTraderIdParam(
-                                                    e,
-                                                    null,
-                                                    false
-                                            );
-                                        }
-                                );
-                            }
-                        }
-                    }
+                    checkShortEntryOrderToCancel();
+                    checkLongEntryOrderToCancel();
                 }
             } else {
                 longActive = false;
@@ -472,38 +447,85 @@
                     tryGenerateQueues();
                 }else {
                     shortPositionSize = size.abs();
-                    //取消空仓仓位线以下的开多仓挂单
-                    List<GridElement> allLongOrders = GridElement.findAllLongOrders(shortEntryPrice);
-                    if (CollUtil.isNotEmpty(allLongOrders)){
-                        for (GridElement e : allLongOrders) {
-                            executor.cancelConditionalOrder(
-                                    e.getLongOrderId(),
-                                    orderId -> {
-                                        longEntryTraderIdParam(
-                                                e,
-                                                null,
-                                                false
-                                        );
-                                    }
-                            );
-                            if (e.getLongTakeProfitOrderId() != null){
-                                executor.cancelConditionalOrder(
-                                        e.getLongTakeProfitOrderId(),
-                                        orderId -> {
-                                            longTakeProfitTraderIdParam(
-                                                    e,
-                                                    null,
-                                                    false
-                                            );
-                                        }
-                                );
-                            }
-                        }
-                    }
+                    checkShortEntryOrderToCancel();
+                    checkLongEntryOrderToCancel();
                 }
             } else {
                 shortActive = false;
                 shortPositionSize = BigDecimal.ZERO;
+            }
+        }
+    }
+
+    private void checkShortEntryOrderToCancel() {
+        List<GridElement> allLongOrders = GridElement.findAllShortOrders(shortEntryPrice);
+        if (CollUtil.isNotEmpty(allLongOrders)){
+            GridElement keep = allLongOrders.stream()
+                    .min((a, b) -> a.getGridPrice().compareTo(b.getGridPrice()))
+                    .orElse(null);
+            for (GridElement e : allLongOrders) {
+                if (e == keep) {
+                    continue;
+                }
+                executor.cancelConditionalOrder(
+                        e.getShortOrderId(),
+                        orderId -> {
+                            shortEntryTraderIdParam(
+                                    e,
+                                    null,
+                                    false
+                            );
+                        }
+                );
+                if (e.getShortTakeProfitOrderId() != null){
+                    executor.cancelConditionalOrder(
+                            e.getShortTakeProfitOrderId(),
+                            orderId -> {
+                                shortTakeProfitTraderIdParam(
+                                        e,
+                                        null,
+                                        false
+                                );
+                            }
+                    );
+                }
+            }
+        }
+    }
+
+    private void checkLongEntryOrderToCancel() {
+        List<GridElement> allShortOrders = GridElement.findAllLongOrders(longEntryPrice);
+        if (CollUtil.isNotEmpty(allShortOrders)){
+            GridElement keep = allShortOrders.stream()
+                    .max((a, b) -> a.getGridPrice().compareTo(b.getGridPrice()))
+                    .orElse(null);
+            for (GridElement e : allShortOrders) {
+                if (e == keep) {
+                    continue;
+                }
+                executor.cancelConditionalOrder(
+                        e.getLongOrderId(),
+                        orderId -> {
+                            longEntryTraderIdParam(
+                                    e,
+                                    null,
+                                    false
+                            );
+                        }
+                );
+
+                if (e.getLongTakeProfitOrderId() != null){
+                    executor.cancelConditionalOrder(
+                            e.getLongTakeProfitOrderId(),
+                            orderId -> {
+                                longTakeProfitTraderIdParam(
+                                        e,
+                                        null,
+                                        false
+                                );
+                            }
+                    );
+                }
             }
         }
     }
@@ -526,6 +548,7 @@
             return;
         }
         cumulativePnl = cumulativePnl.add(pnl);
+        updateUnrealizedPnl();
         BigDecimal totalPnl = cumulativePnl.add(unrealizedPnl);
         log.info("[Gate] 已实现:{}, 未实现:{}, 合计:{}",
                 cumulativePnl, unrealizedPnl, totalPnl);
@@ -535,9 +558,13 @@
                     totalPnl, cumulativePnl, unrealizedPnl);
             state = StrategyState.STOPPED;
         } else if (totalPnl.compareTo(config.getMaxLoss().negate()) <= 0) {
-            log.info("[Gate] 已达亏损上限(合计{})→已停止, 已实现:{}, 未实现:{}",
+            String logMessage = StrUtil.format("[Gate] 已达亏损风险值(合计{}), 已实现:{}, 未实现:{}",
                     totalPnl, cumulativePnl, unrealizedPnl);
-            state = StrategyState.STOPPED;
+            log.info(logMessage);
+
+
+            DingTalkUtils.getDefault().sendActionCard("风险提醒", logMessage, config.getApiKey(), "");
+//            state = StrategyState.STOPPED;
         }
     }
 
@@ -779,43 +806,20 @@
         if (!isMarginSafe()) {
             log.warn("[Gate] 保证金超限,跳过挂条件单");
         } else {
-
-            /**
-             * 下一个开仓位置
-             *      获取队列第一个元素的价格对应的网格
-             *      判断网格是否能开空仓,如果不能则跳过
-             *      前进方向挂空仓条件单
-             *      后置方向挂多空条件单
-             */
-            //下一个开仓位置
-            BigDecimal gridPrice = gridElement.getGridPrice();
-
-            // 判断网格是否能开空仓,如果不能则跳过
-            if (gridElement != null) {
-                TraderParam downShortTraderParam = gridElement.getShortTraderParam();
+            // 判断网格是否能开多仓,如果不能则跳过
+            GridElement upGridElement = GridElement.findById(gridElement.getUpId());
+            if (upGridElement != null){
+                BigDecimal upGridPrice = upGridElement.getGridPrice();
+                TraderParam upLongTraderParam = upGridElement.getLongTraderParam();
                 if (
-                        !gridElement.isHasShortOrder() &&
-                                gridPrice.compareTo(longEntryPrice) <= 0 &&
-                                gridPrice.compareTo(shortEntryPrice) >= 0
+                        !upGridElement.isHasLongOrder() &&
+                                upGridPrice.compareTo(longEntryPrice) <= 0
                 ){
-                    placeEntryOrderWithPreFlag(gridElement, false,
-                            downShortTraderParam.getEntryPrice(),
+                    placeEntryOrderWithPreFlag(upGridElement, true,
+                            upLongTraderParam.getEntryPrice(),
                             FuturesPriceTrigger.RuleEnum.NUMBER_1,
-                            negate(downShortTraderParam.getQuantity()));
-
+                            upLongTraderParam.getQuantity());
                 }
-
-                TraderParam downLongTraderParam = gridElement.getLongTraderParam();
-                if (
-                        !gridElement.isHasLongOrder() &&
-                                gridPrice.compareTo(longEntryPrice) <= 0
-                ){
-                    placeEntryOrderWithPreFlag(gridElement, true,
-                            downLongTraderParam.getEntryPrice(),
-                            FuturesPriceTrigger.RuleEnum.NUMBER_1,
-                            downLongTraderParam.getQuantity());
-                }
-
             }
         }
     }
@@ -824,31 +828,18 @@
         if (!isMarginSafe()) {
             log.warn("[Gate] 保证金超限,跳过挂条件单");
         } else {
-            BigDecimal newLongFirst = gridElement.getGridPrice() ;
+            // 判断网格是否能开空仓,如果不能则跳过
+            GridElement downGridElement = GridElement.findById(gridElement.getDownId());
+            if (downGridElement != null){
 
-            // 判断网格是否能开多空仓,如果不能则跳过
-            if (gridElement != null) {
+                BigDecimal downGridPrice = downGridElement.getGridPrice();
 
-                TraderParam downLongTraderParam = gridElement.getLongTraderParam();
+                TraderParam shortTraderParam = downGridElement.getShortTraderParam();
                 if (
-                        !gridElement.isHasLongOrder() &&
-                                newLongFirst.compareTo(shortEntryPrice) >= 0 &&
-                                newLongFirst.compareTo(longEntryPrice) <= 0
+                        !downGridElement.isHasShortOrder() &&
+                                downGridPrice.compareTo(shortEntryPrice) >= 0
                 ){
-                    placeEntryOrderWithPreFlag(gridElement, true,
-                            downLongTraderParam.getEntryPrice(),
-                            FuturesPriceTrigger.RuleEnum.NUMBER_2,
-                            config.getQuantity());
-
-                }
-
-                TraderParam shortTraderParam = gridElement.getShortTraderParam();
-                if (
-                        !gridElement.isHasShortOrder() &&
-                                newLongFirst.compareTo(shortEntryPrice) >= 0
-                ){
-
-                    placeEntryOrderWithPreFlag(gridElement, false,
+                    placeEntryOrderWithPreFlag(downGridElement, false,
                             shortTraderParam.getEntryPrice(),
                             FuturesPriceTrigger.RuleEnum.NUMBER_2,
                             negate(config.getQuantity()));
@@ -1096,8 +1087,9 @@
         int longSize = longPriceQueue.size();
         //根据精度转换成小数
         int prec = config.getPriceScale();
-        BigDecimal minTick = BigDecimal.ONE.scaleByPowerOfTen(-prec);
-        BigDecimal step = config.getStep().subtract(minTick);
+//        BigDecimal minTick = BigDecimal.ONE.scaleByPowerOfTen(-prec);
+//        BigDecimal step = config.getStep().subtract(minTick);
+        BigDecimal step = config.getStep();
         String qty = config.getQuantity();
 
         // 空仓队列:id 从 -1 自减, shortPriceQueue[i] → id=-(i+1)
@@ -1244,32 +1236,32 @@
             // 判断网格是否能开空仓,如果不能则跳过
             if (UpGridElement != null) {
 
-                if (!UpGridElement.isHasShortOrder() && shortEntryPrice.compareTo(newLongFirst) > 0) {
-
-                    TraderParam upShortTraderParam = UpGridElement.getShortTraderParam();
-                    placeEntryOrderWithPreFlag(UpGridElement, false,
-                            upShortTraderParam.getEntryPrice(),
-                            FuturesPriceTrigger.RuleEnum.NUMBER_2,
-                            negate(upShortTraderParam.getQuantity()));
-                }
+//                if (!UpGridElement.isHasShortOrder() && shortEntryPrice.compareTo(newLongFirst) > 0) {
+//
+//                    TraderParam upShortTraderParam = UpGridElement.getShortTraderParam();
+//                    placeEntryOrderWithPreFlag(UpGridElement, false,
+//                            upShortTraderParam.getEntryPrice(),
+//                            FuturesPriceTrigger.RuleEnum.NUMBER_2,
+//                            negate(upShortTraderParam.getQuantity()));
+//                }
                 int i = UpGridElement.getId() + 2;
                 GridElement downGridElement = GridElement.findById(i);
                 if (downGridElement != null){
 
                     BigDecimal downGridPrice = downGridElement.getGridPrice();
 
-                    TraderParam downShortTraderParam = downGridElement.getShortTraderParam();
-                    if (
-                            !downGridElement.isHasShortOrder() &&
-                                    downGridPrice.compareTo(longEntryPrice) <= 0 &&
-                                    downGridPrice.compareTo(shortEntryPrice) >= 0
-                    ){
-                        placeEntryOrderWithPreFlag(downGridElement, false,
-                                downShortTraderParam.getEntryPrice(),
-                                FuturesPriceTrigger.RuleEnum.NUMBER_1,
-                                negate(downShortTraderParam.getQuantity()));
-
-                    }
+//                    TraderParam downShortTraderParam = downGridElement.getShortTraderParam();
+//                    if (
+//                            !downGridElement.isHasShortOrder() &&
+//                                    downGridPrice.compareTo(longEntryPrice) <= 0 &&
+//                                    downGridPrice.compareTo(shortEntryPrice) >= 0
+//                    ){
+//                        placeEntryOrderWithPreFlag(downGridElement, false,
+//                                downShortTraderParam.getEntryPrice(),
+//                                FuturesPriceTrigger.RuleEnum.NUMBER_1,
+//                                negate(downShortTraderParam.getQuantity()));
+//
+//                    }
 
                     TraderParam downLongTraderParam = downGridElement.getLongTraderParam();
                     if (
@@ -1350,13 +1342,13 @@
             // 判断网格是否能开多仓,如果不能则跳过
             if (UpGridElement != null) {
 
-                if (!UpGridElement.isHasLongOrder() && longEntryPrice.compareTo(newLongFirst) < 0) {
-                    TraderParam upLongTraderParam = UpGridElement.getLongTraderParam();
-                    placeEntryOrderWithPreFlag(UpGridElement, true,
-                            upLongTraderParam.getEntryPrice(),
-                            FuturesPriceTrigger.RuleEnum.NUMBER_1,
-                            config.getQuantity());
-                }
+//                if (!UpGridElement.isHasLongOrder() && longEntryPrice.compareTo(newLongFirst) < 0) {
+//                    TraderParam upLongTraderParam = UpGridElement.getLongTraderParam();
+//                    placeEntryOrderWithPreFlag(UpGridElement, true,
+//                            upLongTraderParam.getEntryPrice(),
+//                            FuturesPriceTrigger.RuleEnum.NUMBER_1,
+//                            config.getQuantity());
+//                }
 
                 int i = UpGridElement.getId() - 2;
                 GridElement downGridElement = GridElement.findById(i);
@@ -1364,18 +1356,18 @@
 
                     BigDecimal downGridPrice = downGridElement.getGridPrice();
 
-                    TraderParam downLongTraderParam = downGridElement.getLongTraderParam();
-                    if (
-                            !downGridElement.isHasLongOrder() &&
-                                    downGridPrice.compareTo(shortEntryPrice) >= 0 &&
-                                    downGridPrice.compareTo(longEntryPrice) <= 0
-                    ){
-                        placeEntryOrderWithPreFlag(downGridElement, true,
-                                downLongTraderParam.getEntryPrice(),
-                                FuturesPriceTrigger.RuleEnum.NUMBER_2,
-                                config.getQuantity());
-
-                    }
+//                    TraderParam downLongTraderParam = downGridElement.getLongTraderParam();
+//                    if (
+//                            !downGridElement.isHasLongOrder() &&
+//                                    downGridPrice.compareTo(shortEntryPrice) >= 0 &&
+//                                    downGridPrice.compareTo(longEntryPrice) <= 0
+//                    ){
+//                        placeEntryOrderWithPreFlag(downGridElement, true,
+//                                downLongTraderParam.getEntryPrice(),
+//                                FuturesPriceTrigger.RuleEnum.NUMBER_2,
+//                                config.getQuantity());
+//
+//                    }
 
                     TraderParam shortTraderParam = downGridElement.getShortTraderParam();
                     if (

--
Gitblit v1.9.1