Administrator
4 days ago 39b09846041021c5bd0fea92d981ccb3490638d1
fix(gateApi): 修复双向持仓模式下的杠杆设置功能

- 更新文档中的API接口路径和方法名,将updateContractPositionLeverageCall改为updateDualModePositionLeverageCall
- 修改Example.java中的杠杆设置方法调用,适配双向持仓模式
- 移除position_mode参数,因为新方法不再需要此参数
- 为杠杆设置功能添加双向持仓模式专用的注释说明
3 files modified
58 ■■■■ changed files
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java 30 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md 26 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -227,14 +227,10 @@
            state = StrategyState.OPENING;
            log.info("[Gate] 首根K线到达,开基底仓位...");
            executor.openLong(config.getQuantity(), () -> {
                baseLongOpened = true;
                longActive = true;
                log.info("[Gate] 基底多已开");
                log.info("[Gate] 基底多单已提交");
            }, null);
            executor.openShort(negate(config.getQuantity()), () -> {
                baseShortOpened = true;
                shortActive = true;
                log.info("[Gate] 基底空已开");
                log.info("[Gate] 基底空单已提交");
            }, null);
            return;
        }
@@ -267,10 +263,10 @@
                    log.info("[Gate] 基底多成交价: {}", longBaseEntryPrice);
                    tryGenerateQueues();
                } else {
                    executor.placeTakeProfit(entryPrice.add(config.getGridRate()).setScale(1, RoundingMode.HALF_UP),
                    BigDecimal tpPrice = entryPrice.multiply(BigDecimal.ONE.add(config.getGridRate())).setScale(1, RoundingMode.HALF_UP);
                    executor.placeTakeProfit(tpPrice,
                            FuturesPriceTrigger.RuleEnum.NUMBER_1, ORDER_TYPE_CLOSE_LONG, AUTO_SIZE_LONG);
                    log.info("[Gate] 多单止盈已设, entry:{}, tp:{}", entryPrice,
                            entryPrice.add(config.getGridRate()).setScale(1, RoundingMode.HALF_UP));
                    log.info("[Gate] 多单止盈已设, entry:{}, tp:{}", entryPrice, tpPrice);
                }
            } else {
                longActive = false;
@@ -287,10 +283,10 @@
                    log.info("[Gate] 基底空成交价: {}", shortBaseEntryPrice);
                    tryGenerateQueues();
                } else {
                    executor.placeTakeProfit(entryPrice.subtract(config.getGridRate()).setScale(1, RoundingMode.HALF_UP),
                    BigDecimal tpPrice = entryPrice.multiply(BigDecimal.ONE.subtract(config.getGridRate())).setScale(1, RoundingMode.HALF_UP);
                    executor.placeTakeProfit(tpPrice,
                            FuturesPriceTrigger.RuleEnum.NUMBER_2, ORDER_TYPE_CLOSE_SHORT, AUTO_SIZE_SHORT);
                    log.info("[Gate] 空单止盈已设, entry:{}, tp:{}", entryPrice,
                            entryPrice.subtract(config.getGridRate()).setScale(1, RoundingMode.HALF_UP));
                    log.info("[Gate] 空单止盈已设, entry:{}, tp:{}", entryPrice, tpPrice);
                }
            } else {
                shortActive = false;
@@ -334,7 +330,7 @@
        shortPriceQueue.clear();
        BigDecimal step = config.getGridRate();
        for (int i = 1; i <= config.getGridQueueSize(); i++) {
            shortPriceQueue.add(shortBaseEntryPrice.subtract(step.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP));
            shortPriceQueue.add(shortBaseEntryPrice.multiply(BigDecimal.ONE.subtract(step.multiply(BigDecimal.valueOf(i)))).setScale(1, RoundingMode.HALF_UP));
        }
        shortPriceQueue.sort((a, b) -> b.compareTo(a));
        //输出队列:shortPriceQueue;
@@ -345,7 +341,7 @@
        longPriceQueue.clear();
        BigDecimal step = config.getGridRate();
        for (int i = 1; i <= config.getGridQueueSize(); i++) {
            longPriceQueue.add(longBaseEntryPrice.add(step.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP));
            longPriceQueue.add(longBaseEntryPrice.multiply(BigDecimal.ONE.add(step.multiply(BigDecimal.valueOf(i)))).setScale(1, RoundingMode.HALF_UP));
        }
        longPriceQueue.sort(BigDecimal::compareTo);
        log.info("[Gate] 多队列:{}", longPriceQueue);
@@ -378,7 +374,7 @@
            BigDecimal min = shortPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : shortPriceQueue.get(shortPriceQueue.size() - 1);
            BigDecimal step = config.getGridRate();
            for (int i = 0; i < matched.size(); i++) {
                min = min.subtract(step).setScale(1, RoundingMode.HALF_UP);
                min = min.multiply(BigDecimal.ONE.subtract(step)).setScale(1, RoundingMode.HALF_UP);
                shortPriceQueue.add(min);
            }
            shortPriceQueue.sort((a, b) -> b.compareTo(a));
@@ -420,7 +416,7 @@
            BigDecimal max = longPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : longPriceQueue.get(longPriceQueue.size() - 1);
            BigDecimal step = config.getGridRate();
            for (int i = 0; i < matched.size(); i++) {
                max = max.add(step).setScale(1, RoundingMode.HALF_UP);
                max = max.multiply(BigDecimal.ONE.add(step)).setScale(1, RoundingMode.HALF_UP);
                longPriceQueue.add(max);
            }
            longPriceQueue.sort(BigDecimal::compareTo);
@@ -481,6 +477,8 @@
            shortPnl = shortPositionSize.multiply(multiplier).multiply(shortEntryPrice.subtract(price));
        }
        unrealizedPnl = longPnl.add(shortPnl);
        log.info("[Gate] 未实现盈亏: {}", unrealizedPnl);
    }
    /**
src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
@@ -55,7 +55,7 @@
                    .leverage("100")
                    .marginMode("cross")
                    .positionMode("dual")
                    .gridRate(new BigDecimal("0.002"))
                    .gridRate(new BigDecimal("0.004"))
                    .overallTp(new BigDecimal("0.5"))
                    .maxLoss(new BigDecimal("7.5"))
                    .quantity("1")
src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md
@@ -141,12 +141,12 @@
### 网格队列生成
以基底入场价为基准,按 `gridRate` 步长生成 N 个价格(N = gridQueueSize,默认50):
以基底入场价为基准,按 `gridRate`(百分比步长)生成 N 个价格(N = gridQueueSize,默认50):
| 队列 | 计算方式 | 排序 |
|------|---------|------|
| 空仓队列 shortPriceQueue | 基底空入场价 - gridRate × i (i=1..N) | 降序(大→小) |
| 多仓队列 longPriceQueue | 基底多入场价 + gridRate × i (i=1..N) | 升序(小→大) |
| 空仓队列 shortPriceQueue | 基底空入场价 × (1 − gridRate × i) (i=1..N) | 降序(大→小) |
| 多仓队列 longPriceQueue | 基底多入场价 × (1 + gridRate × i) (i=1..N) | 升序(小→大) |
### K线触发网格
@@ -158,7 +158,7 @@
│   ├─ 保证金检查: positionInitialMargin / initialPrincipal < marginRatioLimit(20%)
│   │   ├─ 安全 → openShort 开空单(成交后仓位推送会自动设止盈)
│   │   └─ 超限 → 跳过开仓,队列照常更新
│   ├─ 空仓队列: 移除匹配元素,尾部补充新价格(按步长递减)
│   ├─ 空仓队列: 移除匹配元素,尾部补充新价格(尾价 × (1 − gridRate))
│   └─ 多仓队列: 接收匹配元素,升序排列,截断到 gridQueueSize
└─ processLongGrid: 当前价 > 多仓队列元素(价格涨超了队列中的低价)
@@ -166,22 +166,24 @@
    ├─ 保证金检查: 同上
    │   ├─ 安全 → openLong 开多单
    │   └─ 超限 → 跳过开仓
    ├─ 多仓队列: 移除匹配元素,尾部补充新价格(按步长递增)
    ├─ 多仓队列: 移除匹配元素,尾部补充新价格(尾价 × (1 + gridRate))
    └─ 空仓队列: 接收匹配元素,降序排列,截断到 gridQueueSize
```
### 队列转移示意
```
初始状态:
  空仓队列: [100, 99, 98, 97]  (降序)
  多仓队列: [102, 103, 104, 105] (升序)
ETH_USDT, gridRate=0.0035, 基底空入场价=2275, 基底多入场价=2275:
价格跌到 98.5 → processShortGrid 触发:
  匹配: [100, 99](都 > 98.5)
初始状态:
  空仓队列: [2267.1, 2270.0, 2272.5, 2275.0]  (降序,base×0.9965, base×0.993...)
  多仓队列: [2275.0, 2277.5, 2280.0, 2282.5]  (升序,base×1.0035, base×1.007...)
价格跌到 2271 → processShortGrid 触发:
  匹配: [2275.0, 2272.5](都 > 2271)
  
  空仓队列: 移除[100,99] → [98,97] → 补充[96,95] → [98,97,96,95]
  多仓队列: 接收[100,99] → [100,99,102,103,104,105] → 截断到4 → [102,103,104,105]
  空仓队列: 移除[2275.0,2272.5] → [2270.0,2267.1] → 补充[2267.1×0.9965≈2259.1, 2259.1×0.9965≈2251.2] → [2270.0,2267.1,2259.1,2251.2]
  多仓队列: 接收[2275.0,2272.5] → 合并后截断到4 → [2272.5,2275.0,2277.5,2280.0]
```
---