Administrator
2026-05-18 f55640b503ec7d40c72f7943b9227ae60452e1c9
第二个版本
2 files added
4 files modified
1317 ■■■■ changed files
src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java 642 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java 7 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java 42 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java 372 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java 222 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
@@ -1,6 +1,8 @@
package com.xcong.excoin.modules.gateApi;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
 * Gate 模块统一配置。
@@ -92,6 +94,12 @@
    private final PnLPriceMode unrealizedPnlPriceMode;
    /** 网格绝对步长(shortBaseEntryPrice × gridRate),运行时由队列生成逻辑设置 */
    private BigDecimal step;
    /** 网格元素列表,由队列初始化时同步填充,包含完整的多空仓挂单状态 */
    private volatile List<GridElement> gridElements = new ArrayList<>();
    /** 基座多头挂单参数,在基座成交后由 tryGenerateQueues 填充 */
    private TraderParam baseLongTraderParam;
    /** 基座空头挂单参数,在基座成交后由 tryGenerateQueues 填充 */
    private TraderParam baseShortTraderParam;
    private GateConfig(Builder builder) {
        this.apiKey = builder.apiKey;
@@ -195,6 +203,28 @@
    /** 设置网格绝对步长(由 generateShortQueue 在运行时计算并注入) */
    public void setStep(BigDecimal step) { this.step = step; }
    // ==================== 网格元素列表 ====================
    /** @return 网格元素列表(队列初始化后填充),线程不安全,仅由策略线程单线程写入 */
    public List<GridElement> getGridElements() { return gridElements; }
    /** 设置网格元素列表(由队列生成逻辑写入),同时重建全局 ID 索引 */
    public void setGridElements(List<GridElement> gridElements) {
        this.gridElements = gridElements;
        GridElement.rebuildIndex(gridElements);
    }
    // ==================== 基座挂单参数 ====================
    /** @return 基座多头挂单参数 */
    public TraderParam getBaseLongTraderParam() { return baseLongTraderParam; }
    /** 设置基座多头挂单参数(由 tryGenerateQueues 在基座成交后填充) */
    public void setBaseLongTraderParam(TraderParam baseLongTraderParam) { this.baseLongTraderParam = baseLongTraderParam; }
    /** @return 基座空头挂单参数 */
    public TraderParam getBaseShortTraderParam() { return baseShortTraderParam; }
    /** 设置基座空头挂单参数(由 tryGenerateQueues 在基座成交后填充) */
    public void setBaseShortTraderParam(TraderParam baseShortTraderParam) { this.baseShortTraderParam = baseShortTraderParam; }
    // ==================== 环境 ====================
    /** @return 是否为生产环境(true=实盘生产网 / false=模拟盘测试网) */
@@ -240,7 +270,7 @@
        /** 补仓最大重试次数,默认 3 */
        private int reopenMaxRetries = 3;
        /** 网格队列容量,默认 50 */
        private int gridQueueSize = 50;
        private int gridQueueSize = 300;
        /** 保证金占初始本金比例上限,默认 0.2(20%) */
        private BigDecimal marginRatioLimit = new BigDecimal("0.2");
        /** 合约乘数,默认 0.001 */
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -360,15 +360,23 @@
            return;
        }
        //初始化0位置的开仓,并且用空的开仓价格,作为价格基准来划分网格
        if (state == StrategyState.WAITING_KLINE) {
            state = StrategyState.OPENING;
            log.info("[Gate] 首根K线到达,开基底仓位...");
            executor.openLong(config.getQuantity(), (orderId) -> {
                log.info("[Gate] 基底多单已提交{}", orderId);
                TraderParam baseLongTp = TraderParam.builder()
                        .entryOrderId(orderId)
                        .build();
                config.setBaseLongTraderParam(baseLongTp);
            }, null);
            executor.openShort(negate(config.getQuantity()), (orderId) -> {
                log.info("[Gate] 基底空单已提交{}",orderId);
                TraderParam baseShortTp = TraderParam.builder()
                        .entryOrderId(orderId)
                        .build();
                config.setBaseShortTraderParam(baseShortTp);
            }, null);
            return;
        }
@@ -422,32 +430,12 @@
                    baseLongOpened = true;
                    log.info("[Gate] 基底多成交价: {}", longBaseEntryPrice);
                    tryGenerateQueues();
                } else if(size.compareTo(longPositionSize) < 0){
                    if (entryPrice.compareTo(shortEntryPrice) > 0
                            && entryPrice.compareTo(longEntryPrice) < 0
                            && shortPositionSize.compareTo(new BigDecimal("3")) < 0) {
                        BigDecimal reverseShortTp = entryPrice.subtract(config.getStep()).setScale(1, RoundingMode.HALF_UP);
                        executor.openShort(negate(config.getQuantity()),
                                orderId -> { currentShortOrderIds.put(orderId, reverseShortTp);},
                               null);
                        log.info("[Gate] 反向条件空单已挂, trigger:{}, size:{}, 止盈:{}", entryPrice, negate(config.getQuantity()), reverseShortTp);
                    }
                } else {
                }else {
                    longPositionSize = size;
                }
            } else {
                longActive = false;
                longPositionSize = BigDecimal.ZERO;
            }
            synchronized (currentLongOrderIds) {
                if (currentLongOrderIds.size() > 5) {
                    Iterator<String> it = currentLongOrderIds.keySet().iterator();
                    for (int i = 0, remove = currentLongOrderIds.size() - 5; i < remove; i++) {
                        it.next();
                        it.remove();
                    }
                }
            }
        } else if (Position.ModeEnum.DUAL_SHORT == mode) {
            if (hasPosition) {
@@ -459,32 +447,12 @@
                    baseShortOpened = true;
                    log.info("[Gate] 基底空成交价: {}", shortBaseEntryPrice);
                    tryGenerateQueues();
                } else if(size.abs().compareTo(shortPositionSize) < 0){
                    if (entryPrice.compareTo(shortEntryPrice) > 0
                            && entryPrice.compareTo(longEntryPrice) < 0
                            && longPositionSize.compareTo(new BigDecimal("3")) < 0) {
                        BigDecimal reverseLongTp = entryPrice.add(config.getStep()).setScale(1, RoundingMode.HALF_UP);
                        executor.openLong(config.getQuantity(),
                                orderId -> { currentLongOrderIds.put(orderId, reverseLongTp);},
                                null);
                        log.info("[Gate] 反向条件多单已挂, trigger:{}, size:{}, 止盈:{}", entryPrice, negate(config.getQuantity()), reverseLongTp);
                    }
                } else {
                }else {
                    shortPositionSize = size.abs();
                }
            } else {
                shortActive = false;
                shortPositionSize = BigDecimal.ZERO;
            }
            synchronized (currentShortOrderIds) {
                if (currentShortOrderIds.size() > 5) {
                    Iterator<String> it = currentShortOrderIds.keySet().iterator();
                    for (int i = 0, remove = currentShortOrderIds.size() - 5; i < remove; i++) {
                        it.next();
                        it.remove();
                    }
                }
            }
        }
    }
@@ -536,18 +504,84 @@
        if (!"finished".equals(status) || !"filled".equals(finishAs)) {
            return;
        }
        BigDecimal longTp = currentLongOrderIds.remove(orderId);
        if (longTp != null) {
            executor.placeTakeProfit(longTp,
                    FuturesPriceTrigger.RuleEnum.NUMBER_1, ORDER_TYPE_CLOSE_LONG, negate(config.getQuantity()));
            log.info("[Gate] 多单成交匹配止盈, orderId:{}, 止盈价:{}, size:{}", orderId, longTp, negate(config.getQuantity()));
            return;
        /**
         * 匹配止盈单止盈
         */
        GridElement byLongTakeProfitOrderId = GridElement.findByLongTakeProfitOrderId(orderId);
        if (byLongTakeProfitOrderId != null){
            longTakeProfitTraderIdParam(
                    byLongTakeProfitOrderId,
                    null,
                    false
            );
            longEntryTraderIdParam(
                    byLongTakeProfitOrderId,
                    null,
                    false
            );
        }
        BigDecimal shortTp = currentShortOrderIds.remove(orderId);
        if (shortTp != null) {
            executor.placeTakeProfit(shortTp,
                    FuturesPriceTrigger.RuleEnum.NUMBER_2, ORDER_TYPE_CLOSE_SHORT, config.getQuantity());
            log.info("[Gate] 空单成交匹配止盈, orderId:{}, 止盈价:{}, size:{}", orderId, shortTp, config.getQuantity());
        GridElement byShortTakeProfitOrderId = GridElement.findByShortTakeProfitOrderId(orderId);
        if (byShortTakeProfitOrderId != null){
            shortTakeProfitTraderIdParam(
                    byLongTakeProfitOrderId,
                    null,
                    false
            );
            shortEntryTraderIdParam(
                    byLongTakeProfitOrderId,
                    null,
                    false
            );
        }
        /**
         * 匹配挂单
         */
        GridElement longGridElement = GridElement.findByLongOrderId(orderId);
        if (longGridElement != null) {
            if (longGridElement.isHasLongOrder()){
                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()));
                        return;
                    }
                }
            }
        }
        GridElement shortGridElement = GridElement.findByShortOrderId(orderId);
        if (shortGridElement != null) {
            if (shortGridElement.isHasShortOrder()){
                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());
                    }
                }
            }
        }
    }
@@ -567,33 +601,135 @@
     */
    private void tryGenerateQueues() {
        if (baseLongOpened && baseShortOpened) {
            //初始化空仓队列
            generateShortQueue();
            //初始化多仓队列
            generateLongQueue();
            //初始化网格数据
            updateGridElements();
            BigDecimal step = config.getStep();
            /**
             * 挂初始位置多空仓条件单
             * 0位置的多单止盈
             * 0位置的空单止盈
             */
            GridElement baseGridElement = GridElement.findById(0);
            TraderParam baseLongTraderParam = config.getBaseLongTraderParam();
            baseGridElement.setLongOrderId(baseLongTraderParam.getEntryOrderId());
            //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());
            BigDecimal downTakeProfitPrice = baseGridElement.getShortTraderParam().getTakeProfitPrice();
            executor.placeTakeProfit(
                    downTakeProfitPrice,
                    FuturesPriceTrigger.RuleEnum.NUMBER_2,
                    ORDER_TYPE_CLOSE_SHORT,
                    config.getQuantity(),
                    profitId -> {
                        shortTakeProfitTraderIdParam(
                                baseGridElement,
                                profitId,
                                true
                        );
                    }
            );
            BigDecimal longPriceQueueOne = longPriceQueue.get(0);
            BigDecimal longTp = longPriceQueueOne.add(step).setScale(1, RoundingMode.HALF_UP);
            executor.placeConditionalEntryOrder(longPriceQueueOne,
                    FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(),
                    orderId -> { currentLongOrderIds.put(orderId, longTp); log.info("[Gate] 初始条件多单已挂, id:{}, trigger:{}, 止盈:{}", orderId, longPriceQueue.get(0), longTp); },
            /**
             * 挂初始位置的up位置的多单
             * 挂初始位置的down位置的空单
             */
            Integer upId = baseGridElement.getUpId();
            GridElement upGridElementOne = GridElement.findById(upId);
            BigDecimal longTp = upGridElementOne.getGridPrice();
            executor.placeConditionalEntryOrder(
                    longTp,
                    FuturesPriceTrigger.RuleEnum.NUMBER_1,
                    config.getQuantity(),
                    orderId -> {
                        longEntryTraderIdParam(
                                upGridElementOne,
                                orderId,
                                true
                        );
                    },
                    null);
            Integer downId = baseGridElement.getDownId();
            GridElement downGridElementOne = GridElement.findById(downId);
            BigDecimal shortTp = downGridElementOne.getGridPrice();
            executor.placeConditionalEntryOrder(
                    shortTp,
                    FuturesPriceTrigger.RuleEnum.NUMBER_2,
                    negate(config.getQuantity()),
                    orderId -> {
                        shortEntryTraderIdParam(
                                downGridElementOne,
                                orderId,
                                true
                        );
                    },
                    null);
            BigDecimal shortPriceQueueOne = shortPriceQueue.get(0);
            BigDecimal shortTp = shortPriceQueueOne.subtract(step).setScale(1, RoundingMode.HALF_UP);
            executor.placeConditionalEntryOrder(shortPriceQueueOne,
                    FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()),
                    orderId -> { currentShortOrderIds.put(orderId, shortTp); log.info("[Gate] 初始条件空单已挂, id:{}, trigger:{}, 止盈:{}", orderId, shortPriceQueue.get(0), shortTp); },
                    null);
            log.info("[Gate] 网格队列已生成, 空队首:{} → 尾:{}, 多队首:{} → 尾:{}, step:{}, 已激活",
                    shortPriceQueueOne, shortPriceQueue.get(shortPriceQueue.size() - 1),
                    longPriceQueueOne, longPriceQueue.get(longPriceQueue.size() - 1),
                    step);
            state = StrategyState.ACTIVE;
        }
    }
    /**
     * 更新基座止盈信息,将止盈价、订单ID等写入 TraderParam 并回填到 ID=0 的网格元素中。
     */
    private void longTakeProfitTraderIdParam(
            GridElement baseElement,String profitId, boolean flag
    ) {
        TraderParam tp = baseElement.getLongTraderParam();
        tp.setTakeProfitOrderId(profitId);
        tp.setTakeProfitPlaced(flag);
        baseElement.setLongTakeProfitOrderId(profitId);
        GridElement.refreshIndices();
    }
    private void shortTakeProfitTraderIdParam(
            GridElement baseElement,String profitId, boolean flag
    ) {
        TraderParam tp = baseElement.getShortTraderParam();
        tp.setTakeProfitOrderId(profitId);
        tp.setTakeProfitPlaced(flag);
        baseElement.setShortTakeProfitOrderId(profitId);
        GridElement.refreshIndices();
    }
    private void longEntryTraderIdParam(
            GridElement baseElement,String entryId,boolean flag
    ) {
        TraderParam tp = baseElement.getLongTraderParam();
        tp.setEntryOrderId(entryId);
        tp.setEntryOrderPlaced(flag);
        baseElement.setHasLongOrder(flag);
        baseElement.setLongOrderId(entryId);
        GridElement.refreshIndices();
    }
    private void shortEntryTraderIdParam(
            GridElement baseElement, String entryId, boolean flag
    ) {
        TraderParam tp = baseElement.getShortTraderParam();
        tp.setEntryOrderId(entryId);
        tp.setEntryOrderPlaced(flag);
        baseElement.setHasShortOrder(flag);
        baseElement.setShortOrderId(entryId);
        GridElement.refreshIndices();
    }
    /**
@@ -607,10 +743,13 @@
        BigDecimal step = shortBaseEntryPrice.multiply(config.getGridRate()).setScale(1, RoundingMode.HALF_UP);
        config.setStep(step);
        BigDecimal elem = shortBaseEntryPrice.subtract(step).setScale(1, RoundingMode.HALF_UP);
        for (int i = 0; i < config.getGridQueueSize(); i++) {
            shortPriceQueue.add(elem);
            elem = elem.subtract(step).setScale(1, RoundingMode.HALF_UP);
        }
            for (int i = 0; i < config.getGridQueueSize(); i++) {
                shortPriceQueue.add(elem);
                elem = elem.subtract(step).setScale(1, RoundingMode.HALF_UP);
                if (elem.compareTo(BigDecimal.ZERO) <= 0) {
                    break;
                }
            }
        shortPriceQueue.sort((a, b) -> b.compareTo(a));
        log.info("[Gate] 空队列:{}", shortPriceQueue);
    }
@@ -630,6 +769,112 @@
        }
        longPriceQueue.sort(BigDecimal::compareTo);
        log.info("[Gate] 多队列:{}", longPriceQueue);
    }
    /**
     * 根据当前多空价格队列同步构建网格元素列表,写入 config。
     *
     * <h3>ID 分配规则</h3>
     * <ul>
     *   <li>空仓队列:id 从 -1 自减(-1, -2, -3...),第一个元素 upId=0,最后一个 downId=null</li>
     *   <li>位置 0:gridPrice=shortBaseEntryPrice,upId=-1,downId=1,其数据在基座开仓时更新</li>
     *   <li>多仓队列:id 从 1 自增(1, 2, 3...),第一个元素 upId=0,最后一个 downId=null</li>
     * </ul>
     *
     * <h3>链表关系</h3>
     * 所有元素通过 upId/downId 串成一条双向链表:
     * ... → -3 → -2 → -1 → 0 → 1 → 2 → 3 → ...
     */
    private void updateGridElements() {
        List<GridElement> elements = new ArrayList<>();
        int shortSize = shortPriceQueue.size();
        int longSize = longPriceQueue.size();
        BigDecimal step = config.getStep().subtract(config.getContractMultiplier());
        String qty = config.getQuantity();
        // 空仓队列:id 从 -1 自减, shortPriceQueue[i] → id=-(i+1)
        for (int i = 0; i < shortSize; i++) {
            int id = -(i + 1);
            Integer upId = (i == 0) ? 0 : id + 1;
            Integer downId = (i == shortSize - 1) ? null : id - 1;
            BigDecimal price = shortPriceQueue.get(i);
            TraderParam longParam = TraderParam.builder()
                    .direction(TraderParam.Direction.LONG)
                    .entryPrice(price)
                    .takeProfitPrice(price.add(step).setScale(1, RoundingMode.HALF_UP))
                    .quantity(qty)
                    .build();
            TraderParam shortParam = TraderParam.builder()
                    .direction(TraderParam.Direction.SHORT)
                    .entryPrice(price)
                    .takeProfitPrice(price.subtract(step).setScale(1, RoundingMode.HALF_UP))
                    .quantity(qty)
                    .build();
            elements.add(GridElement.builder()
                    .id(id)
                    .gridPrice(price)
                    .upId(upId)
                    .downId(downId)
                    .longTraderParam(longParam)
                    .shortTraderParam(shortParam)
                    .build());
        }
        // 位置 0:基底价格,数据在基座开仓时更新
        {
            BigDecimal price = shortBaseEntryPrice;
            TraderParam longParam = TraderParam.builder()
                    .direction(TraderParam.Direction.LONG)
                    .entryPrice(price)
                    .takeProfitPrice(price.add(step).setScale(1, RoundingMode.HALF_UP))
                    .quantity(qty)
                    .build();
            TraderParam shortParam = TraderParam.builder()
                    .direction(TraderParam.Direction.SHORT)
                    .entryPrice(price)
                    .takeProfitPrice(price.subtract(step).setScale(1, RoundingMode.HALF_UP))
                    .quantity(qty)
                    .build();
            elements.add(GridElement.builder()
                    .id(0)
                    .gridPrice(price)
                    .upId(shortSize > 0 ? -1 : null)
                    .downId(longSize > 0 ? 1 : null)
                    .longTraderParam(longParam)
                    .shortTraderParam(shortParam)
                    .build());
        }
        // 多仓队列:id 从 1 自增, longPriceQueue[i] → id=i+1
        for (int i = 0; i < longSize; i++) {
            int id = i + 1;
            Integer upId = (i == 0) ? 0 : id - 1;
            Integer downId = (i == longSize - 1) ? null : id + 1;
            BigDecimal price = longPriceQueue.get(i);
            TraderParam longParam = TraderParam.builder()
                    .direction(TraderParam.Direction.LONG)
                    .entryPrice(price)
                    .takeProfitPrice(price.add(step).setScale(1, RoundingMode.HALF_UP))
                    .quantity(qty)
                    .build();
            TraderParam shortParam = TraderParam.builder()
                    .direction(TraderParam.Direction.SHORT)
                    .entryPrice(price)
                    .takeProfitPrice(price.subtract(step).setScale(1, RoundingMode.HALF_UP))
                    .quantity(qty)
                    .build();
            elements.add(GridElement.builder()
                    .id(id)
                    .gridPrice(price)
                    .upId(upId)
                    .downId(downId)
                    .longTraderParam(longParam)
                    .shortTraderParam(shortParam)
                    .build());
        }
        config.setGridElements(elements);
        log.info("[Gate] 网格元素列表已构建, 共{}个元素 (空仓:{} 位置:0 多仓:{})", elements.size(), shortSize, longSize);
    }
    /**
@@ -683,39 +928,105 @@
            shortPriceQueue.sort((a, b) -> b.compareTo(a));
        }
//        synchronized (longPriceQueue) {
//            BigDecimal first = longPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : longPriceQueue.get(0);
//            BigDecimal gridStep = config.getStep();
//            for (int i = 1; i <= matched.size(); i++) {
//                BigDecimal elem = first.subtract(gridStep.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP);
//                longPriceQueue.add(elem);
//            }
//            longPriceQueue.sort(BigDecimal::compareTo);
//            while (longPriceQueue.size() > config.getGridQueueSize()) {
//                longPriceQueue.remove(longPriceQueue.size() - 1);
//            }
//        }
        synchronized (longPriceQueue) {
            BigDecimal first = longPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : longPriceQueue.get(0);
            BigDecimal gridStep = config.getStep();
            for (int i = 1; i <= matched.size(); i++) {
                BigDecimal elem = first.subtract(gridStep.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP);
                longPriceQueue.add(elem);
            }
            longPriceQueue.sort(BigDecimal::compareTo);
            while (longPriceQueue.size() > config.getGridQueueSize()) {
                longPriceQueue.remove(longPriceQueue.size() - 1);
            }
        }
        if (!isMarginSafe()) {
            log.warn("[Gate] 保证金超限,跳过挂条件单");
        } else {
            BigDecimal newShortFirst = shortPriceQueue.get(0);
            BigDecimal step = config.getStep();
            BigDecimal stpElem = newShortFirst.subtract(step).setScale(1, RoundingMode.HALF_UP);
            executor.placeConditionalEntryOrder(newShortFirst,
                    FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()),
                    orderId -> { currentShortOrderIds.put(orderId, stpElem); log.info("[Gate] 新条件空单, id:{}, trigger:{}, 止盈:{}", orderId, newShortFirst, stpElem); },
                    null);
            /**
             * 下一个开仓位置
             *      获取队列第一个元素的价格对应的网格
             *      判断网格是否能开空仓,如果不能则跳过
             *      前进方向挂空仓条件单
             *      后置方向挂多空条件单
             */
            //下一个开仓位置
            BigDecimal newLongFirst = shortPriceQueue.get(0);
            GridElement UpGridElement = GridElement.findByPrice(newLongFirst);
            BigDecimal newLongFirst = newShortFirst.add( step.multiply(new BigDecimal("2")));
            if (newLongFirst.compareTo(longEntryPrice) < 0) {
            // 判断网格是否能开空仓,如果不能则跳过
            if (UpGridElement != null) {
                BigDecimal ltpElem = newLongFirst.add(step).setScale(1, RoundingMode.HALF_UP);
                executor.placeConditionalEntryOrder(newLongFirst,
                        FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(),
                        orderId -> { currentLongOrderIds.put(orderId, ltpElem); log.info("[Gate] 新条件多单, id:{}, trigger:{}, 止盈:{}", orderId, newLongFirst, ltpElem); },
                        null);
                if (!UpGridElement.isHasShortOrder()) {
                    //挂空仓条件单
                    TraderParam upShortTraderParam = UpGridElement.getShortTraderParam();
                    executor.placeConditionalEntryOrder(
                            upShortTraderParam.getEntryPrice(),
                            FuturesPriceTrigger.RuleEnum.NUMBER_2,
                            negate(upShortTraderParam.getQuantity()),
                            orderId ->
                            {
                                shortEntryTraderIdParam(
                                        UpGridElement,
                                        orderId,
                                        true
                                );
                            },
                            null
                    );
                }
            }
            int i = UpGridElement.getId() + 2;
            GridElement downGridElement = GridElement.findById(i);
            if (downGridElement != null){
                TraderParam downLongTraderParam = downGridElement.getLongTraderParam();
                if (!downGridElement.isHasShortOrder()){
                    executor.placeConditionalEntryOrder(
                            downLongTraderParam.getEntryPrice(),
                            FuturesPriceTrigger.RuleEnum.NUMBER_1,
                            downLongTraderParam.getQuantity(),
                            orderId ->
                            {
                                longEntryTraderIdParam(
                                        downGridElement,
                                        orderId,
                                        true
                                );
                            },
                            null
                    );
                }
                TraderParam downShortTraderParam = downGridElement.getShortTraderParam();
                BigDecimal downGridPrice = downGridElement.getGridPrice();
                if (
                        !downGridElement.isHasShortOrder() &&
                                downGridPrice.compareTo(longEntryPrice) <= 0 &&
                                downGridPrice.compareTo(shortEntryPrice) >= 0
                ){
                    executor.placeConditionalEntryOrder(
                            downShortTraderParam.getEntryPrice(),
                            FuturesPriceTrigger.RuleEnum.NUMBER_2,
                            negate(downShortTraderParam.getQuantity()),
                            orderId ->
                            {
                                shortEntryTraderIdParam(
                                        downGridElement,
                                        orderId,
                                        true
                                );
                            },
                            null
                    );
                }
            }
        }
@@ -763,6 +1074,11 @@
        log.info("[Gate] 多仓队列触发, 匹配{}个元素, 当前价:{}", matched.size(), currentPrice);
        /**
         * 匹配到元素后,
         *  多仓队列更新
         *  空仓队列更新
         */
        synchronized (longPriceQueue) {
            longPriceQueue.removeAll(matched);
            BigDecimal max = longPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : longPriceQueue.get(longPriceQueue.size() - 1);
@@ -773,44 +1089,104 @@
            }
            longPriceQueue.sort(BigDecimal::compareTo);
        }
//        synchronized (shortPriceQueue) {
//            BigDecimal first = shortPriceQueue.isEmpty() ? matched.get(0) : shortPriceQueue.get(0);
//            BigDecimal gridStep = config.getStep();
//            for (int i = 1; i <= matched.size(); i++) {
//                BigDecimal elem = first.add(gridStep.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP);
//                shortPriceQueue.add(elem);
//            }
//            shortPriceQueue.sort((a, b) -> b.compareTo(a));
//            while (shortPriceQueue.size() > config.getGridQueueSize()) {
//                shortPriceQueue.remove(shortPriceQueue.size() - 1);
//            }
//        }
        synchronized (shortPriceQueue) {
            BigDecimal first = shortPriceQueue.isEmpty() ? matched.get(0) : shortPriceQueue.get(0);
            BigDecimal gridStep = config.getStep();
            for (int i = 1; i <= matched.size(); i++) {
                BigDecimal elem = first.add(gridStep.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP);
                shortPriceQueue.add(elem);
            }
            shortPriceQueue.sort((a, b) -> b.compareTo(a));
            while (shortPriceQueue.size() > config.getGridQueueSize()) {
                shortPriceQueue.remove(shortPriceQueue.size() - 1);
            }
        }
        if (!isMarginSafe()) {
            log.warn("[Gate] 保证金超限,跳过挂条件单");
        } else {
            BigDecimal step = config.getStep();
            /**
             * 下一个开仓位置
             *      获取队列第一个元素的价格对应的网格
             *      判断网格是否能开多仓,如果不能则跳过
             *      前进方向挂多仓条件单
             *      后置方向挂多空条件单
             */
            //下一个开仓位置
            BigDecimal newLongFirst = longPriceQueue.get(0);
            BigDecimal ltpElem = newLongFirst.add(step).setScale(1, RoundingMode.HALF_UP);
            executor.placeConditionalEntryOrder(newLongFirst,
                    FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(),
                    orderId -> { currentLongOrderIds.put(orderId, ltpElem); log.info("[Gate] 新条件多单, id:{}, trigger:{}, 止盈:{}", orderId, newLongFirst, ltpElem); },
                    null);
            GridElement UpGridElement = GridElement.findByPrice(newLongFirst);
            // 判断网格是否能开多仓,如果不能则跳过
            if (UpGridElement != null) {
                if (!UpGridElement.isHasLongOrder()) {
                    //挂多仓条件单
                    TraderParam upLongTraderParam = UpGridElement.getLongTraderParam();
                    executor.placeConditionalEntryOrder(
                            upLongTraderParam.getEntryPrice(),
                            FuturesPriceTrigger.RuleEnum.NUMBER_1,
                            config.getQuantity(),
                            orderId ->
                            {
                                longEntryTraderIdParam(
                                        UpGridElement,
                                        orderId,
                                        true
                                );
                            },
                            null
                    );
                }
            }
            BigDecimal newShortFirst = newLongFirst.subtract( step.multiply(new BigDecimal("2")));
            if (newShortFirst.compareTo(shortEntryPrice) > 0){
                BigDecimal stpElem = newShortFirst.subtract(step).setScale(1, RoundingMode.HALF_UP);
                executor.placeConditionalEntryOrder(newShortFirst,
                        FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()),
                        orderId -> { currentShortOrderIds.put(orderId, stpElem); log.info("[Gate] 新条件空单, id:{}, trigger:{}, 止盈:{}", orderId, newShortFirst, stpElem); },
                        null);
            int i = UpGridElement.getId() - 2;
            GridElement downGridElement = GridElement.findById(i);
            if (downGridElement != null){
                TraderParam shortTraderParam = downGridElement.getShortTraderParam();
                if (!downGridElement.isHasShortOrder()){
                    executor.placeConditionalEntryOrder(
                            shortTraderParam.getEntryPrice(),
                            FuturesPriceTrigger.RuleEnum.NUMBER_2,
                            negate(config.getQuantity()),
                            orderId ->
                            {
                                shortEntryTraderIdParam(
                                        downGridElement,
                                        orderId,
                                        true
                                );
                            },
                            null
                    );
                }
                TraderParam downLongTraderParam = downGridElement.getLongTraderParam();
                BigDecimal downGridPrice = downGridElement.getGridPrice();
                if (
                        !downGridElement.isHasLongOrder() &&
                                downGridPrice.compareTo(longEntryPrice) <= 0 &&
                                downGridPrice.compareTo(shortEntryPrice) >= 0
                ){
                    executor.placeConditionalEntryOrder(
                            downLongTraderParam.getEntryPrice(),
                            FuturesPriceTrigger.RuleEnum.NUMBER_1,
                            config.getQuantity(),
                            orderId ->
                            {
                                longEntryTraderIdParam(
                                        downGridElement,
                                        orderId,
                                        true
                                );
                            },
                            null
                    );
                }
            }
        }
src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
@@ -176,13 +176,18 @@
    public void placeTakeProfit(BigDecimal triggerPrice,
                                 FuturesPriceTrigger.RuleEnum rule,
                                 String orderType,
                                 String size) {
                                 String size,
                                Consumer<String> onSuccess) {
        executor.execute(() -> {
            FuturesPriceTriggeredOrder order = buildTriggeredOrder(triggerPrice, rule, orderType, size);
            try {
                TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order);
                log.info("[TradeExec] 止盈单已创建, 触发价:{}, 类型:{}, size:{}, id:{}",
                        triggerPrice, orderType, size, response.getId());
                String orderId = String.valueOf(response.getId());
                if (onSuccess != null) {
                    onSuccess.accept(orderId);
                }
            } catch (Exception e) {
                log.error("[TradeExec] 止盈单创建失败, 触发价:{}, size:{}, 立即市价止盈", triggerPrice, size, e);
                marketClose(size);
src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
@@ -50,39 +50,39 @@
        try {
            //测试盘
//            config = GateConfig.builder()
//                    .apiKey("d90ca272391992b8e74f8f92cedb21ec")
//                    .apiSecret("1861e4f52de4bb53369ea3208d9ede38ece4777368030f96c77d27934c46c274")
//                    .contract("ETH_USDT")
//                    .leverage("100")
//                    .marginMode("CROSS")
//                    .positionMode("dual")
//                    .gridRate(new BigDecimal("0.003"))
//                    .overallTp(new BigDecimal("5"))
//                    .maxLoss(new BigDecimal("15"))
//                    .quantity("1")
//                    .contractMultiplier(new BigDecimal("0.01"))
//                    .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.LAST_PRICE)
//                    .isProduction(false)
//                    .reopenMaxRetries(3)
//                    .build();
            //实盘
            config = GateConfig.builder()
                    .apiKey("865371cdaccd5d238aceb06a55f0143a")
                    .apiSecret("49589c30dfdc3acba007eed445a94990c4b0aa5faac9843e32defdd7371f5a50")
                    .apiKey("d90ca272391992b8e74f8f92cedb21ec")
                    .apiSecret("1861e4f52de4bb53369ea3208d9ede38ece4777368030f96c77d27934c46c274")
                    .contract("ETH_USDT")
                    .leverage("100")
                    .marginMode("CROSS")
                    .positionMode("dual")
                    .gridRate(new BigDecimal("0.005"))
                    .gridRate(new BigDecimal("0.002"))
                    .overallTp(new BigDecimal("5"))
                    .maxLoss(new BigDecimal("15"))
                    .quantity("1")
                    .contractMultiplier(new BigDecimal("0.01"))
                    .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.LAST_PRICE)
                    .isProduction(true)
                    .isProduction(false)
                    .reopenMaxRetries(3)
                    .build();
            //实盘
//            config = GateConfig.builder()
//                    .apiKey("865371cdaccd5d238aceb06a55f0143a")
//                    .apiSecret("49589c30dfdc3acba007eed445a94990c4b0aa5faac9843e32defdd7371f5a50")
//                    .contract("ETH_USDT")
//                    .leverage("100")
//                    .marginMode("CROSS")
//                    .positionMode("dual")
//                    .gridRate(new BigDecimal("0.005"))
//                    .overallTp(new BigDecimal("5"))
//                    .maxLoss(new BigDecimal("15"))
//                    .quantity("1")
//                    .contractMultiplier(new BigDecimal("0.01"))
//                    .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.LAST_PRICE)
//                    .isProduction(true)
//                    .reopenMaxRetries(3)
//                    .build();
            // 1. 初始化交易服务:查用户ID → 切持仓模式 → 清条件单 → 平已有仓位 → 设杠杆
            gridTradeService = new GateGridTradeService(config);
src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java
New file
@@ -0,0 +1,372 @@
package com.xcong.excoin.modules.gateApi;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 网格元素,表示网格交易策略中的一个价格层级。
 *
 * <p>每个网格层级包含该价格位置上的多仓和空仓挂单信息,
 * 通过编号和网格价格唯一标识一个网格层级。
 *
 * <h3>关键字段</h3>
 * <ul>
 *   <li><b>编号</b>:网格层级的唯一标识</li>
 *   <li><b>网格价格</b>:该层级对应的触发价格</li>
 *   <li><b>多仓挂单</b>:是否已有多头挂单、多头挂单参数</li>
 *   <li><b>空仓挂单</b>:是否有空头挂单、空头挂单参数</li>
 * </ul>
 *
 * <h3>使用示例</h3>
 * <pre>
 *   GridElement element = GridElement.builder()
 *       .id(1)
 *       .gridPrice(new BigDecimal("2300.0"))
 *       .build();
 *
 *   // 挂多仓单
 *   TraderParam longParam = TraderParam.builder()
 *       .direction(TraderParam.Direction.LONG)
 *       .entryPrice(new BigDecimal("2293.7"))
 *       .takeProfitPrice(new BigDecimal("2301.6"))
 *       .build();
 *   element.setHasLongOrder(true);
 *   element.setLongTraderParam(longParam);
 * </pre>
 *
 * @author Administrator
 */
public class GridElement {
    /** 网格层级编号 */
    private int id;
    /** 网格触发价格 */
    private BigDecimal gridPrice;
    /** 是否存在多仓挂单 */
    private boolean hasLongOrder;
    /** 是否存在空仓挂单 */
    private boolean hasShortOrder;
    /** 多仓挂单参数 */
    private TraderParam longTraderParam;
    /** 空仓挂单参数 */
    private TraderParam shortTraderParam;
    /** 上一个网格编号,null 表示无上一级 */
    private Integer upId;
    /** 下一个网格编号,null 表示无下一级 */
    private Integer downId;
    /** 多仓挂单订单 ID */
    private String longOrderId;
    /** 空仓挂单订单 ID */
    private String shortOrderId;
    /** 多仓止盈订单 ID */
    private String longTakeProfitOrderId;
    /** 空仓止盈订单 ID */
    private String shortTakeProfitOrderId;
    /** 全局 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
    private static final Map<Integer, GridElement> INDEX = new ConcurrentHashMap<>();
    /** 全局价格索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
    private static final Map<BigDecimal, GridElement> PRICE_INDEX = new ConcurrentHashMap<>();
    /** 全局多仓订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
    private static final Map<String, GridElement> LONG_ORDER_ID_INDEX = new ConcurrentHashMap<>();
    /** 全局空仓订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
    private static final Map<String, GridElement> SHORT_ORDER_ID_INDEX = new ConcurrentHashMap<>();
    /** 全局多仓止盈订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
    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 快速查找网格元素(O(1))。
     *
     * @param id 网格层级编号
     * @return 对应的 GridElement,不存在则返回 null
     */
    public static GridElement findById(int id) {
        return INDEX.get(id);
    }
    /**
     * 根据价格快速查找网格元素(O(1))。
     *
     * @param price 网格价格
     * @return 对应的 GridElement,不存在则返回 null
     */
    public static GridElement findByPrice(BigDecimal price) {
        return PRICE_INDEX.get(price);
    }
    /**
     * 根据多仓挂单订单 ID 快速查找网格元素(O(1))。
     *
     * @param orderId 多仓挂单订单 ID
     * @return 对应的 GridElement,不存在则返回 null
     */
    public static GridElement findByLongOrderId(String orderId) {
        return LONG_ORDER_ID_INDEX.get(orderId);
    }
    /**
     * 根据空仓挂单订单 ID 快速查找网格元素(O(1))。
     *
     * @param orderId 空仓挂单订单 ID
     * @return 对应的 GridElement,不存在则返回 null
     */
    public static GridElement findByShortOrderId(String orderId) {
        return SHORT_ORDER_ID_INDEX.get(orderId);
    }
    /**
     * 根据多仓止盈订单 ID 快速查找网格元素(O(1))。
     *
     * @param orderId 多仓止盈订单 ID
     * @return 对应的 GridElement,不存在则返回 null
     */
    public static GridElement findByLongTakeProfitOrderId(String orderId) {
        return LONG_TP_ORDER_ID_INDEX.get(orderId);
    }
    /**
     * 根据空仓止盈订单 ID 快速查找网格元素(O(1))。
     *
     * @param orderId 空仓止盈订单 ID
     * @return 对应的 GridElement,不存在则返回 null
     */
    public static GridElement findByShortTakeProfitOrderId(String orderId) {
        return SHORT_TP_ORDER_ID_INDEX.get(orderId);
    }
    /**
     * 从列表中重建全局 ID 索引和价格索引。
     * 由 {@link GateConfig#setGridElements(List)} 在每次列表变更后调用。
     */
    public static void rebuildIndex(List<GridElement> elements) {
        INDEX.clear();
        PRICE_INDEX.clear();
        LONG_ORDER_ID_INDEX.clear();
        SHORT_ORDER_ID_INDEX.clear();
        LONG_TP_ORDER_ID_INDEX.clear();
        SHORT_TP_ORDER_ID_INDEX.clear();
        for (GridElement e : elements) {
            INDEX.put(e.getId(), e);
            putDynamicIndices(e);
        }
    }
    /**
     * 刷新动态索引(价格索引、多仓/空仓订单 ID 索引)。
     * 在对 GridElement 的 {@link #longOrderId}、{@link #shortOrderId}、
     * {@link #longTraderParam}、{@link #shortTraderParam} 等字段修改后调用,
     * 使快速查找方法获取到最新数据。
     */
    public static void refreshIndices() {
        PRICE_INDEX.clear();
        LONG_ORDER_ID_INDEX.clear();
        SHORT_ORDER_ID_INDEX.clear();
        LONG_TP_ORDER_ID_INDEX.clear();
        SHORT_TP_ORDER_ID_INDEX.clear();
        for (GridElement e : INDEX.values()) {
            putDynamicIndices(e);
        }
    }
    private static void putDynamicIndices(GridElement e) {
        PRICE_INDEX.put(e.getGridPrice(), e);
        if (e.getLongOrderId() != null) {
            LONG_ORDER_ID_INDEX.put(e.getLongOrderId(), e);
        }
        if (e.getShortOrderId() != null) {
            SHORT_ORDER_ID_INDEX.put(e.getShortOrderId(), e);
        }
        if (e.getLongTakeProfitOrderId() != null) {
            LONG_TP_ORDER_ID_INDEX.put(e.getLongTakeProfitOrderId(), e);
        }
        if (e.getShortTakeProfitOrderId() != null) {
            SHORT_TP_ORDER_ID_INDEX.put(e.getShortTakeProfitOrderId(), e);
        }
    }
    /**
     * @return 根据 upId 获取上一个网格元素,无上一级则返回 null
     */
    public GridElement getUp() {
        return upId != null ? INDEX.get(upId) : null;
    }
    /**
     * @return 根据 downId 获取下一个网格元素,无下一级则返回 null
     */
    public GridElement getDown() {
        return downId != null ? INDEX.get(downId) : null;
    }
    private GridElement(Builder builder) {
        this.id = builder.id;
        this.gridPrice = builder.gridPrice;
        this.hasLongOrder = builder.hasLongOrder;
        this.hasShortOrder = builder.hasShortOrder;
        this.longTraderParam = builder.longTraderParam;
        this.shortTraderParam = builder.shortTraderParam;
        this.upId = builder.upId;
        this.downId = builder.downId;
        this.longOrderId = builder.longOrderId;
        this.shortOrderId = builder.shortOrderId;
        this.longTakeProfitOrderId = builder.longTakeProfitOrderId;
        this.shortTakeProfitOrderId = builder.shortTakeProfitOrderId;
    }
    // ==================== 网格层级编号 ====================
    /** @return 网格层级编号 */
    public int getId() { return id; }
    /** 设置网格层级编号 */
    public void setId(int id) { this.id = id; }
    // ==================== 网格价格 ====================
    /** @return 网格触发价格 */
    public BigDecimal getGridPrice() { return gridPrice; }
    /** 设置网格触发价格 */
    public void setGridPrice(BigDecimal gridPrice) { this.gridPrice = gridPrice; }
    // ==================== 多仓挂单标记 ====================
    /** @return 是否存在多仓挂单 */
    public boolean isHasLongOrder() { return hasLongOrder; }
    /** 标记是否存在多仓挂单 */
    public void setHasLongOrder(boolean hasLongOrder) { this.hasLongOrder = hasLongOrder; }
    // ==================== 空仓挂单标记 ====================
    /** @return 是否存在空仓挂单 */
    public boolean isHasShortOrder() { return hasShortOrder; }
    /** 标记是否存在空仓挂单 */
    public void setHasShortOrder(boolean hasShortOrder) { this.hasShortOrder = hasShortOrder; }
    // ==================== 多仓挂单参数 ====================
    /** @return 多仓挂单参数 */
    public TraderParam getLongTraderParam() { return longTraderParam; }
    /** 设置多仓挂单参数 */
    public void setLongTraderParam(TraderParam longTraderParam) { this.longTraderParam = longTraderParam; }
    // ==================== 空仓挂单参数 ====================
    /** @return 空仓挂单参数 */
    public TraderParam getShortTraderParam() { return shortTraderParam; }
    /** 设置空仓挂单参数 */
    public void setShortTraderParam(TraderParam shortTraderParam) { this.shortTraderParam = shortTraderParam; }
    // ==================== 上一个网格编号 ====================
    /** @return 上一个网格编号,null 表示无上一级 */
    public Integer getUpId() { return upId; }
    /** 设置上一个网格编号 */
    public void setUpId(Integer upId) { this.upId = upId; }
    // ==================== 下一个网格编号 ====================
    /** @return 下一个网格编号,null 表示无下一级 */
    public Integer getDownId() { return downId; }
    /** 设置下一个网格编号 */
    public void setDownId(Integer downId) { this.downId = downId; }
    // ==================== 多仓挂单订单 ID ====================
    /** @return 多仓挂单订单 ID */
    public String getLongOrderId() { return longOrderId; }
    /** 设置多仓挂单订单 ID */
    public void setLongOrderId(String longOrderId) { this.longOrderId = longOrderId; }
    // ==================== 空仓挂单订单 ID ====================
    /** @return 空仓挂单订单 ID */
    public String getShortOrderId() { return shortOrderId; }
    /** 设置空仓挂单订单 ID */
    public void setShortOrderId(String shortOrderId) { this.shortOrderId = shortOrderId; }
    // ==================== 多仓止盈订单 ID ====================
    /** @return 多仓止盈订单 ID */
    public String getLongTakeProfitOrderId() { return longTakeProfitOrderId; }
    /** 设置多仓止盈订单 ID */
    public void setLongTakeProfitOrderId(String longTakeProfitOrderId) { this.longTakeProfitOrderId = longTakeProfitOrderId; }
    // ==================== 空仓止盈订单 ID ====================
    /** @return 空仓止盈订单 ID */
    public String getShortTakeProfitOrderId() { return shortTakeProfitOrderId; }
    /** 设置空仓止盈订单 ID */
    public void setShortTakeProfitOrderId(String shortTakeProfitOrderId) { this.shortTakeProfitOrderId = shortTakeProfitOrderId; }
    public static Builder builder() {
        return new Builder();
    }
    /**
     * GridElement 的流式构造器。
     *
     * <h3>必填项</h3>
     * {@code id}、{@code gridPrice} 必须设置。
     *
     * <h3>默认值</h3>
     * hasLongOrder/hasShortOrder 默认为 false,upId/downId/TraderParam 默认为 null
     */
    public static class Builder {
        /** 网格层级编号(必填) */
        private int id;
        /** 网格触发价格(必填) */
        private BigDecimal gridPrice;
        /** 是否有多仓挂单,默认 false */
        private boolean hasLongOrder = false;
        /** 是否有空仓挂单,默认 false */
        private boolean hasShortOrder = false;
        /** 多仓挂单参数 */
        private TraderParam longTraderParam;
        /** 空仓挂单参数 */
        private TraderParam shortTraderParam;
        /** 上一个网格编号,默认 null(无上一级) */
        private Integer upId;
        /** 下一个网格编号,默认 null(无下一级) */
        private Integer downId;
        /** 多仓挂单订单 ID */
        private String longOrderId;
        /** 空仓挂单订单 ID */
        private String shortOrderId;
        /** 多仓止盈订单 ID */
        private String longTakeProfitOrderId;
        /** 空仓止盈订单 ID */
        private String shortTakeProfitOrderId;
        /** 设置网格层级编号 */
        public Builder id(int id) { this.id = id; return this; }
        /** 设置网格触发价格 */
        public Builder gridPrice(BigDecimal gridPrice) { this.gridPrice = gridPrice; return this; }
        /** 设置是否存在多仓挂单 */
        public Builder hasLongOrder(boolean hasLongOrder) { this.hasLongOrder = hasLongOrder; return this; }
        /** 设置是否存在空仓挂单 */
        public Builder hasShortOrder(boolean hasShortOrder) { this.hasShortOrder = hasShortOrder; return this; }
        /** 设置多仓挂单参数 */
        public Builder longTraderParam(TraderParam longTraderParam) { this.longTraderParam = longTraderParam; return this; }
        /** 设置空仓挂单参数 */
        public Builder shortTraderParam(TraderParam shortTraderParam) { this.shortTraderParam = shortTraderParam; return this; }
        /** 设置上一个网格编号 */
        public Builder upId(Integer upId) { this.upId = upId; return this; }
        /** 设置下一个网格编号 */
        public Builder downId(Integer downId) { this.downId = downId; return this; }
        /** 设置多仓挂单订单 ID */
        public Builder longOrderId(String longOrderId) { this.longOrderId = longOrderId; return this; }
        /** 设置空仓挂单订单 ID */
        public Builder shortOrderId(String shortOrderId) { this.shortOrderId = shortOrderId; return this; }
        /** 设置多仓止盈订单 ID */
        public Builder longTakeProfitOrderId(String longTakeProfitOrderId) { this.longTakeProfitOrderId = longTakeProfitOrderId; return this; }
        /** 设置空仓止盈订单 ID */
        public Builder shortTakeProfitOrderId(String shortTakeProfitOrderId) { this.shortTakeProfitOrderId = shortTakeProfitOrderId; return this; }
        public GridElement build() {
            return new GridElement(this);
        }
    }
}
src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java
New file
@@ -0,0 +1,222 @@
package com.xcong.excoin.modules.gateApi;
import java.math.BigDecimal;
/**
 * 网格交易单参数,封装单笔条件开仓单及其止盈单的完整状态。
 *
 * <p>每笔挂单对应的参数独立存储为一个 TraderParam 实例,
 * 用于追踪条件开仓单和止盈条件单的挂单状态、价格位置及订单 ID。
 *
 * <h3>关键字段</h3>
 * <ul>
 *   <li><b>方向</b>:多 / 空,决定挂单和止盈的触发方向</li>
 *   <li><b>价格</b>:挂单价(条件开仓触发价)、止盈价(止盈触发价)</li>
 *   <li><b>挂单状态</b>:挂单价是否挂成功、止盈价是否挂成功</li>
 *   <li><b>网格位置</b>:挂单价网格位置、止盈价网格位置(用于定位在价格队列中的索引)</li>
 *   <li><b>订单ID</b>:挂单订单 ID、止盈订单 ID(用于取消和匹配)</li>
 * </ul>
 *
 * <h3>使用示例</h3>
 * <pre>
 *   TraderParam param = TraderParam.builder()
 *       .direction(Direction.LONG)
 *       .entryPrice(new BigDecimal("2293.7"))
 *       .takeProfitPrice(new BigDecimal("2301.6"))
 *       .quantity("1")
 *       .entryGridPosition(3)
 *       .takeProfitGridPosition(4)
 *       .build();
 *
 *   // 挂单成功后更新
 *   param.setEntryOrderPlaced(true);
 *   param.setEntryOrderId("12345");
 *
 *   // 止盈单挂成功后更新
 *   param.setTakeProfitPlaced(true);
 *   param.setTakeProfitOrderId("12346");
 * </pre>
 *
 * @author Administrator
 */
public class TraderParam {
    /**
     * 交易方向。
     * <ul>
     *   <li>{@link #LONG} — 多头</li>
     *   <li>{@link #SHORT} — 空头</li>
     * </ul>
     */
    public enum Direction {
        /** 多头方向 */
        LONG,
        /** 空头方向 */
        SHORT
    }
    /** 交易方向(多 / 空) */
    private Direction direction;
    /** 下单数量(合约张数) */
    private String quantity;
    /** 挂单价在网格队列中的位置(索引,-1 表示未定位) */
    private int entryGridPosition;
    /** 条件开仓触发价 */
    private BigDecimal entryPrice;
    /** 挂单(条件开仓单)订单 ID */
    private String entryOrderId;
    /** 挂单价(条件开仓单)是否挂成功 */
    private boolean entryOrderPlaced;
    /** 止盈价在网格队列中的位置(索引,-1 表示未定位) */
    private int takeProfitGridPosition;
    /** 止盈触发价 */
    private BigDecimal takeProfitPrice;
    /** 止盈条件单订单 ID */
    private String takeProfitOrderId;
    /** 止盈价是否挂成功 */
    private boolean takeProfitPlaced;
    private TraderParam(Builder builder) {
        this.direction = builder.direction;
        this.entryPrice = builder.entryPrice;
        this.takeProfitPrice = builder.takeProfitPrice;
        this.quantity = builder.quantity;
        this.takeProfitPlaced = builder.takeProfitPlaced;
        this.entryOrderPlaced = builder.entryOrderPlaced;
        this.entryGridPosition = builder.entryGridPosition;
        this.takeProfitGridPosition = builder.takeProfitGridPosition;
        this.takeProfitOrderId = builder.takeProfitOrderId;
        this.entryOrderId = builder.entryOrderId;
    }
    // ==================== 交易方向 ====================
    /** @return 交易方向(多 / 空) */
    public Direction getDirection() { return direction; }
    /** 设置交易方向 */
    public void setDirection(Direction direction) { this.direction = direction; }
    // ==================== 条件开仓触发价 ====================
    /** @return 条件开仓触发价 */
    public BigDecimal getEntryPrice() { return entryPrice; }
    /** 设置条件开仓触发价 */
    public void setEntryPrice(BigDecimal entryPrice) { this.entryPrice = entryPrice; }
    // ==================== 止盈触发价 ====================
    /** @return 止盈触发价 */
    public BigDecimal getTakeProfitPrice() { return takeProfitPrice; }
    /** 设置止盈触发价 */
    public void setTakeProfitPrice(BigDecimal takeProfitPrice) { this.takeProfitPrice = takeProfitPrice; }
    // ==================== 下单数量 ====================
    /** @return 下单数量(合约张数) */
    public String getQuantity() { return quantity; }
    /** 设置下单数量 */
    public void setQuantity(String quantity) { this.quantity = quantity; }
    // ==================== 挂单价网格位置 ====================
    /** @return 挂单价在网格队列中的索引位置,-1 表示未定位 */
    public int getEntryGridPosition() { return entryGridPosition; }
    /** 设置挂单价在网格队列中的索引位置 */
    public void setEntryGridPosition(int entryGridPosition) { this.entryGridPosition = entryGridPosition; }
    // ==================== 止盈价网格位置 ====================
    /** @return 止盈价在网格队列中的索引位置,-1 表示未定位 */
    public int getTakeProfitGridPosition() { return takeProfitGridPosition; }
    /** 设置止盈价在网格队列中的索引位置 */
    public void setTakeProfitGridPosition(int takeProfitGridPosition) { this.takeProfitGridPosition = takeProfitGridPosition; }
    // ==================== 止盈价是否挂成功 ====================
    /** @return 止盈条件单是否已挂成功 */
    public boolean isTakeProfitPlaced() { return takeProfitPlaced; }
    /** 标记止盈条件单已挂成功 */
    public void setTakeProfitPlaced(boolean takeProfitPlaced) { this.takeProfitPlaced = takeProfitPlaced; }
    // ==================== 挂单价是否挂成功 ====================
    /** @return 条件开仓单是否已挂成功 */
    public boolean isEntryOrderPlaced() { return entryOrderPlaced; }
    /** 标记条件开仓单已挂成功 */
    public void setEntryOrderPlaced(boolean entryOrderPlaced) { this.entryOrderPlaced = entryOrderPlaced; }
    // ==================== 止盈订单 ID ====================
    /** @return 止盈条件单订单 ID(挂成功后由交易所返回) */
    public String getTakeProfitOrderId() { return takeProfitOrderId; }
    /** 记录止盈条件单订单 ID */
    public void setTakeProfitOrderId(String takeProfitOrderId) { this.takeProfitOrderId = takeProfitOrderId; }
    // ==================== 挂单订单 ID ====================
    /** @return 挂单(条件开仓单)订单 ID(挂成功后由交易所返回) */
    public String getEntryOrderId() { return entryOrderId; }
    /** 记录条件开仓单订单 ID */
    public void setEntryOrderId(String entryOrderId) { this.entryOrderId = entryOrderId; }
    public static Builder builder() {
        return new Builder();
    }
    /**
     * TraderParam 的流式构造器。
     *
     * <h3>必填项</h3>
     * {@code direction}、{@code entryPrice}、{@code takeProfitPrice} 必须设置。
     *
     * <h3>默认值</h3>
     * quantity=1 / entryGridPosition=-1 / takeProfitGridPosition=-1 / 挂单状态均为 false
     */
    public static class Builder {
        /** 交易方向(必填) */
        private Direction direction;
        /** 条件开仓触发价(必填) */
        private BigDecimal entryPrice;
        /** 止盈触发价(必填) */
        private BigDecimal takeProfitPrice;
        /** 下单数量,默认 "1" */
        private String quantity = "1";
        /** 止盈价是否挂成功,默认 false */
        private boolean takeProfitPlaced = false;
        /** 挂单价是否挂成功,默认 false */
        private boolean entryOrderPlaced = false;
        /** 挂单价网格位置,默认 -1(未定位) */
        private int entryGridPosition = -1;
        /** 止盈价网格位置,默认 -1(未定位) */
        private int takeProfitGridPosition = -1;
        /** 止盈订单 ID */
        private String takeProfitOrderId;
        /** 挂单订单 ID */
        private String entryOrderId;
        /** 设置交易方向(多 / 空) */
        public Builder direction(Direction direction) { this.direction = direction; return this; }
        /** 设置条件开仓触发价 */
        public Builder entryPrice(BigDecimal entryPrice) { this.entryPrice = entryPrice; return this; }
        /** 设置止盈触发价 */
        public Builder takeProfitPrice(BigDecimal takeProfitPrice) { this.takeProfitPrice = takeProfitPrice; return this; }
        /** 设置下单数量(合约张数) */
        public Builder quantity(String quantity) { this.quantity = quantity; return this; }
        /** 设置止盈价是否已挂成功 */
        public Builder takeProfitPlaced(boolean takeProfitPlaced) { this.takeProfitPlaced = takeProfitPlaced; return this; }
        /** 设置挂单价是否已挂成功 */
        public Builder entryOrderPlaced(boolean entryOrderPlaced) { this.entryOrderPlaced = entryOrderPlaced; return this; }
        /** 设置挂单价在网格队列中的索引位置 */
        public Builder entryGridPosition(int entryGridPosition) { this.entryGridPosition = entryGridPosition; return this; }
        /** 设置止盈价在网格队列中的索引位置 */
        public Builder takeProfitGridPosition(int takeProfitGridPosition) { this.takeProfitGridPosition = takeProfitGridPosition; return this; }
        /** 设置止盈条件单订单 ID */
        public Builder takeProfitOrderId(String takeProfitOrderId) { this.takeProfitOrderId = takeProfitOrderId; return this; }
        /** 设置挂单(条件开仓单)订单 ID */
        public Builder entryOrderId(String entryOrderId) { this.entryOrderId = entryOrderId; return this; }
        public TraderParam build() {
            return new TraderParam(this);
        }
    }
}