From b7e21f850f899813f397f5d2c35659bd48437990 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Fri, 08 May 2026 10:02:44 +0800
Subject: [PATCH] refactor(gateApi): 重构网格交易服务为持续循环模式

---
 src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java       |  114 ++++++++++++
 src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientMain.java    |   16 +
 src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java |   40 ++++
 src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md                |  243 +++++++++++++++++++++++++++
 src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java   |   75 +++++++
 5 files changed, 466 insertions(+), 22 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 a0d229f..f15b8b2 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -15,6 +15,30 @@
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 
+/**
+ * Gate 网格交易服务类。
+ *
+ * <h3>策略概述</h3>
+ * 多空双开 → 各放止盈条件单 → 仓位推送驱动补仓 → history_pnl 判断停止
+ *
+ * <h3>触发逻辑</h3>
+ * <pre>
+ *   K线首次价格就绪 → dualOpenPositions()     // 多空双开 + 止盈条件单
+ *   仓位推送 size=0  → reopenXxxPosition()     // 该方向被止盈平掉 → 补开
+ *   仓位推送 history_pnl ≥ overallTp → 停止
+ *   仓位推送 history_pnl ≤ -maxLoss → 停止
+ * </pre>
+ *
+ * <h3>止盈计算</h3>
+ * 多头止盈价 = entryPrice × (1 + gridRate)<br>
+ * 空头止盈价 = entryPrice × (1 - gridRate)
+ *
+ * <h3>依赖</h3>
+ * 使用 {@code io.gate:gate-api (7.2.71)} SDK 通过 REST API 下单,
+ * 市场数据由 {@link GateKlineWebSocketClient} 通过 WebSocket 提供。
+ *
+ * @author Administrator
+ */
 @Slf4j
 public class GateGridTradeService {
 
@@ -31,19 +55,36 @@
     private final String quantity;
     private final String positionMode;
 
+    /** 策略是否处于运行状态 */
     private volatile boolean strategyActive = false;
+    /** 是否已完成首次双开 */
     private volatile boolean dualOpened = false;
-
+    /** 多头仓位是否活跃 */
     private volatile boolean longActive = false;
+    /** 空头仓位是否活跃 */
     private volatile boolean shortActive = false;
 
+    /** 多头入场价 */
     private BigDecimal longEntryPrice;
+    /** 空头入场价 */
     private BigDecimal shortEntryPrice;
-
+    /** WebSocket 推送的最新 K 线收盘价 */
     private volatile BigDecimal lastKlinePrice;
-
+    /** 服务器返回的累计已实现盈亏 */
     private BigDecimal totalHistoryPnl = BigDecimal.ZERO;
 
+    /**
+     * 构造函数,初始化 Gate 期货 API 客户端。
+     *
+     * @param contract     合约名称(如 XAU_USDT)
+     * @param leverage     杠杆倍数
+     * @param marginMode   保证金模式(cross/isolated)
+     * @param positionMode 持仓模式(single/dual/dual_plus)
+     * @param gridRate    网格间距比例(如 0.0035)
+     * @param overallTp   整体止盈阈值(USDT)
+     * @param maxLoss     最大亏损阈值(USDT)
+     * @param quantity     下单数量(合约张数)
+     */
     public GateGridTradeService(String apiKey, String apiSecret,
                                  String contract, String leverage,
                                 String marginMode, String positionMode,
@@ -65,6 +106,9 @@
         this.futuresApi = new FuturesApi(apiClient);
     }
 
+    /**
+     * 初始化账户。设置杠杆、查询余额、切换持仓模式。
+     */
     public void init() {
         try {
             futuresApi.updateContractPositionLeverageCall(
@@ -86,6 +130,9 @@
         }
     }
 
+    /**
+     * 启动网格策略。策略激活后等待 K 线价格就绪,然后自动首次双开。
+     */
     public void startGrid() {
         if (strategyActive) {
             log.warn("[GateGrid] 策略已在运行中");
@@ -96,13 +143,19 @@
         log.info("[GateGrid] 网格策略启动,等待K线价格...");
     }
 
+    /**
+     * 停止网格策略。
+     */
     public void stopGrid() {
         strategyActive = false;
         log.info("[GateGrid] 网格策略已停止, history_pnl: {}", totalHistoryPnl);
     }
 
     /**
-     * K线回调:存储最新价格,首次价格就绪时双开
+     * K 线回调入口。由  调用。
+     * 首次收到价格时触发多空双开,后续仅缓存最新价格供补仓使用。
+     *
+     * @param closePrice K 线收盘价
      */
     public void onKline(BigDecimal closePrice) {
         lastKlinePrice = closePrice;
@@ -116,7 +169,20 @@
     }
 
     /**
-     * 仓位推送回调:检测止盈平仓 → 补仓,累加 history_pnl → 判断停止
+     * 仓位推送回调入口。由 {@link GateKlineWebSocketClient} 调用。
+     * 根据仓位模式(dual_long/dual_short)和 size 判断:
+     * <ul>
+     *   <li>size=0 且之前活跃 → 该方向被平仓 → 补开</li>
+     *   <li>size>0 → 确认仓位活跃,更新入场价</li>
+     * </ul>
+     * 每次推送更新 history_pnl 并检查停止条件。
+     *
+     * @param contract   合约名
+     * @param mode       仓位模式(dual_long / dual_short)
+     * @param size       仓位数量(0 表示无仓位)
+     * @param entryPrice 入场价格
+     * @param historyPnl  已实现累计盈亏
+     * @param realisedPnl 已实现盈亏
      */
     public void onPositionUpdate(String contract, String mode, BigDecimal size,
                                   BigDecimal entryPrice, BigDecimal historyPnl,
@@ -151,6 +217,13 @@
         checkStopConditions();
     }
 
+    /**
+     * 检查策略停止条件。满足任一即置 strategyActive=false:
+     * <ul>
+     *   <li>累计盈利 ≥ overallTp</li>
+     *   <li>累计亏损 ≤ -maxLoss</li>
+     * </ul>
+     */
     private void checkStopConditions() {
         if (totalHistoryPnl.compareTo(overallTp) >= 0) {
             log.info("[GateGrid] 累计止盈 {} 达到 {} USDT,停止策略", totalHistoryPnl, overallTp);
@@ -163,6 +236,10 @@
         }
     }
 
+    /**
+     * 首次多空双开。使用当前 K 线价格以市价单同时开多和开空,
+     * 开仓成功后立即为每个方向创建止盈条件单。
+     */
     private void dualOpenPositions() {
         if (lastKlinePrice == null) {
             log.warn("[GateGrid] K线价格未就绪,跳过双开");
@@ -204,6 +281,9 @@
         }
     }
 
+    /**
+     * 补开多头仓位。多头被止盈平掉后调用,市价重新开多并创建止盈单。
+     */
     private void reopenLongPosition() {
         if (lastKlinePrice == null || !strategyActive) {
             return;
@@ -229,6 +309,9 @@
         }
     }
 
+    /**
+     * 补开空头仓位。空头被止盈平掉后调用,市价重新开空并创建止盈单。
+     */
     private void reopenShortPosition() {
         if (lastKlinePrice == null || !strategyActive) {
             return;
@@ -254,18 +337,34 @@
         }
     }
 
+    /**
+     * 创建多头止盈条件单。
+     * 触发价 = entryPrice × (1 + gridRate),价格 ≥ 触发价时平多。
+     */
     private void placeLongTp(BigDecimal entryPrice) {
         BigDecimal tpPrice = entryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP);
         placePriceTriggeredOrder(tpPrice, FuturesPriceTrigger.RuleEnum.NUMBER_1, "close-long-position", "close_long");
         log.info("[GateGrid] 多头止盈已设置, TP:{}", tpPrice);
     }
 
+    /**
+     * 创建空头止盈条件单。
+     * 触发价 = entryPrice × (1 - gridRate),价格 ≤ 触发价时平空。
+     */
     private void placeShortTp(BigDecimal entryPrice) {
         BigDecimal tpPrice = entryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP);
         placePriceTriggeredOrder(tpPrice, FuturesPriceTrigger.RuleEnum.NUMBER_2, "close-short-position", "close_short");
         log.info("[GateGrid] 空头止盈已设置, TP:{}", tpPrice);
     }
 
+    /**
+     * 通过 Gate REST API 创建止盈条件单。
+     *
+     * @param triggerPrice 触发价格
+     * @param rule         触发规则(1: ≥, 2: ≤)
+     * @param orderType    止盈止损类型(close-long-position / close-short-position)
+     * @param autoSize      双仓平仓方向(close_long / close_short)
+     */
     private void placePriceTriggeredOrder(BigDecimal triggerPrice,
                                            FuturesPriceTrigger.RuleEnum rule,
                                            String orderType,
@@ -300,6 +399,9 @@
         }
     }
 
+    /**
+     * 打印当前网格配置和入场信息。
+     */
     private void printGridInfo() {
         BigDecimal longTp = BigDecimal.ZERO;
         BigDecimal shortTp = BigDecimal.ZERO;
@@ -318,6 +420,7 @@
         log.info("=====================================");
     }
 
+    /** 对数量取反(开多用正数,开空用负数) */
     private String negateQuantity(String qty) {
         if (qty.startsWith("-")) {
             return qty.substring(1);
@@ -325,6 +428,7 @@
         return "-" + qty;
     }
 
+    /** 安全转换字符串为 BigDecimal,null 返回 0 */
     private BigDecimal safeDecimal(String val) {
         if (val == null || val.isEmpty()) {
             return BigDecimal.ZERO;
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java
index f210f05..6b900e6 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java
@@ -16,14 +16,33 @@
 import java.math.BigDecimal;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
- * Gate K线 WebSocket 客户端类,用于连接 Gate 的 WebSocket 接口,
- * 实时获取并处理 K线(candlestick)数据。
- * 同时支持心跳检测、自动重连以及异常恢复机制。
+ * Gate WebSocket 客户端,通过 Java-WebSocket 库连接 Gate.io 的 WebSocket 接口。
+ *
+ * <h3>订阅频道</h3>
+ * <ul>
+ *   <li>{@code futures.candlesticks} — 1m K 线数据(公开频道)</li>
+ *   <li>{@code futures.positions} — 用户仓位更新(私有频道,需 HMAC-SHA512 签名认证)</li>
+ *   <li>{@code futures.ping} — 应用层心跳</li>
+ * </ul>
+ *
+ * <h3>数据流</h3>
+ * <pre>
+ *   onMessage → handleWebSocketMessage → 按 channel 分流:
+ *     futures.pong       → 取消心跳超时
+ *     subscribe/unsubscribe/error → 日志
+ *     futures.candlesticks → processPushDataV2 → GateGridTradeService.onKline()
+ *     futures.positions    → processPositionData  → GateGridTradeService.onPositionUpdate()
+ * </pre>
+ *
+ * <h3>连接管理</h3>
+ * 支持心跳检测、指数退避重连(最多 3 次)、优雅关闭。
+ *
  * @author Administrator
  */
 @Slf4j
@@ -35,23 +54,35 @@
     private WebSocketClient webSocketClient;
     private ScheduledExecutorService heartbeatExecutor;
     private volatile ScheduledFuture<?> pongTimeoutFuture;
+    /** 最后收到消息的时间戳,用于心跳超时检测 */
     private final AtomicReference<Long> lastMessageTime = new AtomicReference<>(System.currentTimeMillis());
 
-    // 连接状态标志
+    /** 连接状态 */
     private final AtomicBoolean isConnected = new AtomicBoolean(false);
+    /** 连接中标记,防止重复连接 */
     private final AtomicBoolean isConnecting = new AtomicBoolean(false);
+    /** 初始化标记,防止重复初始化 */
     private final AtomicBoolean isInitialized = new AtomicBoolean(false);
 
+    /** K 线频道 */
     private static final String CHANNEL = "futures.candlesticks";
+    /** 仓位频道(私有,需认证) */
     private static final String POSITIONS_CHANNEL = "futures.positions";
+    /** 应用层 ping 频道 */
     private static final String FUTURES_PING = "futures.ping";
+    /** 应用层 pong 频道 */
     private static final String FUTURES_PONG = "futures.pong";
+    /** K 线周期 */
     private static final String GATE_INTERVAL = "1m";
+    /** 订阅合约 */
     private static final String GATE_CONTRACT = "XAUT_USDT";
 
+    /** 网格交易策略实例 */
     private GateGridTradeService gridTradeService;
 
+    /** API 密钥,用于私有频道签名 */
     private final String apiKey;
+    /** API 密钥 */
     private final String apiSecret;
 
     private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
@@ -251,6 +282,10 @@
         webSocketClient.send(subscribeMsg.toJSONString());
         log.info("已发送 K线频道订阅请求,合约: {}, 周期: {}", GATE_CONTRACT, GATE_INTERVAL);
     }
+    /**
+     * 发送应用层 ping 请求。
+     * 用于探测连接状态,服务器会返回 futures.pong。
+     */
     private void subscribePingChannels() {
         JSONObject subscribeMsg = new JSONObject();
         subscribeMsg.put("time", System.currentTimeMillis() / 1000);
@@ -259,6 +294,10 @@
         log.info("已发送 futures.ping");
     }
 
+    /**
+     * 订阅仓位频道(私有频道,需 HMAC-SHA512 签名认证)。
+     * 签名算法: Hex(HmacSHA512(secret, "channel={channel}&event={event}&time={time}"))
+     */
     private void subscribePositionsChannels() {
         JSONObject subscribeMsg = new JSONObject();
         long timeSec = System.currentTimeMillis() / 1000;
@@ -280,25 +319,38 @@
         log.info("已发送仓位频道订阅请求(含认证),合约: {}", GATE_CONTRACT);
     }
 
+    /**
+     * 计算 Gate API v4 的 HMAC-SHA512 签名。
+     *
+     * @param channel 频道名
+     * @param event   事件名(subscribe/unsubscribe)
+     * @param timeSec 时间戳(秒)
+     * @return 十六进制签名字符串
+     */
     private String hs512Sign(String channel, String event, long timeSec) {
         try {
             String message = "channel=" + channel + "&event=" + event + "&time=" + timeSec;
             Mac mac = Mac.getInstance("HmacSHA512");
-            SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(), "HmacSHA512");
+            SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA512");
             mac.init(spec);
-            byte[] hash = mac.doFinal(message.getBytes());
+            byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
             StringBuilder hex = new StringBuilder(hash.length * 2);
             for (byte b : hash) {
                 hex.append(HEX_ARRAY[(b >> 4) & 0xF]);
                 hex.append(HEX_ARRAY[b & 0xF]);
             }
-            return hex.toString();
+            String sign = hex.toString();
+            log.debug("签名计算, message: {}, sign: {}", message, sign);
+            return sign;
         } catch (Exception e) {
             log.error("签名计算失败", e);
             return "";
         }
     }
 
+    /**
+     * 取消订阅 K 线频道。
+     */
     private void unsubscribeKlineChannels() {
         JSONObject unsubscribeMsg = new JSONObject();
         unsubscribeMsg.put("time", System.currentTimeMillis() / 1000);
@@ -312,6 +364,9 @@
         log.info("已发送 K线频道取消订阅请求,合约: {}, 周期: {}", GATE_CONTRACT, GATE_INTERVAL);
     }
 
+    /**
+     * 取消订阅仓位频道。
+     */
     private void unsubscribePositionsChannels() {
         JSONObject unsubscribeMsg = new JSONObject();
         unsubscribeMsg.put("time", System.currentTimeMillis() / 1000);
@@ -434,6 +489,12 @@
         }
     }
 
+    /**
+     * 解析仓位推送数据,提取目标合约的仓位信息并回调交易服务。
+     * 推送字段: contract, mode(dual_long/dual_short), size, entry_price, history_pnl, realised_pnl
+     *
+     * @param response 仓位推送的 JSON 对象
+     */
     private void processPositionData(JSONObject response) {
         try {
             JSONArray resultArray = response.getJSONArray("result");
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientMain.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientMain.java
index 604f2bc..21c81cf 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientMain.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientMain.java
@@ -1,19 +1,21 @@
 package com.xcong.excoin.modules.gateApi;
 
-import com.xcong.excoin.modules.okxNewPrice.OkxWebSocketClientManager;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 
+/**
+ * Gate 网格交易的独立测试入口(main 方法)。
+ * 通过 Spring XML 上下文初始化管理器,运行一段时间后手动关闭。
+ *
+ * @author Administrator
+ */
 public class GateWebSocketClientMain {
     public static void main(String[] args) throws InterruptedException {
-        // 使用Spring上下文初始化管理器
         ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
         GateWebSocketClientManager manager = context.getBean(GateWebSocketClientManager.class);
 
-        // 运行一段时间以观察结果
-        Thread.sleep(1200000000L); // 运行一小时
-        
-        // 关闭连接
+        Thread.sleep(1200000000L);
+
         manager.destroy();
     }
-}
\ No newline at end of file
+}
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 9575b49..63f478c 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
@@ -2,7 +2,6 @@
 
 import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
 import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListService;
-import com.xcong.excoin.utils.RedisUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -12,7 +11,32 @@
 import java.math.BigDecimal;
 
 /**
- * 管理 Gate WebSocket 客户端和网格交易服务实例
+ * Gate 模块入口管理类,作为 Spring Bean 负责组件的生命周期。
+ *
+ * <h3>启动流程</h3>
+ * <pre>
+ *   @PostConstruct init():
+ *     1. new GateGridTradeService(参数) → init()     // 设杠杆、查余额、切双向持仓
+ *     2. new GateKlineWebSocketClient → init()        // 连接 WebSocket,订阅 K 线 + 仓位
+ *     3. gridTradeService.startGrid()                 // 激活策略,等待 K 线触发首次双开
+ * </pre>
+ *
+ * <h3>销毁流程</h3>
+ * <pre>
+ *   @PreDestroy destroy():
+ *     1. gridTradeService.stopGrid()                  // 停止策略
+ *     2. klinePriceClient.destroy()                   // 取消订阅 → 关闭 WS → 关闭线程池
+ * </pre>
+ *
+ * <h3>配置参数</h3>
+ * 当前配置在代码中硬编码:
+ * <ul>
+ *   <li>合约: XAU_USDT, 杠杆: 30x, 全仓, 双向持仓</li>
+ *   <li>网格间距: 0.35%, 整体止盈: 0.5 USDT, 最大亏损: 7.5 USDT</li>
+ *   <li>数量: 10 张</li>
+ * </ul>
+ *
+ * @author Administrator
  */
 @Slf4j
 @Component
@@ -22,12 +46,19 @@
     @Autowired
     private WangGeListService wangGeListService;
 
+    /** K 线 WebSocket 客户端 */
     private GateKlineWebSocketClient klinePriceClient;
+    /** 网格交易服务 */
     private GateGridTradeService gridTradeService;
 
+    /** Gate 测试网 API Key */
     private static final String API_KEY = "d90ca272391992b8e74f8f92cedb21ec";
+    /** Gate 测试网 API Secret */
     private static final String API_SECRET = "1861e4f52de4bb53369ea3208d9ede38ece4777368030f96c77d27934c46c274";
 
+    /**
+     * Spring 容器启动后自动调用。初始化网格交易服务和 WebSocket 客户端。
+     */
     @PostConstruct
     public void init() {
         log.info("开始初始化GateWebSocketClientManager");
@@ -57,6 +88,9 @@
         }
     }
 
+    /**
+     * Spring 容器销毁前自动调用。停止策略并关闭所有连接。
+     */
     @PreDestroy
     public void destroy() {
         log.info("开始销毁GateWebSocketClientManager");
@@ -83,4 +117,4 @@
     public GateGridTradeService getGridTradeService() {
         return gridTradeService;
     }
-}
\ No newline at end of file
+}
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
new file mode 100644
index 0000000..57e30a5
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md
@@ -0,0 +1,243 @@
+# Gate Api 模块 — 网格交易系统
+
+## 文件列表
+
+| 文件 | 类型 | 说明 |
+|------|------|------|
+| [GateWebSocketClientManager](#gatewebsocketclientmanager) | `@Component` | 启动入口,生命周期管理 |
+| [GateKlineWebSocketClient](#gateklinewebsocketclient) | WebSocket 客户端 | K 线 + 仓位数据监听 |
+| [GateGridTradeService](#gategridtradeservice) | 交易服务 | 网格策略 + REST 下单 |
+| [GateWebSocketClientMain](#gatewebsocketclientmain) | main 入口 | 独立测试启动 |
+| [Example.java](#examplejava) | 示例 | Gate SDK 用法参考 |
+
+---
+
+## 架构总览
+
+```
+┌────────────────────────────────────────────────────────────┐
+│                  GateWebSocketClientManager                │
+│                    (Spring @Component)                     │
+│                                                            │
+│  @PostConstruct init():                                    │
+│    ┌──────────────────────┐   ┌─────────────────────────┐  │
+│    │ GateGridTradeService │   │ GateKlineWebSocketClient │  │
+│    │  ▶ init()            │   │  ▶ init()                │  │
+│    │  ▶ startGrid()       │←──│  ▶ connect()             │  │
+│    └──────┬───────────────┘   └───────────┬─────────────┘  │
+│           │ REST API                      │ WebSocket       │
+│           ▼                               ▼                │
+│     Gate Testnet API           Gate Testnet WS             │
+│  (https://api-testnet...)   (wss://ws-testnet.gate.com)   │
+└────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 数据流
+
+```
+WebSocket 推送
+│
+├─ futures.candlesticks (update)
+│   └─ GateKlineWebSocketClient.processPushDataV2()
+│       ├─ 解析: o/h/l/c/v/a/t/w
+│       ├─ 打印 K 线日志
+│       └─ GateGridTradeService.onKline(closePx)
+│           ├─ 首次 → dualOpenPositions() → 开多 + 开空 + TP 单
+│           └─ 后续 → 仅缓存 lastKlinePrice
+│
+├─ futures.positions (update) [需 HMAC-SHA512 签名]
+│   └─ GateKlineWebSocketClient.processPositionData()
+│       ├─ 解析: contract/mode/size/entry_price/history_pnl/realised_pnl
+│       └─ GateGridTradeService.onPositionUpdate(...)
+│           ├─ size=0 && longActive → reopenLongPosition()
+│           ├─ size=0 && shortActive → reopenShortPosition()
+│           ├─ size>0 → 确认仓位活跃
+│           └─ checkStopConditions(history_pnl)
+│
+├─ futures.pong
+│   └─ cancelPongTimeout()
+│
+└─ subscribe/unsubscribe/error
+    └─ 日志输出
+```
+
+---
+
+## 策略时序
+
+### 阶段 1:启动与初始化
+
+```
+Spring 启动
+  → GateWebSocketClientManager.init()
+    → GateGridTradeService.init()
+      → REST: 设杠杆 30x cross
+      → REST: 查账户余额
+      → REST: 切双向持仓 dual
+    → GateKlineWebSocketClient.init()
+      → WebSocket: connect()
+        → onOpen: subscribe candlesticks + positions + ping
+    → GateGridTradeService.startGrid()
+      → strategyActive = true
+```
+
+### 阶段 2:首次开仓
+
+```
+K 线推送 closePrice=80000
+  → onKline(80000)
+    → dualOpened = true
+    → dualOpenPositions()
+      → REST: 市价开多 10 张 → longEntryPrice=80000
+      → REST: 创建多头止盈单 TP=80000×1.0035=80280
+      → REST: 市价开空 10 张 → shortEntryPrice=80000
+      → REST: 创建空头止盈单 TP=80000×0.9965=79720
+```
+
+### 阶段 3:止盈触发 → 补仓
+
+```
+仓位推送: mode=dual_long, size=0  (多头被止盈平掉了)
+  → longActive=true && size=0
+    → longActive=false
+    → reopenLongPosition()
+      → REST: 市价开多 10 张 (用最新 K 线价)
+      → REST: 创建新的多头止盈单
+
+仓位推送: history_pnl=0.12  (未达阈值,继续)
+```
+
+### 阶段 4:停止
+
+```
+仓位推送: history_pnl ≥ 0.5 (累计盈利达标)
+  → strategyActive = false  → 不再补仓
+
+仓位推送: history_pnl ≤ -7.5 (亏损超限)
+  → strategyActive = false  → 不再补仓
+```
+
+---
+
+## GateWebSocketClientManager
+
+**角色**: Spring Bean 入口,组装并管理所有 Gate 模块组件。
+
+**关键方法**:
+- `init()`: 创建 `GateGridTradeService` → 初始化 → 创建 `GateKlineWebSocketClient` → 启动策略
+- `destroy()`: 停止策略 → 关闭 WebSocket
+
+**当前参数**(硬编码在 `init()` 中):
+
+| 参数 | 值 | 说明 |
+|------|-----|------|
+| 合约 | XAU_USDT | 黄金 |
+| 杠杆 | 30x | 全仓模式 |
+| 持仓模式 | dual | 双向持仓 |
+| 网格间距 | 0.0035 | 0.35% |
+| 整体止盈 | 0.5 USDT | |
+| 最大亏损 | 7.5 USDT | 本金 50×15% |
+| 下单量 | 10 张 | |
+
+---
+
+## GateKlineWebSocketClient
+
+**角色**: WebSocket 客户端,订阅 Gate 的行情和仓位频道。
+
+**订阅频道**:
+
+| 频道 | 类型 | 认证 | 用途 |
+|------|------|------|------|
+| `futures.candlesticks` | 公开 | 否 | 1m K 线数据 |
+| `futures.positions` | 私有 | HMAC-SHA512 | 用户仓位更新 |
+| `futures.ping` | 公开 | 否 | 应用层心跳 |
+
+**签名算法**(`futures.positions` 订阅时使用):
+
+```
+message = "channel=futures.positions&event=subscribe&time={秒级时间戳}"
+SIGN = Hex(HmacSHA512(apiSecret, message))
+```
+
+**连接管理**:
+- 心跳: 10 秒超时,每 25 秒检查,超时发 ping
+- 重连: 指数退避,最多 3 次,初始 5 秒
+- 关闭: 先取消订阅 → 等 500ms → 关闭连接 → 关闭线程池
+
+**消息路由** (`handleWebSocketMessage`):
+
+| channel | event | 处理 |
+|---------|-------|------|
+| `futures.pong` | — | `cancelPongTimeout()` |
+| — | `subscribe` | 打日志 |
+| — | `unsubscribe` | 打日志 |
+| — | `error` | 打错误日志 |
+| `futures.candlesticks` | `update`/`all` | `processPushDataV2()` |
+| `futures.positions` | `update`/`all` | `processPositionData()` |
+
+---
+
+## GateGridTradeService
+
+**角色**: 使用 Gate SDK (`io.gate:gate-api:7.2.71`) 通过 REST API 执行合约下单。
+
+**状态机**:
+
+```
+strategyActive=false ──startGrid()──→ strategyActive=true (等待K线)
+                                          │
+                                    onKline(price)
+                                          │
+                                    dualOpened=true
+                                    dualOpenPositions()
+                                          │
+                              ┌───────────┴───────────┐
+                              ▼                       ▼
+                         longActive=true         shortActive=true
+                              │                       │
+                    仓位推送 size=0             仓位推送 size=0
+                              │                       │
+                              ▼                       ▼
+                       reopenLong()             reopenShort()
+                              │                       │
+                              └───────────┬───────────┘
+                                          │
+                                    checkStopConditions()
+                                          │
+                              ┌───────────┴───────────┐
+                              ▼                       ▼
+                         history_pnl≥0.5       history_pnl≤-7.5
+                         strategyActive=false  strategyActive=false
+```
+
+**止盈计算**:
+
+| 方向 | 公式 | 触发条件 | order_type | auto_size |
+|------|------|----------|------------|-----------|
+| 多头 TP | entryPrice × 1.0035 | 最新价 ≥ 触发价 | `close-long-position` | `close_long` |
+| 空头 TP | entryPrice × 0.9965 | 最新价 ≤ 触发价 | `close-short-position` | `close_short` |
+
+**REST API 调用**:
+
+| 操作 | API |
+|------|-----|
+| 设杠杆 | `POST /futures/usdt/positions/{contract}/leverage` |
+| 查账户 | `GET /futures/usdt/accounts` |
+| 设持仓模式 | `POST /futures/usdt/set_position_mode` |
+| 市价下单 | `POST /futures/usdt/orders` (price=0, tif=IOC) |
+| 止盈条件单 | `POST /futures/usdt/price_orders` |
+
+---
+
+## GateWebSocketClientMain
+
+**角色**: 独立的 `main()` 方法入口,通过 Spring XML 上下文启动。
+
+---
+
+## Example.java
+
+Gate SDK 使用示例,展示 `FuturesApi` 的基本用法:获取账户、设置仓位模式、设置杠杆。仅作参考,不参与实际策略运行。

--
Gitblit v1.9.1