Administrator
2026-07-16 785511709febd82bc366af19ba8c3564b8da47d2
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -134,13 +134,13 @@
    /** 多头是否活跃(有仓位) */
    private volatile boolean longActive = false;
    /** 多头累计止损次数(加仓订单成交后归零),同一网格多次止损不累加 */
    /** 多头累计止损次数(始终递增,加仓订单成交后归零) */
    private volatile int accumulatedLongLossCount = 0;
    /** 空头累计止损次数(加仓订单成交后归零),同一网格多次止损不累加 */
    /** 空头累计止损次数(始终递增,加仓订单成交后归零) */
    private volatile int accumulatedShortLossCount = 0;
    /** 最近触发止损的多头网格ID,用于判断是否是同一网格的连续止损 */
    /** 最近触发止损的多头网格ID */
    private volatile int lastLongStopLossGridId = Integer.MAX_VALUE;
    /** 最近触发止损的空头网格ID,用于判断是否是同一网格的连续止损 */
    /** 最近触发止损的空头网格ID */
    private volatile int lastShortStopLossGridId = Integer.MAX_VALUE;
    private volatile BigDecimal lastKlinePrice;
@@ -633,7 +633,7 @@
            if (StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0") ){
                int filledQty = Integer.parseInt(shortGridElement.getShortTraderParam().getQuantity());
                shortEntryTraderIdParam(shortGridElement, orderId, false);
                accumulatedShortLossCount = 0; // 空单成交,重置止损追单计数
                accumulatedShortLossCount = 0;
                lastShortStopLossGridId = Integer.MAX_VALUE;
                // [Gate-需求2] 加仓后先撤空仓所有止盈+止损,再查交易所持仓后重挂
                cancelAllShortTakeProfitsAndStopLosses();
@@ -666,9 +666,10 @@
//                        }
//                );
                // 空仓止盈:每1张持仓对应1个止盈位,从entry网格向下逐个挂
                // 空仓止盈:超额部分挂止盈,从entry网格向下逐个挂
                int shortBaseQty = Integer.parseInt(config.getBaseQuantity());
                int shortGridQty = Integer.parseInt(config.getQuantity());
                int shortTpCount = posSize / shortGridQty;
                int shortTpCount = posSize > shortBaseQty ? (posSize - shortBaseQty) / shortGridQty : 0;
                for (int i = 0; i < shortTpCount; i++) {
                    int tpGridId = shortGridElement.getId() - 2 * (i + 1);
@@ -698,7 +699,7 @@
                int filledQty = Integer.parseInt(longGridElement.getLongTraderParam().getQuantity());
                longEntryTraderIdParam(longGridElement, orderId, false);
                accumulatedLongLossCount = 0; // 多单成交,重置止损追单计数
                accumulatedLongLossCount = 0;
                lastLongStopLossGridId = Integer.MAX_VALUE;
                // [Gate-需求2] 加仓后先撤多仓所有止盈+止损,再查交易所持仓后重挂
                cancelAllLongTakeProfitsAndStopLosses();
@@ -731,9 +732,10 @@
//                        }
//                );
                // 多仓止盈:每1张持仓对应1个止盈位,从entry网格向上逐个挂
                // 多仓止盈:超额部分挂止盈,从entry网格向上逐个挂
                int longBaseQty = Integer.parseInt(config.getBaseQuantity());
                int longGridQty = Integer.parseInt(config.getQuantity());
                int longTpCount = posSize / longGridQty;
                int longTpCount = posSize > longBaseQty ? (posSize - longBaseQty) / longGridQty : 0;
                for (int i = 0; i < longTpCount; i++) {
                    int tpGridId = longGridElement.getId() + 2 * (i + 1);
@@ -1202,9 +1204,7 @@
                    if (newEntryGrid != null) {
                        GridElement cancelGridElement = GridElement.findById(newEntryGrid.getUpId());
                        String quantity = cancelGridElement != null
                                ? cancelGridElement.getLongTraderParam().getQuantity()
                                : config.getBaseQuantity();
                        String quantity = String.valueOf((accumulatedLongLossCount + 1) * Integer.parseInt(config.getQuantity()));
                        if (cancelGridElement != null && cancelGridElement.isHasLongOrder()) {
                            for (String longOrderId : new ArrayList<>(cancelGridElement.getLongOrderIds())) {
                                executor.cancelConditionalOrder(longOrderId, oid -> {
@@ -1254,9 +1254,7 @@
                        GridElement cancelGridElement = GridElement.findById(newEntryGrid.getDownId());
                        String quantity = cancelGridElement != null
                                ? cancelGridElement.getShortTraderParam().getQuantity()
                                : config.getBaseQuantity();
                        String quantity = String.valueOf((accumulatedShortLossCount + 1) * Integer.parseInt(config.getQuantity()));
                        /**
                         * 看是否有空仓挂单,有就取消
                         */
@@ -1289,12 +1287,11 @@
        gridElement.removeLongStopLossOrderId(orderId);
        int gridId = gridElement.getId();
        // 同一网格的多个止损单触发不累加计数,视为同一批次
        if (gridId != lastLongStopLossGridId) {
            accumulatedLongLossCount++;
            lastLongStopLossGridId = gridId;
        }
        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);
@@ -1308,7 +1305,7 @@
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        int posSize = Math.min(queryPositionSize(Position.ModeEnum.DUAL_LONG), longPositionSize.intValue());
        int maxPos = config.getMaxPositionSize();
        int targetAmount = accumulatedLongLossCount + 1;
        int targetAmount = sameGrid ? Integer.parseInt(config.getQuantity()) : (accumulatedLongLossCount + 1) * Integer.parseInt(config.getQuantity());
        int addSize;
        if (maxPos > 0) {
            int remainingRoom = maxPos - posSize;
@@ -1366,12 +1363,11 @@
        gridElement.removeShortStopLossOrderId(orderId);
        int gridId = gridElement.getId();
        // 同一网格的多个止损单触发不累加计数,视为同一批次
        if (gridId != lastShortStopLossGridId) {
            accumulatedShortLossCount++;
            lastShortStopLossGridId = gridId;
        }
        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);
@@ -1385,7 +1381,7 @@
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        int posSize = Math.min(queryPositionSize(Position.ModeEnum.DUAL_SHORT), shortPositionSize.intValue());
        int maxPos = config.getMaxPositionSize();
        int targetAmount = accumulatedShortLossCount + 1;
        int targetAmount = sameGrid ? Integer.parseInt(config.getQuantity()) : (accumulatedShortLossCount + 1) * Integer.parseInt(config.getQuantity());
        int addSize;
        if (maxPos > 0) {
            int remainingRoom = maxPos - posSize;
@@ -1655,41 +1651,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);
    }