From 2fede14ef1191ecd8738af4be3808c087131d8a5 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Fri, 05 Jun 2026 16:03:42 +0800
Subject: [PATCH] feat(okx): 添加WebSocket订阅确认机制和优化网格交易配置

---
 src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java               |  361 +------------------------
 src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/RequestBuilder.java |    3 
 src/main/java/com/xcong/excoin/modules/okxNewPrice/StopLossManager.java                   |  302 +++++++++++++++++++++
 src/main/java/com/xcong/excoin/modules/okxNewPrice/GridQueueBuilder.java                  |  152 ++++++++++
 4 files changed, 479 insertions(+), 339 deletions(-)

diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/GridQueueBuilder.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/GridQueueBuilder.java
new file mode 100644
index 0000000..a2d03e4
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/GridQueueBuilder.java
@@ -0,0 +1,152 @@
+package com.xcong.excoin.modules.okxNewPrice;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 网格价格队列构建器 — 从基底入场价生成双向价格队列和 OkxGridElement 列表。
+ *
+ * <h3>职责</h3>
+ * <ul>
+ *   <li>根据基底空头入场价,向下递减排空仓队列</li>
+ *   <li>向上递增排多仓队列</li>
+ *   <li>整合两队列为 OkxGridElement 列表并注入 {@link OkxConfig}</li>
+ * </ul>
+ *
+ * <h3>线程安全</h3>
+ * 本类为纯函数式(除 config.setStep / config.setGridElements 副作用外),
+ * 每次调用返回新构建的对象,不持有可变状态。
+ *
+ * @author Administrator
+ */
+@Slf4j
+public final class GridQueueBuilder {
+
+    private GridQueueBuilder() { /* utility class */ }
+
+    /**
+     * 从基底入场价向下递减生成空仓价格队列,降序排列(大→小)。
+     *
+     * @param config   全局配置(step 会被计算并写入)
+     * @param basePrice 空仓基底入场价
+     * @return 空仓价格队列,按降序排列
+     */
+    public static List<BigDecimal> buildShortQueue(OkxConfig config, BigDecimal basePrice) {
+        int prec = config.getPriceScale();
+        BigDecimal step = basePrice.multiply(config.getGridRate()).setScale(prec, RoundingMode.HALF_UP);
+        config.setStep(step); // 其它方法依赖此 step
+
+        List<BigDecimal> queue = new ArrayList<>();
+        BigDecimal elem = basePrice.subtract(step).setScale(prec, RoundingMode.HALF_UP);
+        for (int i = 0; i < config.getGridQueueSize(); i++) {
+            queue.add(elem);
+            elem = elem.subtract(step).setScale(prec, RoundingMode.HALF_UP);
+            if (elem.compareTo(BigDecimal.ZERO) <= 0) break;
+        }
+        queue.sort((a, b) -> b.compareTo(a)); // 降序
+        log.info("[OKX] 空队列:{}", queue);
+        return queue;
+    }
+
+    /**
+     * 从基底入场价向上递增生成多仓价格队列,升序排列(小→大)。
+     *
+     * @param config   全局配置(使用已设置的 step)
+     * @param basePrice 空仓基底入场价(对齐 Gate 版本,多仓队列也以此为基准)
+     * @return 多仓价格队列,按升序排列
+     */
+    public static List<BigDecimal> buildLongQueue(OkxConfig config, BigDecimal basePrice) {
+        int prec = config.getPriceScale();
+        BigDecimal step = config.getStep();
+        List<BigDecimal> queue = new ArrayList<>();
+        BigDecimal elem = basePrice.add(step).setScale(prec, RoundingMode.HALF_UP);
+        for (int i = 0; i < config.getGridQueueSize(); i++) {
+            queue.add(elem);
+            elem = elem.add(step).setScale(prec, RoundingMode.HALF_UP);
+        }
+        queue.sort(BigDecimal::compareTo); // 升序
+        log.info("[OKX] 多队列:{}", queue);
+        return queue;
+    }
+
+    /**
+     * 将空/多仓队列整合为 OkxGridElement 列表。
+     * <ul>
+     *   <li>空仓网格: id = -1, -2, -3 …</li>
+     *   <li>基底网格: id = 0(短仓入场价位置)</li>
+     *   <li>多仓网格: id = +1, +2, +3 …</li>
+     * </ul>
+     * 每个网格元素包含两个方向的 {@link OkxTraderParam}(入场价 / 止盈价 / 数量)。
+     *
+     * @param config     全局配置
+     * @param shortQueue 空仓价格队列
+     * @param longQueue  多仓价格队列
+     * @param basePrice  空仓基底入场价
+     * @return 构建好的网格元素列表(已注入 config)
+     */
+    public static List<OkxGridElement> buildGridElements(OkxConfig config,
+                                                          List<BigDecimal> shortQueue,
+                                                          List<BigDecimal> longQueue,
+                                                          BigDecimal basePrice) {
+        List<OkxGridElement> elements = new ArrayList<>();
+        int shortSize = shortQueue.size();
+        int longSize = longQueue.size();
+        int prec = config.getPriceScale();
+        BigDecimal step = config.getStep();
+        String qty = config.getQuantity();
+
+        // ---- 空仓网格: id = -1, -2, … ----
+        for (int i = 0; i < shortSize; i++) {
+            int id = -(i + 1);
+            Integer upId = (i == 0) ? 0 : id + 1;
+            Integer downId = (i == shortSize - 1) ? null : id - 1;
+            BigDecimal price = shortQueue.get(i);
+            OkxTraderParam longParam = OkxTraderParam.builder()
+                    .direction(OkxTraderParam.Direction.LONG)
+                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
+            OkxTraderParam shortParam = OkxTraderParam.builder()
+                    .direction(OkxTraderParam.Direction.SHORT)
+                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
+            elements.add(OkxGridElement.builder().id(id).gridPrice(price).upId(upId).downId(downId)
+                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
+        }
+
+        // ---- 基底网格: id = 0 ----
+        {
+            BigDecimal price = basePrice;
+            OkxTraderParam longParam = OkxTraderParam.builder()
+                    .direction(OkxTraderParam.Direction.LONG)
+                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
+            OkxTraderParam shortParam = OkxTraderParam.builder()
+                    .direction(OkxTraderParam.Direction.SHORT)
+                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
+            elements.add(OkxGridElement.builder().id(0).gridPrice(price)
+                    .upId(shortSize > 0 ? 1 : null).downId(longSize > 0 ? -1 : null)
+                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
+        }
+
+        // ---- 多仓网格: id = 1, 2, … ----
+        for (int i = 0; i < longSize; i++) {
+            int id = i + 1;
+            Integer downId = (i == 0) ? 0 : id - 1;
+            Integer upId = (i == longSize - 1) ? null : id + 1;
+            BigDecimal price = longQueue.get(i);
+            OkxTraderParam longParam = OkxTraderParam.builder()
+                    .direction(OkxTraderParam.Direction.LONG)
+                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
+            OkxTraderParam shortParam = OkxTraderParam.builder()
+                    .direction(OkxTraderParam.Direction.SHORT)
+                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
+            elements.add(OkxGridElement.builder().id(id).gridPrice(price).upId(upId).downId(downId)
+                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
+        }
+
+        config.setGridElements(elements);
+        log.info("[OKX] 网格元素列表已构建, 共{}个元素", elements.size());
+        return elements;
+    }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java
index 841cfb0..611049b 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java
@@ -57,6 +57,7 @@
     private final OkxConfig config;
     private final OkxTradeExecutor executor;
     private final OKXAccount okxAccount;
+    private final StopLossManager stopLossManager;
 
     private volatile StrategyState state = StrategyState.WAITING_KLINE;
 
@@ -105,6 +106,7 @@
         this.config = config;
         this.okxAccount = okxAccount;
         this.executor = new OkxTradeExecutor(okxAccount, config.getInstId(), config.getTdMode());
+        this.stopLossManager = new StopLossManager(config, executor);
     }
 
     // ---- 初始化 ----
@@ -417,32 +419,32 @@
             return;
         }
 
-        // 匹配止损单
+        // 匹配止损单 → 委托 StopLossManager
         OkxGridElement byLongStopLoss = OkxGridElement.findByLongStopLossOrderId(algoId);
         if (byLongStopLoss != null) {
-            handleLongStopLossTriggered(byLongStopLoss);
+            stopLossManager.handleLongStopLossTriggered(byLongStopLoss, longEntryPrice);
             return;
         }
         OkxGridElement byShortStopLoss = OkxGridElement.findByShortStopLossOrderId(algoId);
         if (byShortStopLoss != null) {
-            handleShortStopLossTriggered(byShortStopLoss);
+            stopLossManager.handleShortStopLossTriggered(byShortStopLoss, shortEntryPrice);
             return;
         }
 
-        // 匹配挂单 —— 条件单成交后:清空挂单状态 + 追挂止损 + 挂止盈单
+        // 匹配挂单 —— 条件单成交后:清空挂单状态 + 追挂止损
         OkxGridElement shortGridElement = OkxGridElement.findByShortOrderId(algoId);
         if (shortGridElement != null && shortGridElement.isHasShortOrder()) {
             int filledQty = Integer.parseInt(shortGridElement.getShortTraderParam().getQuantity());
-            shortEntryTraderIdParam(shortGridElement, null, false);
-            extendShortStopLoss(filledQty);
+            stopLossManager.clearShortEntryState(shortGridElement);
+            stopLossManager.extendShortStopLoss(filledQty);
             log.info("[OKX] 空单成交 gridId:{}, qty:{}, 追挂止损", shortGridElement.getId(), filledQty);
             return;
         }
         OkxGridElement longGridElement = OkxGridElement.findByLongOrderId(algoId);
         if (longGridElement != null && longGridElement.isHasLongOrder()) {
             int filledQty = Integer.parseInt(longGridElement.getLongTraderParam().getQuantity());
-            longEntryTraderIdParam(longGridElement, null, false);
-            extendLongStopLoss(filledQty);
+            stopLossManager.clearLongEntryState(longGridElement);
+            stopLossManager.extendLongStopLoss(filledQty);
             log.info("[OKX] 多单成交 gridId:{}, qty:{}, 追挂止损", longGridElement.getId(), filledQty);
             return;
         }
@@ -465,9 +467,18 @@
                 return;
             }
 
-            generateShortQueue();
-            generateLongQueue();
-            updateGridElements();
+            // 委托 GridQueueBuilder 构建价格队列 + GridElements
+            List<BigDecimal> tmpShort = GridQueueBuilder.buildShortQueue(config, shortBaseEntryPrice);
+            List<BigDecimal> tmpLong = GridQueueBuilder.buildLongQueue(config, shortBaseEntryPrice);
+            synchronized (shortPriceQueue) {
+                shortPriceQueue.clear();
+                shortPriceQueue.addAll(tmpShort);
+            }
+            synchronized (longPriceQueue) {
+                longPriceQueue.clear();
+                longPriceQueue.addAll(tmpLong);
+            }
+            GridQueueBuilder.buildGridElements(config, shortPriceQueue, longPriceQueue, shortBaseEntryPrice);
 
             // 标记基座挂单
             OkxGridElement baseGridElement = OkxGridElement.findById(0);
@@ -476,336 +487,14 @@
             baseGridElement.setShortOrderId(baseShortTp.getEntryOrderId());
             baseGridElement.setHasShortOrder(true);
 
-            // 挂多仓止损 (id=-2 到 -11),每格 quantity 张
-            for (int id = -2; id >= -11; id--) {
-                OkxGridElement elem = OkxGridElement.findById(id);
-                if (elem == null) continue;
-                BigDecimal triggerPrice = elem.getGridPrice();
-                int finalId = id;
-                executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(),
-                        profitId -> {
-                            elem.setLongStopLossOrderId(profitId);
-                            OkxGridElement.refreshIndices();
-                            log.info("[OKX] 多仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}",
-                                    finalId, triggerPrice, config.getQuantity(), profitId);
-                        });
-            }
-
-            // 挂空仓止损 (id=2 到 11),每格 quantity 张
-            for (int id = 2; id <= 11; id++) {
-                OkxGridElement elem = OkxGridElement.findById(id);
-                if (elem == null) continue;
-                BigDecimal triggerPrice = elem.getGridPrice();
-                int finalId = id;
-                executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(),
-                        profitId -> {
-                            elem.setShortStopLossOrderId(profitId);
-                            OkxGridElement.refreshIndices();
-                            log.info("[OKX] 空仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}",
-                                    finalId, triggerPrice, config.getQuantity(), profitId);
-                        });
-            }
-
-            log.info("[OKX] 止损单已全部挂完, 空仓止损: 2~11, 多仓止损: -2~-11");
+            // 委托 StopLossManager 挂止损单
+            stopLossManager.setupBaseStopLosses();
             state = StrategyState.ACTIVE;
         }
     }
 
-    private void generateShortQueue() {
-        shortPriceQueue.clear();
-        int prec = config.getPriceScale();
-        BigDecimal step = shortBaseEntryPrice.multiply(config.getGridRate()).setScale(prec, RoundingMode.HALF_UP);
-        config.setStep(step);
-        BigDecimal elem = shortBaseEntryPrice.subtract(step).setScale(prec, RoundingMode.HALF_UP);
-        for (int i = 0; i < config.getGridQueueSize(); i++) {
-            shortPriceQueue.add(elem);
-            elem = elem.subtract(step).setScale(prec, RoundingMode.HALF_UP);
-            if (elem.compareTo(BigDecimal.ZERO) <= 0) break;
-        }
-        shortPriceQueue.sort((a, b) -> b.compareTo(a));
-        log.info("[OKX] 空队列:{}", shortPriceQueue);
-    }
 
-    private void generateLongQueue() {
-        longPriceQueue.clear();
-        int prec = config.getPriceScale();
-        BigDecimal step = config.getStep();
-        BigDecimal elem = shortBaseEntryPrice.add(step).setScale(prec, RoundingMode.HALF_UP);
-        for (int i = 0; i < config.getGridQueueSize(); i++) {
-            longPriceQueue.add(elem);
-            elem = elem.add(step).setScale(prec, RoundingMode.HALF_UP);
-        }
-        longPriceQueue.sort(BigDecimal::compareTo);
-        log.info("[OKX] 多队列:{}", longPriceQueue);
-    }
-
-    private void updateGridElements() {
-        List<OkxGridElement> elements = new ArrayList<>();
-        int shortSize = shortPriceQueue.size();
-        int longSize = longPriceQueue.size();
-        int prec = config.getPriceScale();
-        BigDecimal step = config.getStep();
-        String qty = config.getQuantity();
-
-        // 空仓队列: id=-1, -2, ...
-        for (int i = 0; i < shortSize; i++) {
-            int id = -(i + 1);
-            Integer upId = (i == 0) ? 0 : id + 1;
-            Integer downId = (i == shortSize - 1) ? null : id - 1;
-            BigDecimal price = shortPriceQueue.get(i);
-            OkxTraderParam longParam = OkxTraderParam.builder()
-                    .direction(OkxTraderParam.Direction.LONG)
-                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
-            OkxTraderParam shortParam = OkxTraderParam.builder()
-                    .direction(OkxTraderParam.Direction.SHORT)
-                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
-            elements.add(OkxGridElement.builder().id(id).gridPrice(price).upId(upId).downId(downId)
-                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
-        }
-
-        // 位置 0: 基底价格
-        {
-            BigDecimal price = shortBaseEntryPrice;
-            OkxTraderParam longParam = OkxTraderParam.builder()
-                    .direction(OkxTraderParam.Direction.LONG)
-                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
-            OkxTraderParam shortParam = OkxTraderParam.builder()
-                    .direction(OkxTraderParam.Direction.SHORT)
-                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
-            elements.add(OkxGridElement.builder().id(0).gridPrice(price)
-                    .upId(shortSize > 0 ? 1 : null).downId(longSize > 0 ? -1 : null)
-                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
-        }
-
-        // 多仓队列: id=1, 2, ...
-        for (int i = 0; i < longSize; i++) {
-            int id = i + 1;
-            Integer downId = (i == 0) ? 0 : id - 1;
-            Integer upId = (i == longSize - 1) ? null : id + 1;
-            BigDecimal price = longPriceQueue.get(i);
-            OkxTraderParam longParam = OkxTraderParam.builder()
-                    .direction(OkxTraderParam.Direction.LONG)
-                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
-            OkxTraderParam shortParam = OkxTraderParam.builder()
-                    .direction(OkxTraderParam.Direction.SHORT)
-                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
-            elements.add(OkxGridElement.builder().id(id).gridPrice(price).upId(upId).downId(downId)
-                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
-        }
-
-        config.setGridElements(elements);
-        log.info("[OKX] 网格元素列表已构建, 共{}个元素", elements.size());
-    }
-
-    // ---- 止损触发处理 ----
-
-    /**
-     * 多仓止损触发处理(Gate 模式逐步缩进)。
-     * 止损触发后向基底方向缩进 1 格挂条件多单,数量 = |触发价 - 当前持仓均价| / 网格步长,取整。
-     * 若 N>2,先取消上一步的旧挂单。
-     */
-    private void handleLongStopLossTriggered(OkxGridElement gridElement) {
-        int gridId = gridElement.getId();
-        int N = Math.abs(gridId);
-        gridElement.setLongStopLossOrderId(null);
-        log.info("[OKX] 多仓止损触发 gridId:{}, 逐步缩进", gridId);
-
-        int newEntryGridId = -(N - 1);
-        OkxGridElement newEntryGrid = OkxGridElement.findById(newEntryGridId);
-        if (newEntryGrid == null) {
-            OkxGridElement.refreshIndices();
-            log.warn("[OKX] 多仓止损触发 gridId:{} 找不到入单网格({})", gridId, newEntryGridId);
-            return;
-        }
-
-        if (N > 2) {
-            int cancelGridId = -(N - 2);
-            OkxGridElement cancelGrid = OkxGridElement.findById(cancelGridId);
-            if (cancelGrid != null && cancelGrid.isHasLongOrder()) {
-                executor.cancelAlgoOrder(cancelGrid.getLongOrderId(), oid -> {
-                    longEntryTraderIdParam(cancelGrid, null, false);
-                    log.info("[OKX] 多仓止损触发, 取消gridId:{}的多单", cancelGridId);
-                });
-            }
-        }
-
-        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
-        BigDecimal priceDiff = longEntryPrice.subtract(triggerPrice).abs();
-        // 精度补偿:步长被setScale截断,priceDiff/step可能产生1.99998→Down截断为1的问题
-        BigDecimal epsilon = new BigDecimal("0.00000001");
-        int count = priceDiff.add(epsilon).divide(config.getStep(), 0, RoundingMode.DOWN).intValue();
-        count = Math.max(1, count);
-        int entryQty = count * Integer.parseInt(config.getQuantity());
-        String size = String.valueOf(entryQty);
-        log.info("[OKX] 多仓止损触发 gridId:{}, 在gridId:{}挂{}张多单(价差:{},步长:{},count:{},qty:{})",
-                gridId, newEntryGridId, entryQty, priceDiff, config.getStep(), count, config.getQuantity());
-        newEntryGrid.getLongTraderParam().setQuantity(size);
-        placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice, size);
-    }
-
-    /**
-     * 空仓止损触发处理(Gate 模式逐步缩进)。
-     * 止损触发后向基底方向缩进 1 格挂条件空单,数量 = |触发价 - 当前持仓均价| / 网格步长,取整。
-     * 若 N>2,先取消上一步的旧挂单。
-     */
-    private void handleShortStopLossTriggered(OkxGridElement gridElement) {
-        int gridId = gridElement.getId();
-        int N = gridId;
-        gridElement.setShortStopLossOrderId(null);
-        log.info("[OKX] 空仓止损触发 gridId:{}, 逐步缩进", gridId);
-
-        int newEntryGridId = N - 1;
-        OkxGridElement newEntryGrid = OkxGridElement.findById(newEntryGridId);
-        if (newEntryGrid == null) {
-            OkxGridElement.refreshIndices();
-            log.warn("[OKX] 空仓止损触发 gridId:{} 找不到入单网格({})", gridId, newEntryGridId);
-            return;
-        }
-
-        if (N > 2) {
-            int cancelGridId = N - 2;
-            OkxGridElement cancelGrid = OkxGridElement.findById(cancelGridId);
-            if (cancelGrid != null && cancelGrid.isHasShortOrder()) {
-                executor.cancelAlgoOrder(cancelGrid.getShortOrderId(), oid -> {
-                    shortEntryTraderIdParam(cancelGrid, null, false);
-                    log.info("[OKX] 空仓止损触发, 取消gridId:{}的空单", cancelGridId);
-                });
-            }
-        }
-
-        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
-        BigDecimal priceDiff = shortEntryPrice.subtract(triggerPrice).abs();
-        // 精度补偿:步长被setScale截断,priceDiff/step可能产生1.99998→Down截断为1的问题
-        BigDecimal epsilon = new BigDecimal("0.00000001");
-        int count = priceDiff.add(epsilon).divide(config.getStep(), 0, RoundingMode.DOWN).intValue();
-        count = Math.max(1, count);
-        int entryQty = count * Integer.parseInt(config.getQuantity());
-        String size = String.valueOf(entryQty);
-        log.info("[OKX] 空仓止损触发 gridId:{}, 在gridId:{}挂{}张空单(价差:{},步长:{},count:{},qty:{})",
-                gridId, newEntryGridId, entryQty, priceDiff, config.getStep(), count, config.getQuantity());
-        newEntryGrid.getShortTraderParam().setQuantity(size);
-        placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice, size);
-    }
-
-    private void extendLongStopLoss(int filledQty) {
-        // filledQty 为本次新增止损张数 = count * quantity, 需要按 quantity 为粒度拆分为 count 个止损单
-        int qty = Integer.parseInt(config.getQuantity());
-        int stopLossCount = filledQty / qty;
-        int furthestSlId = 0;
-        for (OkxGridElement e : config.getGridElements()) {
-            if (e.getLongStopLossOrderId() != null && e.getId() < furthestSlId) {
-                furthestSlId = e.getId();
-            }
-        }
-        if (furthestSlId == 0) furthestSlId = -11;
-        log.info("[OKX] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty);
-        for (int i = 0; i < stopLossCount; i++) {
-            int newSlId = furthestSlId - i - 1;
-            OkxGridElement elem = OkxGridElement.findById(newSlId);
-            if (elem == null) continue;
-            BigDecimal triggerPrice = elem.getGridPrice();
-            int finalSlId = newSlId;
-            executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(),
-                    profitId -> {
-                        elem.setLongStopLossOrderId(profitId);
-                        OkxGridElement.refreshIndices();
-                        log.info("[OKX] 多仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
-                    });
-        }
-    }
-
-    private void extendShortStopLoss(int filledQty) {
-        int qty = Integer.parseInt(config.getQuantity());
-        int stopLossCount = filledQty / qty;
-        int furthestSlId = 0;
-        for (OkxGridElement e : config.getGridElements()) {
-            if (e.getShortStopLossOrderId() != null && e.getId() > furthestSlId) {
-                furthestSlId = e.getId();
-            }
-        }
-        if (furthestSlId == 0) furthestSlId = 11;
-        log.info("[OKX] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty);
-        for (int i = 0; i < stopLossCount; i++) {
-            int newSlId = furthestSlId + i + 1;
-            OkxGridElement elem = OkxGridElement.findById(newSlId);
-            if (elem == null) continue;
-            BigDecimal triggerPrice = elem.getGridPrice();
-            int finalSlId = newSlId;
-            executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(),
-                    profitId -> {
-                        elem.setShortStopLossOrderId(profitId);
-                        OkxGridElement.refreshIndices();
-                        log.info("[OKX] 空仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
-                    });
-        }
-    }
-
-    // ---- 辅助方法 ----
-
-    private void longTakeProfitTraderIdParam(OkxGridElement baseElement, String profitId, boolean flag) {
-        OkxTraderParam tp = baseElement.getLongTraderParam();
-        tp.setTakeProfitOrderId(profitId);
-        tp.setTakeProfitPlaced(flag);
-        baseElement.setLongTakeProfitOrderId(profitId);
-        OkxGridElement.refreshIndices();
-    }
-
-    private void shortTakeProfitTraderIdParam(OkxGridElement baseElement, String profitId, boolean flag) {
-        OkxTraderParam tp = baseElement.getShortTraderParam();
-        tp.setTakeProfitOrderId(profitId);
-        tp.setTakeProfitPlaced(flag);
-        baseElement.setShortTakeProfitOrderId(profitId);
-        OkxGridElement.refreshIndices();
-    }
-
-    private void longEntryTraderIdParam(OkxGridElement baseElement, String entryId, boolean flag) {
-        OkxTraderParam tp = baseElement.getLongTraderParam();
-        tp.setEntryOrderId(entryId);
-        tp.setEntryOrderPlaced(flag);
-        baseElement.setHasLongOrder(flag);
-        baseElement.setLongOrderId(entryId);
-        OkxGridElement.refreshIndices();
-    }
-
-    private void shortEntryTraderIdParam(OkxGridElement baseElement, String entryId, boolean flag) {
-        OkxTraderParam tp = baseElement.getShortTraderParam();
-        tp.setEntryOrderId(entryId);
-        tp.setEntryOrderPlaced(flag);
-        baseElement.setHasShortOrder(flag);
-        baseElement.setShortOrderId(entryId);
-        OkxGridElement.refreshIndices();
-    }
-
-    private void placeEntryOrderWithPreFlag(OkxGridElement gridElement, boolean isLong,
-                                             BigDecimal triggerPrice, String size) {
-        if (isLong) {
-            gridElement.setHasLongOrder(true);
-        } else {
-            gridElement.setHasShortOrder(true);
-        }
-        String side = isLong ? "buy" : "sell";
-        String posSide = isLong ? "long" : "short";
-        executor.placeConditionalEntryOrder(triggerPrice.toString(), side, posSide, size,
-                orderId -> {
-                    if (isLong) {
-                        longEntryTraderIdParam(gridElement, orderId, true);
-                    } else {
-                        shortEntryTraderIdParam(gridElement, orderId, true);
-                    }
-                },
-                () -> {
-                    if (isLong) {
-                        gridElement.setHasLongOrder(false);
-                        gridElement.setLongOrderId(null);
-                    } else {
-                        gridElement.setHasShortOrder(false);
-                        gridElement.setShortOrderId(null);
-                    }
-                    OkxGridElement.refreshIndices();
-                    log.warn("[OKX] 条件单创建失败,回滚标志位 gridId:{}, isLong:{}", gridElement.getId(), isLong);
-                }
-        );
-    }
+    // ---- 盈亏计算 ----
 
     private void updateUnrealizedPnl() {
         if (lastKlinePrice == null || lastKlinePrice.compareTo(BigDecimal.ZERO) == 0) return;
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/StopLossManager.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/StopLossManager.java
new file mode 100644
index 0000000..9b864eb
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/StopLossManager.java
@@ -0,0 +1,302 @@
+package com.xcong.excoin.modules.okxNewPrice;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * 止损管理器 — 负责基底止损单挂载、止损触发后的逐步缩进、以及止损追挂。
+ *
+ * <h3>止损策略(对齐 Gate 版本)</h3>
+ * <ol>
+ *   <li>基底开仓后挂多仓止损(id=-2~-11)和空仓止损(id=2~11),每格 1 倍 quantity</li>
+ *   <li>条件单成交后以成交量为粒度追挂止损(extend 系列)</li>
+ *   <li>止损触发后向基底方向缩进 1 格,数量由价格差 / 步长决定,N&gt;2 时先取消旧挂单</li>
+ * </ol>
+ *
+ * <h3>线程安全</h3>
+ * 本类通过 executor 的异步回调串行化下单操作;对 OkxGridElement 的读写依赖调用方保证有序。
+ *
+ * @author Administrator
+ */
+@Slf4j
+public class StopLossManager {
+
+    private final OkxConfig config;
+    private final OkxTradeExecutor executor;
+
+    public StopLossManager(OkxConfig config, OkxTradeExecutor executor) {
+        this.config = config;
+        this.executor = executor;
+    }
+
+    // ========== 基底止损挂载 ==========
+
+    /**
+     * 在基底开仓完成后挂载初始止损单。
+     * 多仓止损: id=-2 到 -11,每格 quantity 张,方向 sell/long
+     * 空仓止损: id=2 到 11,每格 quantity 张,方向 buy/short
+     */
+    public void setupBaseStopLosses() {
+        // 挂多仓止损 (id=-2 到 -11)
+        for (int id = -2; id >= -11; id--) {
+            OkxGridElement elem = OkxGridElement.findById(id);
+            if (elem == null) continue;
+            BigDecimal triggerPrice = elem.getGridPrice();
+            int finalId = id;
+            executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(),
+                    profitId -> {
+                        elem.setLongStopLossOrderId(profitId);
+                        OkxGridElement.refreshIndices();
+                        log.info("[OKX] 多仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}",
+                                finalId, triggerPrice, config.getQuantity(), profitId);
+                    });
+        }
+
+        // 挂空仓止损 (id=2 到 11)
+        for (int id = 2; id <= 11; id++) {
+            OkxGridElement elem = OkxGridElement.findById(id);
+            if (elem == null) continue;
+            BigDecimal triggerPrice = elem.getGridPrice();
+            int finalId = id;
+            executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(),
+                    profitId -> {
+                        elem.setShortStopLossOrderId(profitId);
+                        OkxGridElement.refreshIndices();
+                        log.info("[OKX] 空仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}",
+                                finalId, triggerPrice, config.getQuantity(), profitId);
+                    });
+        }
+
+        log.info("[OKX] 止损单已全部挂完, 空仓止损: 2~11, 多仓止损: -2~-11");
+    }
+
+    // ========== 止损触发处理 ==========
+
+    /**
+     * 多仓止损触发处理。
+     * 止损触发后向基底方向缩进 1 格挂条件多单,数量 = |触发价 - 当前持仓均价| / 网格步长,取整。
+     * 若 N &gt; 2,先取消上一步的旧挂单。
+     */
+    public void handleLongStopLossTriggered(OkxGridElement gridElement, BigDecimal longEntryPrice) {
+        int gridId = gridElement.getId();
+        int N = Math.abs(gridId);
+        gridElement.setLongStopLossOrderId(null);
+        log.info("[OKX] 多仓止损触发 gridId:{}, 逐步缩进", gridId);
+
+        int newEntryGridId = -(N - 1);
+        OkxGridElement newEntryGrid = OkxGridElement.findById(newEntryGridId);
+        if (newEntryGrid == null) {
+            OkxGridElement.refreshIndices();
+            log.warn("[OKX] 多仓止损触发 gridId:{} 找不到入单网格({})", gridId, newEntryGridId);
+            return;
+        }
+
+        if (N > 2) {
+            int cancelGridId = -(N - 2);
+            OkxGridElement cancelGrid = OkxGridElement.findById(cancelGridId);
+            if (cancelGrid != null && cancelGrid.isHasLongOrder()) {
+                executor.cancelAlgoOrder(cancelGrid.getLongOrderId(), oid -> {
+                    clearLongEntryState(cancelGrid);
+                    log.info("[OKX] 多仓止损触发, 取消gridId:{}的多单", cancelGridId);
+                });
+            }
+        }
+
+        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
+        BigDecimal priceDiff = longEntryPrice.subtract(triggerPrice).abs();
+        BigDecimal epsilon = new BigDecimal("0.00000001");
+        int count = priceDiff.add(epsilon).divide(config.getStep(), 0, RoundingMode.DOWN).intValue();
+        count = Math.max(1, count);
+        int entryQty = count * Integer.parseInt(config.getQuantity());
+        String size = String.valueOf(entryQty);
+        log.info("[OKX] 多仓止损触发 gridId:{}, 在gridId:{}挂{}张多单(价差:{},步长:{},count:{},qty:{})",
+                gridId, newEntryGridId, entryQty, priceDiff, config.getStep(), count, config.getQuantity());
+        newEntryGrid.getLongTraderParam().setQuantity(size);
+        placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice, size);
+    }
+
+    /**
+     * 空仓止损触发处理。
+     * 止损触发后向基底方向缩进 1 格挂条件空单,数量 = |触发价 - 当前持仓均价| / 网格步长,取整。
+     * 若 N &gt; 2,先取消上一步的旧挂单。
+     */
+    public void handleShortStopLossTriggered(OkxGridElement gridElement, BigDecimal shortEntryPrice) {
+        int gridId = gridElement.getId();
+        int N = gridId;
+        gridElement.setShortStopLossOrderId(null);
+        log.info("[OKX] 空仓止损触发 gridId:{}, 逐步缩进", gridId);
+
+        int newEntryGridId = N - 1;
+        OkxGridElement newEntryGrid = OkxGridElement.findById(newEntryGridId);
+        if (newEntryGrid == null) {
+            OkxGridElement.refreshIndices();
+            log.warn("[OKX] 空仓止损触发 gridId:{} 找不到入单网格({})", gridId, newEntryGridId);
+            return;
+        }
+
+        if (N > 2) {
+            int cancelGridId = N - 2;
+            OkxGridElement cancelGrid = OkxGridElement.findById(cancelGridId);
+            if (cancelGrid != null && cancelGrid.isHasShortOrder()) {
+                executor.cancelAlgoOrder(cancelGrid.getShortOrderId(), oid -> {
+                    clearShortEntryState(cancelGrid);
+                    log.info("[OKX] 空仓止损触发, 取消gridId:{}的空单", cancelGridId);
+                });
+            }
+        }
+
+        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
+        BigDecimal priceDiff = shortEntryPrice.subtract(triggerPrice).abs();
+        BigDecimal epsilon = new BigDecimal("0.00000001");
+        int count = priceDiff.add(epsilon).divide(config.getStep(), 0, RoundingMode.DOWN).intValue();
+        count = Math.max(1, count);
+        int entryQty = count * Integer.parseInt(config.getQuantity());
+        String size = String.valueOf(entryQty);
+        log.info("[OKX] 空仓止损触发 gridId:{}, 在gridId:{}挂{}张空单(价差:{},步长:{},count:{},qty:{})",
+                gridId, newEntryGridId, entryQty, priceDiff, config.getStep(), count, config.getQuantity());
+        newEntryGrid.getShortTraderParam().setQuantity(size);
+        placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice, size);
+    }
+
+    // ========== 止损追挂 ==========
+
+    /**
+     * 多仓追挂止损:按 quantity 粒度拆分为 count 个止损单,从当前最远止损格再往外延伸。
+     */
+    public void extendLongStopLoss(int filledQty) {
+        int qty = Integer.parseInt(config.getQuantity());
+        int stopLossCount = filledQty / qty;
+        int furthestSlId = 0;
+        for (OkxGridElement e : config.getGridElements()) {
+            if (e.getLongStopLossOrderId() != null && e.getId() < furthestSlId) {
+                furthestSlId = e.getId();
+            }
+        }
+        if (furthestSlId == 0) furthestSlId = -11;
+        log.info("[OKX] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty);
+        for (int i = 0; i < stopLossCount; i++) {
+            int newSlId = furthestSlId - i - 1;
+            OkxGridElement elem = OkxGridElement.findById(newSlId);
+            if (elem == null) continue;
+            BigDecimal triggerPrice = elem.getGridPrice();
+            int finalSlId = newSlId;
+            executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(),
+                    profitId -> {
+                        elem.setLongStopLossOrderId(profitId);
+                        OkxGridElement.refreshIndices();
+                        log.info("[OKX] 多仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
+                    });
+        }
+    }
+
+    /**
+     * 空仓追挂止损:按 quantity 粒度拆分为 count 个止损单,从当前最远止损格再往外延伸。
+     */
+    public void extendShortStopLoss(int filledQty) {
+        int qty = Integer.parseInt(config.getQuantity());
+        int stopLossCount = filledQty / qty;
+        int furthestSlId = 0;
+        for (OkxGridElement e : config.getGridElements()) {
+            if (e.getShortStopLossOrderId() != null && e.getId() > furthestSlId) {
+                furthestSlId = e.getId();
+            }
+        }
+        if (furthestSlId == 0) furthestSlId = 11;
+        log.info("[OKX] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty);
+        for (int i = 0; i < stopLossCount; i++) {
+            int newSlId = furthestSlId + i + 1;
+            OkxGridElement elem = OkxGridElement.findById(newSlId);
+            if (elem == null) continue;
+            BigDecimal triggerPrice = elem.getGridPrice();
+            int finalSlId = newSlId;
+            executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(),
+                    profitId -> {
+                        elem.setShortStopLossOrderId(profitId);
+                        OkxGridElement.refreshIndices();
+                        log.info("[OKX] 空仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
+                    });
+        }
+    }
+
+    // ========== 辅助:条件单创建 & 状态回写 ==========
+
+    /**
+     * 预置标志位后发送条件入单请求,成功/失败均通过回调写入 GridElement 状态。
+     */
+    void placeEntryOrderWithPreFlag(OkxGridElement gridElement, boolean isLong,
+                                     BigDecimal triggerPrice, String size) {
+        if (isLong) {
+            gridElement.setHasLongOrder(true);
+        } else {
+            gridElement.setHasShortOrder(true);
+        }
+        String side = isLong ? "buy" : "sell";
+        String posSide = isLong ? "long" : "short";
+        executor.placeConditionalEntryOrder(triggerPrice.toString(), side, posSide, size,
+                orderId -> {
+                    if (isLong) {
+                        setLongEntryState(gridElement, orderId, true);
+                    } else {
+                        setShortEntryState(gridElement, orderId, true);
+                    }
+                },
+                () -> {
+                    if (isLong) {
+                        gridElement.setHasLongOrder(false);
+                        gridElement.setLongOrderId(null);
+                    } else {
+                        gridElement.setHasShortOrder(false);
+                        gridElement.setShortOrderId(null);
+                    }
+                    OkxGridElement.refreshIndices();
+                    log.warn("[OKX] 条件单创建失败,回滚标志位 gridId:{}, isLong:{}", gridElement.getId(), isLong);
+                }
+        );
+    }
+
+    // ---- GridElement 状态修改(package-private,OkxGridTradeService 也可调用) ----
+
+    void setLongEntryState(OkxGridElement gridElement, String entryId, boolean flag) {
+        OkxTraderParam tp = gridElement.getLongTraderParam();
+        tp.setEntryOrderId(entryId);
+        tp.setEntryOrderPlaced(flag);
+        gridElement.setHasLongOrder(flag);
+        gridElement.setLongOrderId(entryId);
+        OkxGridElement.refreshIndices();
+    }
+
+    void setShortEntryState(OkxGridElement gridElement, String entryId, boolean flag) {
+        OkxTraderParam tp = gridElement.getShortTraderParam();
+        tp.setEntryOrderId(entryId);
+        tp.setEntryOrderPlaced(flag);
+        gridElement.setHasShortOrder(flag);
+        gridElement.setShortOrderId(entryId);
+        OkxGridElement.refreshIndices();
+    }
+
+    void clearLongEntryState(OkxGridElement gridElement) {
+        setLongEntryState(gridElement, null, false);
+    }
+
+    void clearShortEntryState(OkxGridElement gridElement) {
+        setShortEntryState(gridElement, null, false);
+    }
+
+    void setLongTakeProfitState(OkxGridElement gridElement, String profitId, boolean flag) {
+        OkxTraderParam tp = gridElement.getLongTraderParam();
+        tp.setTakeProfitOrderId(profitId);
+        tp.setTakeProfitPlaced(flag);
+        gridElement.setLongTakeProfitOrderId(profitId);
+        OkxGridElement.refreshIndices();
+    }
+
+    void setShortTakeProfitState(OkxGridElement gridElement, String profitId, boolean flag) {
+        OkxTraderParam tp = gridElement.getShortTraderParam();
+        tp.setTakeProfitOrderId(profitId);
+        tp.setTakeProfitPlaced(flag);
+        gridElement.setShortTakeProfitOrderId(profitId);
+        OkxGridElement.refreshIndices();
+    }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/RequestBuilder.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/RequestBuilder.java
index 30bf027..e5e5fd3 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/RequestBuilder.java
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/RequestBuilder.java
@@ -96,7 +96,6 @@
                             .get()
                             .addHeader("Content-Type", "application/x-www-form-urlencoded")
                             .addHeader("OK-ACCESS-KEY", apiKey)
-                            .addHeader("OK-ACCESS-KEY", apiKey)
                             .addHeader("OK-ACCESS-SIGN", sign)
                             .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
                             .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
@@ -112,7 +111,6 @@
                             .put(RequestBody.create(JSON_TYPE, ""))
                             .addHeader("Content-Type", "application/x-www-form-urlencoded")
                             .addHeader("OK-ACCESS-KEY", apiKey)
-                            .addHeader("OK-ACCESS-KEY", apiKey)
                             .addHeader("OK-ACCESS-SIGN", sign)
                             .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
                             .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
@@ -127,7 +125,6 @@
                             .url(fullUrl)
                             .delete()
                             .addHeader("Content-Type", "application/x-www-form-urlencoded")
-                            .addHeader("OK-ACCESS-KEY", apiKey)
                             .addHeader("OK-ACCESS-KEY", apiKey)
                             .addHeader("OK-ACCESS-SIGN", sign)
                             .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)

--
Gitblit v1.9.1