1 files added
2 files modified
1096 ■■■■■ changed files
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java 276 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/simulation/GridSimulator.java 812 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -383,7 +383,7 @@
        }
        checkProfitAndReset();
//        checkProfitAndReset();
        if (state == StrategyState.ACTIVE &&
@@ -606,21 +606,76 @@
        GridElement shortGridElement = GridElement.findByShortOrderId(orderId);
        if (shortGridElement != null) {
            if (shortGridElement.isHasShortOrder() && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0") ){
                int filledQty = Integer.parseInt(shortGridElement.getShortTraderParam().getQuantity());
                shortEntryTraderIdParam(shortGridElement, null, false);
                int filledQty = shortGridElement.getId();
                extendShortStopLoss(filledQty);
                extendShortStopLoss(filledQty,shortGridElement.getId());
                log.info("[Gate] 空单成交 gridId:{}", filledQty);
                // 空仓持仓超过baseQuantity时,从gridId-2开始向外追挂止盈
                BigDecimal shortBaseQty = new BigDecimal(config.getBaseQuantity());
                BigDecimal shortGridQty = new BigDecimal(config.getQuantity());
                if (shortPositionSize.compareTo(shortBaseQty) > 0) {
                    BigDecimal shortExcess = shortPositionSize.subtract(shortBaseQty);
                    int shortExcessCount = shortExcess.divide(shortGridQty, 0, RoundingMode.DOWN).intValue();
                    for (int i = 0; i < shortExcessCount; i++) {
                        int tpGridId = shortGridElement.getId() - 2 - i;
                        GridElement tpElem = GridElement.findById(tpGridId);
                        if (tpElem == null || tpElem.getShortTakeProfitOrderId() != null) {
                            continue;
                        }
                        BigDecimal tpPrice = tpElem.getGridPrice();
                        int finalTpGridId = tpGridId;
                        executor.placeTakeProfit(
                                tpPrice,
                                FuturesPriceTrigger.RuleEnum.NUMBER_2,
                                ORDER_TYPE_CLOSE_SHORT,
                                config.getQuantity(),
                                profitId -> {
                                    shortTakeProfitTraderIdParam(tpElem, profitId, true);
                                    log.info("[Gate] 空仓止盈挂单, gridId:{}, 触发价:{}, takeProfitId:{}",
                                            finalTpGridId, tpPrice, profitId);
                                }
                        );
                    }
                }
            }
        }
        GridElement longGridElement = GridElement.findByLongOrderId(orderId);
        if (longGridElement != null) {
            if (longGridElement.isHasLongOrder() && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")){
                longEntryTraderIdParam(longGridElement, null, false);
                int filledQty = longGridElement.getId();
                extendLongStopLoss(filledQty);
                int filledQty = Integer.parseInt(longGridElement.getLongTraderParam().getQuantity());
                longEntryTraderIdParam(longGridElement, null, false);
                extendLongStopLoss(filledQty,longGridElement.getId());
                log.info("[Gate] 多单成交 gridId:{}", filledQty);
                // 多仓持仓超过baseQuantity时,从gridId+2开始向外追挂止盈
                BigDecimal longBaseQty = new BigDecimal(config.getBaseQuantity());
                BigDecimal longGridQty = new BigDecimal(config.getQuantity());
                if (longPositionSize.compareTo(longBaseQty) > 0) {
                    BigDecimal longExcess = longPositionSize.subtract(longBaseQty);
                    int longExcessCount = longExcess.divide(longGridQty, 0, RoundingMode.DOWN).intValue();
                    for (int i = 0; i < longExcessCount; i++) {
                        int tpGridId = longGridElement.getId() + 2 + i;
                        GridElement tpElem = GridElement.findById(tpGridId);
                        if (tpElem == null || tpElem.getLongTakeProfitOrderId() != null) {
                            continue;
                        }
                        BigDecimal tpPrice = tpElem.getGridPrice();
                        int finalTpGridId = tpGridId;
                        executor.placeTakeProfit(
                                tpPrice,
                                FuturesPriceTrigger.RuleEnum.NUMBER_1,
                                ORDER_TYPE_CLOSE_LONG,
                                negate(config.getQuantity()),
                                profitId -> {
                                    longTakeProfitTraderIdParam(tpElem, profitId, true);
                                    log.info("[Gate] 多仓止盈挂单, gridId:{}, 触发价:{}, takeProfitId:{}",
                                            finalTpGridId, tpPrice, profitId);
                                }
                        );
                    }
                }
            }
        }
    }
@@ -653,39 +708,84 @@
            baseGridElement.setShortOrderId(baseShortTraderParam.getEntryOrderId());
            baseGridElement.setHasShortOrder(true);
            int shortTime = 2;
            GridElement elemShort = GridElement.findById(shortTime);
            if (elemShort != null) {
                BigDecimal triggerPrice = elemShort.getGridPrice();
                String size = config.getBaseQuantity();
//            int shortTime = 2;
//            GridElement elemShort = GridElement.findById(shortTime);
//            if (elemShort != null) {
//                BigDecimal triggerPrice = elemShort.getGridPrice();
//                String size = config.getBaseQuantity();
//                executor.placeTakeProfit(
//                        triggerPrice,
//                        FuturesPriceTrigger.RuleEnum.NUMBER_1,
//                        ORDER_TYPE_CLOSE_SHORT,
//                        size,
//                        profitId -> {
//                            elemShort.setShortStopLossOrderId(profitId);
//                            GridElement.refreshIndices();
//                            log.info("[Gate] 空仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", shortTime, triggerPrice, profitId);
//                        }
//                );
//            }
//
//
//            int longTime = -2;
//            GridElement elemLong = GridElement.findById(longTime);
//            if (elemLong != null) {
//                BigDecimal triggerPrice = elemLong.getGridPrice();
//                String size = config.getBaseQuantity();
//                executor.placeTakeProfit(
//                        triggerPrice,
//                        FuturesPriceTrigger.RuleEnum.NUMBER_2,
//                        ORDER_TYPE_CLOSE_LONG,
//                        negate(size),
//                        profitId -> {
//                            elemLong.setLongStopLossOrderId(profitId);
//                            GridElement.refreshIndices();
//                            log.info("[Gate] 多仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", longTime, triggerPrice, profitId);
//                        }
//                );
//            }
            int shortTime = Integer.parseInt(config.getBaseQuantity()) / Integer.parseInt(config.getQuantity()) + 1;
            for (int id = 2; id <= shortTime; id++) {
                GridElement elem = GridElement.findById(id);
                if (elem == null) {
                    continue;
                }
                BigDecimal triggerPrice = elem.getGridPrice();
                String size = config.getQuantity();
                int finalId = id;
                executor.placeTakeProfit(
                        triggerPrice,
                        FuturesPriceTrigger.RuleEnum.NUMBER_1,
                        ORDER_TYPE_CLOSE_SHORT,
                        size,
                        profitId -> {
                            elemShort.setShortStopLossOrderId(profitId);
                            elem.setShortStopLossOrderId(profitId);
                            GridElement.refreshIndices();
                            log.info("[Gate] 空仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", shortTime, triggerPrice, profitId);
                            log.info("[Gate] 空仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", finalId, triggerPrice, profitId);
                        }
                );
            }
            int longTime = -2;
            GridElement elemLong = GridElement.findById(longTime);
            if (elemLong != null) {
                BigDecimal triggerPrice = elemLong.getGridPrice();
                String size = config.getBaseQuantity();
            int longTime = Integer.parseInt(config.getBaseQuantity()) / Integer.parseInt(config.getQuantity()) + 1;
            for (int id = -2; id >= -longTime; id--) {
                GridElement elem = GridElement.findById(id);
                if (elem == null) {
                    continue;
                }
                BigDecimal triggerPrice = elem.getGridPrice();
                String size = config.getQuantity();
                int finalId = id;
                executor.placeTakeProfit(
                        triggerPrice,
                        FuturesPriceTrigger.RuleEnum.NUMBER_2,
                        ORDER_TYPE_CLOSE_LONG,
                        negate(size),
                        profitId -> {
                            elemLong.setLongStopLossOrderId(profitId);
                            elem.setLongStopLossOrderId(profitId);
                            GridElement.refreshIndices();
                            log.info("[Gate] 多仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", longTime, triggerPrice, profitId);
                            log.info("[Gate] 多仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", finalId, triggerPrice, profitId);
                        }
                );
            }
@@ -811,7 +911,8 @@
        //根据精度转换成小数
        int prec = config.getPriceScale();
        BigDecimal step = config.getStep();
        String qty = config.getBaseQuantity();
//        String qty = config.getBaseQuantity();
        String qty = config.getQuantity();
        // 空仓队列:id 从 -1 自减, shortPriceQueue[i] → id=-(i+1)
        for (int i = 0; i < shortSize; i++) {
@@ -1012,11 +1113,47 @@
        }
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        String size = config.getBaseQuantity();
        log.info("[Gate] 多仓止损触发 gridId:{}, 在gridId:{}挂{}基础张多单=",
        BigDecimal baseQuantity = new BigDecimal(config.getBaseQuantity());
        BigDecimal subtract = baseQuantity.subtract(longPositionSize);
        String size = new BigDecimal(config.getQuantity()).add(new BigDecimal(config.getQuantity())).toString();
        if (subtract.compareTo(BigDecimal.ZERO) > 0){
            size = subtract.add(new BigDecimal(config.getQuantity())).toString();
        }
        log.info("[Gate] 多仓止损触发 gridId:{}, 在gridId:{}挂{}基础张多单",
                gridId, newEntryGridId, size);
        newEntryGrid.getLongTraderParam().setQuantity(size);
        placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice,
                FuturesPriceTrigger.RuleEnum.NUMBER_1, size);
        int cancelGridId = gridId + 2;
        GridElement cancelGrid = GridElement.findById(cancelGridId);
        if (cancelGrid != null && cancelGrid.isHasLongOrder()) {
            executor.cancelConditionalOrder(cancelGrid.getLongOrderId(), oid -> {
                longEntryTraderIdParam(cancelGrid, null, false);
                log.info("[Gate] 多仓止损触发, 取消gridId:{}的多单", cancelGridId);
            });
        }
        // 止损触发时,取消最远的多仓止盈订单
        GridElement farthestLongTp = null;
        for (GridElement e : config.getGridElements()) {
            if (e.getLongTakeProfitOrderId() != null) {
                if (farthestLongTp == null || e.getGridPrice().compareTo(farthestLongTp.getGridPrice()) > 0) {
                    farthestLongTp = e;
                }
            }
        }
        if (farthestLongTp != null) {
            String tpOrderId = farthestLongTp.getLongTakeProfitOrderId();
            GridElement finalFarthestLongTp = farthestLongTp;
            executor.cancelConditionalOrder(tpOrderId, oid -> {
                longTakeProfitTraderIdParam(finalFarthestLongTp, null, false);
                log.info("[Gate] 多仓止损触发, 取消最远止盈 gridId:{}, orderId:{}", finalFarthestLongTp.getId(), tpOrderId);
            });
        }
    }
    private void handleShortStopLossTriggered(GridElement gridElement) {
@@ -1034,27 +1171,73 @@
        }
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        String size =config.getBaseQuantity();
        BigDecimal baseQuantity = new BigDecimal(config.getBaseQuantity());
        BigDecimal subtract = baseQuantity.subtract(shortPositionSize);
        String size = new BigDecimal(config.getQuantity()).add(new BigDecimal(config.getQuantity())).toString();
        if (subtract.compareTo(BigDecimal.ZERO) > 0){
            size = subtract.add(new BigDecimal(config.getQuantity())).toString();
        }
        log.info("[Gate] 空仓止损触发 gridId:{}, 在gridId:{}挂{}基础张空单",
                gridId, newEntryGridId, size);
        newEntryGrid.getShortTraderParam().setQuantity(size);
        placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice,
                FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(size));
        int cancelGridId = gridId - 2;
        GridElement cancelGrid = GridElement.findById(cancelGridId);
        if (cancelGrid != null && cancelGrid.isHasShortOrder()) {
            executor.cancelConditionalOrder(cancelGrid.getShortOrderId(), oid -> {
                shortEntryTraderIdParam(cancelGrid, null, false);
                log.info("[Gate] 空仓止损触发, 取消gridId:{}的空单", cancelGridId);
            });
        }
        // 止损触发时,取消最远的空仓止盈订单
        GridElement farthestShortTp = null;
        for (GridElement e : config.getGridElements()) {
            if (e.getShortTakeProfitOrderId() != null) {
                if (farthestShortTp == null || e.getGridPrice().compareTo(farthestShortTp.getGridPrice()) < 0) {
                    farthestShortTp = e;
                }
            }
        }
        if (farthestShortTp != null) {
            String tpOrderId = farthestShortTp.getShortTakeProfitOrderId();
            GridElement finalFarthestShortTp = farthestShortTp;
            executor.cancelConditionalOrder(tpOrderId, oid -> {
                shortTakeProfitTraderIdParam(finalFarthestShortTp, null, false);
                log.info("[Gate] 空仓止损触发, 取消最远止盈 gridId:{}, orderId:{}", finalFarthestShortTp.getId(), tpOrderId);
            });
        }
    }
    private void extendLongStopLoss(int filledQty) {
        int furthestSlId = filledQty - 2;
        log.info("[Gate] 多仓追挂止损, 当前最远止损gridId:{}", furthestSlId);
        GridElement elem = GridElement.findById(furthestSlId);
        if (elem != null) {
    private void extendLongStopLoss(int filledQty,int gridId) {
        int furthestSlId = 0;
        for (GridElement e : config.getGridElements()) {
            if (e.getLongStopLossOrderId() != null && e.getId() < furthestSlId) {
                furthestSlId = e.getId();
            }
        }
        if (furthestSlId == 0) {
            furthestSlId = gridId;
        }
        log.info("[Gate] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}张", furthestSlId, filledQty);
        for (int i = 0; i < filledQty; i++) {
            int newSlId = furthestSlId - i - 1;
            GridElement elem = GridElement.findById(newSlId);
            if (elem == null) {
                continue;
            }
            BigDecimal triggerPrice = elem.getGridPrice();
            int finalSlId = elem.getId();
            String size = config.getBaseQuantity();
            int finalSlId = newSlId;
            executor.placeTakeProfit(
                    triggerPrice,
                    FuturesPriceTrigger.RuleEnum.NUMBER_2,
                    ORDER_TYPE_CLOSE_LONG,
                    negate(size),
                    negate(config.getQuantity()),
                    profitId -> {
                        elem.setLongStopLossOrderId(profitId);
                        GridElement.refreshIndices();
@@ -1064,19 +1247,30 @@
        }
    }
    private void extendShortStopLoss(int filledQty) {
        int furthestSlId = filledQty + 2;
        log.info("[Gate] 空仓追挂止损, 当前最远止损gridId:{}", furthestSlId);
        GridElement elem = GridElement.findById(furthestSlId);
        if (elem != null) {
    private void extendShortStopLoss(int filledQty, int gridId) {
        int furthestSlId = 0;
        for (GridElement e : config.getGridElements()) {
            if (e.getShortStopLossOrderId() != null && e.getId() > furthestSlId) {
                furthestSlId = e.getId();
            }
        }
        if (furthestSlId == 0) {
            furthestSlId = gridId;
        }
        log.info("[Gate] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}张", furthestSlId, filledQty);
        for (int i = 0; i < filledQty; i++) {
            int newSlId = furthestSlId + i + 1;
            GridElement elem = GridElement.findById(newSlId);
            if (elem == null) {
                continue;
            }
            BigDecimal triggerPrice = elem.getGridPrice();
            int finalSlId = elem.getId();
            String size = config.getBaseQuantity();
            int finalSlId = newSlId;
            executor.placeTakeProfit(
                    triggerPrice,
                    FuturesPriceTrigger.RuleEnum.NUMBER_1,
                    ORDER_TYPE_CLOSE_SHORT,
                    size,
                    config.getQuantity(),
                    profitId -> {
                        elem.setShortStopLossOrderId(profitId);
                        GridElement.refreshIndices();
src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
@@ -64,11 +64,11 @@
                    .leverage("100")
                    .marginMode("CROSS")
                    .positionMode("dual")
                    .gridRate(new BigDecimal("0.0025"))
                    .expectedProfit(new BigDecimal("0.05"))
                    .gridRate(new BigDecimal("0.0035"))
                    .expectedProfit(new BigDecimal("2.5"))
                    .maxLoss(new BigDecimal("1.5"))
                    .baseQuantity("1")
                    .quantity("1")
                    .baseQuantity("2")
                    .quantity("2")
                    .maxPositionSize(2)
                    .priceScale(2)
                    .contractMultiplier(new BigDecimal("0.01"))
src/main/java/com/xcong/excoin/modules/gateApi/simulation/GridSimulator.java
New file
@@ -0,0 +1,812 @@
package com.xcong.excoin.modules.gateApi.simulation;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
/**
 * 网格策略模拟器 — 模拟 GateGridTradeService 完整生命周期并发现逻辑 BUG。
 *
 * <h3>预设参数</h3>
 * shortBaseEntryPrice = 100.0, gridRate = 0.005 (0.5%), step = 0.5, priceScale = 1, baseQuantity = 10, quantity = 1
 */
public class GridSimulator {
    static int SIM_PRICE_PREC = 1;
    static BigDecimal BASE_PRICE = new BigDecimal("100.0");
    static BigDecimal STEP = BASE_PRICE.multiply(new BigDecimal("0.005")).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP); // 0.5
    static int BASE_QTY = 10;
    static int QTY = 1;
    static int GRID_QUEUE_SIZE = 10; // 模拟用缩减队列
    // ---- 网格数据结构 ----
    static class Grid {
        int id;
        BigDecimal price;
        boolean hasLongOrder, hasShortOrder;
        String longOrderId, shortOrderId;
        String longTpOrderId, shortTpOrderId;   // 止盈
        String longSlOrderId, shortSlOrderId;   // 止损
        BigDecimal longTpPrice, shortTpPrice;
        int longFilledQty = QTY, shortFilledQty = QTY;
        Grid(int id, BigDecimal price) {
            this.id = id; this.price = price;
            this.longTpPrice = price.add(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
            this.shortTpPrice = price.subtract(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
        }
        @Override public String toString() {
            return String.format("G[%2d] p=%.1f | L:o=%s tp=%s sl=%s | S:o=%s tp=%s sl=%s",
                id, price,
                hasLongOrder ? "Y" : "N", longTpOrderId != null ? longTpOrderId : "-", longSlOrderId != null ? longSlOrderId : "-",
                hasShortOrder ? "Y" : "N", shortTpOrderId != null ? shortTpOrderId : "-", shortSlOrderId != null ? shortSlOrderId : "-");
        }
    }
    static Map<Integer, Grid> INDEX = new LinkedHashMap<>();
    static List<Grid> gridElements = new ArrayList<>();
    // ---- 队列 ----
    static List<BigDecimal> shortQueue = new ArrayList<>(); // 降序
    static List<BigDecimal> longQueue = new ArrayList<>();  // 升序
    static List<BigDecimal> totalShortQ = new ArrayList<>(); // 降序
    static List<BigDecimal> totalLongQ = new ArrayList<>();  // 升序
    // ---- 状态 ----
    enum State { WAITING_KLINE, OPENING, ACTIVE, STOPPED }
    static State state = State.WAITING_KLINE;
    static boolean baseLongOpened = false, baseShortOpened = false;
    static boolean longActive = false, shortActive = false;
    static BigDecimal longPositionSize = BigDecimal.ZERO;
    static BigDecimal shortPositionSize = BigDecimal.ZERO;
    static BigDecimal longEntryPrice = BigDecimal.ZERO;
    static BigDecimal shortEntryPrice = BigDecimal.ZERO;
    static BigDecimal cumulativePnl = BigDecimal.ZERO;
    static BigDecimal unrealizedPnl = BigDecimal.ZERO;
    static BigDecimal shortBaseEntryPrice = BigDecimal.ZERO;
    static BigDecimal longBaseEntryPrice = BigDecimal.ZERO;
    static int orderIdCounter = 1000;
    static List<String> eventLog = new ArrayList<>();
    static List<String> bugLog = new ArrayList<>();
    static void log(String msg) { eventLog.add(msg); System.out.println("  " + msg); }
    static void bug(String msg) { String s = "  [BUG] " + msg; bugLog.add(s); System.out.println(s); }
    static String newOrderId() { return "O-" + (orderIdCounter++); }
    static String repeat(String s, int n) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) sb.append(s);
        return sb.toString();
    }
    // ================================================================
    // 步骤1: 初始化
    // ================================================================
    static void init() {
        log("=== Phase 1: 初始化 ===");
        log("参数: basePrice=" + BASE_PRICE + " step=" + STEP + " baseQty=" + BASE_QTY + " qty=" + QTY);
        state = State.WAITING_KLINE;
    }
    // ================================================================
    // 步骤2: 首根K线 → 市价双开基底
    // ================================================================
    static void onFirstKline() {
        log("\n=== Phase 2: 首根K线到达,市价双开基底 ===");
        state = State.OPENING;
        log("市价开多 " + BASE_QTY + " 张,开空 " + BASE_QTY + " 张");
        // 模拟成交
        longPositionSize = new BigDecimal(BASE_QTY);
        shortPositionSize = new BigDecimal(BASE_QTY);
        longEntryPrice = BASE_PRICE;
        shortEntryPrice = BASE_PRICE;
        longActive = true;
        shortActive = true;
        // 模拟 onPositionUpdate → 基底成交
        log("基底多成交价: " + BASE_PRICE);
        longBaseEntryPrice = BASE_PRICE;
        baseLongOpened = true;
        log("基底空成交价: " + BASE_PRICE);
        shortBaseEntryPrice = BASE_PRICE;
        baseShortOpened = true;
        tryGenerateQueues();
    }
    // ================================================================
    // 步骤3: 生成队列和网格
    // ================================================================
    static void tryGenerateQueues() {
        if (!baseLongOpened || !baseShortOpened) return;
        log("\n=== Phase 3: 生成网格队列 ===");
        // 生成队列
        generateShortQueue();
        generateLongQueue();
        updateGridElements();
        // 标记基底元素 (模拟 baseLongTraderParam/baseShortTraderParam)
        Grid baseG = INDEX.get(0);
        baseG.hasLongOrder = true;
        baseG.longOrderId = "BASE-LONG-IOC";
        baseG.hasShortOrder = true;
        baseG.shortOrderId = "BASE-SHORT-IOC";
        log("基底元素 ID=0 标记 hasLongOrder=true, hasShortOrder=true");
        // 初始化止损单
        initStopLossOrders();
        state = State.ACTIVE;
        log("状态切换为 ACTIVE");
        printGridState();
    }
    static void generateShortQueue() {
        shortQueue.clear();
        BigDecimal elem = BASE_PRICE.subtract(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
        for (int i = 0; i < GRID_QUEUE_SIZE; i++) {
            shortQueue.add(elem);
            totalLongQ.add(elem);
            totalShortQ.add(elem);
            elem = elem.subtract(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
            if (elem.compareTo(BigDecimal.ZERO) <= 0) break;
        }
        shortQueue.sort((a, b) -> b.compareTo(a));
        log("空仓队列(降序): " + shortQueue);
    }
    static void generateLongQueue() {
        longQueue.clear();
        BigDecimal elem = BASE_PRICE.add(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
        for (int i = 0; i < GRID_QUEUE_SIZE; i++) {
            longQueue.add(elem);
            totalLongQ.add(elem);
            totalShortQ.add(elem);
            elem = elem.add(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
        }
        longQueue.sort(BigDecimal::compareTo);
        log("多仓队列(升序): " + longQueue);
    }
    static void updateGridElements() {
        gridElements.clear();
        INDEX.clear();
        int shortSize = shortQueue.size();
        int longSize = longQueue.size();
        // 空仓区域: id 从 -1 自减
        for (int i = 0; i < shortSize; i++) {
            int id = -(i + 1);
            BigDecimal price = shortQueue.get(i);
            Grid g = new Grid(id, price);
            g.longFilledQty = QTY;
            g.shortFilledQty = QTY;
            gridElements.add(g);
            INDEX.put(id, g);
        }
        // 位置 0
        Grid g0 = new Grid(0, BASE_PRICE);
        g0.longFilledQty = QTY;
        g0.shortFilledQty = QTY;
        gridElements.add(g0);
        INDEX.put(0, g0);
        // 多仓区域: id 从 1 自增
        for (int i = 0; i < longSize; i++) {
            int id = i + 1;
            BigDecimal price = longQueue.get(i);
            Grid g = new Grid(id, price);
            g.longFilledQty = QTY;
            g.shortFilledQty = QTY;
            gridElements.add(g);
            INDEX.put(id, g);
        }
        log("网格元素: " + gridElements.size() + " 个");
    }
    static void initStopLossOrders() {
        log("\n--- 初始化止损单 ---");
        // 空仓止损: ID 2 到 BASE_QTY+1
        int shortTime = BASE_QTY + 1;
        for (int id = 2; id <= shortTime; id++) {
            Grid g = INDEX.get(id);
            if (g == null) continue;
            String slId = newOrderId();
            g.shortSlOrderId = slId;
            log(String.format("  空仓止损 gridId:%d price:%.1f slId:%s (CLOSE_SHORT NUMBER_1)", id, g.price, slId));
        }
        // 多仓止损: ID -2 到 -(BASE_QTY+1)
        int longTime = BASE_QTY + 1;
        for (int id = -2; id >= -longTime; id--) {
            Grid g = INDEX.get(id);
            if (g == null) continue;
            String slId = newOrderId();
            g.longSlOrderId = slId;
            log(String.format("  多仓止损 gridId:%d price:%.1f slId:%s (CLOSE_LONG NUMBER_2)", id, g.price, slId));
        }
        log("止损单初始化完成");
    }
    // ================================================================
    // 步骤4: onKline ACTIVE 状态下的网格处理
    // ================================================================
    static void onKline(BigDecimal closePrice) {
        if (state != State.ACTIVE) return;
        if (!longActive && longPositionSize.compareTo(BigDecimal.ZERO) == 0) {
            processShortGrid(closePrice);
        }
        if (!shortActive && shortPositionSize.compareTo(BigDecimal.ZERO) == 0) {
            processLongGrid(closePrice);
        }
    }
    static void processShortGrid(BigDecimal currentPrice) {
        log("\n--- processShortGrid (多仓归零, 触发空仓队列) 当前价:" + currentPrice + " ---");
        BigDecimal matched = BigDecimal.ZERO;
        for (BigDecimal p : totalLongQ) {
            if (p.compareTo(currentPrice) >= 0) {
                matched = p;
                break;
            }
        }
        if (matched.compareTo(BigDecimal.ZERO) == 0) {
            log("  未匹配到价格");
            return;
        }
        log("  匹配价格: " + matched);
        Grid matchedG = findByPrice(matched);
        if (matchedG == null) { log("  Grid不存在"); return; }
        if (!matchedG.hasLongOrder) {
            // 在 upId 位置挂多单
            int upId = matchedG.id + 1; // upId逻辑简化
            Grid newEntryG = INDEX.get(upId);
            if (newEntryG != null && !newEntryG.hasLongOrder) {
                String oid = newOrderId();
                newEntryG.hasLongOrder = true;
                newEntryG.longOrderId = oid;
                newEntryG.longFilledQty = BASE_QTY;
                log(String.format("  在gridId:%d挂多单(size=%d) orderId:%s", upId, BASE_QTY, oid));
            }
            // 取消upId+1位置的多单
            int cancelId = upId + 1;
            Grid cancelG = INDEX.get(cancelId);
            if (cancelG != null && cancelG.hasLongOrder) {
                log(String.format("  取消gridId:%d的多单 orderId:%s", cancelId, cancelG.longOrderId));
                cancelG.hasLongOrder = false;
                cancelG.longOrderId = null;
            }
            // BUG: 这里新挂的单 size = BASE_QTY,但如果之前已有其他追单
            // 可能导致持仓远超预期
        }
    }
    static void processLongGrid(BigDecimal currentPrice) {
        log("\n--- processLongGrid (空仓归零, 触发多仓队列) 当前价:" + currentPrice + " ---");
        BigDecimal matched = BigDecimal.ZERO;
        for (BigDecimal p : totalShortQ) {
            if (p.compareTo(currentPrice) <= 0) {
                matched = p;
                break;
            }
        }
        if (matched.compareTo(BigDecimal.ZERO) == 0) {
            log("  未匹配到价格");
            return;
        }
        log("  匹配价格: " + matched);
        Grid matchedG = findByPrice(matched);
        if (matchedG == null) { log("  Grid不存在"); return; }
        if (!matchedG.hasShortOrder) {
            int downId = matchedG.id - 1;
            Grid newEntryG = INDEX.get(downId);
            if (newEntryG != null && !newEntryG.hasShortOrder) {
                String oid = newOrderId();
                newEntryG.hasShortOrder = true;
                newEntryG.shortOrderId = oid;
                newEntryG.shortFilledQty = BASE_QTY;
                log(String.format("  在gridId:%d挂空单(size=%d) orderId:%s", downId, BASE_QTY, oid));
            }
            int cancelId = downId - 1;
            Grid cancelG = INDEX.get(cancelId);
            if (cancelG != null && cancelG.hasShortOrder) {
                log(String.format("  取消gridId:%d的空单 orderId:%s", cancelId, cancelG.shortOrderId));
                cancelG.hasShortOrder = false;
                cancelG.shortOrderId = null;
            }
        }
    }
    // ================================================================
    // 步骤5: onAutoOrder 条件单成交回调
    // ================================================================
    static void onAutoOrder(String orderId, String status, String orderType, String tradeId) {
        if (state == State.STOPPED) return;
        if (!"finished".equals(status)) return;
        log("\n--- onAutoOrder: id=" + orderId + " type=" + orderType + " tradeId=" + tradeId + " ---");
        // 1. 检查是否是止损单成交
        Grid longSl = findByLongSlOrderId(orderId);
        if (longSl != null && tradeId != null && !"0".equals(tradeId)) {
            handleLongStopLossTriggered(longSl);
            return;
        }
        Grid shortSl = findByShortSlOrderId(orderId);
        if (shortSl != null && tradeId != null && !"0".equals(tradeId)) {
            handleShortStopLossTriggered(shortSl);
            return;
        }
        // 2. 检查是否是空仓挂单成交
        Grid shortG = findByShortOrderId(orderId);
        if (shortG != null && shortG.hasShortOrder && tradeId != null && !"0".equals(tradeId)) {
            int filledQty = shortG.shortFilledQty;
            shortG.hasShortOrder = false;
            shortG.shortOrderId = null;
            extendShortStopLoss(filledQty);
            log(String.format("  空单成交 gridId:%d filledQty:%d", shortG.id, filledQty));
            // 新逻辑: 持仓超过baseQuantity时,从gridId-2开始追挂止盈
            BigDecimal baseQty = new BigDecimal(BASE_QTY);
            BigDecimal qty = new BigDecimal(QTY);
            if (shortPositionSize.compareTo(baseQty) > 0) {
                BigDecimal excess = shortPositionSize.subtract(baseQty);
                int excessCount = excess.divide(qty, 0, RoundingMode.DOWN).intValue();
                for (int i = 0; i < excessCount; i++) {
                    int tpId = shortG.id - 2 - i;
                    Grid tpG = INDEX.get(tpId);
                    if (tpG == null || tpG.shortTpOrderId != null) continue;
                    String tpIdStr = newOrderId();
                    tpG.shortTpOrderId = tpIdStr;
                    log(String.format("  空仓止盈挂单 gridId:%d price:%.1f tpId:%s", tpId, tpG.price, tpIdStr));
                }
            }
        }
        // 3. 检查是否是多仓挂单成交
        Grid longG = findByLongOrderId(orderId);
        if (longG != null && longG.hasLongOrder && tradeId != null && !"0".equals(tradeId)) {
            int filledQty = longG.longFilledQty;
            longG.hasLongOrder = false;
            longG.longOrderId = null;
            extendLongStopLoss(filledQty);
            log(String.format("  多单成交 gridId:%d filledQty:%d", longG.id, filledQty));
            BigDecimal baseQty = new BigDecimal(BASE_QTY);
            BigDecimal qty = new BigDecimal(QTY);
            if (longPositionSize.compareTo(baseQty) > 0) {
                BigDecimal excess = longPositionSize.subtract(baseQty);
                int excessCount = excess.divide(qty, 0, RoundingMode.DOWN).intValue();
                for (int i = 0; i < excessCount; i++) {
                    int tpId = longG.id + 2 + i;
                    Grid tpG = INDEX.get(tpId);
                    if (tpG == null || tpG.longTpOrderId != null) continue;
                    String tpIdStr = newOrderId();
                    tpG.longTpOrderId = tpIdStr;
                    log(String.format("  多仓止盈挂单 gridId:%d price:%.1f tpId:%s", tpId, tpG.price, tpIdStr));
                }
            }
        }
    }
    static void handleLongStopLossTriggered(Grid g) {
        log("  多仓止损触发 gridId:" + g.id);
        g.longSlOrderId = null;
        // 模拟平仓: longPositionSize 减少
        longPositionSize = longPositionSize.subtract(new BigDecimal(QTY));
        if (longPositionSize.compareTo(BigDecimal.ZERO) < 0) longPositionSize = BigDecimal.ZERO;
        // 在gridId+1位置挂新追单
        int newId = g.id + 1;
        Grid newG = INDEX.get(newId);
        if (newG == null) {
            log("  gridId:" + newId + " 不存在,止损追单失败");
            return;
        }
        // 计算size
        BigDecimal baseQty = new BigDecimal(BASE_QTY);
        BigDecimal subtract = baseQty.subtract(longPositionSize);
        int size = QTY + 1;
        if (subtract.compareTo(BigDecimal.ZERO) >= 0) {
            size = subtract.intValue() + 1;
        }
        log(String.format("  挂多单 gridId:%d size:%d (pos=%s)", newId, size, longPositionSize));
        newG.hasLongOrder = true;
        newG.longOrderId = newOrderId();
        newG.longFilledQty = size;
        // 取消gridId+2的多单
        int cancelId = g.id + 2;
        Grid cancelG = INDEX.get(cancelId);
        if (cancelG != null && cancelG.hasLongOrder) {
            log(String.format("  取消gridId:%d的多单 orderId:%s", cancelId, cancelG.longOrderId));
            cancelG.hasLongOrder = false;
            cancelG.longOrderId = null;
        }
        // 取消最远多仓止盈
        Grid farthest = null;
        for (Grid e : gridElements) {
            if (e.longTpOrderId != null) {
                if (farthest == null || e.price.compareTo(farthest.price) > 0) {
                    farthest = e;
                }
            }
        }
        if (farthest != null) {
            log(String.format("  取消最远多仓止盈 gridId:%d orderId:%s", farthest.id, farthest.longTpOrderId));
            farthest.longTpOrderId = null;
        }
        // 持仓归零检查
        if (longPositionSize.compareTo(BigDecimal.ZERO) == 0) {
            longActive = false;
            log("  多仓已全部止损,longActive=false");
        }
    }
    static void handleShortStopLossTriggered(Grid g) {
        log("  空仓止损触发 gridId:" + g.id);
        g.shortSlOrderId = null;
        shortPositionSize = shortPositionSize.subtract(new BigDecimal(QTY));
        if (shortPositionSize.compareTo(BigDecimal.ZERO) < 0) shortPositionSize = BigDecimal.ZERO;
        int newId = g.id - 1;
        Grid newG = INDEX.get(newId);
        if (newG == null) {
            log("  gridId:" + newId + " 不存在,止损追单失败");
            return;
        }
        BigDecimal baseQty = new BigDecimal(BASE_QTY);
        BigDecimal subtract = baseQty.subtract(shortPositionSize);
        int size = QTY + 1;
        if (subtract.compareTo(BigDecimal.ZERO) >= 0) {
            size = subtract.intValue() + 1;
        }
        log(String.format("  挂空单 gridId:%d size:%d (pos=%s)", newId, size, shortPositionSize));
        newG.hasShortOrder = true;
        newG.shortOrderId = newOrderId();
        newG.shortFilledQty = size;
        int cancelId = g.id - 2;
        Grid cancelG = INDEX.get(cancelId);
        if (cancelG != null && cancelG.hasShortOrder) {
            log(String.format("  取消gridId:%d的空单 orderId:%s", cancelId, cancelG.shortOrderId));
            cancelG.hasShortOrder = false;
            cancelG.shortOrderId = null;
        }
        Grid farthest = null;
        for (Grid e : gridElements) {
            if (e.shortTpOrderId != null) {
                if (farthest == null || e.price.compareTo(farthest.price) < 0) {
                    farthest = e;
                }
            }
        }
        if (farthest != null) {
            log(String.format("  取消最远空仓止盈 gridId:%d orderId:%s", farthest.id, farthest.shortTpOrderId));
            farthest.shortTpOrderId = null;
        }
        if (shortPositionSize.compareTo(BigDecimal.ZERO) == 0) {
            shortActive = false;
            log("  空仓已全部止损,shortActive=false");
        }
    }
    static void extendLongStopLoss(int filledQty) {
        // 找到最远止损
        int furthestId = 0;
        for (Grid e : gridElements) {
            if (e.longSlOrderId != null && e.id < furthestId) {
                furthestId = e.id;
            }
        }
        if (furthestId == 0) furthestId = -11;
        for (int i = 0; i < filledQty; i++) {
            int newId = furthestId - i - 1;
            Grid g = INDEX.get(newId);
            if (g == null) continue;
            String slId = newOrderId();
            g.longSlOrderId = slId;
            log(String.format("  多仓止损追加 gridId:%d slId:%s", newId, slId));
        }
    }
    static void extendShortStopLoss(int filledQty) {
        int furthestId = 0;
        for (Grid e : gridElements) {
            if (e.shortSlOrderId != null && e.id > furthestId) {
                furthestId = e.id;
            }
        }
        if (furthestId == 0) furthestId = 11;
        for (int i = 0; i < filledQty; i++) {
            int newId = furthestId + i + 1;
            Grid g = INDEX.get(newId);
            if (g == null) continue;
            String slId = newOrderId();
            g.shortSlOrderId = slId;
            log(String.format("  空仓止损追加 gridId:%d slId:%s", newId, slId));
        }
    }
    // ================================================================
    // 模拟 onPositionUpdate
    // ================================================================
    static void onPositionUpdate(boolean isLong, BigDecimal size, BigDecimal entryPrice) {
        if (isLong) {
            if (size.compareTo(BigDecimal.ZERO) > 0) {
                longActive = true;
                longPositionSize = size;
                longEntryPrice = entryPrice;
            } else {
                longActive = false;
                longPositionSize = BigDecimal.ZERO;
                longEntryPrice = BigDecimal.ZERO;
            }
        } else {
            if (size.compareTo(BigDecimal.ZERO) > 0) {
                shortActive = true;
                shortPositionSize = size;
                shortEntryPrice = entryPrice;
            } else {
                shortActive = false;
                shortPositionSize = BigDecimal.ZERO;
                shortEntryPrice = BigDecimal.ZERO;
            }
        }
    }
    // ================================================================
    // 查找方法
    // ================================================================
    static Grid findByPrice(BigDecimal price) {
        for (Grid g : gridElements) {
            if (g.price.compareTo(price) == 0) return g;
        }
        return null;
    }
    static Grid findByLongSlOrderId(String oid) {
        for (Grid g : gridElements) if (oid.equals(g.longSlOrderId)) return g;
        return null;
    }
    static Grid findByShortSlOrderId(String oid) {
        for (Grid g : gridElements) if (oid.equals(g.shortSlOrderId)) return g;
        return null;
    }
    static Grid findByLongOrderId(String oid) {
        for (Grid g : gridElements) if (oid.equals(g.longOrderId)) return g;
        return null;
    }
    static Grid findByShortOrderId(String oid) {
        for (Grid g : gridElements) if (oid.equals(g.shortOrderId)) return g;
        return null;
    }
    // ================================================================
    // 打印
    // ================================================================
    static void printGridState() {
        System.out.println("\n  === 网格状态 ===");
        System.out.println("  多仓: pos=" + longPositionSize + " active=" + longActive + " entryPrice=" + longEntryPrice);
        System.out.println("  空仓: pos=" + shortPositionSize + " active=" + shortActive + " entryPrice=" + shortEntryPrice);
        for (Grid g : gridElements) {
            System.out.println("  " + g);
        }
    }
    // ================================================================
    // 主模拟流程
    // ================================================================
    public static void main(String[] args) {
        System.out.println(repeat("=",70));
        System.out.println("Gate 网格策略模拟器");
        System.out.println("参数: basePrice=100.0, step=0.5, baseQty=10, qty=1, gridSize=10");
        System.out.println(repeat("=",70));
        init();
        // ---- 场景 A: 价格稳定 → 基底双开 → 进入ACTIVE ----
        System.out.println("\n\n◆◆◆ 场景A: 首根K线,基底双开 ◆◆◆");
        onFirstKline();
        // 模拟 onPositionUpdate 设置基底持仓
        onPositionUpdate(true, new BigDecimal(BASE_QTY), BASE_PRICE);
        onPositionUpdate(false, new BigDecimal(BASE_QTY), BASE_PRICE);
        printGridState();
        // ---- 场景 B: 价格下跌触发多仓止损 ----
        System.out.println("\n\n◆◆◆ 场景B: 价格跌至99.0,触发多仓止损(gridId:-2) ◆◆◆");
        Grid gMinus2 = INDEX.get(-2);
        String slOrderId = gMinus2.longSlOrderId;
        if (slOrderId != null) {
            // 模拟止损成交
            onAutoOrder(slOrderId, "finished", "plan-close-long-position", "T001");
            // 模拟 onPositionUpdate: 多仓从10降到9
            onPositionUpdate(true, new BigDecimal("9"), new BigDecimal("99.5"));
            printGridState();
        } else {
            log("  gridId:-2 无止损单!");
        }
        // ---- 场景 C: 价格持续下跌,再次触发止损 ----
        System.out.println("\n\n◆◆◆ 场景C: 价格跌至98.5,再次触发多仓止损(gridId:-3) ◆◆◆");
        Grid gMinus3 = INDEX.get(-3);
        slOrderId = gMinus3.longSlOrderId;
        if (slOrderId != null) {
            onAutoOrder(slOrderId, "finished", "plan-close-long-position", "T002");
            onPositionUpdate(true, new BigDecimal("8"), new BigDecimal("99.0"));
            printGridState();
        }
        // ---- 场景 D: 止损追单成交,测试止盈追挂 ----
        System.out.println("\n\n◆◆◆ 场景D: 追单成交,触发止盈追挂 ◆◆◆");
        // 找到最近挂的多单
        Grid pendingLong = null;
        for (Grid g : gridElements) {
            if (g.hasLongOrder) {
                pendingLong = g;
                break;
            }
        }
        if (pendingLong != null) {
            // 模拟追单成交,追了2张
            log("\n模拟追单成交: gridId:" + pendingLong.id + " orderId:" + pendingLong.longOrderId);
            longPositionSize = longPositionSize.add(new BigDecimal(pendingLong.longFilledQty));
            log("多仓持仓变为: " + longPositionSize);
            onAutoOrder(pendingLong.longOrderId, "finished", "", "T003");
            onPositionUpdate(true, longPositionSize, BASE_PRICE);
            printGridState();
        }
        // ---- 场景 E: 多仓全部止损归零 ----
        System.out.println("\n\n◆◆◆ 场景E: 价格继续暴跌,多仓全部止损归零 ◆◆◆");
        log("模拟多仓全部平仓...");
        onPositionUpdate(true, BigDecimal.ZERO, BigDecimal.ZERO);
        log("多仓归零, longActive=" + longActive + ", pos=" + longPositionSize);
        // ACTIVE状态下多仓归零 → 下一根K线触发processShortGrid
        System.out.println("\n  下一根K线触发 processShortGrid:");
        onKline(new BigDecimal("95.0"));
        printGridState();
        // ---- 场景 F: 空仓止损触发 ----
        System.out.println("\n\n◆◆◆ 场景F: 价格暴涨至101.0,触发空仓止损(gridId:2) ◆◆◆");
        Grid g2 = INDEX.get(2);
        if (g2 != null && g2.shortSlOrderId != null) {
            onAutoOrder(g2.shortSlOrderId, "finished", "plan-close-short-position", "T004");
            onPositionUpdate(false, new BigDecimal("9"), new BigDecimal("100.5"));
            printGridState();
        }
        // ================================================================
        // BUG 分析
        // ================================================================
        System.out.println("\n\n" + repeat("=",70));
        System.out.println("BUG 分析报告");
        System.out.println(repeat("=",70));
        int bugNum = 1;
        // BUG 1
        System.out.println("\n[BUG-" + (bugNum++) + "] 基座IOC成交不触发onAutoOrder,hasLongOrder/hasShortOrder永久为true");
        System.out.println("  位置: GateGridTradeService.onKline() + tryGenerateQueues()");
        System.out.println("  描述: 基底市价IOC开仓后,tryGenerateQueues中在ID=0设置了hasLongOrder=true/hasShortOrder=true。");
        System.out.println("        但IOC成交不会触发onAutoOrder(条件单回调),导致ID=0的挂单状态永远不会被清除。");
        System.out.println("  影响: ID=0的hasLongOrder永远为true。当止损触发在gridId=-2时,会尝试取消gridId=0的多单");
        System.out.println("        (cancelGridId = -2 + 2 = 0),由于IOC已成交,取消会失败但只产生warn日志,无严重后果。");
        System.out.println("        但状态不一致可能影响后续processShortGrid/processLongGrid的匹配逻辑。");
        System.out.println("  修复: tryGenerateQueues中不要用IOC的orderId标记hasLongOrder,或者增加onOrderUpdate回调处理普通订单。");
        // BUG 2
        System.out.println("\n[BUG-" + (bugNum++) + "] handleShortStopLossTriggered 使用了longPositionSize计算size");
        System.out.println("  位置: handleShortStopLossTriggered() line 1176");
        System.out.println("  代码: BigDecimal subtract = baseQuantity.subtract(longPositionSize);");
        System.out.println("  描述: 空仓止损触发时,用 longPositionSize 来计算追单size,应该用 shortPositionSize。");
        System.out.println("  影响: 空仓止损追单的size计算基于多头持仓而非空头持仓,导致补仓数量错误。");
        System.out.println("        例如多仓持仓5,空仓持仓9,空仓止损触发时: subtract = 10 - 5 = 5, size = 5+1 = 6.");
        System.out.println("        应该是: subtract = 10 - 9 = 1, size = 1+1 = 2.");
        System.out.println("  修复: 将 longPositionSize 改为 shortPositionSize。");
        // BUG 3
        System.out.println("\n[BUG-" + (bugNum++) + "] onAutoOrder中持仓判断存在竞态问题");
        System.out.println("  位置: onAutoOrder() 追挂止盈逻辑 (line 617/655)");
        System.out.println("  描述: onAutoOrder被调用时,longPositionSize/shortPositionSize可能尚未被onPositionUpdate更新。");
        System.out.println("        WebSocket消息顺序虽然是顺序的,但onAutoOrder基于orders/autoorders频道,");
        System.out.println("        onPositionUpdate基于positions频道,两者到达顺序不一定严格匹配。");
        System.out.println("  影响: 追挂止盈的excessCount计算可能偏小(持仓尚未更新),导致少挂止盈单。");
        System.out.println("        下次fill时会再次尝试追挂,重复计算可能产生多余止盈单。");
        System.out.println("  修复: 考虑在onPositionUpdate中做止盈追挂,或基于filledQty累加预估新持仓。");
        // BUG 4
        System.out.println("\n[BUG-" + (bugNum++) + "] 止盈追挂从gridId±2开始,可能覆盖已有止损单的网格位置");
        System.out.println("  位置: onAutoOrder() 止盈追挂逻辑");
        System.out.println("  描述: 止盈追挂从 filledGridId+2(多)/filledGridId-2(空) 开始。");
        System.out.println("        但这些位置可能已经被止损单占用(如initStopLossOrders在ID 2~11和-2~-11挂止损)。");
        System.out.println("        虽然止盈和止损是不同的订单(分别存在takeProfitOrderId和stopLossOrderId中),");
        System.out.println("        但同一个GridElement同时有止盈和止损,在状态追踪上可能模糊。");
        System.out.println("  影响: 同一个网格位置可能同时有止盈和止损订单,当订单成交回调时可能匹配错误。");
        System.out.println("  风险: 中低。onAutoOrder先用findByLongStopLossOrderId匹配止损,再匹配入口单,止盈不会被匹配。");
        // BUG 5 → 已确认为设计意图,移除
        System.out.println("\n[OK-5] processShortGrid/processLongGrid 只在对方持仓归零时触发 — 属于设计意图,非BUG");
        System.out.println("  位置: onKline() line 389-400");
        System.out.println("  描述: 策略有两套驱动机制:有持仓时走订单驱动(onAutoOrder处理挂单成交/止损追单),");
        System.out.println("        当某方向持仓归零后切换到价格驱动(onKline→processGrid),挂新入场单后回到订单驱动。");
        System.out.println("        两套机制交替运作,确保仓位归零后能基于当前价格重新入场。");
        // BUG 6
        System.out.println("\n[BUG-" + (bugNum++) + "] processShortGrid/processLongGrid 中 size 使用 BASE_QTY 而非 QTY");
        System.out.println("  位置: processShortGrid() line 1027, processLongGrid() line 1077");
        System.out.println("  描述: 仓位归零后重新挂单时,使用 baseQuantity 而不是 quantity。");
        System.out.println("        例如 baseQuantity=10,会在新位置挂10张,而其他网格层级只挂1张。");
        System.out.println("  影响: 每次仓位归零后的重新入场都是10张,可能导致持仓量远超预期。");
        System.out.println("  风险: 可能是设计意图(仓位归零后需要重建基底仓位),但需确认。");
        // BUG 7
        System.out.println("\n[BUG-" + (bugNum++) + "] extendLongStopLoss/extendShortStopLoss 不停向外扩展");
        System.out.println("  位置: extendLongStopLoss() line 1216, extendShortStopLoss() line 1249");
        System.out.println("  描述: 每次成交都调用extend,从当前最远止损位置继续向外挂filledQty个止损单。");
        System.out.println("        如果成交次数多,止损单会无限向外扩展(比如挂到gridId=-100)。");
        System.out.println("  影响: 大量止损单可能超出交易所限制(Gate条件单上限通常200个)。并且止损位置离当前价");
        System.out.println("        越来越远,实际作用不大但占用资源。");
        System.out.println("  风险: 低中。止损位置越来越远意味着回撤越来越大,风控上可能已经触发maxLoss停止策略。");
        // BUG 8 → 已确认为设计意图,移除
        System.out.println("\n[OK-8] onPositionUpdate 中两仓归零异步重启策略 — 属于设计意图,非BUG");
        System.out.println("  位置: onPositionUpdate() line 522-538");
        System.out.println("  描述: 当 shortActive==false && longActive==false 时,取消所有条件单并平仓,");
        System.out.println("        然后异步sleep 3s后调用startGrid重启;双仓归零后重新开启一轮新策略。");
        // BUG 9
        System.out.println("\n[BUG-" + (bugNum++) + "] onAutoOrder中stopLoss匹配缺少positionSize校验");
        System.out.println("  位置: onAutoOrder() line 595-604");
        System.out.println("  描述: 原始代码有 longPositionSize > 0 的校验但被注释掉了:");
        System.out.println("        // if (longStopLossElem != null && longPositionSize.compareTo(BigDecimal.ZERO) > 0 ...");
        System.out.println("       现在只要tradeId非空非0就触发handler。");
        System.out.println("  影响: 如果多仓已全部平仓但止损单尚未取消(异步延迟),止损单的finished回调");
        System.out.println("        仍会触发handleLongStopLossTriggered,产生多余的追单操作。");
        System.out.println("  修复: 恢复positionSize > 0的校验。");
        // BUG 10
        System.out.println("\n[BUG-" + (bugNum++) + "] tryGenerateQueues中上/下ID分配有误导性命名");
        System.out.println("  位置: updateGridElements() line 960-967");
        System.out.println("  描述: ID=0的upId=1(指向高价), downId=-1(指向低价)。");
        System.out.println("        在注释和文档中写的是 upId=-1, downId=1,但代码实际是反过来的。");
        System.out.println("        实际语义: upId=\"向上的价格\"(更大ID), downId=\"向下的价格\"(更小ID)。");
        System.out.println("  影响: 代码实际工作正确,但注释和代码不一致,容易误导维护者。");
        System.out.println("  状态: 可能是早期代码重构遗留,实际语义是\"up=价格向上, down=价格向下\"。");
        // BUG 11
        System.out.println("\n[BUG-" + (bugNum++) + "] currentLongOrderIds/currentShortOrderIds 声明但从未使用");
        System.out.println("  位置: GateGridTradeService line 119-121");
        System.out.println("  描述: currentLongOrderIds 和 currentShortOrderIds 是 synchronized LinkedHashMap,");
        System.out.println("        声明但代码中没有任何put操作,只有clear。");
        System.out.println("  影响: 死代码,占用内存但无实际作用。代码中对此Map的使用意图已过时。");
        // BUG 12
        System.out.println("\n[BUG-" + (bugNum++) + "] onAutoOrder中已成交的挂单仍通过isHasLongOrder检测");
        System.out.println("  位置: onAutoOrder() line 608/617");
        System.out.println("  描述: 当查找shortGridElement时,检查 isHasShortOrder() 为true才处理。");
        System.out.println("        但同一个orderId被匹配到说明它确实是挂单,检查 isHasShortOrder 可能因");
        System.out.println("        placeEntryOrderWithPreFlag的竞态导致问题(API失败→回滚→但WS已推送)。");
        System.out.println("  影响: 如果挂单API失败但WS已推送finished回调,isHasShortOrder可能已回滚为false,");
        System.out.println("        导致成交回调被忽略,持仓状态与实际不符。");
        System.out.println("  风险: 极低(需要API失败+WS推送的精确时序)。");
    }
}