Administrator
2026-09-20 cd3de8987bca1a9c29b542813b87ee06889af56a
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -15,6 +15,7 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.xcong.excoin.modules.gateApi.wsHandler.handler.CandlestickChannelHandler;
@@ -148,6 +149,8 @@
    private volatile int lastLongStopLossGridId = Integer.MAX_VALUE;
    /** 最近触发止损的空头网格ID */
    private volatile int lastShortStopLossGridId = Integer.MAX_VALUE;
    /** 自定义止损模式激活标记:true=尚未发生第一次止损触发,第一次触发后置 false(触发未触发侧重挂默认) */
    private final AtomicBoolean customStopLossModeActive = new AtomicBoolean(false);
    private volatile BigDecimal lastKlinePrice;
    private volatile BigDecimal markPrice = BigDecimal.ZERO;
@@ -362,6 +365,7 @@
        accumulatedShortLossCount = 0;
        lastLongStopLossGridId = Integer.MAX_VALUE;
        lastShortStopLossGridId = Integer.MAX_VALUE;
        customStopLossModeActive.set(false);
        shortPriceQueue.clear();
        longPriceQueue.clear();
        totalShortPriceQueue.clear();
@@ -974,6 +978,87 @@
//                );
//            }
            if (config.isCustomStopLossEnabled()) {
                placeCustomStopLosses();
            } else {
                placeDefaultStopLossLadders();
            }
            state = StrategyState.ACTIVE;
        }
    }
    // ========== 自定义止损(按触发价各挂一单) ==========
    /**
     * 自定义止损初始化:按多空止损触发价匹配最近网格,各挂一张覆盖全仓位(baseQuantity)的止损单。
     * 多仓止损:NUMBER_2(价格 ≤ 触发价平多),空仓止损:NUMBER_1(价格 ≥ 触发价平空)。
     */
    private void placeCustomStopLosses() {
        customStopLossModeActive.set(true);
        String baseQty = config.getBaseQuantity();
        BigDecimal longPrice = config.getLongCustomStopLossPrice();
        BigDecimal shortPrice = config.getShortCustomStopLossPrice();
        if (longPrice != null && longPrice.compareTo(BigDecimal.ZERO) > 0) {
            GridElement longElem = findNearestGridElementByPrice(longPrice);
            if (longElem != null) {
                BigDecimal triggerPrice = longElem.getGridPrice();
                executor.placeTakeProfit(
                        triggerPrice,
                        FuturesPriceTrigger.RuleEnum.NUMBER_2,
                        ORDER_TYPE_CLOSE_LONG,
                        negate(baseQty),
                        profitId -> {
                            longElem.addLongStopLossOrderId(profitId);
                            GridElement.refreshIndices();
                            log.info("[Gate] 自定义多仓止损已挂, 目标价:{}, 匹配gridId:{}, 触发价:{}, stopLossId:{}",
                                    longPrice, longElem.getId(), triggerPrice, profitId);
                        }
                );
            } else {
                log.warn("[Gate] 自定义多仓止损价:{} 未匹配到任何网格", longPrice);
            }
        }
        if (shortPrice != null && shortPrice.compareTo(BigDecimal.ZERO) > 0) {
            GridElement shortElem = findNearestGridElementByPrice(shortPrice);
            if (shortElem != null) {
                BigDecimal triggerPrice = shortElem.getGridPrice();
                executor.placeTakeProfit(
                        triggerPrice,
                        FuturesPriceTrigger.RuleEnum.NUMBER_1,
                        ORDER_TYPE_CLOSE_SHORT,
                        baseQty,
                        profitId -> {
                            shortElem.addShortStopLossOrderId(profitId);
                            GridElement.refreshIndices();
                            log.info("[Gate] 自定义空仓止损已挂, 目标价:{}, 匹配gridId:{}, 触发价:{}, stopLossId:{}",
                                    shortPrice, shortElem.getId(), triggerPrice, profitId);
                        }
                );
            } else {
                log.warn("[Gate] 自定义空仓止损价:{} 未匹配到任何网格", shortPrice);
            }
        }
        log.info("[Gate] 自定义止损初始化完成, 多仓止损价:{}, 空仓止损价:{}", longPrice, shortPrice);
    }
    /**
     * 默认止损阶梯(原有逻辑):空仓从 ID=2 向上、多仓从 ID=-2 向下,各挂 baseQuantity/quantity 格、每格 quantity 张。
     */
    private void placeDefaultStopLossLadders() {
        placeDefaultShortStopLossLadder();
        placeDefaultLongStopLossLadder();
        int shortTime = Integer.parseInt(config.getBaseQuantity()) / Integer.parseInt(config.getQuantity()) + 1;
        int longTime = Integer.parseInt(config.getBaseQuantity()) / Integer.parseInt(config.getQuantity()) + 1;
        log.info("[Gate] 止损止盈单已全部挂完, 空仓止损:2~{}, 多仓止损:-2~-{}", shortTime, longTime);
    }
    /**
     * 默认空仓止损阶梯:从 ID=2 向上挂,每格 quantity 张,NUMBER_1(价格 ≥ 触发价平空)。
     */
    private void placeDefaultShortStopLossLadder() {
            int shortTime = Integer.parseInt(config.getBaseQuantity()) / Integer.parseInt(config.getQuantity()) + 1;
            for (int id = 2; id <= shortTime; id++) {
                GridElement elem = GridElement.findById(id);
@@ -995,9 +1080,12 @@
                        }
                );
            }
    }
    /**
     * 默认多仓止损阶梯:从 ID=-2 向下挂,每格 quantity 张,NUMBER_2(价格 ≤ 触发价平多)。
     */
    private void placeDefaultLongStopLossLadder() {
            int longTime = Integer.parseInt(config.getBaseQuantity()) / Integer.parseInt(config.getQuantity()) + 1;
            for (int id = -2; id >= -longTime; id--) {
                GridElement elem = GridElement.findById(id);
@@ -1019,11 +1107,53 @@
                        }
                );
            }
            log.info("[Gate] 止损止盈单已全部挂完, 空仓止损:2~{}, 多仓止损:-2~-{}", shortTime, longTime);
            state = StrategyState.ACTIVE;
        }
    /**
     * 按「价格 → 最近网格」匹配:返回 gridPrice 与目标价绝对值差最小的网格元素。
     */
    private GridElement findNearestGridElementByPrice(BigDecimal price) {
        if (price == null || price.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }
        GridElement nearest = null;
        BigDecimal minDiff = null;
        for (GridElement e : config.getGridElements()) {
            BigDecimal diff = e.getGridPrice().subtract(price).abs();
            if (minDiff == null || diff.compareTo(minDiff) < 0) {
                minDiff = diff;
                nearest = e;
            }
        }
        return nearest;
    }
    /**
     * 只取消空仓止损单(保留空仓止盈),用于自定义止损首次触发时重挂未触发侧。
     */
    private void cancelAllShortStopLossesOnly() {
        for (GridElement e : config.getGridElements()) {
            for (String slId : new ArrayList<>(e.getShortStopLossOrderIds())) {
                executor.cancelConditionalOrder(slId, oid -> {});
            }
            e.clearShortStopLossOrderIds();
        }
        GridElement.refreshIndices();
        log.info("[Gate] 已提交取消所有空仓止损(保留止盈)");
    }
    /**
     * 只取消多仓止损单(保留多仓止盈),用于自定义止损首次触发时重挂未触发侧。
     */
    private void cancelAllLongStopLossesOnly() {
        for (GridElement e : config.getGridElements()) {
            for (String slId : new ArrayList<>(e.getLongStopLossOrderIds())) {
                executor.cancelConditionalOrder(slId, oid -> {});
            }
            e.clearLongStopLossOrderIds();
        }
        GridElement.refreshIndices();
        log.info("[Gate] 已提交取消所有多仓止损(保留止盈)");
    }
    /**
@@ -1523,6 +1653,13 @@
            }
        }
        // 自定义止损模式:第一次止损触发时,取消未触发侧(空仓)自定义止损单并重挂默认阶梯
        if (config.isCustomStopLossEnabled() && customStopLossModeActive.compareAndSet(true, false)) {
            cancelAllShortStopLossesOnly();
            placeDefaultShortStopLossLadder();
            log.info("[Gate] 自定义止损首次触发(多仓), 已取消空仓自定义止损并重挂默认阶梯");
        }
    }
    private void handleShortStopLossTriggered(GridElement gridElement, String orderId) {
@@ -1574,6 +1711,13 @@
                });
            }
        }
        // 自定义止损模式:第一次止损触发时,取消未触发侧(多仓)自定义止损单并重挂默认阶梯
        if (config.isCustomStopLossEnabled() && customStopLossModeActive.compareAndSet(true, false)) {
            cancelAllLongStopLossesOnly();
            placeDefaultLongStopLossLadder();
            log.info("[Gate] 自定义止损首次触发(空仓), 已取消多仓自定义止损并重挂默认阶梯");
        }
    }
    // ========== 止盈/止损取消辅助方法 ==========