From bae908e42a8f96042684eaff203fba0aedcdee6c Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Mon, 01 Jun 2026 10:38:43 +0800
Subject: [PATCH] feat(gateApi): 添加基底开仓和预期收益配置及止损机制

---
 src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java       |  343 +++++++++++++++++++++++++++++-------------
 src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java                |   71 ++++++++
 src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java |   24 ++
 src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java                 |   18 ++
 4 files changed, 342 insertions(+), 114 deletions(-)

diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
index a583c2f..9b207cc 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
@@ -81,6 +81,10 @@
     private final BigDecimal maxLoss;
     /** 下单数量(合约张数) */
     private final String quantity;
+    /** 基底开仓张数(初始化时多空各开的张数,如 "10") */
+    private final String baseQuantity;
+    /** 预期收益(USDT),unrealisedPnl + available > 初始本金 + 此值时重置 */
+    private final BigDecimal expectedProfit;
     /** 是否为生产环境 */
     private final boolean isProduction;
     /** 补仓最大重试次数 */
@@ -115,6 +119,8 @@
         this.overallTp = builder.overallTp;
         this.maxLoss = builder.maxLoss;
         this.quantity = builder.quantity;
+        this.baseQuantity = builder.baseQuantity;
+        this.expectedProfit = builder.expectedProfit;
         this.isProduction = builder.isProduction;
         this.reopenMaxRetries = builder.reopenMaxRetries;
         this.gridQueueSize = builder.gridQueueSize;
@@ -183,6 +189,10 @@
     public BigDecimal getMaxLoss() { return maxLoss; }
     /** @return 每次下单的张数(如 "1" 表示 1 张合约) */
     public String getQuantity() { return quantity; }
+    /** @return 基底开仓张数(初始化时多空各开的张数,如 "10") */
+    public String getBaseQuantity() { return baseQuantity; }
+    /** @return 预期收益(USDT),unrealisedPnl + available > 初始本金 + 此值时重置 */
+    public BigDecimal getExpectedProfit() { return expectedProfit; }
     /** @return 网格价格队列的容量上限(超出时截断尾部) */
     public int getGridQueueSize() { return gridQueueSize; }
 
@@ -271,6 +281,10 @@
         private BigDecimal maxLoss = new BigDecimal("7.5");
         /** 每次下单张数,默认 "1" */
         private String quantity = "1";
+        /** 基底开仓张数,默认 "10"(初始化时多空各开10张) */
+        private String baseQuantity = "10";
+        /** 预期收益(USDT),默认 0.5 */
+        private BigDecimal expectedProfit = new BigDecimal("0.5");
         /** 是否为生产环境,默认 false(测试网) */
         private boolean isProduction = false;
         /** 补仓最大重试次数,默认 3 */
@@ -306,6 +320,10 @@
         public Builder maxLoss(BigDecimal maxLoss) { this.maxLoss = maxLoss; return this; }
         /** 设置每次下单张数 */
         public Builder quantity(String quantity) { this.quantity = quantity; return this; }
+        /** 设置基底开仓张数 */
+        public Builder baseQuantity(String baseQuantity) { this.baseQuantity = baseQuantity; return this; }
+        /** 设置预期收益(USDT) */
+        public Builder expectedProfit(BigDecimal expectedProfit) { this.expectedProfit = expectedProfit; return this; }
         /** 设置环境(true=实盘生产网 / false=模拟盘测试网) */
         public Builder isProduction(boolean isProduction) { this.isProduction = isProduction; return this; }
         /** 设置补仓最大重试次数 */
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 b3a4ea7..a51fb12 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -359,14 +359,14 @@
         //初始化0位置的开仓,并且用空的开仓价格,作为价格基准来划分网格
         if (state == StrategyState.WAITING_KLINE) {
             state = StrategyState.OPENING;
-            log.info("[Gate] 首根K线到达,开基底仓位...");
-            executor.openLong(config.getQuantity(), (orderId) -> {
+            log.info("[Gate] 首根K线到达,开基底仓位 多空各{}张...", config.getBaseQuantity());
+            executor.openLong(config.getBaseQuantity(), (orderId) -> {
                 TraderParam baseLongTp = TraderParam.builder()
                         .entryOrderId(orderId)
                         .build();
                 config.setBaseLongTraderParam(baseLongTp);
             }, null);
-            executor.openShort(negate(config.getQuantity()), (orderId) -> {
+            executor.openShort(negate(config.getBaseQuantity()), (orderId) -> {
                 TraderParam baseShortTp = TraderParam.builder()
                         .entryOrderId(orderId)
                         .build();
@@ -379,8 +379,7 @@
         if (state != StrategyState.ACTIVE) {
             return;
         }
-        processLongGrid(closePrice);
-        processShortGrid(closePrice);
+        checkProfitAndReset();
     }
 
     // ---- 仓位推送回调 ----
@@ -432,6 +431,10 @@
                     checkLongEntryOrderToCancel();
                 }
             } else {
+                if (longActive && state == StrategyState.ACTIVE) {
+                    log.info("[Gate] 多仓持仓归零,重置策略");
+                    handlePositionZeroAndReset("多仓");
+                }
                 longActive = false;
                 longPositionSize = BigDecimal.ZERO;
             }
@@ -451,6 +454,10 @@
                     checkLongEntryOrderToCancel();
                 }
             } else {
+                if (shortActive && state == StrategyState.ACTIVE) {
+                    log.info("[Gate] 空仓持仓归零,重置策略");
+                    handlePositionZeroAndReset("空仓");
+                }
                 shortActive = false;
                 shortPositionSize = BigDecimal.ZERO;
             }
@@ -716,9 +723,17 @@
             return;
         }
 
-        /**
-         * 匹配止盈单止盈
-         */
+        GridElement longStopLossElem = GridElement.findByLongStopLossOrderId(orderId);
+        if (longStopLossElem != null) {
+            handleLongStopLossTriggered(longStopLossElem);
+            return;
+        }
+        GridElement shortStopLossElem = GridElement.findByShortStopLossOrderId(orderId);
+        if (shortStopLossElem != null) {
+            handleShortStopLossTriggered(shortStopLossElem);
+            return;
+        }
+
         GridElement byShortTakeProfitOrderId = GridElement.findByShortTakeProfitOrderId(orderId);
         if (byShortTakeProfitOrderId != null){
             shortTakeProfitTraderIdParam(
@@ -748,56 +763,22 @@
             TPonUserTradeLongEntry(byLongTakeProfitOrderId);
         }
 
-        /**
-         * 匹配挂单
-         */
-
         GridElement shortGridElement = GridElement.findByShortOrderId(orderId);
         if (shortGridElement != null) {
             if (shortGridElement.isHasShortOrder() && !tradeId.equals("0")){
-
-                onUserTradeShortEntry(shortGridElement);
-                if (shortGridElement.getShortTakeProfitOrderId() == null){
-                    BigDecimal shortTp = shortGridElement.getShortTraderParam().getTakeProfitPrice();
-                    if (shortTp != null) {
-                        executor.placeTakeProfit(shortTp,
-                                FuturesPriceTrigger.RuleEnum.NUMBER_2,
-                                ORDER_TYPE_CLOSE_SHORT,
-                                config.getQuantity(),
-                                (profitId) -> {
-                                    shortTakeProfitTraderIdParam(
-                                            shortGridElement,
-                                            profitId,
-                                            true
-                                    );
-                                });
-                        log.info("[Gate] 空单成交匹配止盈, orderId:{}, 止盈价:{}, size:{}", orderId, shortTp, config.getQuantity());
-                    }
-                }
+                int filledQty = Integer.parseInt(shortGridElement.getShortTraderParam().getQuantity());
+                shortEntryTraderIdParam(shortGridElement, null, false);
+                extendShortStopLoss(filledQty);
+                log.info("[Gate] 空单成交 gridId:{}, qty:{}, 追挂止损", shortGridElement.getId(), filledQty);
             }
         }
         GridElement longGridElement = GridElement.findByLongOrderId(orderId);
         if (longGridElement != null) {
             if (longGridElement.isHasLongOrder() && !tradeId.equals("0")){
-
-                onUserTradeLongEntry(longGridElement);
-                if (longGridElement.getLongTakeProfitOrderId() == null){
-                    BigDecimal longTp = longGridElement.getLongTraderParam().getTakeProfitPrice();
-                    if (longTp != null) {
-                        executor.placeTakeProfit(longTp,
-                                FuturesPriceTrigger.RuleEnum.NUMBER_1,
-                                ORDER_TYPE_CLOSE_LONG,
-                                negate(config.getQuantity()),
-                                (profitId) -> {
-                                    longTakeProfitTraderIdParam(
-                                            longGridElement,
-                                            profitId,
-                                            true
-                                    );
-                                });
-                        log.info("[Gate] 多单成交匹配止盈, orderId:{}, 止盈价:{}, size:{}", orderId, longTp, negate(config.getQuantity()));
-                    }
-                }
+                int filledQty = Integer.parseInt(longGridElement.getLongTraderParam().getQuantity());
+                longEntryTraderIdParam(longGridElement, null, false);
+                extendLongStopLoss(filledQty);
+                log.info("[Gate] 多单成交 gridId:{}, qty:{}, 追挂止损", longGridElement.getId(), filledQty);
             }
         }
     }
@@ -909,75 +890,59 @@
      */
     private void tryGenerateQueues() {
         if (baseLongOpened && baseShortOpened) {
-            //初始化空仓队列
             generateShortQueue();
-            //初始化多仓队列
             generateLongQueue();
-            //初始化网格数据
             updateGridElements();
 
-            /**
-             * 挂初始位置多空仓条件单
-             * 0位置的多单止盈
-             * 0位置的空单止盈
-             */
             GridElement baseGridElement = GridElement.findById(0);
             TraderParam baseLongTraderParam = config.getBaseLongTraderParam();
             baseGridElement.setLongOrderId(baseLongTraderParam.getEntryOrderId());
             baseGridElement.setHasLongOrder(true);
-            //0位置的网格的多单止盈
-            BigDecimal upTakeProfitPrice = baseGridElement.getLongTraderParam().getTakeProfitPrice();
-            executor.placeTakeProfit(
-                    upTakeProfitPrice,
-                    FuturesPriceTrigger.RuleEnum.NUMBER_1,
-                    ORDER_TYPE_CLOSE_LONG,
-                    negate(config.getQuantity()),
-                    profitId -> {
-                        longTakeProfitTraderIdParam(
-                                baseGridElement,
-                                profitId,
-                                true
-                        );
-                    }
-            );
-            //0位置的网格的空单止盈
             TraderParam baseShortTraderParam = config.getBaseShortTraderParam();
             baseGridElement.setShortOrderId(baseShortTraderParam.getEntryOrderId());
             baseGridElement.setHasShortOrder(true);
-            BigDecimal downTakeProfitPrice = baseGridElement.getShortTraderParam().getTakeProfitPrice();
-            executor.placeTakeProfit(
-                    downTakeProfitPrice,
-                    FuturesPriceTrigger.RuleEnum.NUMBER_2,
-                    ORDER_TYPE_CLOSE_SHORT,
-                    config.getQuantity(),
-                    profitId -> {
-                        shortTakeProfitTraderIdParam(
-                                baseGridElement,
-                                profitId,
-                                true
-                        );
-                    }
-            );
 
-            /**
-             * 挂初始位置的up位置的多单
-             * 挂初始位置的down位置的空单
-             */
-            Integer upId = baseGridElement.getUpId();
-            GridElement upGridElementOne = GridElement.findById(upId);
-            BigDecimal longTp = upGridElementOne.getGridPrice();
-            placeEntryOrderWithPreFlag(upGridElementOne, true,
-                    longTp,
-                    FuturesPriceTrigger.RuleEnum.NUMBER_1,
-                    config.getQuantity());
-            Integer downId = baseGridElement.getDownId();
-            GridElement downGridElementOne = GridElement.findById(downId);
-            BigDecimal shortTp = downGridElementOne.getGridPrice();
-            placeEntryOrderWithPreFlag(downGridElementOne, false,
-                    shortTp,
-                    FuturesPriceTrigger.RuleEnum.NUMBER_2,
-                    negate(config.getQuantity()));
+            for (int id = 2; id <= 11; id++) {
+                GridElement elem = GridElement.findById(id);
+                if (elem == null) {
+                    continue;
+                }
+                BigDecimal triggerPrice = elem.getGridPrice();
+                int finalId = id;
+                executor.placeTakeProfit(
+                        triggerPrice,
+                        FuturesPriceTrigger.RuleEnum.NUMBER_1,
+                        ORDER_TYPE_CLOSE_SHORT,
+                        "1",
+                        profitId -> {
+                            elem.setShortStopLossOrderId(profitId);
+                            GridElement.refreshIndices();
+                            log.info("[Gate] 空仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", finalId, triggerPrice, profitId);
+                        }
+                );
+            }
 
+            for (int id = -2; id >= -11; id--) {
+                GridElement elem = GridElement.findById(id);
+                if (elem == null) {
+                    continue;
+                }
+                BigDecimal triggerPrice = elem.getGridPrice();
+                int finalId = id;
+                executor.placeTakeProfit(
+                        triggerPrice,
+                        FuturesPriceTrigger.RuleEnum.NUMBER_2,
+                        ORDER_TYPE_CLOSE_LONG,
+                        "-1",
+                        profitId -> {
+                            elem.setLongStopLossOrderId(profitId);
+                            GridElement.refreshIndices();
+                            log.info("[Gate] 多仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", finalId, triggerPrice, profitId);
+                        }
+                );
+            }
+
+            log.info("[Gate] 止损单已全部挂完, 空仓止损: 2~11, 多仓止损: -2~-11");
             state = StrategyState.ACTIVE;
         }
     }
@@ -1385,6 +1350,172 @@
         }
     }
 
+    private void handleLongStopLossTriggered(GridElement gridElement) {
+        int gridId = gridElement.getId();
+        int N = Math.abs(gridId);
+        gridElement.setLongStopLossOrderId(null);
+        log.info("[Gate] 多仓止损触发 gridId:{}, 开始追单", gridId);
+
+        int newEntryGridId = -(N - 1);
+        int entryQty = N - 1;
+
+        GridElement newEntryGrid = GridElement.findById(newEntryGridId);
+        if (newEntryGrid == null) {
+            log.warn("[Gate] 多仓止损触发 but gridId:{} 不存在", newEntryGridId);
+            GridElement.refreshIndices();
+            return;
+        }
+
+        if (N > 2) {
+            int cancelGridId = -(N - 2);
+            GridElement cancelGrid = GridElement.findById(cancelGridId);
+            if (cancelGrid != null && cancelGrid.isHasLongOrder()) {
+                executor.cancelConditionalOrder(cancelGrid.getLongOrderId(), oid -> {
+                    longEntryTraderIdParam(cancelGrid, null, false);
+                    log.info("[Gate] 多仓止损触发, 取消gridId:{}的多单", cancelGridId);
+                });
+            }
+        }
+
+        String size = String.valueOf(entryQty);
+        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
+        log.info("[Gate] 多仓止损触发 gridId:{}, 在gridId:{}挂{}张多单", gridId, newEntryGridId, entryQty);
+        newEntryGrid.getLongTraderParam().setQuantity(size);
+        placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice,
+                FuturesPriceTrigger.RuleEnum.NUMBER_2, size);
+    }
+
+    private void handleShortStopLossTriggered(GridElement gridElement) {
+        int gridId = gridElement.getId();
+        int N = gridId;
+        gridElement.setShortStopLossOrderId(null);
+        log.info("[Gate] 空仓止损触发 gridId:{}, 开始追单", gridId);
+
+        int newEntryGridId = N - 1;
+        int entryQty = N - 1;
+
+        GridElement newEntryGrid = GridElement.findById(newEntryGridId);
+        if (newEntryGrid == null) {
+            log.warn("[Gate] 空仓止损触发 but gridId:{} 不存在", newEntryGridId);
+            GridElement.refreshIndices();
+            return;
+        }
+
+        if (N > 2) {
+            int cancelGridId = N - 2;
+            GridElement cancelGrid = GridElement.findById(cancelGridId);
+            if (cancelGrid != null && cancelGrid.isHasShortOrder()) {
+                executor.cancelConditionalOrder(cancelGrid.getShortOrderId(), oid -> {
+                    shortEntryTraderIdParam(cancelGrid, null, false);
+                    log.info("[Gate] 空仓止损触发, 取消gridId:{}的空单", cancelGridId);
+                });
+            }
+        }
+
+        String size = String.valueOf(entryQty);
+        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
+        log.info("[Gate] 空仓止损触发 gridId:{}, 在gridId:{}挂{}张空单", gridId, newEntryGridId, entryQty);
+        newEntryGrid.getShortTraderParam().setQuantity(size);
+        placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice,
+                FuturesPriceTrigger.RuleEnum.NUMBER_1, negate(size));
+    }
+
+    private void extendLongStopLoss(int filledQty) {
+        int furthestSlId = 0;
+        for (GridElement e : config.getGridElements()) {
+            if (e.getLongStopLossOrderId() != null && e.getId() < furthestSlId) {
+                furthestSlId = e.getId();
+            }
+        }
+        if (furthestSlId == 0) {
+            furthestSlId = -11;
+        }
+        log.info("[Gate] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}张", furthestSlId, filledQty);
+        for (int i = 0; i < filledQty; i++) {
+            int newSlId = furthestSlId - i - 1;
+            GridElement elem = GridElement.findById(newSlId);
+            if (elem == null) {
+                continue;
+            }
+            BigDecimal triggerPrice = elem.getGridPrice();
+            int finalSlId = newSlId;
+            executor.placeTakeProfit(
+                    triggerPrice,
+                    FuturesPriceTrigger.RuleEnum.NUMBER_2,
+                    ORDER_TYPE_CLOSE_LONG,
+                    "-1",
+                    profitId -> {
+                        elem.setLongStopLossOrderId(profitId);
+                        GridElement.refreshIndices();
+                        log.info("[Gate] 多仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
+                    }
+            );
+        }
+    }
+
+    private void extendShortStopLoss(int filledQty) {
+        int furthestSlId = 0;
+        for (GridElement e : config.getGridElements()) {
+            if (e.getShortStopLossOrderId() != null && e.getId() > furthestSlId) {
+                furthestSlId = e.getId();
+            }
+        }
+        if (furthestSlId == 0) {
+            furthestSlId = 11;
+        }
+        log.info("[Gate] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}张", furthestSlId, filledQty);
+        for (int i = 0; i < filledQty; i++) {
+            int newSlId = furthestSlId + i + 1;
+            GridElement elem = GridElement.findById(newSlId);
+            if (elem == null) {
+                continue;
+            }
+            BigDecimal triggerPrice = elem.getGridPrice();
+            int finalSlId = newSlId;
+            executor.placeTakeProfit(
+                    triggerPrice,
+                    FuturesPriceTrigger.RuleEnum.NUMBER_1,
+                    ORDER_TYPE_CLOSE_SHORT,
+                    "1",
+                    profitId -> {
+                        elem.setShortStopLossOrderId(profitId);
+                        GridElement.refreshIndices();
+                        log.info("[Gate] 空仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
+                    }
+            );
+        }
+    }
+
+    private void checkProfitAndReset() {
+        try {
+            FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
+            BigDecimal unrealisedPnl = new BigDecimal(account.getCrossUnrealisedPnl());
+            BigDecimal available = new BigDecimal(account.getCrossAvailable());
+            BigDecimal totalEquity = unrealisedPnl.add(available);
+            BigDecimal target = initialPrincipal.add(config.getExpectedProfit());
+            log.info("[Gate] 盈亏检查 cross_unrealised_pnl:{}, cross_available:{}, 合计:{}, 目标:{}",
+                    unrealisedPnl, available, totalEquity, target);
+            if (totalEquity.compareTo(target) > 0) {
+                log.info("[Gate] 盈亏达标({}>{}),重置策略", totalEquity, target);
+                closeExistingPositions();
+                futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract());
+                startGrid();
+            }
+        } catch (Exception e) {
+            log.warn("[Gate] 盈亏检查失败", e);
+        }
+    }
+
+    private void handlePositionZeroAndReset(String direction) {
+        try {
+            futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract());
+        } catch (Exception e) {
+            log.warn("[Gate] {}持仓归零后取消条件单失败", direction, e);
+        }
+        closeExistingPositions();
+        startGrid();
+    }
+
     // ---- 保证金安全阀 ----
 
     /**
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
index ef58b29..4059f1e 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
@@ -76,10 +76,28 @@
 //                    .isProduction(false)
 //                    .reopenMaxRetries(3)
 //                    .build();
+//            //实盘
+//            config = GateConfig.builder()
+//                    .apiKey("a2338398e00b7935104520e16be96918")
+//                    .apiSecret("9111d897f2346d5217619f2da76536632715fef4d7eb304c6c61e869a2a74e98")
+//                    .contract("ETH_USDT")
+//                    .leverage("100")
+//                    .marginMode("CROSS")
+//                    .positionMode("dual")
+//                    .gridRate(new BigDecimal("0.005"))
+//                    .overallTp(new BigDecimal("100"))
+//                    .maxLoss(new BigDecimal("15"))
+//                    .quantity("5")
+//                    .priceScale(2)
+//                    .contractMultiplier(new BigDecimal("0.01"))
+//                    .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.LAST_PRICE)
+//                    .isProduction(true)
+//                    .reopenMaxRetries(3)
+//                    .build();
             //实盘
             config = GateConfig.builder()
-                    .apiKey("a2338398e00b7935104520e16be96918")
-                    .apiSecret("9111d897f2346d5217619f2da76536632715fef4d7eb304c6c61e869a2a74e98")
+                    .apiKey("d90ca272391992b8e74f8f92cedb21ec")
+                    .apiSecret("1861e4f52de4bb53369ea3208d9ede38ece4777368030f96c77d27934c46c274")
                     .contract("ETH_USDT")
                     .leverage("100")
                     .marginMode("CROSS")
@@ -91,7 +109,7 @@
                     .priceScale(2)
                     .contractMultiplier(new BigDecimal("0.01"))
                     .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.LAST_PRICE)
-                    .isProduction(true)
+                    .isProduction(false)
                     .reopenMaxRetries(3)
                     .build();
 
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java b/src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java
index 010042e..8601f69 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java
@@ -87,6 +87,10 @@
     private String longTakeProfitOrderId;
     /** 空仓止盈订单 ID */
     private String shortTakeProfitOrderId;
+    /** 多仓止损订单 ID */
+    private String longStopLossOrderId;
+    /** 空仓止损订单 ID */
+    private String shortStopLossOrderId;
 
     /** 全局 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
     private static final Map<Integer, GridElement> INDEX = new ConcurrentHashMap<>();
@@ -100,6 +104,10 @@
     private static final Map<String, GridElement> LONG_TP_ORDER_ID_INDEX = new ConcurrentHashMap<>();
     /** 全局空仓止盈订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
     private static final Map<String, GridElement> SHORT_TP_ORDER_ID_INDEX = new ConcurrentHashMap<>();
+    /** 全局多仓止损订单 ID 索引 */
+    private static final Map<String, GridElement> LONG_SL_ORDER_ID_INDEX = new ConcurrentHashMap<>();
+    /** 全局空仓止损订单 ID 索引 */
+    private static final Map<String, GridElement> SHORT_SL_ORDER_ID_INDEX = new ConcurrentHashMap<>();
 
     /**
      * 根据 ID 快速查找网格元素(O(1))。
@@ -162,6 +170,20 @@
     }
 
     /**
+     * 根据多仓止损订单 ID 快速查找网格元素(O(1))。
+     */
+    public static GridElement findByLongStopLossOrderId(String orderId) {
+        return LONG_SL_ORDER_ID_INDEX.get(orderId);
+    }
+
+    /**
+     * 根据空仓止损订单 ID 快速查找网格元素(O(1))。
+     */
+    public static GridElement findByShortStopLossOrderId(String orderId) {
+        return SHORT_SL_ORDER_ID_INDEX.get(orderId);
+    }
+
+    /**
      * 从列表中重建全局 ID 索引和价格索引。
      * 由 {@link GateConfig#setGridElements(List)} 在每次列表变更后调用。
      */
@@ -172,6 +194,8 @@
         SHORT_ORDER_ID_INDEX.clear();
         LONG_TP_ORDER_ID_INDEX.clear();
         SHORT_TP_ORDER_ID_INDEX.clear();
+        LONG_SL_ORDER_ID_INDEX.clear();
+        SHORT_SL_ORDER_ID_INDEX.clear();
         for (GridElement e : elements) {
             INDEX.put(e.getId(), e);
             putDynamicIndices(e);
@@ -191,6 +215,8 @@
         SHORT_ORDER_ID_INDEX.clear();
         LONG_TP_ORDER_ID_INDEX.clear();
         SHORT_TP_ORDER_ID_INDEX.clear();
+        LONG_SL_ORDER_ID_INDEX.clear();
+        SHORT_SL_ORDER_ID_INDEX.clear();
         for (GridElement e : INDEX.values()) {
             putDynamicIndices(e);
         }
@@ -205,9 +231,10 @@
         sorted.sort((a, b) -> Integer.compare(a.getId(), b.getId()));
         StringBuilder sb = new StringBuilder("\n========== 网格数据 ==========\n");
         for (GridElement e : sorted) {
-            if (e.isHasLongOrder() || e.isHasShortOrder()){
+            if (e.isHasLongOrder() || e.isHasShortOrder()
+                    || e.getLongStopLossOrderId() != null || e.getShortStopLossOrderId() != null){
                 sb.append(String.format(
-                        "  ID=%4d  价格=%s  up=%s  down=%s  多仓=%s(%s)  空仓=%s(%s)  多止盈=%s  空止盈=%s\n",
+                        "  ID=%4d  价格=%s  up=%s  down=%s  多仓=%s(%s)  空仓=%s(%s)  多止盈=%s  空止盈=%s  多止损=%s  空止损=%s\n",
                         e.getId(),
                         e.getGridPrice(),
                         e.getUpId(),
@@ -217,19 +244,23 @@
                         e.isHasShortOrder() ? "有" : "无",
                         e.getShortOrderId() != null ? e.getShortOrderId() : "-",
                         e.getLongTakeProfitOrderId() != null ? e.getLongTakeProfitOrderId() : "-",
-                        e.getShortTakeProfitOrderId() != null ? e.getShortTakeProfitOrderId() : "-"
+                        e.getShortTakeProfitOrderId() != null ? e.getShortTakeProfitOrderId() : "-",
+                        e.getLongStopLossOrderId() != null ? e.getLongStopLossOrderId() : "-",
+                        e.getShortStopLossOrderId() != null ? e.getShortStopLossOrderId() : "-"
                 ));
             }
         }
         sb.append(String.format(
                 "------------------------------------------------------------\n" +
-                "  索引统计: ID=%d  价格=%d  多仓订单ID=%d  空仓订单ID=%d  多止盈ID=%d  空止盈ID=%d\n",
+                "  索引统计: ID=%d  价格=%d  多仓订单ID=%d  空仓订单ID=%d  多止盈ID=%d  空止盈ID=%d  多止损ID=%d  空止损ID=%d\n",
                 INDEX.size(),
                 PRICE_INDEX.size(),
                 LONG_ORDER_ID_INDEX.size(),
                 SHORT_ORDER_ID_INDEX.size(),
                 LONG_TP_ORDER_ID_INDEX.size(),
-                SHORT_TP_ORDER_ID_INDEX.size()
+                SHORT_TP_ORDER_ID_INDEX.size(),
+                LONG_SL_ORDER_ID_INDEX.size(),
+                SHORT_SL_ORDER_ID_INDEX.size()
         ));
         sb.append(String.format("  多仓订单ID索引: %s\n", LONG_ORDER_ID_INDEX.keySet()));
         sb.append(String.format("  空仓订单ID索引: %s\n", SHORT_ORDER_ID_INDEX.keySet()));
@@ -285,6 +316,12 @@
         if (e.getShortTakeProfitOrderId() != null) {
             SHORT_TP_ORDER_ID_INDEX.put(e.getShortTakeProfitOrderId(), e);
         }
+        if (e.getLongStopLossOrderId() != null) {
+            LONG_SL_ORDER_ID_INDEX.put(e.getLongStopLossOrderId(), e);
+        }
+        if (e.getShortStopLossOrderId() != null) {
+            SHORT_SL_ORDER_ID_INDEX.put(e.getShortStopLossOrderId(), e);
+        }
     }
 
     /**
@@ -314,6 +351,8 @@
         this.shortOrderId = builder.shortOrderId;
         this.longTakeProfitOrderId = builder.longTakeProfitOrderId;
         this.shortTakeProfitOrderId = builder.shortTakeProfitOrderId;
+        this.longStopLossOrderId = builder.longStopLossOrderId;
+        this.shortStopLossOrderId = builder.shortStopLossOrderId;
     }
 
     // ==================== 网格层级编号 ====================
@@ -400,6 +439,20 @@
     /** 设置空仓止盈订单 ID */
     public void setShortTakeProfitOrderId(String shortTakeProfitOrderId) { this.shortTakeProfitOrderId = shortTakeProfitOrderId; }
 
+    // ==================== 多仓止损订单 ID ====================
+
+    /** @return 多仓止损订单 ID */
+    public String getLongStopLossOrderId() { return longStopLossOrderId; }
+    /** 设置多仓止损订单 ID */
+    public void setLongStopLossOrderId(String longStopLossOrderId) { this.longStopLossOrderId = longStopLossOrderId; }
+
+    // ==================== 空仓止损订单 ID ====================
+
+    /** @return 空仓止损订单 ID */
+    public String getShortStopLossOrderId() { return shortStopLossOrderId; }
+    /** 设置空仓止损订单 ID */
+    public void setShortStopLossOrderId(String shortStopLossOrderId) { this.shortStopLossOrderId = shortStopLossOrderId; }
+
     public static Builder builder() {
         return new Builder();
     }
@@ -438,6 +491,10 @@
         private String longTakeProfitOrderId;
         /** 空仓止盈订单 ID */
         private String shortTakeProfitOrderId;
+        /** 多仓止损订单 ID */
+        private String longStopLossOrderId;
+        /** 空仓止损订单 ID */
+        private String shortStopLossOrderId;
 
         /** 设置网格层级编号 */
         public Builder id(int id) { this.id = id; return this; }
@@ -463,6 +520,10 @@
         public Builder longTakeProfitOrderId(String longTakeProfitOrderId) { this.longTakeProfitOrderId = longTakeProfitOrderId; return this; }
         /** 设置空仓止盈订单 ID */
         public Builder shortTakeProfitOrderId(String shortTakeProfitOrderId) { this.shortTakeProfitOrderId = shortTakeProfitOrderId; return this; }
+        /** 设置多仓止损订单 ID */
+        public Builder longStopLossOrderId(String longStopLossOrderId) { this.longStopLossOrderId = longStopLossOrderId; return this; }
+        /** 设置空仓止损订单 ID */
+        public Builder shortStopLossOrderId(String shortStopLossOrderId) { this.shortStopLossOrderId = shortStopLossOrderId; return this; }
 
         public GridElement build() {
             return new GridElement(this);

--
Gitblit v1.9.1