Administrator
2026-07-15 522e0de6b879a910f2aa57b1af3fa49e8dc6f313
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -1,4 +1,4 @@
package com.xcong.excoin.modules.gateApi;
package com.xcong.excoin.modules.gateApi;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
@@ -134,10 +134,14 @@
    /** 多头是否活跃(有仓位) */
    private volatile boolean longActive = false;
    /** 多头累计止损张数(加仓订单成交后归零) */
    /** 多头累计止损次数(始终递增,加仓订单成交后归零) */
    private volatile int accumulatedLongLossCount = 0;
    /** 空头累计止损张数(加仓订单成交后归零) */
    /** 空头累计止损次数(始终递增,加仓订单成交后归零) */
    private volatile int accumulatedShortLossCount = 0;
    /** 最近触发止损的多头网格ID */
    private volatile int lastLongStopLossGridId = Integer.MAX_VALUE;
    /** 最近触发止损的空头网格ID */
    private volatile int lastShortStopLossGridId = Integer.MAX_VALUE;
    private volatile BigDecimal lastKlinePrice;
    private volatile BigDecimal markPrice = BigDecimal.ZERO;
@@ -307,6 +311,8 @@
        shortActive = false;
        accumulatedLongLossCount = 0;
        accumulatedShortLossCount = 0;
        lastLongStopLossGridId = Integer.MAX_VALUE;
        lastShortStopLossGridId = Integer.MAX_VALUE;
        shortPriceQueue.clear();
        longPriceQueue.clear();
        totalShortPriceQueue.clear();
@@ -612,21 +618,23 @@
        GridElement longStopLossElem = GridElement.findByLongStopLossOrderId(orderId);
//        if (longStopLossElem != null && longPositionSize.compareTo(BigDecimal.ZERO) > 0 && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
        if (longStopLossElem != null && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
            handleLongStopLossTriggered(longStopLossElem);
            handleLongStopLossTriggered(longStopLossElem, orderId);
            return;
        }
        GridElement shortStopLossElem = GridElement.findByShortStopLossOrderId(orderId);
//        if (shortStopLossElem != null && shortPositionSize.compareTo(BigDecimal.ZERO) > 0 && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
        if (shortStopLossElem != null && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
            handleShortStopLossTriggered(shortStopLossElem);
            handleShortStopLossTriggered(shortStopLossElem, orderId);
            return;
        }
        GridElement shortGridElement = GridElement.findByShortOrderId(orderId);
        if (shortGridElement != null) {
            if (shortGridElement.isHasShortOrder() && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0") ){
            if (StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0") ){
                int filledQty = Integer.parseInt(shortGridElement.getShortTraderParam().getQuantity());
                shortEntryTraderIdParam(shortGridElement, null, false);
                shortEntryTraderIdParam(shortGridElement, orderId, false);
                accumulatedShortLossCount = 0;
                lastShortStopLossGridId = Integer.MAX_VALUE;
                // [Gate-需求2] 加仓后先撤空仓所有止盈+止损,再查交易所持仓后重挂
                cancelAllShortTakeProfitsAndStopLosses();
                // REST 查询可能因交易所延迟返回旧值,与 WS 本地缓存取最大值兜底
@@ -658,52 +666,41 @@
//                        }
//                );
                // 空仓持仓超过baseQuantity时,先找多仓第一个止损位置,从该位置向下挂止盈(间隔=1)
                BigDecimal shortBaseQty = new BigDecimal(config.getBaseQuantity());
                BigDecimal shortGridQty = new BigDecimal(config.getQuantity());
                if (BigDecimal.valueOf(posSize).compareTo(shortBaseQty) > 0) {
                    BigDecimal shortExcess = BigDecimal.valueOf(posSize).subtract(shortBaseQty);
                    int shortExcessCount = shortExcess.divide(shortGridQty, 0, RoundingMode.DOWN).intValue();
                // 空仓止盈:超额部分挂止盈,从entry网格向下逐个挂
                int shortBaseQty = Integer.parseInt(config.getBaseQuantity());
                int shortGridQty = Integer.parseInt(config.getQuantity());
                int shortTpCount = posSize > shortBaseQty ? (posSize - shortBaseQty) / shortGridQty : 0;
//                    // 找多仓第一个(最近的)止损位置
//                    int firstLongSlId = 0;
//                    for (GridElement e : config.getGridElements()) {
//                        if (e.hasLongStopLossOrders()) {
//                            if (firstLongSlId == 0 || e.getId() > firstLongSlId) {
//                                firstLongSlId = e.getId();
//                            }
//                        }
//                    }
                    for (int i = 0; i < shortExcessCount; i++) {
                        int tpGridId = shortGridElement.getId() - 2 * (i + 1);
                        GridElement tpElem = GridElement.findById(tpGridId);
                        if (tpElem == null || tpElem.getShortTakeProfitOrderId() != null) {
                            continue;
                        }
                        BigDecimal tpPrice = tpElem.getGridPrice();
                        int finalTpGridId = tpGridId;
                        executor.placeTakeProfit(
                                tpPrice,
                                FuturesPriceTrigger.RuleEnum.NUMBER_2,
                                ORDER_TYPE_CLOSE_SHORT,
                                config.getQuantity(),
                                profitId -> {
                                    shortTakeProfitTraderIdParam(tpElem, profitId, true);
                                    log.info("[Gate] 空仓止盈挂单, gridId:{}, 触发价:{}, takeProfitId:{}",
                                            finalTpGridId, tpPrice, profitId);
                                }
                        );
                for (int i = 0; i < shortTpCount; i++) {
                    int tpGridId = shortGridElement.getId() - 2 * (i + 1);
                    GridElement tpElem = GridElement.findById(tpGridId);
                    if (tpElem == null || tpElem.getShortTakeProfitOrderId() != null) {
                        continue;
                    }
                    BigDecimal tpPrice = tpElem.getGridPrice();
                    int finalTpGridId = tpGridId;
                    executor.placeTakeProfit(
                            tpPrice,
                            FuturesPriceTrigger.RuleEnum.NUMBER_2,
                            ORDER_TYPE_CLOSE_SHORT,
                            config.getQuantity(),
                            profitId -> {
                                shortTakeProfitTraderIdParam(tpElem, profitId, true);
                                log.info("[Gate] 空仓止盈挂单, gridId:{}, 触发价:{}, takeProfitId:{}",
                                        finalTpGridId, tpPrice, profitId);
                            }
                    );
                }
            }
        }
        GridElement longGridElement = GridElement.findByLongOrderId(orderId);
        if (longGridElement != null) {
            if (longGridElement.isHasLongOrder() && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")){
            if (StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")){
                int filledQty = Integer.parseInt(longGridElement.getLongTraderParam().getQuantity());
                longEntryTraderIdParam(longGridElement, null, false);
                longEntryTraderIdParam(longGridElement, orderId, false);
                accumulatedLongLossCount = 0;
                lastLongStopLossGridId = Integer.MAX_VALUE;
                // [Gate-需求2] 加仓后先撤多仓所有止盈+止损,再查交易所持仓后重挂
                cancelAllLongTakeProfitsAndStopLosses();
                // REST 查询可能因交易所延迟返回旧值,与 WS 本地缓存取最大值兜底
@@ -735,43 +732,30 @@
//                        }
//                );
                // 多仓持仓超过baseQuantity时,先找空仓第一个止损位置,从该位置向上挂止盈(间隔=1)
                BigDecimal longBaseQty = new BigDecimal(config.getBaseQuantity());
                BigDecimal longGridQty = new BigDecimal(config.getQuantity());
                if (BigDecimal.valueOf(posSize).compareTo(longBaseQty) > 0) {
                    BigDecimal longExcess = BigDecimal.valueOf(posSize).subtract(longBaseQty);
                    int longExcessCount = longExcess.divide(longGridQty, 0, RoundingMode.DOWN).intValue();
                // 多仓止盈:超额部分挂止盈,从entry网格向上逐个挂
                int longBaseQty = Integer.parseInt(config.getBaseQuantity());
                int longGridQty = Integer.parseInt(config.getQuantity());
                int longTpCount = posSize > longBaseQty ? (posSize - longBaseQty) / longGridQty : 0;
//                    // 找空仓第一个(最近的)止损位置
//                    int firstShortSlId = 0;
//                    for (GridElement e : config.getGridElements()) {
//                        if (e.hasShortStopLossOrders()) {
//                            if (firstShortSlId == 0 || e.getId() < firstShortSlId) {
//                                firstShortSlId = e.getId();
//                            }
//                        }
//                    }
                    for (int i = 0; i < longExcessCount; i++) {
                        int tpGridId = longGridElement.getId() + 2 * (i + 1);
                        GridElement tpElem = GridElement.findById(tpGridId);
                        if (tpElem == null || tpElem.getLongTakeProfitOrderId() != null) {
                            continue;
                        }
                        BigDecimal tpPrice = tpElem.getGridPrice();
                        int finalTpGridId = tpGridId;
                        executor.placeTakeProfit(
                                tpPrice,
                                FuturesPriceTrigger.RuleEnum.NUMBER_1,
                                ORDER_TYPE_CLOSE_LONG,
                                negate(config.getQuantity()),
                                profitId -> {
                                    longTakeProfitTraderIdParam(tpElem, profitId, true);
                                    log.info("[Gate] 多仓止盈挂单, gridId:{}, 触发价:{}, takeProfitId:{}",
                                            finalTpGridId, tpPrice, profitId);
                                }
                        );
                for (int i = 0; i < longTpCount; i++) {
                    int tpGridId = longGridElement.getId() + 2 * (i + 1);
                    GridElement tpElem = GridElement.findById(tpGridId);
                    if (tpElem == null || tpElem.getLongTakeProfitOrderId() != null) {
                        continue;
                    }
                    BigDecimal tpPrice = tpElem.getGridPrice();
                    int finalTpGridId = tpGridId;
                    executor.placeTakeProfit(
                            tpPrice,
                            FuturesPriceTrigger.RuleEnum.NUMBER_1,
                            ORDER_TYPE_CLOSE_LONG,
                            negate(config.getQuantity()),
                            profitId -> {
                                longTakeProfitTraderIdParam(tpElem, profitId, true);
                                log.info("[Gate] 多仓止盈挂单, gridId:{}, 触发价:{}, takeProfitId:{}",
                                        finalTpGridId, tpPrice, profitId);
                            }
                    );
                }
            }
        }
@@ -1007,8 +991,14 @@
        TraderParam tp = baseElement.getLongTraderParam();
        tp.setEntryOrderId(entryId);
        tp.setEntryOrderPlaced(flag);
        baseElement.setHasLongOrder(flag);
        baseElement.setLongOrderId(entryId);
        if (flag && entryId != null) {
            baseElement.addLongOrderId(entryId);
            baseElement.setHasLongOrder(true);
        } else if (!flag && entryId != null) {
            baseElement.removeLongOrderId(entryId);
            // 只有列表全部清空才关闭预置标志,避免还有未成交挂单时被 processGrid 误判
            baseElement.setHasLongOrder(baseElement.hasLongOrderIds());
        }
        GridElement.refreshIndices();
    }
@@ -1018,8 +1008,14 @@
        TraderParam tp = baseElement.getShortTraderParam();
        tp.setEntryOrderId(entryId);
        tp.setEntryOrderPlaced(flag);
        baseElement.setHasShortOrder(flag);
        baseElement.setShortOrderId(entryId);
        if (flag && entryId != null) {
            baseElement.addShortOrderId(entryId);
            baseElement.setHasShortOrder(true);
        } else if (!flag && entryId != null) {
            baseElement.removeShortOrderId(entryId);
            // 只有列表全部清空才关闭预置标志,避免还有未成交挂单时被 processGrid 误判
            baseElement.setHasShortOrder(baseElement.hasShortOrderIds());
        }
        GridElement.refreshIndices();
    }
@@ -1212,11 +1208,12 @@
                                ? cancelGridElement.getLongTraderParam().getQuantity()
                                : config.getBaseQuantity();
                        if (cancelGridElement != null && cancelGridElement.isHasLongOrder()) {
                            String longOrderId = cancelGridElement.getLongOrderId();
                            executor.cancelConditionalOrder(longOrderId, oid -> {
                                longEntryTraderIdParam(cancelGridElement, null, false);
                                log.info("[Gate] 多仓仓位归零, 取消gridId:{}的多单,{}", cancelGridElement.getId(),longOrderId);
                            });
                            for (String longOrderId : new ArrayList<>(cancelGridElement.getLongOrderIds())) {
                                executor.cancelConditionalOrder(longOrderId, oid -> {
                                    longEntryTraderIdParam(cancelGridElement, longOrderId, false);
                                    log.info("[Gate] 多仓仓位归零, 取消gridId:{}的多单,{}", cancelGridElement.getId(), longOrderId);
                                });
                            }
                        }
//                        log.info("[Gate-2] 多仓仓位归零 空仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
                        if (!newEntryGrid.isHasLongOrder()) {
@@ -1266,11 +1263,12 @@
                         * 看是否有空仓挂单,有就取消
                         */
                        if (cancelGridElement != null && cancelGridElement.isHasShortOrder()) {
                            String shortOrderId = cancelGridElement.getShortOrderId();
                            executor.cancelConditionalOrder(shortOrderId, oid -> {
                                shortEntryTraderIdParam(cancelGridElement, null, false);
                                log.info("[Gate] 空仓仓位归零, 取消gridId:{}的多单{}", cancelGridElement.getId(),shortOrderId);
                            });
                            for (String shortOrderId : new ArrayList<>(cancelGridElement.getShortOrderIds())) {
                                executor.cancelConditionalOrder(shortOrderId, oid -> {
                                    shortEntryTraderIdParam(cancelGridElement, shortOrderId, false);
                                    log.info("[Gate] 空仓仓位归零, 取消gridId:{}的多单{}", cancelGridElement.getId(), shortOrderId);
                                });
                            }
                        }
//                        log.info("[Gate-4] 空仓仓位归零 多仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
                        if (!newEntryGrid.isHasShortOrder()){
@@ -1289,12 +1287,15 @@
        }
    }
    private void handleLongStopLossTriggered(GridElement gridElement) {
        gridElement.clearLongStopLossOrderIds();
    private void handleLongStopLossTriggered(GridElement gridElement, String orderId) {
        gridElement.removeLongStopLossOrderId(orderId);
        accumulatedLongLossCount++;
        int gridId = gridElement.getId();
        log.info("[Gate] 多仓止损触发 gridId:{}, 止损次数:{}, 开始追单", gridId, accumulatedLongLossCount);
        boolean sameGrid = (gridId == lastLongStopLossGridId);
        accumulatedLongLossCount++;
        lastLongStopLossGridId = gridId;
        log.info("[Gate] 多仓止损触发 gridId:{}, 止损次数:{}{}, 开始追单",
                gridId, accumulatedLongLossCount, sameGrid ? "(同网格)" : "");
        int newEntryGridId = gridId + 1;
        GridElement newEntryGrid = GridElement.findById(newEntryGridId);
@@ -1304,52 +1305,43 @@
            return;
        }
        // [Gate-BugFix] 防止与"仓位归零"重复下单:若该网格已有挂单则跳过
        if (!newEntryGrid.isHasLongOrder()) {
            BigDecimal triggerPrice = newEntryGrid.getGridPrice();
            // 止损触发后持仓在减少,取REST和WS缓存中较小值更准确
            int posSize = Math.min(queryPositionSize(Position.ModeEnum.DUAL_LONG), longPositionSize.intValue());
            int maxPos = config.getMaxPositionSize();
            // 止损阶梯:止损次数≤阈值时挂单量=单笔数量,超过后恢复默认逻辑(quantity*2)
            int targetAmount;
            if (config.getStopLossCount() > 0 && accumulatedLongLossCount <= config.getStopLossCount()) {
                targetAmount = Integer.parseInt(config.getQuantity());
        // 止损追单:同一网格可有多笔挂单,不判断 isHasLongOrder,直接挂单
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        int posSize = Math.min(queryPositionSize(Position.ModeEnum.DUAL_LONG), longPositionSize.intValue());
        int maxPos = config.getMaxPositionSize();
        int targetAmount = sameGrid ? Integer.parseInt(config.getQuantity()) : accumulatedLongLossCount + 1;
        int addSize;
        if (maxPos > 0) {
            int remainingRoom = maxPos - posSize;
            if (remainingRoom <= 0) {
                log.warn("[Gate] 多仓止损触发 gridId:{}, 当前持仓{}/{}已达上限,跳过追单",
                        gridId, posSize, maxPos);
                addSize = 0;
            } else {
                targetAmount = Integer.parseInt(config.getQuantity()) * 2; // quantity + 本次止损量
                addSize = Math.min(remainingRoom, targetAmount);
            }
            int addSize;
            if (maxPos > 0) {
                int remainingRoom = maxPos - posSize;
                if (remainingRoom <= 0) {
                    log.warn("[Gate] 多仓止损触发 gridId:{}, 当前持仓{}/{}已达上限,跳过追单",
                            gridId, posSize, maxPos);
                    addSize = 0;
                } else {
                    addSize = Math.min(remainingRoom, targetAmount);
                }
            } else {
                addSize = targetAmount;
            }
            if (addSize > 0) {
                String size = String.valueOf(addSize);
                log.info("[Gate] 多仓止损触发 gridId:{}, 止损次数:{}, 在gridId:{}补{}张多单(当前{}/上限{})",
                        gridId, accumulatedLongLossCount, newEntryGridId, size, posSize, maxPos > 0 ? maxPos : "无");
                newEntryGrid.getLongTraderParam().setQuantity(size);
                placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice,
                        FuturesPriceTrigger.RuleEnum.NUMBER_1, size);
            }
        }else{
            log.warn("[Gate] 多仓止损触发 gridId:{}, 目标gridId:{}已有挂单,跳过重复下单", gridId, newEntryGridId);
        } else {
            addSize = targetAmount;
        }
        if (addSize > 0) {
            String size = String.valueOf(addSize);
            log.info("[Gate] 多仓止损触发 gridId:{}, 止损次数:{}, 在gridId:{}补{}张多单(当前{}/上限{})",
                    gridId, accumulatedLongLossCount, newEntryGridId, size, posSize, maxPos > 0 ? maxPos : "无");
            newEntryGrid.getLongTraderParam().setQuantity(size);
            placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice,
                    FuturesPriceTrigger.RuleEnum.NUMBER_1, size);
        }
        // 取消 gridId+2 位置的所有多单
        int cancelGridId = gridId + 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);
            });
            for (String cancelOrderId : new ArrayList<>(cancelGrid.getLongOrderIds())) {
                executor.cancelConditionalOrder(cancelOrderId, oid -> {
                    longEntryTraderIdParam(cancelGrid, cancelOrderId, false);
                    log.info("[Gate] 多仓止损触发, 取消gridId:{}的多单,{}", cancelGridId, cancelOrderId);
                });
            }
        }
        // 止损触发时,取消最远的多仓止盈订单
@@ -1371,12 +1363,15 @@
        }
    }
    private void handleShortStopLossTriggered(GridElement gridElement) {
        gridElement.clearShortStopLossOrderIds();
    private void handleShortStopLossTriggered(GridElement gridElement, String orderId) {
        gridElement.removeShortStopLossOrderId(orderId);
        accumulatedShortLossCount++;
        int gridId = gridElement.getId();
        log.info("[Gate] 空仓止损触发 gridId:{}, 止损次数:{}, 开始追单", gridId, accumulatedShortLossCount);
        boolean sameGrid = (gridId == lastShortStopLossGridId);
        accumulatedShortLossCount++;
        lastShortStopLossGridId = gridId;
        log.info("[Gate] 空仓止损触发 gridId:{}, 止损次数:{}{}, 开始追单",
                gridId, accumulatedShortLossCount, sameGrid ? "(同网格)" : "");
        int newEntryGridId = gridId - 1;
        GridElement newEntryGrid = GridElement.findById(newEntryGridId);
@@ -1386,55 +1381,43 @@
            return;
        }
        // [Gate-BugFix] 防止与"仓位归零"重复下单:若该网格已有挂单则跳过
        if (!newEntryGrid.isHasShortOrder()) {
            BigDecimal triggerPrice = newEntryGrid.getGridPrice();
            // 止损触发后持仓在减少,取REST和WS缓存中较小值更准确
            int posSize = Math.min(queryPositionSize(Position.ModeEnum.DUAL_SHORT), shortPositionSize.intValue());
            int maxPos = config.getMaxPositionSize();
            // 止损阶梯:止损次数≤阈值时挂单量=单笔数量,超过后恢复默认逻辑(quantity*2)
            int targetAmount;
            if (config.getStopLossCount() > 0 && accumulatedShortLossCount <= config.getStopLossCount()) {
                targetAmount = Integer.parseInt(config.getQuantity());
        // 止损追单:同一网格可有多笔挂单,不判断 isHasShortOrder,直接挂单
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        int posSize = Math.min(queryPositionSize(Position.ModeEnum.DUAL_SHORT), shortPositionSize.intValue());
        int maxPos = config.getMaxPositionSize();
        int targetAmount = sameGrid ? Integer.parseInt(config.getQuantity()) : accumulatedShortLossCount + 1;
        int addSize;
        if (maxPos > 0) {
            int remainingRoom = maxPos - posSize;
            if (remainingRoom <= 0) {
                log.warn("[Gate] 空仓止损触发 gridId:{}, 当前持仓{}/{}已达上限,跳过追单",
                        gridId, posSize, maxPos);
                addSize = 0;
            } else {
                targetAmount = Integer.parseInt(config.getQuantity()) * 2; // quantity + 本次止损量
                addSize = Math.min(remainingRoom, targetAmount);
            }
            int addSize;
            if (maxPos > 0) {
                int remainingRoom = maxPos - posSize;
                if (remainingRoom <= 0) {
                    log.warn("[Gate] 空仓止损触发 gridId:{}, 当前持仓{}/{}已达上限,跳过追单",
                            gridId, posSize, maxPos);
                    addSize = 0;
                } else {
                    addSize = Math.min(remainingRoom, targetAmount);
                }
            } else {
                addSize = targetAmount;
            }
            if (addSize > 0) {
                String size = String.valueOf(addSize);
                log.info("[Gate] 空仓止损触发 gridId:{}, 止损次数:{}, 在gridId:{}补{}张空单(当前{}/上限{})",
                        gridId, accumulatedShortLossCount, newEntryGridId, size, posSize, maxPos > 0 ? maxPos : "无");
                newEntryGrid.getShortTraderParam().setQuantity(size);
                placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice,
                        FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(size));
            }
        }else{
            log.warn("[Gate] 空仓止损触发 gridId:{}, 目标gridId:{}已有挂单,跳过重复下单", gridId, newEntryGridId);
        } else {
            addSize = targetAmount;
        }
        if (addSize > 0) {
            String size = String.valueOf(addSize);
            log.info("[Gate] 空仓止损触发 gridId:{}, 止损次数:{}, 在gridId:{}补{}张空单(当前{}/上限{})",
                    gridId, accumulatedShortLossCount, newEntryGridId, size, posSize, maxPos > 0 ? maxPos : "无");
            newEntryGrid.getShortTraderParam().setQuantity(size);
            placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice,
                    FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(size));
        }
        // 取消 gridId-2 位置的所有空单
        int cancelGridId = gridId - 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);
            });
            for (String cancelOrderId : new ArrayList<>(cancelGrid.getShortOrderIds())) {
                executor.cancelConditionalOrder(cancelOrderId, oid -> {
                    shortEntryTraderIdParam(cancelGrid, cancelOrderId, false);
                    log.info("[Gate] 空仓止损触发, 取消gridId:{}的空单,{}", cancelGridId, cancelOrderId);
                });
            }
        }
        // 止损触发时,取消最远的空仓止盈订单
@@ -1672,41 +1655,46 @@
    private void extendLongStopLoss(int filledQty, int gridId) {
        int qty = Integer.parseInt(config.getQuantity());
        int aCount = Math.max(0, filledQty / qty - 2);
        log.info("[Gate] 多仓追挂止损, 成交{}张, gridId:{}, A({})x{}  B({})x1  C({})x1",
                filledQty, gridId, gridId - 2, aCount, gridId - 3, gridId - 4);
        if (aCount < 0) return;
        // A 位置:挂 aCount 个独立止损单
        placeStopLossOrders(gridId - 2, aCount, qty, FuturesPriceTrigger.RuleEnum.NUMBER_2,
        int a, b, c;
        if (aCount == 0) { a = 2; b = 0; c = 0; }
        else if (aCount == 1) { a = 2; b = 1; c = 0; }
        else { a = aCount; b = 1; c = 1; }
        log.info("[Gate] 多仓追挂止损, 成交{}张, gridId:{}, A({})x{}  B({})x{}  C({})x{}",
                filledQty, gridId, gridId - 2, a, gridId - 3, b, gridId - 4, c);
        if (a > 0) placeStopLossOrders(gridId - 2, a, qty, FuturesPriceTrigger.RuleEnum.NUMBER_2,
                ORDER_TYPE_CLOSE_LONG, true);
        // B 位置:挂 1 个
        placeStopLossOrders(gridId - 3, 1, qty, FuturesPriceTrigger.RuleEnum.NUMBER_2,
        if (b > 0) placeStopLossOrders(gridId - 3, b, qty, FuturesPriceTrigger.RuleEnum.NUMBER_2,
                ORDER_TYPE_CLOSE_LONG, true);
        // C 位置:挂 1 个
        placeStopLossOrders(gridId - 4, 1, qty, FuturesPriceTrigger.RuleEnum.NUMBER_2,
        if (c > 0) placeStopLossOrders(gridId - 4, c, qty, FuturesPriceTrigger.RuleEnum.NUMBER_2,
                ORDER_TYPE_CLOSE_LONG, true);
    }
    /**
     * 空仓止损追单 — 3 个固定位置,从近到远分配合约。
     * A(gridId+2): (filledQty/qty - 2) 个独立止损单,每个 qty 张
     * B(gridId+3): 1 个止损单,qty 张
     * C(gridId+4): 1 个止损单,qty 张
     * 注意:空仓止损在 gridId 正方向,触发价 ≥ 网格价。
     * aCount<0跳过, aCount=0→A=2, aCount=1→A=2,B=1, aCount>=2→A=aCount,B=1,C=1
     */
    private void extendShortStopLoss(int filledQty, int gridId) {
        int qty = Integer.parseInt(config.getQuantity());
        int aCount = Math.max(0, filledQty / qty - 2);
        log.info("[Gate] 空仓追挂止损, 成交{}张, gridId:{}, A({})x{}  B({})x1  C({})x1",
                filledQty, gridId, gridId + 2, aCount, gridId + 3, gridId + 4);
        if (aCount < 0) return;
        // A 位置:挂 aCount 个独立止损单
        placeStopLossOrders(gridId + 2, aCount, qty, FuturesPriceTrigger.RuleEnum.NUMBER_1,
        int a, b, c;
        if (aCount == 0) { a = 2; b = 0; c = 0; }
        else if (aCount == 1) { a = 2; b = 1; c = 0; }
        else { a = aCount; b = 1; c = 1; }
        log.info("[Gate] 空仓追挂止损, 成交{}张, gridId:{}, A({})x{}  B({})x{}  C({})x{}",
                filledQty, gridId, gridId + 2, a, gridId + 3, b, gridId + 4, c);
        if (a > 0) placeStopLossOrders(gridId + 2, a, qty, FuturesPriceTrigger.RuleEnum.NUMBER_1,
                ORDER_TYPE_CLOSE_SHORT, false);
        // B 位置:挂 1 个
        placeStopLossOrders(gridId + 3, 1, qty, FuturesPriceTrigger.RuleEnum.NUMBER_1,
        if (b > 0) placeStopLossOrders(gridId + 3, b, qty, FuturesPriceTrigger.RuleEnum.NUMBER_1,
                ORDER_TYPE_CLOSE_SHORT, false);
        // C 位置:挂 1 个
        placeStopLossOrders(gridId + 4, 1, qty, FuturesPriceTrigger.RuleEnum.NUMBER_1,
        if (c > 0) placeStopLossOrders(gridId + 4, c, qty, FuturesPriceTrigger.RuleEnum.NUMBER_1,
                ORDER_TYPE_CLOSE_SHORT, false);
    }
@@ -1786,15 +1774,18 @@
                    }
                },
                () -> {
                    // 仅当列表为空(无其他有效订单)时才清预置标志,避免误伤其他并发挂单
                    if (isLong) {
                        gridElement.setHasLongOrder(false);
                        gridElement.setLongOrderId(null);
                        if (!gridElement.hasLongOrderIds()) {
                            gridElement.setHasLongOrder(false);
                        }
                    } else {
                        gridElement.setHasShortOrder(false);
                        gridElement.setShortOrderId(null);
                        if (!gridElement.hasShortOrderIds()) {
                            gridElement.setHasShortOrder(false);
                        }
                    }
                    GridElement.refreshIndices();
                    log.warn("[Gate] 条件单创建失败,回滚标志位 gridId:{}, isLong:{}", gridElement.getId(), isLong);
                    log.warn("[Gate] 条件单创建失败 gridId:{}, isLong:{}", gridElement.getId(), isLong);
                }
        );
    }