Administrator
2 days ago ca21152eec5d609adc0312ca8b67c4f6ac98e838
```
fix(gateApi): 修复止损追单时持仓计算时序竞态问题

- 修改 calculateEntryQuantity 方法签名,添加 stopLossSize 参数用于修正持仓计算
- 在持仓查询后减去本次止损单张数,避免因 REST 查询时序竞态导致的持仓数据滞后
- 更新止损追单调用处,传入配置数量作为止损单张数参数
- 修正多仓和空仓止损追单时的实际下单量计算逻辑
```
1 files modified
18 ■■■■■ changed files
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -1765,7 +1765,7 @@
//                        String quantity = String.valueOf(config.getBaseQuantity());
                        String quantity = calculateEntryQuantity(true, false);
                        String quantity = calculateEntryQuantity(true, false, 0);
                        // 向下检查是否已有多单挂在更低价格网格,有则跳过(防止价格回升后重复挂单)
                        boolean hasLongOrderBelow = false;
                        GridElement checkDownCursor = GridElement.findById(newEntryGrid.getDownId());
@@ -1839,7 +1839,7 @@
//                        String quantity = String.valueOf(Math.max(queryPositionSize(Position.ModeEnum.DUAL_LONG), longPositionSize.intValue()) + Integer.parseInt(config.getQuantity()));
//                        String quantity = String.valueOf(config.getBaseQuantity());
                        String quantity = calculateEntryQuantity(false, false);
                        String quantity = calculateEntryQuantity(false, false, 0);
                        // 向上检查是否已有空单挂在更高价格网格,有则跳过(防止价格回落后重复挂单)
                        boolean hasShortOrderAbove = false;
                        GridElement checkUpCursor = GridElement.findById(newEntryGrid.getUpId());
@@ -1925,9 +1925,10 @@
     *
     * @param isLong                 {@code true}=多仓方向,{@code false}=空仓方向
     * @param respectCurrentPosition {@code true}=按「单边总持仓 ≤ maxPositionPerSide」扣减当前持仓;{@code false}=仅限制单次下单量
     * @param stopLossSize           本次止损单张数(仅止损追单场景传入,用于修正 REST 时序竞态;其余场景传 0)
     * @return 实际下单张数(字符串)
     */
    private String calculateEntryQuantity(boolean isLong, boolean respectCurrentPosition) {
    private String calculateEntryQuantity(boolean isLong, boolean respectCurrentPosition, int stopLossSize) {
        int baseQty = Integer.parseInt(config.getBaseQuantity());
        int interval = config.getAddPositionInterval();
        int addQtyPerUnit = config.getAddPositionQuantity();
@@ -1948,11 +1949,14 @@
            int upperLimit = maxPerSide;
            if (respectCurrentPosition) {
                // 止损追单:单边总持仓不得超过 maxPerSide,追单量 = maxPerSide - 当前持仓。
                // 止损单刚成交、WS 持仓推送尚未到达,必须用 REST 实时查询绕过 WS 缓存时序竞态,
                // 否则会取到滞后的旧持仓(如止损前仍为 10),把追单量错误扣减。
                // 止损单刚成交、REST 查询可能仍返回止损前持仓(时序竞态),
                // 减去本次止损单张数得到止损后的真实持仓,避免追单量被错误扣减。
                int currentPos = isLong
                        ? queryPositionSize(Position.ModeEnum.DUAL_LONG)
                        : queryPositionSize(Position.ModeEnum.DUAL_SHORT);
                if (stopLossSize > 0) {
                    currentPos = Math.max(0, currentPos - stopLossSize);
                }
                upperLimit = maxPerSide - currentPos;
            }
            if (upperLimit <= 0) {
@@ -1998,7 +2002,7 @@
        // 止损追单:同一网格可有多笔挂单,不判断 isHasLongOrder,直接挂单
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        String size = calculateEntryQuantity(true, true);
        String size = calculateEntryQuantity(true, true, Integer.parseInt(config.getQuantity()));
        log.info("[Gate] 多仓止损追单 有效次数:{}, 基础:{}张 → 实际:{}张, 模式:{}",
                getEffectiveStopLossCount(true), config.getBaseQuantity(), size, config.getStopLossCountMode());
        if (Integer.parseInt(size) <= 0) {
@@ -2061,7 +2065,7 @@
        // 止损追单:同一网格可有多笔挂单,不判断 isHasShortOrder,直接挂单
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        String size = calculateEntryQuantity(false, true);
        String size = calculateEntryQuantity(false, true, Integer.parseInt(config.getQuantity()));
        log.info("[Gate] 空仓止损追单 有效次数:{}, 基础:{}张 → 实际:{}张, 模式:{}",
                getEffectiveStopLossCount(false), config.getBaseQuantity(), size, config.getStopLossCountMode());
        if (Integer.parseInt(size) <= 0) {