From 10a177a86145fbf34736306b4bccf4363814d767 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Thu, 11 Jun 2026 12:59:55 +0800
Subject: [PATCH] ``` feat(gateApi): 添加网格交易盈亏检查和自动重置功能

---
 src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java |   69 +++++++++++++++++++++++++++++-----
 1 files changed, 58 insertions(+), 11 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 acc0ce9..6e79f5e 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -14,11 +14,7 @@
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import com.xcong.excoin.modules.gateApi.wsHandler.handler.CandlestickChannelHandler;
 import com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionClosesChannelHandler;
@@ -386,6 +382,10 @@
             return;
         }
 
+
+        checkProfitAndReset();
+
+
         if (state == StrategyState.ACTIVE &&
                 longActive == false &&
                     longPositionSize.compareTo(BigDecimal.ZERO) == 0){
@@ -397,6 +397,46 @@
                 shortActive == false &&
                         shortPositionSize.compareTo(BigDecimal.ZERO) == 0){
             processLongGrid(closePrice);
+        }
+    }
+
+    /** Gate 永续合约 taker 费率 0.05% */
+    private static final BigDecimal TAKER_FEE_RATE = new BigDecimal("0.0005");
+    private void checkProfitAndReset() {
+        try {
+
+            BigDecimal target = initialPrincipal.add(config.getExpectedProfit());
+
+            FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
+            BigDecimal unrealisedPnl = new BigDecimal(account.getCrossUnrealisedPnl());
+            BigDecimal available = new BigDecimal(account.getCrossAvailable());
+            BigDecimal totalEquity = unrealisedPnl.add(available);
+
+            // 估算平仓手续费:(多仓张数+空仓张数) × 合约面值 × 当前价 × taker费率
+            BigDecimal totalSize = longPositionSize.abs().add(shortPositionSize.abs());
+            BigDecimal closeContractValue =
+                    totalSize.multiply(config.getContractMultiplier()).multiply(lastKlinePrice != null ? lastKlinePrice : BigDecimal.ZERO);
+            BigDecimal estimatedFee = closeContractValue.multiply(TAKER_FEE_RATE);
+            BigDecimal netEquity = totalEquity.subtract(estimatedFee);
+            log.info("[Gate] 盈亏检查,总张数:{}, upl:{}, avail:{}, 合计:{}, 估手续费:{}, 净权益:{}, 目标:{}",
+                    totalSize,unrealisedPnl, available, totalEquity, estimatedFee, netEquity, target);
+            if (netEquity.compareTo(target) > 0) {
+                log.info("[Gate] 盈亏达标(净权益{}>目标{}),重置策略", netEquity, target);
+                state = StrategyState.STOPPED;
+                try {
+                    futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract());
+                } catch (ApiException e) {
+                    e.printStackTrace();
+                }
+                closeExistingPositions();
+                // 提交到 executor 末尾:单线程FIFO保证前面所有平仓/取消任务完成后才重置
+                executor.submitTask(() -> {
+                    try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
+                    startGrid();
+                });
+            }
+        } catch (Exception e) {
+            log.warn("[Gate] 盈亏检查失败", e);
         }
     }
 
@@ -458,6 +498,8 @@
                     longPositionSize = size;
                     longEntryPrice = entryPrice;
                 } else {
+
+                    log.info("[Gate-0]多仓: {}", shortBaseEntryPrice);
                     longActive = false;
                     longPositionSize = BigDecimal.ZERO;
                     longEntryPrice = BigDecimal.ZERO;
@@ -468,6 +510,8 @@
                     shortPositionSize = size.abs();
                     shortEntryPrice = entryPrice;
                 } else {
+
+                    log.info("[Gate-0]空仓: {}", shortBaseEntryPrice);
                     shortActive = false;
                     shortPositionSize = BigDecimal.ZERO;
                     shortEntryPrice = BigDecimal.ZERO;
@@ -741,7 +785,9 @@
         longPriceQueue.sort(BigDecimal::compareTo);
         log.info("[Gate] 多队列:{}", longPriceQueue);
         totalShortPriceQueue.sort((a, b) -> b.compareTo(a));
+        log.info("[Gate] 队列从大到小:{}", totalShortPriceQueue);
         totalLongPriceQueue.sort(BigDecimal::compareTo);
+        log.info("[Gate] 队列从小到大:{}", totalLongPriceQueue);
     }
 
     /**
@@ -852,16 +898,17 @@
         log.info("[Gate] 网格元素列表已构建, 共{}个元素 (空仓:{} 位置:0 多仓:{})", elements.size(), shortSize, longSize);
     }
 
+
     private void processShortGrid(BigDecimal currentPrice) {
         BigDecimal matched = BigDecimal.ZERO;
         synchronized (totalLongPriceQueue) {
             for (BigDecimal p : totalLongPriceQueue) {
                 if (p.compareTo(currentPrice) >= 0) {
                     matched =  p;
-                } else {
                     break;
                 }
             }
+//            log.info("[Gate-1] 多仓仓位归零 空仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
             if (BigDecimal.ZERO.compareTo( matched) == 0) {
                 return;
             }
@@ -873,7 +920,7 @@
                     GridElement newEntryGrid = GridElement.findById(upId);
 
                     if (newEntryGrid != null) {
-                        log.info("[Gate] 多仓仓位归零 空仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
+//                        log.info("[Gate-2] 多仓仓位归零 空仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
                         if (!newEntryGrid.isHasLongOrder()) {
                             BigDecimal triggerPrice = newEntryGrid.getGridPrice();
                             String size = config.getBaseQuantity();
@@ -888,8 +935,8 @@
                         GridElement cancelGridElement = GridElement.findById(newEntryGrid.getUpId());
                         if (cancelGridElement != null && cancelGridElement.isHasLongOrder()) {
                             String longOrderId = cancelGridElement.getLongOrderId();
-                            longEntryTraderIdParam(cancelGridElement, null, false);
                             executor.cancelConditionalOrder(longOrderId, oid -> {
+                                longEntryTraderIdParam(cancelGridElement, null, false);
                                 log.info("[Gate] 多仓仓位归零, 取消gridId:{}的多单,{}", cancelGridElement.getId(),longOrderId);
                             });
                         }
@@ -906,10 +953,10 @@
             for (BigDecimal p : totalShortPriceQueue) {
                 if (p.compareTo(currentPrice) <= 0) {
                     matched = p;
-                } else {
                     break;
                 }
             }
+//            log.info("[Gate-3] 空仓仓位归零 多仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
             if (BigDecimal.ZERO.compareTo( matched) == 0) {
                 return;
             }
@@ -921,7 +968,7 @@
                     GridElement newEntryGrid = GridElement.findById(downId);
 
                     if (newEntryGrid != null) {
-                        log.info("[Gate] 空仓仓位归零 多仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
+//                        log.info("[Gate-4] 空仓仓位归零 多仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
                         if (!newEntryGrid.isHasShortOrder()){
                             BigDecimal triggerPrice = newEntryGrid.getGridPrice();
                             String size = config.getBaseQuantity();
@@ -938,8 +985,8 @@
                          */
                         if (cancelGridElement != null && cancelGridElement.isHasShortOrder()) {
                             String shortOrderId = cancelGridElement.getShortOrderId();
-                            shortEntryTraderIdParam(cancelGridElement, null, false);
                             executor.cancelConditionalOrder(shortOrderId, oid -> {
+                                shortEntryTraderIdParam(cancelGridElement, null, false);
                                 log.info("[Gate] 空仓仓位归零, 取消gridId:{}的多单{}", cancelGridElement.getId(),shortOrderId);
                             });
                         }

--
Gitblit v1.9.1