From e692f08fddcfb73b8a830957a14309917deccf24 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Fri, 08 May 2026 22:23:52 +0800
Subject: [PATCH] refactor(gateApi): 重构网格交易策略为队列驱动模式

---
 src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java                           |  425 ++++++++++++++++++++++----------
 src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java                     |    8 
 src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md                                    |  237 ++++++++++++-----
 src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/CandlestickChannelHandler.java    |    4 
 src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionClosesChannelHandler.java |    1 
 src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionsChannelHandler.java      |    8 
 src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java                                     |   36 ++
 src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java                              |   11 
 8 files changed, 506 insertions(+), 224 deletions(-)

diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
index 2a5317a..ca6446c 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
@@ -16,6 +16,7 @@
  *       .contract("XAU_USDT")
  *       .leverage("100")
  *       .gridRate(new BigDecimal("0.0035"))
+ *       .contractMultiplier("0.001")
  *       .isProduction(false)
  *       .build();
  *
@@ -26,13 +27,22 @@
  * <h3>默认值</h3>
  * <ul>
  *   <li>合约: BTC_USDT, 杠杆: 10x, 全仓, 双向持仓</li>
- *   <li>网格: 0.35%, 止盈: 0.5 USDT, 亏损: 7.5 USDT</li>
- *   <li>数量: 1 张, 环境: 测试网, 重试: 3 次</li>
+ *   <li>网格间距: 0.35%, 队列容量: 50, 保证金比例上限: 20%</li>
+ *   <li>止盈: 0.5 USDT, 亏损上限: 7.5 USDT</li>
+ *   <li>数量: 1 张, 合约乘数: 0.001, 环境: 测试网</li>
  * </ul>
  *
  * @author Administrator
  */
 public class GateConfig {
+
+    /** 未实现盈亏计价模式 */
+    public enum PnLPriceMode {
+        /** 按最新成交价计算 */
+        LAST_PRICE,
+        /** 按标记价格计算 */
+        MARK_PRICE
+    }
 
     /** Gate API v4 密钥 */
     private final String apiKey;
@@ -58,6 +68,14 @@
     private final boolean isProduction;
     /** 补仓最大重试次数 */
     private final int reopenMaxRetries;
+    /** 网格队列容量 */
+    private final int gridQueueSize;
+    /** 保证金占初始本金比例上限 */
+    private final BigDecimal marginRatioLimit;
+    /** 合约乘数(单张合约代表的基础资产数量,如 BTC_USDT=0.001, ETH_USDT=0.01) */
+    private final BigDecimal contractMultiplier;
+    /** 未实现盈亏计价模式:最新价 / 标记价格 */
+    private final PnLPriceMode unrealizedPnlPriceMode;
 
     private GateConfig(Builder builder) {
         this.apiKey = builder.apiKey;
@@ -72,6 +90,10 @@
         this.quantity = builder.quantity;
         this.isProduction = builder.isProduction;
         this.reopenMaxRetries = builder.reopenMaxRetries;
+        this.gridQueueSize = builder.gridQueueSize;
+        this.marginRatioLimit = builder.marginRatioLimit;
+        this.contractMultiplier = builder.contractMultiplier;
+        this.unrealizedPnlPriceMode = builder.unrealizedPnlPriceMode;
     }
 
     /**
@@ -112,6 +134,10 @@
     public String getQuantity() { return quantity; }
     public boolean isProduction() { return isProduction; }
     public int getReopenMaxRetries() { return reopenMaxRetries; }
+    public int getGridQueueSize() { return gridQueueSize; }
+    public BigDecimal getMarginRatioLimit() { return marginRatioLimit; }
+    public BigDecimal getContractMultiplier() { return contractMultiplier; }
+    public PnLPriceMode getUnrealizedPnlPriceMode() { return unrealizedPnlPriceMode; }
 
     public static Builder builder() {
         return new Builder();
@@ -133,6 +159,10 @@
         private String quantity = "1";
         private boolean isProduction = false;
         private int reopenMaxRetries = 3;
+        private int gridQueueSize = 50;
+        private BigDecimal marginRatioLimit = new BigDecimal("0.2");
+        private BigDecimal contractMultiplier = new BigDecimal("0.001");
+        private PnLPriceMode unrealizedPnlPriceMode = PnLPriceMode.LAST_PRICE;
 
         public Builder apiKey(String apiKey) { this.apiKey = apiKey; return this; }
         public Builder apiSecret(String apiSecret) { this.apiSecret = apiSecret; return this; }
@@ -146,6 +176,8 @@
         public Builder quantity(String quantity) { this.quantity = quantity; return this; }
         public Builder isProduction(boolean isProduction) { this.isProduction = isProduction; return this; }
         public Builder reopenMaxRetries(int reopenMaxRetries) { this.reopenMaxRetries = reopenMaxRetries; return this; }
+        public Builder contractMultiplier(BigDecimal contractMultiplier) { this.contractMultiplier = contractMultiplier; return this; }
+        public Builder unrealizedPnlPriceMode(PnLPriceMode mode) { this.unrealizedPnlPriceMode = mode; return this; }
 
         public GateConfig build() {
             return new GateConfig(this);
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 45b5659..dfb885a 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -14,23 +14,40 @@
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
- * Gate 网格交易服务类。使用 Gate SDK 通过 REST API 下单。
+ * Gate 网格交易服务 — 策略核心。
+ *
+ * <h3>策略</h3>
+ * 多空双开基底 → 生成价格网格队列 → K线触达网格线 → 开仓+设止盈 → 队列动态转移。
+ * 每根 K 线更新 {@code unrealizedPnl}(浮动盈亏),平仓后累加到 {@code cumulativePnl}(已实现盈亏)。
+ *
+ * <h3>未实现盈亏公式(正向合约)</h3>
+ * <pre>
+ *   多仓: 持仓量 × 合约乘数 × (计价价格 − 开仓均价)
+ *   空仓: 持仓量 × 合约乘数 × (开仓均价 − 计价价格)
+ * </pre>
+ * 计价价格支持切换:{@link GateConfig.PnLPriceMode#LAST_PRICE 最新成交价} 或
+ * {@link GateConfig.PnLPriceMode#MARK_PRICE 标记价格}(通过 {@link #setMarkPrice(BigDecimal)} 注入)。
+ * 入场价和持仓量由 {@link #onPositionUpdate(String, Position.ModeEnum, BigDecimal, BigDecimal)} 实时更新。
  *
  * <h3>状态机</h3>
  * <pre>
- *   WAITING_KLINE → (首次 K 线) → OPENING → ACTIVE
- *                             双开失败 → STOPPED
+ *   WAITING_KLINE → (首K线) → 异步双开基底
+ *
+ *   仓位推送(dual_long/dual_short) → 基底成交 → 记录入场价 → 双基底都成交 → 生成队列 → ACTIVE
  *
  *   ACTIVE:
- *     ├─ 仓位 size=0 且方向活跃 → REOPENING_L/S → ACTIVE
- *     │   补仓失败 → 重试 → 仍失败 → STOPPED
+ *     ├─ 每根K线 → 更新 unrealizedPnl + processShortGrid + processLongGrid
+ *     │    ├─ 当前价 &lt; 空仓队列元素 → 匹配 → 开空 + 队列元素转移到多仓队列
+ *     │    └─ 当前价 &gt; 多仓队列元素 → 匹配 → 开多 + 队列元素转移到空仓队列
+ *     ├─ 仓位推送(非基底) → 设止盈条件单 entry × (1±gridRate)
+ *     ├─ 保证金≥初始本金 marginRatioLimit → 跳过开仓,队列照常更新
  *     └─ cumulativePnl ≥ overallTp 或 ≤ -maxLoss → STOPPED
  * </pre>
- *
- * <h3>架构</h3>
- * REST 下单委派给 {@link GateTradeExecutor}(独立线程池,避免阻塞 WS 回调线程)。
  *
  * @author Administrator
  */
@@ -38,7 +55,7 @@
 public class GateGridTradeService {
 
     public enum StrategyState {
-        WAITING_KLINE, OPENING, ACTIVE, REOPENING_LONG, REOPENING_SHORT, STOPPED
+        WAITING_KLINE, OPENING, ACTIVE, STOPPED
     }
 
     private static final String AUTO_SIZE_LONG = "close_long";
@@ -53,21 +70,35 @@
 
     private volatile StrategyState state = StrategyState.WAITING_KLINE;
 
-    /** 多头是否活跃(有仓位) */
-    private volatile boolean longActive = false;
+    /** 空仓价格队列,降序排列(大→小),容量 gridQueueSize */
+    private final List<BigDecimal> shortPriceQueue = Collections.synchronizedList(new ArrayList<>());
+    /** 多仓价格队列,升序排列(小→大),容量 gridQueueSize */
+    private final List<BigDecimal> longPriceQueue = Collections.synchronizedList(new ArrayList<>());
+
+    /** 基底空头入场价 */
+    private BigDecimal shortBaseEntryPrice;
+    /** 基底多头入场价 */
+    private BigDecimal longBaseEntryPrice;
+    /** 基底多头是否已开 */
+    private volatile boolean baseLongOpened = false;
+    /** 基底空头是否已开 */
+    private volatile boolean baseShortOpened = false;
+
     /** 空头是否活跃(有仓位) */
     private volatile boolean shortActive = false;
+    /** 多头是否活跃(有仓位) */
+    private volatile boolean longActive = false;
 
-    private BigDecimal longEntryPrice;
-    private BigDecimal shortEntryPrice;
     private volatile BigDecimal lastKlinePrice;
+    private volatile BigDecimal markPrice = BigDecimal.ZERO;
     private volatile BigDecimal cumulativePnl = BigDecimal.ZERO;
+    private volatile BigDecimal unrealizedPnl = BigDecimal.ZERO;
+    private volatile BigDecimal longEntryPrice = BigDecimal.ZERO;
+    private volatile BigDecimal shortEntryPrice = BigDecimal.ZERO;
+    private volatile BigDecimal longPositionSize = BigDecimal.ZERO;
+    private volatile BigDecimal shortPositionSize = BigDecimal.ZERO;
     private Long userId;
-
-    /** 多头补仓连续失败次数 */
-    private int longReopenFails = 0;
-    /** 空头补仓连续失败次数 */
-    private int shortReopenFails = 0;
+    private volatile BigDecimal initialPrincipal = BigDecimal.ZERO;
 
     public GateGridTradeService(GateConfig config) {
         this.config = config;
@@ -77,6 +108,8 @@
         this.futuresApi = new FuturesApi(apiClient);
         this.executor = new GateTradeExecutor(apiClient, config.getContract());
     }
+
+    // ---- 初始化 ----
 
     public void init() {
         try {
@@ -88,6 +121,9 @@
             log.info("[Gate] 用户ID: {}", userId);
 
             FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
+            this.initialPrincipal = new BigDecimal(account.getTotal());
+            log.info("[Gate] 初始本金: {} USDT", initialPrincipal);
+
             if (!config.getPositionMode().equals(account.getPositionMode())) {
                 futuresApi.setPositionMode(SETTLE, config.getPositionMode());
             }
@@ -109,18 +145,10 @@
         }
     }
 
-    /**
-     * 平掉当前合约所有已有仓位。
-     * 策略启动前的准备工作,确保从零持仓状态开始运行。
-     */
     private void closeExistingPositions() {
         try {
-            java.util.List<Position> positions = futuresApi.listPositions(SETTLE).execute();
-            if (positions == null || positions.isEmpty()) {
-                log.info("[Gate] 无已有仓位,无需平仓");
-                return;
-            }
-
+            List<Position> positions = futuresApi.listPositions(SETTLE).execute();
+            if (positions == null || positions.isEmpty()) { log.info("[Gate] 无已有仓位"); return; }
             for (Position pos : positions) {
                 if (!config.getContract().equals(pos.getContract())) {
                     continue;
@@ -130,11 +158,8 @@
                 if (size == 0) {
                     continue;
                 }
-
                 String closeSize = size > 0 ? String.valueOf(-size) : String.valueOf(Math.abs(size));
-                boolean isLong = size > 0;
                 Position.ModeEnum mode = pos.getMode();
-
                 FuturesOrder closeOrder = new FuturesOrder();
                 closeOrder.setContract(config.getContract());
                 closeOrder.setPrice("0");
@@ -143,20 +168,22 @@
                 if (mode != null && mode.getValue() != null && mode.getValue().contains("dual")) {
                     closeOrder.setSize("0");
                     closeOrder.setClose(false);
-                    closeOrder.setAutoSize(isLong ? FuturesOrder.AutoSizeEnum.LONG : FuturesOrder.AutoSizeEnum.SHORT);
+                    closeOrder.setAutoSize(size > 0 ? FuturesOrder.AutoSizeEnum.LONG : FuturesOrder.AutoSizeEnum.SHORT);
                 } else {
                     closeOrder.setSize(closeSize);
                 }
                 closeOrder.setText("t-grid-init-close");
                 futuresApi.createFuturesOrder(SETTLE, closeOrder, null);
-                log.info("[Gate] 已平掉已有仓位, 方向:{}, sizes:{}, mode:{}", isLong ? "多头" : "空头", sizeStr, mode);
+                log.info("[Gate] 平已有仓位, 方向:{}, size:{}, mode:{}", size > 0 ? "多" : "空", sizeStr, mode);
             }
         } catch (GateApiException e) {
-            log.warn("[Gate] 平已有仓位失败, label:{}, msg:{}, 可能无仓位", e.getErrorLabel(), e.getMessage());
+            log.warn("[Gate] 平仓位失败, label:{}, msg:{}", e.getErrorLabel(), e.getMessage());
         } catch (Exception e) {
-            log.warn("[Gate] 平已有仓位异常, 可能无仓位", e);
+            log.warn("[Gate] 平仓位异常", e);
         }
     }
+
+    // ---- 启动/停止 ----
 
     public void startGrid() {
         if (state != StrategyState.WAITING_KLINE && state != StrategyState.STOPPED) {
@@ -165,10 +192,18 @@
         }
         state = StrategyState.WAITING_KLINE;
         cumulativePnl = BigDecimal.ZERO;
+        unrealizedPnl = BigDecimal.ZERO;
+        markPrice = BigDecimal.ZERO;
+        longEntryPrice = BigDecimal.ZERO;
+        shortEntryPrice = BigDecimal.ZERO;
+        longPositionSize = BigDecimal.ZERO;
+        shortPositionSize = BigDecimal.ZERO;
+        baseLongOpened = false;
+        baseShortOpened = false;
         longActive = false;
         shortActive = false;
-        longReopenFails = 0;
-        shortReopenFails = 0;
+        shortPriceQueue.clear();
+        longPriceQueue.clear();
         log.info("[Gate] 网格策略已启动");
     }
 
@@ -179,44 +214,40 @@
         log.info("[Gate] 策略已停止, 累计盈亏: {}", cumulativePnl);
     }
 
-    /**
-     * K 线回调。首次价格就绪 → 异步双开。
-     */
+    // ---- K线回调 ----
+
     public void onKline(BigDecimal closePrice) {
         lastKlinePrice = closePrice;
-        if (state != StrategyState.WAITING_KLINE) {
+        updateUnrealizedPnl();
+        if (state == StrategyState.STOPPED) {
             return;
         }
 
-        state = StrategyState.OPENING;
-        log.info("[Gate] 首根K线到达,开始双开...");
-
-        executor.openLong(config.getQuantity(), () -> {
-            synchronized (this) {
-                longEntryPrice = lastKlinePrice;
+        if (state == StrategyState.WAITING_KLINE) {
+            state = StrategyState.OPENING;
+            log.info("[Gate] 首根K线到达,开基底仓位...");
+            executor.openLong(config.getQuantity(), () -> {
+                baseLongOpened = true;
                 longActive = true;
-            }
-            executor.placeTakeProfit(longTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_1,
-                    ORDER_TYPE_CLOSE_LONG, AUTO_SIZE_LONG);
-        }, null);
-        executor.openShort(negate(config.getQuantity()), () -> {
-            synchronized (this) {
-                shortEntryPrice = lastKlinePrice;
+                log.info("[Gate] 基底多已开");
+            }, null);
+            executor.openShort(negate(config.getQuantity()), () -> {
+                baseShortOpened = true;
                 shortActive = true;
-            }
-            executor.placeTakeProfit(shortTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_2,
-                    ORDER_TYPE_CLOSE_SHORT, AUTO_SIZE_SHORT);
-            if (longActive && shortActive && state != StrategyState.STOPPED) {
-                state = StrategyState.ACTIVE;
-                log.info("[Gate] 已激活, 多头入场:{}, 空头入场:{}, 多头止盈:{}, 空头止盈:{}",
-                        longEntryPrice, shortEntryPrice, longTpPrice(), shortTpPrice());
-            }
-        }, null);
+                log.info("[Gate] 基底空已开");
+            }, null);
+            return;
+        }
+
+        if (state != StrategyState.ACTIVE) {
+            return;
+        }
+        processShortGrid(closePrice);
+        processLongGrid(closePrice);
     }
 
-    /**
-     * 仓位推送回调。检测 size=0 触发补仓。
-     */
+    // ---- 仓位推送回调 ----
+
     public void onPositionUpdate(String contract, Position.ModeEnum mode, BigDecimal size,
                                   BigDecimal entryPrice) {
         if (state == StrategyState.STOPPED || state == StrategyState.WAITING_KLINE) {
@@ -226,29 +257,50 @@
         boolean hasPosition = size.abs().compareTo(BigDecimal.ZERO) > 0;
 
         if (Position.ModeEnum.DUAL_LONG == mode) {
-            if (longActive && !hasPosition) {
-                log.info("[Gate] 多头已平仓");
-                longActive = false;
-                tryReopenLong();
-            } else if (hasPosition) {
+            if (hasPosition) {
                 longActive = true;
                 longEntryPrice = entryPrice;
+                longPositionSize = size;
+                if (!baseLongOpened) {
+                    longBaseEntryPrice = entryPrice;
+                    baseLongOpened = true;
+                    log.info("[Gate] 基底多成交价: {}", longBaseEntryPrice);
+                    tryGenerateQueues();
+                } else {
+                    executor.placeTakeProfit(entryPrice.add(config.getGridRate()).setScale(1, RoundingMode.HALF_UP),
+                            FuturesPriceTrigger.RuleEnum.NUMBER_1, ORDER_TYPE_CLOSE_LONG, AUTO_SIZE_LONG);
+                    log.info("[Gate] 多单止盈已设, entry:{}, tp:{}", entryPrice,
+                            entryPrice.add(config.getGridRate()).setScale(1, RoundingMode.HALF_UP));
+                }
+            } else {
+                longActive = false;
+                longPositionSize = BigDecimal.ZERO;
             }
         } else if (Position.ModeEnum.DUAL_SHORT == mode) {
-            if (shortActive && !hasPosition) {
-                log.info("[Gate] 空头已平仓");
-                shortActive = false;
-                tryReopenShort();
-            } else if (hasPosition) {
+            if (hasPosition) {
                 shortActive = true;
                 shortEntryPrice = entryPrice;
+                shortPositionSize = size.abs();
+                if (!baseShortOpened) {
+                    shortBaseEntryPrice = entryPrice;
+                    baseShortOpened = true;
+                    log.info("[Gate] 基底空成交价: {}", shortBaseEntryPrice);
+                    tryGenerateQueues();
+                } else {
+                    executor.placeTakeProfit(entryPrice.subtract(config.getGridRate()).setScale(1, RoundingMode.HALF_UP),
+                            FuturesPriceTrigger.RuleEnum.NUMBER_2, ORDER_TYPE_CLOSE_SHORT, AUTO_SIZE_SHORT);
+                    log.info("[Gate] 空单止盈已设, entry:{}, tp:{}", entryPrice,
+                            entryPrice.subtract(config.getGridRate()).setScale(1, RoundingMode.HALF_UP));
+                }
+            } else {
+                shortActive = false;
+                shortPositionSize = BigDecimal.ZERO;
             }
         }
     }
 
-    /**
-     * 平仓推送回调。累加 pnl 并检查停止条件。
-     */
+    // ---- 平仓推送回调 ----
+
     public void onPositionClose(String contract, String side, BigDecimal pnl) {
         if (state == StrategyState.STOPPED) {
             return;
@@ -265,90 +317,185 @@
         }
     }
 
-    // ---- 补仓(含失败重试) ----
+    // ---- 网格队列处理 ----
 
-    private void tryReopenLong() {
-        if (state == StrategyState.STOPPED) {
-            return;
+    private void tryGenerateQueues() {
+        if (baseLongOpened && baseShortOpened) {
+            generateShortQueue();
+            generateLongQueue();
+            state = StrategyState.ACTIVE;
+            log.info("[Gate] 网格队列已生成, 空队首:{} → 尾:{}, 多队首:{} → 尾:{}, 已激活",
+                    shortPriceQueue.get(0), shortPriceQueue.get(shortPriceQueue.size() - 1),
+                    longPriceQueue.get(0), longPriceQueue.get(longPriceQueue.size() - 1));
         }
-        if (longActive) {
-            return;
-        }
-
-        longReopenFails++;
-        if (longReopenFails > config.getReopenMaxRetries()) {
-            log.warn("[Gate] 多头补仓连续失败{}次,停止策略", longReopenFails);
-            state = StrategyState.STOPPED;
-            return;
-        }
-
-        state = StrategyState.REOPENING_LONG;
-        executor.openLong(config.getQuantity(), () -> {
-            synchronized (this) {
-                longEntryPrice = lastKlinePrice;
-                longActive = true;
-            }
-            executor.placeTakeProfit(longTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_1,
-                    ORDER_TYPE_CLOSE_LONG, AUTO_SIZE_LONG);
-            longReopenFails = 0;
-            if (state != StrategyState.STOPPED) {
-                state = StrategyState.ACTIVE;
-            }
-            log.info("[Gate] 多头已补开, 价格:{}", longEntryPrice);
-        }, this::tryReopenLong);
     }
 
-    private void tryReopenShort() {
-        if (state == StrategyState.STOPPED) {
-            return;
+    private void generateShortQueue() {
+        shortPriceQueue.clear();
+        BigDecimal step = config.getGridRate();
+        for (int i = 1; i <= config.getGridQueueSize(); i++) {
+            shortPriceQueue.add(shortBaseEntryPrice.subtract(step.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP));
         }
-        if (shortActive) {
-            return;
-        }
+        shortPriceQueue.sort((a, b) -> b.compareTo(a));
+    }
 
-        shortReopenFails++;
-        if (shortReopenFails > config.getReopenMaxRetries()) {
-            log.warn("[Gate] 空头补仓连续失败{}次,停止策略", shortReopenFails);
-            state = StrategyState.STOPPED;
-            return;
+    private void generateLongQueue() {
+        longPriceQueue.clear();
+        BigDecimal step = config.getGridRate();
+        for (int i = 1; i <= config.getGridQueueSize(); i++) {
+            longPriceQueue.add(longBaseEntryPrice.add(step.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP));
         }
+        longPriceQueue.sort(BigDecimal::compareTo);
+    }
 
-        state = StrategyState.REOPENING_SHORT;
-        executor.openShort(negate(config.getQuantity()), () -> {
-            synchronized (this) {
-                shortEntryPrice = lastKlinePrice;
-                shortActive = true;
+    private void processShortGrid(BigDecimal currentPrice) {
+        List<BigDecimal> matched = new ArrayList<>();
+        synchronized (shortPriceQueue) {
+            for (BigDecimal p : shortPriceQueue) {
+                if (p.compareTo(currentPrice) > 0) {
+                    matched.add(p);
+                } else {
+                    break;
+                }
             }
-            executor.placeTakeProfit(shortTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_2,
-                    ORDER_TYPE_CLOSE_SHORT, AUTO_SIZE_SHORT);
-            shortReopenFails = 0;
-            if (state != StrategyState.STOPPED) {
-                state = StrategyState.ACTIVE;
+        }
+        if (matched.isEmpty()) {
+            return;
+        }
+
+        log.info("[Gate] 空仓队列触发, 匹配{}个元素, 当前价:{}", matched.size(), currentPrice);
+        if (!isMarginSafe()) {
+            log.warn("[Gate] 保证金超限,跳过空单开仓");
+        } else {
+            executor.openShort(negate(config.getQuantity()), null, null);
+        }
+
+        synchronized (shortPriceQueue) {
+            shortPriceQueue.removeAll(matched);
+            BigDecimal min = shortPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : shortPriceQueue.get(shortPriceQueue.size() - 1);
+            BigDecimal step = config.getGridRate();
+            for (int i = 0; i < matched.size(); i++) {
+                min = min.subtract(step).setScale(1, RoundingMode.HALF_UP);
+                shortPriceQueue.add(min);
             }
-            log.info("[Gate] 空头已补开, 价格:{}", shortEntryPrice);
-        }, this::tryReopenShort);
+            shortPriceQueue.sort((a, b) -> b.compareTo(a));
+        }
+
+        synchronized (longPriceQueue) {
+            longPriceQueue.addAll(matched);
+            longPriceQueue.sort(BigDecimal::compareTo);
+            while (longPriceQueue.size() > config.getGridQueueSize()) {
+                longPriceQueue.remove(longPriceQueue.size() - 1);
+            }
+        }
     }
 
-    // ---- 止盈价格计算 ----
+    private void processLongGrid(BigDecimal currentPrice) {
+        List<BigDecimal> matched = new ArrayList<>();
+        synchronized (longPriceQueue) {
+            for (BigDecimal p : longPriceQueue) {
+                if (p.compareTo(currentPrice) < 0) {
+                    matched.add(p);
+                } else {
+                    break;
+                }
+            }
+        }
+        if (matched.isEmpty()) {
+            return;
+        }
 
-    private BigDecimal longTpPrice() {
-        return lastKlinePrice.multiply(BigDecimal.ONE.add(config.getGridRate()))
-                .setScale(1, RoundingMode.HALF_UP);
+        log.info("[Gate] 多仓队列触发, 匹配{}个元素, 当前价:{}", matched.size(), currentPrice);
+        if (!isMarginSafe()) {
+            log.warn("[Gate] 保证金超限,跳过多单开仓");
+        } else {
+            executor.openLong(config.getQuantity(), null, null);
+        }
+
+        synchronized (longPriceQueue) {
+            longPriceQueue.removeAll(matched);
+            BigDecimal max = longPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : longPriceQueue.get(longPriceQueue.size() - 1);
+            BigDecimal step = config.getGridRate();
+            for (int i = 0; i < matched.size(); i++) {
+                max = max.add(step).setScale(1, RoundingMode.HALF_UP);
+                longPriceQueue.add(max);
+            }
+            longPriceQueue.sort(BigDecimal::compareTo);
+        }
+
+        synchronized (shortPriceQueue) {
+            shortPriceQueue.addAll(matched);
+            shortPriceQueue.sort((a, b) -> b.compareTo(a));
+            while (shortPriceQueue.size() > config.getGridQueueSize()) {
+                shortPriceQueue.remove(shortPriceQueue.size() - 1);
+            }
+        }
     }
 
-    private BigDecimal shortTpPrice() {
-        return lastKlinePrice.multiply(BigDecimal.ONE.subtract(config.getGridRate()))
-                .setScale(1, RoundingMode.HALF_UP);
+    // ---- 保证金安全阀 ----
+
+    private boolean isMarginSafe() {
+        try {
+            FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
+            BigDecimal margin = new BigDecimal(account.getPositionInitialMargin());
+            BigDecimal ratio = margin.divide(initialPrincipal, 4, RoundingMode.HALF_UP);
+            log.debug("[Gate] 保证金比例: {}/{}={}", margin, initialPrincipal, ratio);
+            return ratio.compareTo(config.getMarginRatioLimit()) < 0;
+        } catch (Exception e) {
+            log.warn("[Gate] 查保证金失败,默认放行", e);
+            return true;
+        }
     }
 
-    /** 对数量取反(开多用正数,开空用负数) */
+    // ---- 工具 ----
+
     private String negate(String qty) {
         return qty.startsWith("-") ? qty.substring(1) : "-" + qty;
     }
 
+    /**
+     * 根据持仓和当前价格计算未实现盈亏。
+     *
+     * <h3>正向合约公式</h3>
+     * <pre>
+     *   多仓: 持仓量 × 合约乘数 × (计价价格 − 开仓均价)
+     *   空仓: 持仓量 × 合约乘数 × (开仓均价 − 计价价格)
+     * </pre>
+     * 计价价格由 {@link GateConfig.PnLPriceMode} 决定:LAST_PRICE 用最新成交价,MARK_PRICE 用标记价格。
+     */
+    private void updateUnrealizedPnl() {
+        BigDecimal price = resolvePnlPrice();
+        if (price == null || price.compareTo(BigDecimal.ZERO) == 0) {
+            return;
+        }
+        BigDecimal multiplier = config.getContractMultiplier();
+        BigDecimal longPnl = BigDecimal.ZERO;
+        BigDecimal shortPnl = BigDecimal.ZERO;
+        if (longPositionSize.compareTo(BigDecimal.ZERO) > 0 && longEntryPrice.compareTo(BigDecimal.ZERO) > 0) {
+            longPnl = longPositionSize.multiply(multiplier).multiply(price.subtract(longEntryPrice));
+        }
+        if (shortPositionSize.compareTo(BigDecimal.ZERO) > 0 && shortEntryPrice.compareTo(BigDecimal.ZERO) > 0) {
+            shortPnl = shortPositionSize.multiply(multiplier).multiply(shortEntryPrice.subtract(price));
+        }
+        unrealizedPnl = longPnl.add(shortPnl);
+    }
+
+    /**
+     * 根据配置的 PnLPriceMode 返回计价价格。
+     */
+    private BigDecimal resolvePnlPrice() {
+        if (config.getUnrealizedPnlPriceMode() == GateConfig.PnLPriceMode.MARK_PRICE
+                && markPrice.compareTo(BigDecimal.ZERO) > 0) {
+            return markPrice;
+        }
+        return lastKlinePrice;
+    }
+
     public BigDecimal getLastKlinePrice() { return lastKlinePrice; }
+    public void setMarkPrice(BigDecimal markPrice) { this.markPrice = markPrice; }
     public boolean isStrategyActive() { return state != StrategyState.STOPPED && state != StrategyState.WAITING_KLINE; }
     public BigDecimal getCumulativePnl() { return cumulativePnl; }
+    public BigDecimal getUnrealizedPnl() { return unrealizedPnl; }
     public Long getUserId() { return userId; }
     public StrategyState getState() { return state; }
 }
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
index ab1d3f3..dc72762 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
@@ -24,8 +24,11 @@
  * 下单 REST API 调用可能耗时数百毫秒,若同步执行会阻塞 WS 回调线程,导致心跳超时误判。
  * 本类将所有 REST 调用提交到独立线程池异步执行。
  *
+ * <h3>回调设计</h3>
+ * 每个下单方法接受 onSuccess/onFailure 两个 Runnable。
+ * 基底开仓时 onSuccess 用于标记基底已开,网格触发时通常为 null(成交状态由仓位推送驱动)。
+ *
  * <h3>线程模型</h3>
- * 单线程 ThreadPoolExecutor + 有界队列 64 + CallerRunsPolicy:
  * <ul>
  *   <li><b>单线程</b>:保证下单顺序(开多→开空→止盈单),避免并发竞争</li>
  *   <li><b>有界队列 64</b>:防止堆积。极端行情下最多累积 64 个任务</li>
@@ -35,9 +38,9 @@
  *
  * <h3>调用链</h3>
  * <pre>
- *   GateGridTradeService.onKline → executor.openLong/openShort → REST API
- *   GateGridTradeService.onPositionUpdate → executor.openLong/openShort → REST API
- *   (每一次开仓后) → executor.placeTakeProfit → REST API
+ *   GateGridTradeService.onKline → executor.openLong/openShort (基底双开 + 网格触发)
+ *   GateGridTradeService.onPositionUpdate → executor.placeTakeProfit (开仓成交后设止盈)
+ *   GateGridTradeService.stopGrid → executor.cancelAllPriceTriggeredOrders
  * </pre>
  *
  * @author Administrator
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
index dffccd1..341be5b 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
@@ -51,14 +51,16 @@
             config = GateConfig.builder()
                     .apiKey("d90ca272391992b8e74f8f92cedb21ec")
                     .apiSecret("1861e4f52de4bb53369ea3208d9ede38ece4777368030f96c77d27934c46c274")
-                    .contract("XAUT_USDT")
+                    .contract("ETH_USDT")
                     .leverage("30")
                     .marginMode("cross")
                     .positionMode("dual")
-                    .gridRate(new BigDecimal("0.0035"))
+                    .gridRate(new BigDecimal("0.001"))
                     .overallTp(new BigDecimal("0.5"))
                     .maxLoss(new BigDecimal("7.5"))
-                    .quantity("10")
+                    .quantity("1")
+                    .contractMultiplier(new BigDecimal("0.001"))
+                    .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.LAST_PRICE)
                     .isProduction(false)
                     .reopenMaxRetries(3)
                     .build();
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md b/src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md
index a4dd080..1acd8f8 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md
@@ -7,7 +7,7 @@
 | [GateWebSocketClientManager](#gatewebsocketclientmanager) | `@Component` | Spring 启动入口,组装组件 + 生命周期 |
 | [GateConfig](#gateconfig) | 配置 | Builder 模式:API 密钥、合约、策略参数、环境切换 |
 | [GateKlineWebSocketClient](#gateklinewebsocketclient) | WS 连接管理 | 连接/心跳/重连/消息路由 |
-| [GateGridTradeService](#gategridtradeservice) | 交易服务 | 网格策略状态机 + 盈亏管理 + 补仓重试 |
+| [GateGridTradeService](#gategridtradeservice) | 交易服务 | 网格队列策略 + 保证金安全阀 + 盈亏管理 |
 | [GateTradeExecutor](#gatetradeexecutor) | 异步执行器 | 独立线程池执行 REST 下单,成功/失败双回调 |
 | [GateWebSocketClientMain](#gatewebsocketclientmain) | main 入口 | 独立测试启动 |
 | [Example.java](#examplejava) | 示例 | Gate SDK 用法参考 |
@@ -57,25 +57,27 @@
 ├─ futures.candlesticks (公开)
 │   └─ CandlestickChannelHandler
 │       └─ gridTradeService.onKline(closePx)
-│           ├─ state=WAITING_KLINE → 异步双开 + 止盈单
-│           └─ 后续 → 仅缓存 lastKlinePrice
+│           ├─ 更新 unrealizedPnl(浮动盈亏)
+│           ├─ state=WAITING_KLINE → 异步双开基底仓位
+│           └─ state=ACTIVE → processShortGrid/processLongGrid 网格触发
 │
 ├─ futures.positions (私有, HMAC-SHA512)
 │   └─ PositionsChannelHandler
 │       ├─ 解析 mode → Position.ModeEnum(DUAL_LONG / DUAL_SHORT)
-│       └─ gridTradeService.onPositionUpdate(mode, size, entryPrice)
-│           ├─ DUAL_LONG, size=0 && longActive → tryReopenLong()
-│           └─ DUAL_SHORT, size=0 && shortActive → tryReopenShort()
+│       └─ gridTradeService.onPositionUpdate(contract, mode, size, entryPrice)
+│           ├─ 有仓位: 标记活跃,首次成交记录入场价并设止盈
+│           │   ├─ 基底开仓 → 首次成交 → 记录基底入场价 → 双基底都成交后生成网格队列
+│           │   └─ 网格开仓 → 成交后立即设止盈单
+│           └─ 无仓位(size=0): 标记不活跃
 │
 ├─ futures.position_closes (私有, HMAC-SHA512)
 │   └─ PositionClosesChannelHandler
-│       └─ gridTradeService.onPositionClose(side, pnl)
+│       └─ gridTradeService.onPositionClose(contract, side, pnl)
 │           └─ cumulativePnl += pnl → checkStopConditions()
 │
 └─ 所有下单操作
     └─ GateTradeExecutor (单线程 + 64队列 + CallerRunsPolicy)
         ├─ openLong/Short(qty, onSuccess, onFailure)
-        │   └─ 失败回调 → tryReopenXxx() 递归重试
         └─ placeTakeProfit → 条件单 (REST)
 ```
 
@@ -95,32 +97,92 @@
 - **unsubscribe**: 发送取消订阅请求(私有频道也带签名认证)
 - **handleMessage**: 解析推送数据并回调GateGridTradeService,返回true表示已处理
 - 消息路由: update/all事件 → 遍历channelHandlers → handler内部二次匹配channel名 → 匹配成功回调并停止遍历
-- **PositionsChannelHandler 特殊处理**: 推送的 mode 字符串("dual_long")通过 `Position.ModeEnum.fromValue()` 转为枚举,避免调用方用字符串匹配
+- **PositionsChannelHandler 特殊处理**: 推送的 mode 字符串("dual_long")通过 `Position.ModeEnum.fromValue()` 转为枚举
 
 ---
 
 ## 策略状态机
 
 ```
-WAITING_KLINE ──onKline──→ OPENING ──双开成功──→ ACTIVE
-     │                        │                     │
-     │                    双开失败             ├─ size=0 → REOPENING_L/S
-     │                                         │   ├─ 补仓成功 → ACTIVE
-     │                                         │   └─ 补仓失败 → 递归重试
-     │                                         │       └─ 超 reopenMaxRetries → STOPPED
-     │                                         └─ cumPnl≥TP 或 ≤-maxLoss → STOPPED
-     ▼
-   STOPPED ←─────────────────────────────────────────┘
+WAITING_KLINE ──onKline──→ OPENING ──双基底都成交──→ ACTIVE
+     │                        │                         │
+     │                    下单异常               ├─ 每根K线: 更新 unrealizedPnl
+     │                        │                 ├─ cumPnl ≥ overallTp → STOPPED
+     │                        │                 ├─ cumPnl ≤ -maxLoss → STOPPED
+     │                        │                 ├─ 保证金超限 → 跳过开仓,队列照常更新
+     │                        │                 └─ 网格触发 → 开仓+设止盈+队列转移
+     ▼                        ▼
+   STOPPED  ←──────────────────┘
 ```
 
 | 状态 | 含义 |
 |------|------|
 | `WAITING_KLINE` | 等待首次K线价格 |
-| `OPENING` | 正在异步双开(开多+开空已提交到GateTradeExecutor) |
-| `ACTIVE` | 网格运行中,等待止盈触发 |
-| `REOPENING_LONG` | 正在补开多头 |
-| `REOPENING_SHORT` | 正在补开空头 |
-| `STOPPED` | 停止(盈利达标 / 亏损超限 / 补仓重试耗尽 / 异常退出) |
+| `OPENING` | 正在异步开基底多空仓位(已提交到GateTradeExecutor) |
+| `ACTIVE` | 网格队列激活,K线触发网格元素 → 开仓+止盈 |
+| `STOPPED` | 停止(盈利达标 / 亏损超限) |
+
+---
+
+## 策略核心:网格队列机制
+
+### 概述
+
+策略采用"基底 + 价格网格队列"模式:先开一对基底多空仓位,然后以基底入场价为基准生成价格队列。每当K线穿破队列元素,就开新仓位并设止盈条件单,同时将穿破的元素转移到反方向队列。
+
+### 基底开仓
+
+```
+K线到达 → 双开基底(市价开多 + 市价开空)
+  → 成交回调: baseLongOpened=true, longActive=true
+  → 成交回调: baseShortOpened=true, shortActive=true
+  → 两者都成交 → generateShortQueue() + generateLongQueue() → state=ACTIVE
+```
+
+### 网格队列生成
+
+以基底入场价为基准,按 `gridRate` 步长生成 N 个价格(N = gridQueueSize,默认50):
+
+| 队列 | 计算方式 | 排序 |
+|------|---------|------|
+| 空仓队列 shortPriceQueue | 基底空入场价 - gridRate × i (i=1..N) | 降序(大→小) |
+| 多仓队列 longPriceQueue | 基底多入场价 + gridRate × i (i=1..N) | 升序(小→大) |
+
+### K线触发网格
+
+```
+K线到达(ACTIVE状态):
+│
+├─ processShortGrid: 当前价 < 空仓队列元素(价格跌破了队列中的高价)
+│   ├─ 匹配: 收集所有 > 当前价的空仓队列元素
+│   ├─ 保证金检查: positionInitialMargin / initialPrincipal < marginRatioLimit(20%)
+│   │   ├─ 安全 → openShort 开空单(成交后仓位推送会自动设止盈)
+│   │   └─ 超限 → 跳过开仓,队列照常更新
+│   ├─ 空仓队列: 移除匹配元素,尾部补充新价格(按步长递减)
+│   └─ 多仓队列: 接收匹配元素,升序排列,截断到 gridQueueSize
+│
+└─ processLongGrid: 当前价 > 多仓队列元素(价格涨超了队列中的低价)
+    ├─ 匹配: 收集所有 < 当前价的多仓队列元素
+    ├─ 保证金检查: 同上
+    │   ├─ 安全 → openLong 开多单
+    │   └─ 超限 → 跳过开仓
+    ├─ 多仓队列: 移除匹配元素,尾部补充新价格(按步长递增)
+    └─ 空仓队列: 接收匹配元素,降序排列,截断到 gridQueueSize
+```
+
+### 队列转移示意
+
+```
+初始状态:
+  空仓队列: [100, 99, 98, 97]  (降序)
+  多仓队列: [102, 103, 104, 105] (升序)
+
+价格跌到 98.5 → processShortGrid 触发:
+  匹配: [100, 99](都 > 98.5)
+  
+  空仓队列: 移除[100,99] → [98,97] → 补充[96,95] → [98,97,96,95]
+  多仓队列: 接收[100,99] → [100,99,102,103,104,105] → 截断到4 → [102,103,104,105]
+```
 
 ---
 
@@ -133,8 +195,8 @@
   → GateConfig.builder()...build()
   → GateGridTradeService(config)
     → init():
-      1. 查用户ID
-      2. 查账户 → 如需要切持仓模式
+      1. 查用户ID(用于私有频道订阅)
+      2. 查账户 → 记录初始本金 initialPrincipal → 如需要切持仓模式
       3. 清除旧止盈止损条件单
       4. 查当前合约所有仓位 → 逐个市价平仓(reduce_only, IOC)
          - 单向持仓: size=相反数平仓
@@ -146,39 +208,38 @@
   → gridTradeService.startGrid() → state=WAITING_KLINE
 ```
 
-### 阶段 2:首次开仓
+### 阶段 2:首次开仓 → 生成网格队列
 
 ```
 K线推送 → onKline(closePrice) → state=OPENING
-  → GateTradeExecutor.openLong(qty, onSuccess, null)
-    → 市价开多 → onSuccess: longActive=true, placeTakeProfit(多头TP)
-  → GateTradeExecutor.openShort(-qty, onSuccess, null)
-    → 市价开空 → onSuccess: shortActive=true, placeTakeProfit(空头TP)
-  → 双开均完成 → state=ACTIVE
+  → executor.openLong(qty, onSuccess, onFailure)
+    → 成交 → 仓位推送: DUAL_LONG, size>0, entryPrice=X
+      → baseLongOpened=true, longBaseEntryPrice=X
+      → tryGenerateQueues(): 双基底都成交? → 生成队列 → state=ACTIVE
+  → executor.openShort(-qty, onSuccess, onFailure)
+    → 成交 → 仓位推送: DUAL_SHORT, size<0, entryPrice=Y
+      → baseShortOpened=true, shortBaseEntryPrice=Y
+      → tryGenerateQueues(): 双基底都成交? → 生成队列 → state=ACTIVE
 ```
 
-### 阶段 3:止盈触发 → 补仓(含重试)
+### 阶段 3:ACTIVE 状态 — K线驱动网格
 
 ```
-仓位推送: mode=DUAL_LONG, size=0 → longActive且无仓位 → tryReopenLong()
-  ┌─ longReopenFails++  → 超 reopenMaxRetries → STOPPED
-  └─ GateTradeExecutor.openLong(qty,
-        onSuccess: failCount=0, ACTIVE, 下新TP单,
-        onFailure: → 递归调用 tryReopenLong() 再试
-     )
+每根K线 → onKline → updateUnrealizedPnl → processShortGrid + processLongGrid
 
-仓位推送: mode=DUAL_SHORT, size=0 → 同理 tryReopenShort()
+仓位推送(每次开仓成交后自动触发):
+  → DUAL_LONG, size>0, 非基底 → 设多头止盈单 entryPrice × (1+gridRate)
+  → DUAL_SHORT, size<0, 非基底 → 设空头止盈单 entryPrice × (1-gridRate)
 ```
 
-> 止盈由 Gate 服务端条件单自动执行。只补被平掉的单方向,另一方不受影响。
-> 补仓失败通过 onFailure 回调递归重试,连续失败超过 `reopenMaxRetries`(默认3次)则停止策略。
+> 止盈由 Gate 服务端条件单自动执行。服务端监控价格,达到触发价后自动平仓。
+> 平仓后仓位变为0,盈亏通过 position_closes 频道推送到 cumulativePnl。
 
 ### 阶段 4:停止
 
 ```
-平仓推送: pnl=+0.6 → cumulativePnl=0.6 ≥ overallTp → state=STOPPED
-平仓推送: pnl=-8.0 → cumulativePnl=-8.0 ≤ -maxLoss → state=STOPPED
-补仓连续失败 4 次 → 超过 reopenMaxRetries=3 → state=STOPPED
+平仓推送: pnl=+0.6 → cumulativePnl=0.6 ≥ overallTp(0.5) → state=STOPPED
+平仓推送: pnl=-8.0 → cumulativePnl=-8.0 ≤ -maxLoss(7.5) → state=STOPPED
 ```
 
 ---
@@ -203,22 +264,27 @@
 | overallTp | 0.5 USDT | 整体止盈 |
 | maxLoss | 7.5 USDT | 最大亏损 |
 | quantity | 1 | 下单张数 |
-| reopenMaxRetries | 3 | 补仓最大重试次数 |
+| reopenMaxRetries | 3 | 补仓最大重试次数(当前版本未使用) |
+| gridQueueSize | 50 | 网格价格队列容量 |
+| marginRatioLimit | 0.2 | 保证金占初始本金比例上限(20%),超限跳过开仓 |
+| contractMultiplier | 0.001 | 合约乘数(单张合约代表的基础资产数量) |
+| unrealizedPnlPriceMode | LAST_PRICE | 未实现盈亏计价模式:LAST_PRICE / MARK_PRICE |
 
 ---
 
 ## GateTradeExecutor
 
-**角色**: 独立线程池执行 REST API 下单。采用成功/失败双回调模式支持补仓重试。
+**角色**: 独立线程池执行 REST API 下单。采用成功/失败双回调模式。
 
 **线程模型**:
 - `ThreadPoolExecutor(1, 1, 60s, LinkedBlockingQueue(64), CallerRunsPolicy)`
 - 单线程保序 + 有界队列防堆积 + CallerRuns背压
+- allowCoreThreadTimeOut: 60s 空闲后线程回收
 
 **回调设计**:
 - 每个下单方法接受 `onSuccess` 和 `onFailure` 两个 `Runnable`
-- REST 调用成功 → 执行 `onSuccess`(更新状态、下止盈单、重置失败计数)
-- REST 调用失败 → 执行 `onFailure`(递归重试入口)
+- REST 调用成功 → 执行 `onSuccess`(标记基底已开等)
+- REST 调用失败 → 执行 `onFailure`(当前版本多为 null,依赖 position 推送修正)
 
 | 方法 | 说明 |
 |------|------|
@@ -232,11 +298,11 @@
 
 ## GateGridTradeService
 
-**角色**: 策略核心,使用 Gate SDK 管理状态和执行下单。
+**角色**: 策略核心,管理网格队列状态和执行下单。
 
-**状态**: `StrategyState` enum + `longActive`/`shortActive` boolean
+**状态**: `StrategyState` enum: `WAITING_KLINE` / `OPENING` / `ACTIVE` / `STOPPED`
 
-**关键常量**(替代字面字符串):
+**关键常量**:
 ```java
 private static final String AUTO_SIZE_LONG = "close_long";
 private static final String AUTO_SIZE_SHORT = "close_short";
@@ -244,28 +310,57 @@
 private static final String ORDER_TYPE_CLOSE_SHORT = "close-short-position";
 ```
 
-**回调方法**:
-- `onKline(closePrice)`: 缓存价格,WAITING_KLINE状态下首次触发双开
-- `onPositionUpdate(Position.ModeEnum mode, size, entryPrice)`: `== DUAL_LONG/DUAL_SHORT` 枚举比较,size=0且方向活跃 → 补仓重试
-- `onPositionClose(side, pnl)`: 累加盈亏,检查停止条件
+**核心数据结构**:
 
-**补仓重试逻辑**:
-```
-tryReopenLong():
-  1. longReopenFails++(失败计数递增)
-  2. 超过 reopenMaxRetries → STOPPED
-  3. openLong(qty,
-       onSuccess → failCount=0, ACTIVE, 下新TP单,
-       onFailure → 递归 tryReopenLong() 重试
-     )
-```
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| shortPriceQueue | List\<BigDecimal\> | 空仓价格队列,降序(大→小),容量 gridQueueSize |
+| longPriceQueue | List\<BigDecimal\> | 多仓价格队列,升序(小→大),容量 gridQueueSize |
+| shortBaseEntryPrice | BigDecimal | 基底空头入场价 |
+| longBaseEntryPrice | BigDecimal | 基底多头入场价 |
+| shortEntryPrice | BigDecimal | 当前空仓入场价(推送更新) |
+| longEntryPrice | BigDecimal | 当前多仓入场价(推送更新) |
+| shortPositionSize | BigDecimal | 当前空仓持仓量(绝对值) |
+| longPositionSize | BigDecimal | 当前多仓持仓量 |
+| baseLongOpened | boolean | 基底多头是否已开 |
+| baseShortOpened | boolean | 基底空头是否已开 |
+| longActive / shortActive | boolean | 多/空方向是否持有仓位 |
+| cumulativePnl | BigDecimal | 累计已实现盈亏(平仓推送驱动) |
+| unrealizedPnl | BigDecimal | 未实现盈亏(每根K线更新,浮动盈亏) |
+| markPrice | BigDecimal | 标记价格(外部注入,MARK_PRICE 模式使用) |
+| initialPrincipal | BigDecimal | 初始本金(启动时账户总资产) |
+
+**回调方法**:
+- `onKline(closePrice)`: 更新 lastKlinePrice → 计算 unrealizedPnl → WAITING_KLINE 时触发基底双开,ACTIVE 时驱动 processShortGrid+processLongGrid
+- `onPositionUpdate(contract, mode, size, entryPrice)`: 记录当前入场价和持仓量 → 有仓位时标记活跃+设止盈,无仓位时清空持仓量并标记不活跃
+- `onPositionClose(contract, side, pnl)`: 累加已实现盈亏,检查停止条件
+
+**未实现盈亏计算** (`updateUnrealizedPnl()`):
+
+正向合约公式(含合约乘数):
+
+| 方向 | 公式 |
+|------|------|
+| 多仓 | 持仓量 × contractMultiplier × (计价价格 − 开仓均价) |
+| 空仓 | 持仓量(绝对值)× contractMultiplier × (开仓均价 − 计价价格) |
+
+计价价格由 `unrealizedPnlPriceMode` 决定:
+- `LAST_PRICE`:使用最新成交价(`lastKlinePrice`,每根 K 线更新)
+- `MARK_PRICE`:使用标记价格(通过 `setMarkPrice()` 外部注入,如未注入则回退到最新成交价)
+
+入场价和持仓量由 `onPositionUpdate` 实时推送更新。
+
+**保证金安全阀** (`isMarginSafe()`):
+- 实时查询 `positionInitialMargin / initialPrincipal`
+- 比例 ≥ marginRatioLimit(默认20%)→ 跳过开仓,队列照常更新
+- REST 查询失败 → 默认放行(避免因查询异常阻塞策略)
 
 **止盈计算**:
 
-| 方向 | 公式 | order_type | auto_size |
-|------|------|------------|-----------|
-| 多头 TP | entry × (1+gridRate) | `close-long-position` | `close_long` |
-| 空头 TP | entry × (1-gridRate) | `close-short-position` | `close_short` |
+| 方向 | 公式 | order_type | auto_size | rule |
+|------|------|------------|-----------|------|
+| 多头 TP | entry × (1+gridRate) | `close-long-position` | `close_long` | NUMBER_1(≥触发价) |
+| 空头 TP | entry × (1-gridRate) | `close-short-position` | `close_short` | NUMBER_2(≤触发价) |
 
 **REST API 调用**:
 
@@ -276,7 +371,7 @@
 | 查仓位 | `GET /futures/usdt/positions` | `FuturesApi.listPositions()` | 遍历所有仓位,按合约过滤 |
 | 市价平仓 | `POST /futures/usdt/orders` | `FuturesApi.createFuturesOrder()` | reduce_only, IOC。双向: size=0+close=false+autoSize |
 | 设杠杆 | `POST /futures/usdt/positions/{contract}/leverage` | `FuturesApi.updateContractPositionLeverageCall()` | |
-| 查账户 | `GET /futures/usdt/accounts` | `FuturesApi.listFuturesAccounts()` | |
+| 查账户 | `GET /futures/usdt/accounts` | `FuturesApi.listFuturesAccounts()` | 获取初始本金和保证金 |
 | 清除条件单 | `DELETE /futures/usdt/price_orders` | `FuturesApi.cancelPriceTriggeredOrderList()` | |
 | 市价单 | `POST /futures/usdt/orders` | `FuturesApi.createFuturesOrder()` | price=0, tif=IOC |
 | 条件单 | `POST /futures/usdt/price_orders` | `FuturesApi.createPriceTriggeredOrder()` | strategy=0, price_type=0, expiration=0 |
@@ -284,7 +379,7 @@
 **初始化顺序** (`init()`):
 ```
 1. 获取用户 ID
-2. 查账户 → 如需要切持仓模式
+2. 查账户 → 记录初始本金 → 如需要切持仓模式
 3. 清除旧的止盈止损条件单
 4. 查当前合约所有仓位 → 逐个市价平仓
    - 单向持仓(single): size=相反数, reduce_only=true
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/CandlestickChannelHandler.java b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/CandlestickChannelHandler.java
index 614dc41..949f64e 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/CandlestickChannelHandler.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/CandlestickChannelHandler.java
@@ -20,8 +20,8 @@
  * <pre>
  *   WebSocket 推送 update event
  *     → handleMessage() → 解析 OHLCV → log 打印 → gridTradeService.onKline(closePx)
- *       → 首次 K 线触发双开
- *       → 后续 K 线仅缓存 lastKlinePrice 供补仓参考
+ *       → WAITING_KLINE: 首次 K 线触发基底双开
+ *       → ACTIVE: 驱动 processShortGrid + processLongGrid 网格触发
  * </pre>
  *
  * <h3>订阅格式</h3>
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionClosesChannelHandler.java b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionClosesChannelHandler.java
index 78950bb..32615b2 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionClosesChannelHandler.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionClosesChannelHandler.java
@@ -14,6 +14,7 @@
  * <h3>数据用途</h3>
  * 每笔平仓发生时推送 pnl(盈亏金额),累加到 {@code cumulativePnl} 用于判断策略停止条件:
  * cumulativePnl ≥ overallTp(达到止盈目标)或 ≤ -maxLoss(超过亏损上限)。
+ * 止盈由 Gate 服务端条件单自动触发,平仓后仓位变为 0,盈亏通过本频道推送。
  *
  * <h3>推送字段</h3>
  * contract, side(long / short), pnl(该次平仓的盈亏,如 "+0.2" 或 "-0.1")
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionsChannelHandler.java b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionsChannelHandler.java
index 16c26e3..e16ac35 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionsChannelHandler.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionsChannelHandler.java
@@ -13,15 +13,17 @@
  * 仓位频道处理器。
  *
  * <h3>数据用途</h3>
- * 监控仓位数量(size)。当 size 从有变为 0 时,表示该方向被止盈条件单平仓,
- * 触发补仓(reopenLongPosition / reopenShortPosition)。
+ * 监控仓位数量(size)和入场价(entry_price)。
+ * 有仓位时(size.abs > 0):标记方向活跃,记录入场价 → 基底首次成交记录基底入场价并等待生成网格队列,
+ * 非基底成交立即设止盈条件单。无仓位时(size=0):标记方向不活跃。
  *
  * <h3>推送字段</h3>
- * contract, mode(dual_long / dual_short), size(正=持有,0=无仓位), entry_price
+ * contract, mode(dual_long / dual_short), size(正=多头,负=空头),entry_price
  *
  * <h3>注意</h3>
  * 双向持仓模式下空头 size 为负数,使用 {@code size.abs()} 判断是否有仓位。
  * 累计盈亏不由本频道计算,而是由 {@link PositionClosesChannelHandler} 独立处理。
+ * 止盈条件单由服务端自动触发平仓,本频道不负责开仓操作。
  *
  * @author Administrator
  */

--
Gitblit v1.9.1