From e37c7f6ac047e43efd1b127f571d91cd75f1e72c Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Thu, 08 Oct 2026 10:37:07 +0800
Subject: [PATCH] fix(gate-api): 修复网格交易重置策略中的竞态条件问题
---
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java | 40 ++++++++++++++++++++++------------------
1 files changed, 22 insertions(+), 18 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 481b041..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())) {
@@ -3053,9 +3057,9 @@
* @param entryGridId 入场网格 ID
*/
private void placeBaseTakeProfit(int posSize, boolean isLong, int entryGridId) {
- int perGrid = config.getBaseTakeProfitPerGrid();
+ BigDecimal perGrid = config.getBaseTakeProfitPerGrid();
int gridCount = config.getBaseTakeProfitGridCount();
- if (perGrid <= 0 || gridCount <= 0) {
+ if (perGrid == null || perGrid.compareTo(BigDecimal.ZERO) <= 0 || gridCount <= 0) {
return;
}
int baseQty = Integer.parseInt(config.getBaseQuantity());
@@ -3078,10 +3082,10 @@
// 分配范围到止损位的前一格截止(与分散止盈一致)
int endId = endElem.getId() - dir;
- int remaining = maxQty;
+ BigDecimal remaining = BigDecimal.valueOf(maxQty);
int placed = 0;
int id = startId;
- while (placed < gridCount && remaining > 0) {
+ while (placed < gridCount && remaining.compareTo(BigDecimal.ZERO) > 0) {
if (isLong ? id > endId : id < endId) {
break;
}
@@ -3089,11 +3093,11 @@
if (elem == null) {
break;
}
- int qty = Math.min(perGrid, remaining);
+ BigDecimal qty = perGrid.min(remaining);
final GridElement fe = elem;
final int gid = id;
final BigDecimal triggerPrice = elem.getGridPrice();
- final String size = isLong ? negate(String.valueOf(qty)) : String.valueOf(qty);
+ final String size = isLong ? negate(qty.toPlainString()) : qty.toPlainString();
final String orderType = isLong ? ORDER_TYPE_CLOSE_LONG : ORDER_TYPE_CLOSE_SHORT;
final FuturesPriceTrigger.RuleEnum rule = isLong ? FuturesPriceTrigger.RuleEnum.NUMBER_1
: FuturesPriceTrigger.RuleEnum.NUMBER_2;
@@ -3108,7 +3112,7 @@
isLong ? "多仓" : "空仓", gid, triggerPrice, size, profitId);
}
);
- remaining -= qty;
+ remaining = remaining.subtract(qty);
placed++;
id += dir;
}
--
Gitblit v1.9.1