From 0d9c31cc7be76229cf71e141444598992758ebf6 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Tue, 12 May 2026 16:46:22 +0800
Subject: [PATCH] feat(gateApi): 更新网格交易逻辑文档和实现

---
 src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java |   38 +++++++-----
 src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md          |   42 +++++++++-----
 src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java    |   72 ++++++++++++++++++++++++
 3 files changed, 121 insertions(+), 31 deletions(-)

diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
index e791e63..b4bf382 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -513,11 +513,13 @@
             log.info("[Gate] 多止盈队列:{}", longTakeProfitQueue);
             log.info("[Gate] 空止盈队列:{}", shortTakeProfitQueue);
 
-            executor.placeGridLimitOrder(longPriceQueue.get(0), config.getQuantity(),
-                    orderId -> { currentLongOrderId = orderId; log.info("[Gate] 初始限价多单已挂, id:{}", orderId); },
+            executor.placeConditionalEntryOrder(longPriceQueue.get(0),
+                    FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(),
+                    orderId -> { currentLongOrderId = orderId; log.info("[Gate] 初始条件多单已挂, id:{}, trigger:{}", orderId, longPriceQueue.get(0)); },
                     null);
-            executor.placeGridLimitOrder(shortPriceQueue.get(0), negate(config.getQuantity()),
-                    orderId -> { currentShortOrderId = orderId; log.info("[Gate] 初始限价空单已挂, id:{}", orderId); },
+            executor.placeConditionalEntryOrder(shortPriceQueue.get(0),
+                    FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()),
+                    orderId -> { currentShortOrderId = orderId; log.info("[Gate] 初始条件空单已挂, id:{}, trigger:{}", orderId, shortPriceQueue.get(0)); },
                     null);
 
             state = StrategyState.ACTIVE;
@@ -638,15 +640,17 @@
         log.info("[Gate] 空止盈队列增加:{}, 现止盈队列:{}", stpElem, shortTakeProfitQueue);
 
         if (!isMarginSafe()) {
-            log.warn("[Gate] 保证金超限,跳过挂限价单");
+            log.warn("[Gate] 保证金超限,跳过挂条件单");
         } else {
             String oldLongId = currentLongOrderId;
-            executor.cancelOrder(oldLongId);
-            executor.placeGridLimitOrder(newShortFirst, negate(config.getQuantity()),
-                    orderId -> { currentShortOrderId = orderId; log.info("[Gate] 新限价空单, id:{}, price:{}", orderId, newShortFirst); },
+            executor.cancelConditionalOrder(oldLongId);
+            executor.placeConditionalEntryOrder(newShortFirst,
+                    FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()),
+                    orderId -> { currentShortOrderId = orderId; log.info("[Gate] 新条件空单, id:{}, trigger:{}", orderId, newShortFirst); },
                     null);
-            executor.placeGridLimitOrder(newLongFirst, config.getQuantity(),
-                    orderId -> { currentLongOrderId = orderId; log.info("[Gate] 新限价多单, id:{}, price:{}", orderId, newLongFirst); },
+            executor.placeConditionalEntryOrder(newLongFirst,
+                    FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(),
+                    orderId -> { currentLongOrderId = orderId; log.info("[Gate] 新条件多单, id:{}, trigger:{}", orderId, newLongFirst); },
                     null);
         }
 
@@ -736,15 +740,17 @@
         log.info("[Gate] 多止盈队列增加:{}, 现止盈队列:{}", ltpElem, longTakeProfitQueue);
 
         if (!isMarginSafe()) {
-            log.warn("[Gate] 保证金超限,跳过挂限价单");
+            log.warn("[Gate] 保证金超限,跳过挂条件单");
         } else {
             String oldShortId = currentShortOrderId;
-            executor.cancelOrder(oldShortId);
-            executor.placeGridLimitOrder(newLongFirst, config.getQuantity(),
-                    orderId -> { currentLongOrderId = orderId; log.info("[Gate] 新限价多单, id:{}, price:{}", orderId, newLongFirst); },
+            executor.cancelConditionalOrder(oldShortId);
+            executor.placeConditionalEntryOrder(newLongFirst,
+                    FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(),
+                    orderId -> { currentLongOrderId = orderId; log.info("[Gate] 新条件多单, id:{}, trigger:{}", orderId, newLongFirst); },
                     null);
-            executor.placeGridLimitOrder(newShortFirst, negate(config.getQuantity()),
-                    orderId -> { currentShortOrderId = orderId; log.info("[Gate] 新限价空单, id:{}, price:{}", orderId, newShortFirst); },
+            executor.placeConditionalEntryOrder(newShortFirst,
+                    FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()),
+                    orderId -> { currentShortOrderId = orderId; log.info("[Gate] 新条件空单, id:{}, trigger:{}", orderId, newShortFirst); },
                     null);
         }
 
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 1de7805..1752dd7 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
@@ -260,6 +260,78 @@
     }
 
     /**
+     * 异步创建条件开仓单(价格触发开仓)。
+     *
+     * <p>服务器监控价格,达到触发价后以市价 IOC 开仓。与止盈单不同,不设 order_type(默认开仓),
+     * reduce_only=false。适用于"价格到达 X 才买入 / 跌到 Y 才卖出"的场景。
+     *
+     * @param triggerPrice 触发价格
+     * @param rule         触发规则(NUMBER_1: 最新价≥触发价时执行;NUMBER_2: 最新价≤触发价时执行)
+     * @param size         开仓张数(正=开多,负=开空)
+     * @param onSuccess    成功回调,接收 conditionOrderId
+     * @param onFailure    失败回调
+     */
+    public void placeConditionalEntryOrder(BigDecimal triggerPrice,
+                                            FuturesPriceTrigger.RuleEnum rule,
+                                            String size,
+                                            Consumer<String> onSuccess,
+                                            Runnable onFailure) {
+        executor.execute(() -> {
+            try {
+                FuturesPriceTrigger trigger = new FuturesPriceTrigger();
+                trigger.setStrategyType(FuturesPriceTrigger.StrategyTypeEnum.NUMBER_0);
+                trigger.setPriceType(FuturesPriceTrigger.PriceTypeEnum.NUMBER_0);
+                trigger.setPrice(triggerPrice.toString());
+                trigger.setRule(rule);
+                trigger.setExpiration(0);
+
+                FuturesInitialOrder initial = new FuturesInitialOrder();
+                initial.setContract(contract);
+                initial.setSize(Long.parseLong(size));
+                initial.setPrice("0");
+                initial.setTif(FuturesInitialOrder.TifEnum.IOC);
+                initial.setReduceOnly(false);
+
+                FuturesPriceTriggeredOrder order = new FuturesPriceTriggeredOrder();
+                order.setTrigger(trigger);
+                order.setInitial(initial);
+
+                TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order);
+                String orderId = String.valueOf(response.getId());
+                log.info("[TradeExec] 条件开仓单已创建, trigger:{}, rule:{}, size:{}, id:{}",
+                        triggerPrice, rule, size, orderId);
+                if (onSuccess != null) {
+                    onSuccess.accept(orderId);
+                }
+            } catch (Exception e) {
+                log.error("[TradeExec] 条件开仓单创建失败, trigger:{}, size:{}", triggerPrice, size, e);
+                if (onFailure != null) {
+                    onFailure.run();
+                }
+            }
+        });
+    }
+
+    /**
+     * 异步取消单个条件单。
+     *
+     * @param orderId 条件单 ID,为 null 时跳过
+     */
+    public void cancelConditionalOrder(String orderId) {
+        if (orderId == null) {
+            return;
+        }
+        executor.execute(() -> {
+            try {
+                futuresApi.cancelPriceTriggeredOrder(SETTLE, Long.parseLong(orderId));
+                log.info("[TradeExec] 条件单已取消, id:{}", orderId);
+            } catch (Exception e) {
+                log.warn("[TradeExec] 取消条件单失败(可能已触发), id:{}", orderId);
+            }
+        });
+    }
+
+    /**
      * 构建 FuturesPriceTriggeredOrder 对象。
      *
      * <p>策略=0(价格触发),price_type=0(最新价),expiration=0(永不过期),
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 5230301..f3b4128 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
@@ -411,14 +411,17 @@
 - allowCoreThreadTimeOut: 60s 空闲后线程回收
 
 **回调设计**:
-- 每个下单方法接受 `onSuccess` 和 `onFailure` 两个 `Runnable`
-- REST 调用成功 → 执行 `onSuccess`(标记基底已开等)
+- `openLong`/`openShort`/`placeTakeProfit` 接受 `onSuccess` 和 `onFailure` 两个 `Runnable`
+- `placeGridLimitOrder` 的 `onSuccess` 为 `Consumer<String>`(接收 orderId)
+- REST 调用成功 → 执行 `onSuccess`(标记基底已开、记录 orderId 等)
 - REST 调用失败 → 执行 `onFailure`(当前版本多为 null,依赖 position 推送修正)
 
 | 方法 | 说明 |
 |------|------|
 | `openLong(qty, onSuccess, onFailure)` | 异步 IOC 市价开多,双回调 |
 | `openShort(qty, onSuccess, onFailure)` | 异步 IOC 市价开空,双回调 |
+| `placeGridLimitOrder(price, size, onSuccess, onFailure)` | 异步 GTC 限价单(网格开仓用),onSuccess 接收 orderId |
+| `cancelOrder(orderId)` | 异步取消指定挂单(orderId 为 null 时跳过) |
 | `placeTakeProfit(trigger, rule, type, size)` | 异步止盈条件单(plan-close-*-position)。size 为显式平仓张数(正=平空,负=平多),多次调用互不影响 |
 | `cancelAllPriceTriggeredOrders()` | 清除所有条件单 |
 | `shutdown()` | 等待10秒,超时强制关闭 |
@@ -453,6 +456,10 @@
 |------|------|------|
 | shortPriceQueue | List\<BigDecimal\> | 空仓价格队列,降序(大→小),容量 gridQueueSize |
 | longPriceQueue | List\<BigDecimal\> | 多仓价格队列,升序(小→大),容量 gridQueueSize |
+| longTakeProfitQueue | List\<BigDecimal\> | 多仓止盈队列,升序(小→大),仓位推送时消费 |
+| shortTakeProfitQueue | List\<BigDecimal\> | 空仓止盈队列,降序(大→小),仓位推送时消费 |
+| currentLongOrderId | String | 当前多仓限价单 ID(用于取消旧单) |
+| currentShortOrderId | String | 当前空仓限价单 ID(用于取消旧单) |
 | shortBaseEntryPrice | BigDecimal | 基底空头入场价(仅首次记录,用于生成队列) |
 | longBaseEntryPrice | BigDecimal | 基底多头入场价(仅首次记录,用于生成队列) |
 | shortEntryPrice | BigDecimal | 当前空仓入场价(推送实时更新,加权均价) |
@@ -468,8 +475,8 @@
 | initialPrincipal | BigDecimal | 初始本金(启动时账户总资产) |
 
 **回调方法**:
-- `onKline(closePrice)`: 更新 lastKlinePrice → 计算 unrealizedPnl → WAITING_KLINE 时触发基底双开,ACTIVE 时驱动 processShortGrid+processLongGrid
-- `onPositionUpdate(contract, mode, size, entryPrice)`: 记录当前入场价和持仓量 → 基底:首次成交记录入场价、生成队列;非基底:按 quantity 张数创建独立止盈条件单(plan-close-*-position)。无仓位时清空持仓量并标记不活跃
+- `onKline(closePrice)`: 更新 lastKlinePrice → 计算 unrealizedPnl → WAITING_KLINE 时触发基底双开,ACTIVE 时方向区分(closePrice>longPriceQueue[0]→processLongGrid,closePrice<shortPriceQueue[0]→processShortGrid,其余跳过)
+- `onPositionUpdate(contract, mode, size, entryPrice)`: 记录当前入场价和持仓量 → 基底:首次成交记录入场价、生成队列+止盈队列+挂限价单;非基底(仓位增加):按 quantity 为单位计算增量,逐个从止盈队列消费止盈价(队列不足时 entryPrice ± step 兜底),每批挂独立止盈条件单。无仓位时清空持仓量并标记不活跃
 - `onPositionClose(contract, side, pnl)`: 累加已实现盈亏,检查停止条件
 
 **processShortGrid / processLongGrid 核心逻辑**:
@@ -477,9 +484,11 @@
 | 步骤 | processShortGrid | processLongGrid |
 |------|-----------------|-----------------|
 | 匹配 | 收集 shortPriceQueue 中 > currentPrice 的元素 | 收集 longPriceQueue 中 < currentPrice 的元素 |
-| 本队补充 | 尾价 − step 循环递减 | 尾价 + step 循环递增 |
-| 对方转移 | 以多仓首元素为种子,递减 step | 以空仓首元素为种子,递增 step |
-| 贴近过滤 | 新增与 longEntryPrice 太近 → 跳过 | 新增与 shortEntryPrice 太近 → 跳过 |
+| 本队补充 | 尾价 − step 循环递减 × matched.size() 次 | 尾价 + step 循环递增 × matched.size() 次 |
+| 对方转移 | 以多仓首元素为种子,递减 step × i | 以空仓首元素为种子,递增 step × i |
+| 止盈队列 | shortTakeProfitQueue.add(新 short[0] − step),降序 | longTakeProfitQueue.add(新 long[0] + step),升序 |
+| 下单 | 取消旧多仓限价单 → 挂新空单(新 short[0]) + 新多单(新 long[0]) | 取消旧空仓限价单 → 挂新多单(新 long[0]) + 新空单(新 short[0]) |
+| 额外反向 | closePrice在[空均价,多均价]间 且 多>空 → openLong | closePrice在[空均价,多均价]间 且 多>空 → openShort |
 
 **未实现盈亏计算** (`updateUnrealizedPnl()`):
 
@@ -505,10 +514,11 @@
 
 | 方向 | 公式 | order_type | size(平仓张数) | rule |
 |------|------|------------|-----------|------|
-| 多头 TP | longPriceQueue[0](多仓队列首元素) | `plan-close-long-position` | `-quantity`(负=平多) | NUMBER_1(≥触发价) |
-| 空头 TP | shortPriceQueue[0](空仓队列首元素) | `plan-close-short-position` | `+quantity`(正=平空) | NUMBER_2(≤触发价) |
+| 多头 TP | longTakeProfitQueue.remove(0),空时兜底 longEntryPrice + step | `plan-close-long-position` | `-quantity`(负=平多) | NUMBER_1(≥触发价) |
+| 空头 TP | shortTakeProfitQueue.remove(0),空时兜底 shortEntryPrice − step | `plan-close-short-position` | `+quantity`(正=平空) | NUMBER_2(≤触发价) |
 
-> 止盈价取自对应方向队列的首元素(多仓队列升序首=最小价,空仓队列降序首=最高价)。止盈单使用显式张数而非 autoSize。每次网格触发开仓 quantity 张,只为该批张数创建独立的条件单,多个止盈单之间互不覆盖。
+> 止盈价从止盈队列消费。止盈队列由 K线触发时新增元素(新 queue[0] ± step),仓位推送时按每批 quantity 张数逐个消费。
+> 止盈队列空时兜底用当前入场价 ± step。每批挂独立止盈条件单,互不覆盖。
 
 **REST API 调用**:
 
@@ -522,6 +532,8 @@
 | 查账户 | `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/orders` | `FuturesApi.createFuturesOrder()` | price=具体价, tif=GTC(网格开仓用) |
+| 取消订单 | `DELETE /futures/usdt/orders/{order_id}` | `FuturesApi.cancelFuturesOrder()` | 取消指定挂单 |
 | 条件单 | `POST /futures/usdt/price_orders` | `FuturesApi.createPriceTriggeredOrder()` | strategy=0, price_type=0, expiration=0, order_type=plan-close-*-position, size=显式张数,多次调用互不冲突 |
 
 **初始化顺序** (`init()`):
@@ -529,11 +541,11 @@
 1. 获取用户 ID
 2. 查账户 → 记录初始本金 → 如需要切持仓模式
 3. 清除旧的止盈止损条件单
-4. 查当前合约所有仓位 → 逐个市价平仓
-   - 单向持仓(single): size=相反数, reduce_only=true
-   - 双向持仓(dual): size=0, close=false, autoSize=LONG/SHORT, reduce_only=true
-5. 设杠杆
-6. 打印账户余额
+4. 设多/空方向杠杆
+5. 重置策略状态 + 清空队列 + 清空止盈队列 + 清除 orderId
+6. 等待 K 线回调 → 双开基底(市价开多+市价开空,IOC)
+7. 双基底成交 → 生成网格队列 + 止盈队列初始化 + 挂限价多单+空单(GTC) → state=ACTIVE
+8. 异常回退: 10s 内任一基底未成交 → state=STOPPED
 ```
 
 ---

--
Gitblit v1.9.1