From e1a79a9125fcaac7de0696b22a4c34b2ed4c25af Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Thu, 08 Oct 2026 10:35:07 +0800
Subject: [PATCH] fix(gate-api): 修复网格交易重置策略中的竞态条件问题

---
 src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java |   26 +++++++++++++++-----------
 1 files changed, 15 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 e2f4e88..ac5024b 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -609,14 +609,16 @@
 
                 log.info("[Gate] 重置策略,开始第{}轮...", currentRound);
                 state = StrategyState.STOPPED;
-                try {
-                    futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract());
-                } catch (ApiException e) {
-                    e.printStackTrace();
-                }
-                closeExistingPositions();
-                // 提交到 executor 末尾:单线程FIFO保证前面所有平仓/取消任务完成后才重置
+                // 关键修复:取消+平仓+延时+重启必须整体放进 executor 队列末尾,让上一轮排队的
+                // 挂单任务先执行完(挂到交易所),随后的 cancel 才能取消到它们。若 cancel 仍留在
+                // WS 线程同步执行,会早于上一轮挂单任务,取消不到"尚未挂出"的单(竞态)。
                 executor.submitTask(() -> {
+                    try {
+                        futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract());
+                    } catch (ApiException e) {
+                        e.printStackTrace();
+                    }
+                    closeExistingPositions();
                     try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                     // 注意:startGrid() 会将 currentRound 归零,这里需要保留
                     int savedRound = currentRound;
@@ -836,8 +838,9 @@
                 lastShortStopLossGridId = Integer.MAX_VALUE;
                 // [Gate-需求2] 加仓后先撤空仓所有止盈+止损,再查交易所持仓后重挂
                 cancelAllShortTakeProfitsAndStopLosses();
-                // REST 查询可能因交易所延迟返回旧值,与 WS 本地缓存取最大值兜底
-                int posSize = Math.max(queryPositionSize(Position.ModeEnum.DUAL_SHORT), shortPositionSize.intValue());
+                // REST/WS 持仓可能因交易所延迟返回旧值(仓位归零后重开甚至为 0),
+                // 与本次成交张数 filledQty 取最大值兜底,避免 posSize=0 导致止盈止损全部跳过
+                int posSize = Math.max(Math.max(queryPositionSize(Position.ModeEnum.DUAL_SHORT), shortPositionSize.intValue()), filledQty);
                 extendShortStopLoss(posSize, shortGridElement.getId());
                 // [Gate] 止盈挂单:超出基础仓位的部分,挂在多仓第一止损位
                 if (GateConfig.EXCESS_TP_CONCENTRATED.equals(config.getPlaceExcessTakeProfit())) {
@@ -886,8 +889,9 @@
                 lastLongStopLossGridId = Integer.MAX_VALUE;
                 // [Gate-需求2] 加仓后先撤多仓所有止盈+止损,再查交易所持仓后重挂
                 cancelAllLongTakeProfitsAndStopLosses();
-                // REST 查询可能因交易所延迟返回旧值,与 WS 本地缓存取最大值兜底
-                int posSize = Math.max(queryPositionSize(Position.ModeEnum.DUAL_LONG), longPositionSize.intValue());
+                // REST/WS 持仓可能因交易所延迟返回旧值(仓位归零后重开甚至为 0),
+                // 与本次成交张数 filledQty 取最大值兜底,避免 posSize=0 导致止盈止损全部跳过
+                int posSize = Math.max(Math.max(queryPositionSize(Position.ModeEnum.DUAL_LONG), longPositionSize.intValue()), filledQty);
                 extendLongStopLoss(posSize, longGridElement.getId());
                 // [Gate] 止盈挂单:超出基础仓位的部分,挂在空仓第一止损位
                 if (GateConfig.EXCESS_TP_CONCENTRATED.equals(config.getPlaceExcessTakeProfit())) {

--
Gitblit v1.9.1