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