From e3ce01f4de275faa6d2b05727cf21f70fc5cf73b Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Thu, 07 May 2026 14:22:42 +0800
Subject: [PATCH] feat(gateApi): 添加网格交易的止盈止损功能

---
 src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java |  126 ++++++++++++++++++++++++++++++++++++++---
 1 files changed, 116 insertions(+), 10 deletions(-)

diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
index 13b72e4..e1e01a8 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -5,7 +5,11 @@
 import io.gate.gateapi.GateApiException;
 import io.gate.gateapi.api.FuturesApi;
 import io.gate.gateapi.models.FuturesAccount;
+import io.gate.gateapi.models.FuturesInitialOrder;
 import io.gate.gateapi.models.FuturesOrder;
+import io.gate.gateapi.models.FuturesPriceTrigger;
+import io.gate.gateapi.models.FuturesPriceTriggeredOrder;
+import io.gate.gateapi.models.TriggerOrderResponse;
 import lombok.extern.slf4j.Slf4j;
 
 import java.math.BigDecimal;
@@ -42,6 +46,7 @@
     private final int maxCycles;
     private final BigDecimal maxLoss;
     private final String quantity;
+    private final String positionMode;
 
     private volatile boolean strategyActive = false;
     private int currentCycle = 0;
@@ -50,13 +55,19 @@
     private BigDecimal shortEntryPrice;
     private Long longOrderId;
     private Long shortOrderId;
+    private Long longTpOrderId;
+    private Long longSlOrderId;
+    private Long shortTpOrderId;
+    private Long shortSlOrderId;
 
     private volatile BigDecimal lastClosePrice;
 
     public GateGridTradeService(String apiKey, String apiSecret,
-                                 String contract, String leverage, String marginMode,
+                                 String contract, String leverage,
+                                String marginMode,String positionMode,
                                  BigDecimal gridRate, BigDecimal overallTp,
-                                 int maxCycles, BigDecimal maxLoss, String quantity) {
+                                 int maxCycles, BigDecimal maxLoss,
+                                String quantity) {
         this.contract = contract;
         this.leverage = leverage;
         this.marginMode = marginMode;
@@ -65,6 +76,7 @@
         this.maxCycles = maxCycles;
         this.maxLoss = maxLoss;
         this.quantity = quantity;
+        this.positionMode = positionMode;
 
         this.apiClient = new ApiClient();
         this.apiClient.setBasePath("https://api-testnet.gateapi.io/api/v4");
@@ -77,16 +89,18 @@
      */
     public void init() {
         try {
-            futuresApi.setPositionMode(SETTLE, "dual");
-            log.info("[GateGrid] 已设置双向持仓模式");
-
             futuresApi.updateContractPositionLeverageCall(
-                    SETTLE, contract, leverage, marginMode, "dual", null);
+                    SETTLE, contract, leverage, marginMode, positionMode, null);
             log.info("[GateGrid] 已设置杠杆: {}x, 保证金模式: {}", leverage, marginMode);
 
             FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
             log.info("[GateGrid] 账户可用余额: {}, 总资产: {}",
                     account.getAvailable(), account.getTotal());
+            String positionModeSet = account.getPositionMode();
+            if (!positionMode.equals(positionModeSet)){
+                futuresApi.setPositionMode(SETTLE, positionMode);
+            }
+            log.info("[GateGrid] 已设置双向持仓模式");
         } catch (GateApiException e) {
             log.error("[GateGrid] 初始化失败, label: {}, msg: {}", e.getErrorLabel(), e.getMessage());
         } catch (ApiException e) {
@@ -114,6 +128,10 @@
      */
     public void stopGrid() {
         strategyActive = false;
+        cancelTpSl(longTpOrderId);
+        cancelTpSl(longSlOrderId);
+        cancelTpSl(shortTpOrderId);
+        cancelTpSl(shortSlOrderId);
         closeAllPositions();
         log.info("[GateGrid] 网格策略已停止, 总盈亏: {}, 循环: {}", totalProfit, currentCycle);
     }
@@ -144,6 +162,7 @@
             longOrderId = longResult.getId();
             longEntryPrice = safeDecimal(longResult.getFillPrice());
             log.info("[GateGrid] 开多成功, price: {}, id: {}", longEntryPrice, longOrderId);
+            placeLongTpSl(longEntryPrice);
 
             FuturesOrder shortOrder = new FuturesOrder();
             shortOrder.setContract(contract);
@@ -155,6 +174,7 @@
             shortOrderId = shortResult.getId();
             shortEntryPrice = safeDecimal(shortResult.getFillPrice());
             log.info("[GateGrid] 开空成功, price: {}, id: {}", shortEntryPrice, shortOrderId);
+            placeShortTpSl(shortEntryPrice);
 
             printGridInfo();
         } catch (GateApiException e) {
@@ -163,6 +183,68 @@
         } catch (Exception e) {
             log.error("[GateGrid] 双开异常", e);
             strategyActive = false;
+        }
+    }
+
+    private void placeLongTpSl(BigDecimal entryPrice) {
+        BigDecimal tpPrice = entryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP);
+        BigDecimal slPrice = entryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP);
+        longTpOrderId = placePriceTriggeredOrder(tpPrice, FuturesPriceTrigger.RuleEnum.NUMBER_1, "close-long-position");
+        longSlOrderId = placePriceTriggeredOrder(slPrice, FuturesPriceTrigger.RuleEnum.NUMBER_2, "close-long-position");
+        log.info("[GateGrid] 多头TP/SL已设置, TP:{}, SL:{}, tpId:{}, slId:{}", tpPrice, slPrice, longTpOrderId, longSlOrderId);
+    }
+
+    private void placeShortTpSl(BigDecimal entryPrice) {
+        BigDecimal tpPrice = entryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP);
+        BigDecimal slPrice = entryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP);
+        shortTpOrderId = placePriceTriggeredOrder(tpPrice, FuturesPriceTrigger.RuleEnum.NUMBER_2, "close-short-position");
+        shortSlOrderId = placePriceTriggeredOrder(slPrice, FuturesPriceTrigger.RuleEnum.NUMBER_1, "close-short-position");
+        log.info("[GateGrid] 空头TP/SL已设置, TP:{}, SL:{}, tpId:{}, slId:{}", tpPrice, slPrice, shortTpOrderId, shortSlOrderId);
+    }
+
+    private Long placePriceTriggeredOrder(BigDecimal triggerPrice,
+                                           FuturesPriceTrigger.RuleEnum rule,
+                                           String orderType) {
+        try {
+            FuturesPriceTrigger trigger = new FuturesPriceTrigger();
+            trigger.setStrategyType(FuturesPriceTrigger.StrategyTypeEnum.NUMBER_0);
+            trigger.setPriceType(FuturesPriceTrigger.PriceTypeEnum.NUMBER_0);
+            trigger.setPrice(triggerPrice.toString());
+            trigger.setRule(rule);
+            trigger.setExpiration(0);
+
+            FuturesInitialOrder initial = new FuturesInitialOrder();
+            initial.setContract(contract);
+            initial.setSize(1L);
+            initial.setPrice("0");
+            initial.setTif(FuturesInitialOrder.TifEnum.IOC);
+            initial.setReduceOnly(true);
+
+            FuturesPriceTriggeredOrder order = new FuturesPriceTriggeredOrder();
+            order.setTrigger(trigger);
+            order.setInitial(initial);
+            order.setOrderType(orderType);
+
+            TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order);
+            log.info("[GateGrid] 条件单已创建, triggerPrice:{}, rule:{}, type:{}, id:{}",
+                    triggerPrice, rule, orderType, response.getId());
+            return response.getId();
+        } catch (Exception e) {
+            log.error("[GateGrid] 创建条件单失败, triggerPrice:{}, rule:{}, type:{}",
+                    triggerPrice, rule, orderType, e);
+            return null;
+        }
+    }
+
+    private void cancelTpSl(Long orderId) {
+        if (orderId == null) {
+            return;
+        }
+        try {
+            futuresApi.cancelPriceTriggeredOrder(SETTLE, orderId);
+            log.info("[GateGrid] 已取消条件单, id:{}", orderId);
+        } catch (Exception e) {
+            log.warn("[GateGrid] 取消条件单失败, id:{}", orderId, e);
         }
     }
 
@@ -258,7 +340,9 @@
     }
 
     private void closeLongPosition() {
-        if (longEntryPrice == null) return;
+        if (longEntryPrice == null) {
+            return;
+        }
         try {
             FuturesOrder closeOrder = new FuturesOrder();
             closeOrder.setContract(contract);
@@ -274,10 +358,16 @@
         }
         longEntryPrice = null;
         longOrderId = null;
+        cancelTpSl(longTpOrderId);
+        cancelTpSl(longSlOrderId);
+        longTpOrderId = null;
+        longSlOrderId = null;
     }
 
     private void closeShortPosition() {
-        if (shortEntryPrice == null) return;
+        if (shortEntryPrice == null) {
+            return;
+        }
         try {
             FuturesOrder closeOrder = new FuturesOrder();
             closeOrder.setContract(contract);
@@ -293,6 +383,10 @@
         }
         shortEntryPrice = null;
         shortOrderId = null;
+        cancelTpSl(shortTpOrderId);
+        cancelTpSl(shortSlOrderId);
+        shortTpOrderId = null;
+        shortSlOrderId = null;
     }
 
     private void closeAllPositions() {
@@ -301,10 +395,22 @@
     }
 
     private void printGridInfo() {
+        BigDecimal longTp = BigDecimal.ZERO;
+        BigDecimal longSl = BigDecimal.ZERO;
+        BigDecimal shortTp = BigDecimal.ZERO;
+        BigDecimal shortSl = BigDecimal.ZERO;
+        if (longEntryPrice != null) {
+            longTp = longEntryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP);
+            longSl = longEntryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP);
+        }
+        if (shortEntryPrice != null) {
+            shortTp = shortEntryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP);
+            shortSl = shortEntryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP);
+        }
         System.out.println("========== Gate 网格开仓 ==========");
         System.out.println("合约: " + contract + " 杠杆: " + leverage + "x " + marginMode);
-        System.out.println("多头入场: " + longEntryPrice);
-        System.out.println("空头入场: " + shortEntryPrice);
+        System.out.println("多头入场: " + longEntryPrice + " TP: " + longTp + " SL: " + longSl);
+        System.out.println("空头入场: " + shortEntryPrice + " TP: " + shortTp + " SL: " + shortSl);
         System.out.println("数量: " + quantity + " 网格间距: " + gridRate.multiply(new BigDecimal("100")) + "%");
         System.out.println("整体止盈: " + overallTp + " USDT 最大循环: " + maxCycles);
         System.out.println("最大亏损: " + maxLoss + " USDT");

--
Gitblit v1.9.1