From 3015f832329028cbabef2c45fc0d4586bf60510c Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Thu, 07 May 2026 13:53:09 +0800
Subject: [PATCH] fix(okxWs): 修复OKX生产环境配置的调试模式设置
---
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java | 343 ++++++++++++++++++++++++++++++++++++++
src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java | 71 ++++---
src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java | 88 +--------
3 files changed, 392 insertions(+), 110 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
new file mode 100644
index 0000000..13b72e4
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -0,0 +1,343 @@
+package com.xcong.excoin.modules.gateApi;
+
+import io.gate.gateapi.ApiClient;
+import io.gate.gateapi.ApiException;
+import io.gate.gateapi.GateApiException;
+import io.gate.gateapi.api.FuturesApi;
+import io.gate.gateapi.models.FuturesAccount;
+import io.gate.gateapi.models.FuturesOrder;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+
+/**
+ * Gate 网格交易服务类,使用 gate-api SDK 进行合约下单。
+ * 策略:多空双开 → 设置止盈止损点位 → 网格循环交易
+ *
+ * 测试参数:
+ * 品种: XAU_USDT(黄金)
+ * 杠杆: 100x(全仓)
+ * 数量: 0.01 XAU
+ * 网格: 0.0035(千分之三点五)
+ * 整体止盈: 0.5 USDT
+ * 循环次数: 3
+ * 报警: 本金亏损 15%(初始本金 50 USDT)
+ *
+ * @author Administrator
+ */
+@Slf4j
+public class GateGridTradeService {
+
+ private final ApiClient apiClient;
+ private final FuturesApi futuresApi;
+ private static final String SETTLE = "usdt";
+
+ private final String contract;
+ private final String leverage;
+ private final String marginMode;
+ private final BigDecimal gridRate;
+ private final BigDecimal overallTp;
+ private final int maxCycles;
+ private final BigDecimal maxLoss;
+ private final String quantity;
+
+ private volatile boolean strategyActive = false;
+ private int currentCycle = 0;
+ private BigDecimal totalProfit = BigDecimal.ZERO;
+ private BigDecimal longEntryPrice;
+ private BigDecimal shortEntryPrice;
+ private Long longOrderId;
+ private Long shortOrderId;
+
+ private volatile BigDecimal lastClosePrice;
+
+ public GateGridTradeService(String apiKey, String apiSecret,
+ String contract, String leverage, String marginMode,
+ BigDecimal gridRate, BigDecimal overallTp,
+ int maxCycles, BigDecimal maxLoss, String quantity) {
+ this.contract = contract;
+ this.leverage = leverage;
+ this.marginMode = marginMode;
+ this.gridRate = gridRate;
+ this.overallTp = overallTp;
+ this.maxCycles = maxCycles;
+ this.maxLoss = maxLoss;
+ this.quantity = quantity;
+
+ this.apiClient = new ApiClient();
+ this.apiClient.setBasePath("https://api-testnet.gateapi.io/api/v4");
+ this.apiClient.setApiKeySecret(apiKey, apiSecret);
+ this.futuresApi = new FuturesApi(apiClient);
+ }
+
+ /**
+ * 初始化账户:设置持仓模式 + 杠杆
+ */
+ public void init() {
+ try {
+ futuresApi.setPositionMode(SETTLE, "dual");
+ log.info("[GateGrid] 已设置双向持仓模式");
+
+ futuresApi.updateContractPositionLeverageCall(
+ SETTLE, contract, leverage, marginMode, "dual", null);
+ log.info("[GateGrid] 已设置杠杆: {}x, 保证金模式: {}", leverage, marginMode);
+
+ FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
+ log.info("[GateGrid] 账户可用余额: {}, 总资产: {}",
+ account.getAvailable(), account.getTotal());
+ } catch (GateApiException e) {
+ log.error("[GateGrid] 初始化失败, label: {}, msg: {}", e.getErrorLabel(), e.getMessage());
+ } catch (ApiException e) {
+ log.error("[GateGrid] 初始化API调用失败, code: {}", e.getCode());
+ }
+ }
+
+ /**
+ * 启动网格策略
+ */
+ public void startGrid() {
+ if (strategyActive) {
+ log.warn("[GateGrid] 策略已在运行中");
+ return;
+ }
+ strategyActive = true;
+ currentCycle = 0;
+ totalProfit = BigDecimal.ZERO;
+ log.info("[GateGrid] 网格策略启动, cycle: {}", currentCycle + 1);
+ dualOpenPositions();
+ }
+
+ /**
+ * 停止网格策略
+ */
+ public void stopGrid() {
+ strategyActive = false;
+ closeAllPositions();
+ log.info("[GateGrid] 网格策略已停止, 总盈亏: {}, 循环: {}", totalProfit, currentCycle);
+ }
+
+ /**
+ * K线回调:收到新的收盘价
+ */
+ public void onKline(BigDecimal closePrice, boolean isKlineClosed) {
+ lastClosePrice = closePrice;
+ if (!strategyActive || !isKlineClosed) {
+ return;
+ }
+ checkPositions(closePrice);
+ }
+
+ /**
+ * 多空双开
+ */
+ private void dualOpenPositions() {
+ try {
+ FuturesOrder longOrder = new FuturesOrder();
+ longOrder.setContract(contract);
+ longOrder.setSize(quantity);
+ longOrder.setPrice("0");
+ longOrder.setTif(FuturesOrder.TifEnum.IOC);
+ longOrder.setText("t-grid-long-" + (currentCycle + 1));
+ FuturesOrder longResult = futuresApi.createFuturesOrder(SETTLE, longOrder, null);
+ longOrderId = longResult.getId();
+ longEntryPrice = safeDecimal(longResult.getFillPrice());
+ log.info("[GateGrid] 开多成功, price: {}, id: {}", longEntryPrice, longOrderId);
+
+ FuturesOrder shortOrder = new FuturesOrder();
+ shortOrder.setContract(contract);
+ shortOrder.setSize(negateQuantity(quantity));
+ shortOrder.setPrice("0");
+ shortOrder.setTif(FuturesOrder.TifEnum.IOC);
+ shortOrder.setText("t-grid-short-" + (currentCycle + 1));
+ FuturesOrder shortResult = futuresApi.createFuturesOrder(SETTLE, shortOrder, null);
+ shortOrderId = shortResult.getId();
+ shortEntryPrice = safeDecimal(shortResult.getFillPrice());
+ log.info("[GateGrid] 开空成功, price: {}, id: {}", shortEntryPrice, shortOrderId);
+
+ printGridInfo();
+ } catch (GateApiException e) {
+ log.error("[GateGrid] 双开失败, label: {}, msg: {}", e.getErrorLabel(), e.getMessage());
+ strategyActive = false;
+ } catch (Exception e) {
+ log.error("[GateGrid] 双开异常", e);
+ strategyActive = false;
+ }
+ }
+
+ /**
+ * 检查多空仓位是否触及止盈止损
+ */
+ private void checkPositions(BigDecimal currentPrice) {
+ if (longEntryPrice == null || shortEntryPrice == null) {
+ return;
+ }
+
+ BigDecimal longTp = longEntryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP);
+ BigDecimal longSl = longEntryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP);
+ BigDecimal shortTp = shortEntryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP);
+ BigDecimal shortSl = shortEntryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP);
+
+ System.out.println("========== Gate 网格状态 ==========");
+ System.out.println("当前价格: " + currentPrice);
+ System.out.println("多头入场: " + longEntryPrice + " TP: " + longTp + " SL: " + longSl);
+ System.out.println("空头入场: " + shortEntryPrice + " TP: " + shortTp + " SL: " + shortSl);
+ System.out.println("累计盈亏: " + totalProfit + " 循环: " + currentCycle + "/" + maxCycles);
+ System.out.println("===================================");
+
+ // 多头止盈
+ if (currentPrice.compareTo(longTp) >= 0) {
+ log.info("[GateGrid] 多头止盈触发! entry:{}, current:{}", longEntryPrice, currentPrice);
+ BigDecimal cnt = new BigDecimal(quantity);
+ BigDecimal profit = currentPrice.subtract(longEntryPrice).multiply(cnt);
+ totalProfit = totalProfit.add(profit);
+ log.info("[GateGrid] 多头止盈 profit:{}, totalProfit:{}", profit, totalProfit);
+ closeLongPosition();
+ closeShortPosition();
+ currentCycle++;
+ checkStopConditions();
+ return;
+ }
+ // 多头止损
+ if (currentPrice.compareTo(longSl) <= 0) {
+ log.info("[GateGrid] 多头止损触发! entry:{}, current:{}", longEntryPrice, currentPrice);
+ BigDecimal cnt = new BigDecimal(quantity);
+ BigDecimal loss = longEntryPrice.subtract(currentPrice).multiply(cnt);
+ totalProfit = totalProfit.subtract(loss);
+ log.info("[GateGrid] 多头止损 loss:{}, totalProfit:{}", loss, totalProfit);
+ closeLongPosition();
+ currentCycle++;
+ checkStopConditions();
+ return;
+ }
+ // 空头止盈
+ if (currentPrice.compareTo(shortTp) <= 0) {
+ log.info("[GateGrid] 空头止盈触发! entry:{}, current:{}", shortEntryPrice, currentPrice);
+ BigDecimal cnt = new BigDecimal(quantity);
+ BigDecimal profit = shortEntryPrice.subtract(currentPrice).multiply(cnt);
+ totalProfit = totalProfit.add(profit);
+ log.info("[GateGrid] 空头止盈 profit:{}, totalProfit:{}", profit, totalProfit);
+ closeShortPosition();
+ closeLongPosition();
+ currentCycle++;
+ checkStopConditions();
+ return;
+ }
+ // 空头止损
+ if (currentPrice.compareTo(shortSl) >= 0) {
+ log.info("[GateGrid] 空头止损触发! entry:{}, current:{}", shortEntryPrice, currentPrice);
+ BigDecimal cnt = new BigDecimal(quantity);
+ BigDecimal loss = currentPrice.subtract(shortEntryPrice).multiply(cnt);
+ totalProfit = totalProfit.subtract(loss);
+ log.info("[GateGrid] 空头止损 loss:{}, totalProfit:{}", loss, totalProfit);
+ closeShortPosition();
+ currentCycle++;
+ checkStopConditions();
+ }
+ }
+
+ private void checkStopConditions() {
+ if (totalProfit.compareTo(overallTp) >= 0) {
+ log.info("[GateGrid] 达到整体止盈 {} USDT,停止策略", overallTp);
+ strategyActive = false;
+ return;
+ }
+ if (BigDecimal.ZERO.subtract(totalProfit).compareTo(maxLoss) >= 0) {
+ log.info("[GateGrid] 亏损 {} 达到上限 {} USDT,停止策略", totalProfit.negate(), maxLoss);
+ strategyActive = false;
+ return;
+ }
+ if (currentCycle >= maxCycles) {
+ log.info("[GateGrid] 达到最大循环次数 {},停止策略", maxCycles);
+ strategyActive = false;
+ return;
+ }
+ log.info("[GateGrid] 进入下一轮循环: {}", currentCycle + 1);
+ dualOpenPositions();
+ }
+
+ private void closeLongPosition() {
+ if (longEntryPrice == null) return;
+ try {
+ FuturesOrder closeOrder = new FuturesOrder();
+ closeOrder.setContract(contract);
+ closeOrder.setSize(negateQuantity(quantity));
+ closeOrder.setPrice("0");
+ closeOrder.setTif(FuturesOrder.TifEnum.IOC);
+ closeOrder.setReduceOnly(true);
+ closeOrder.setText("t-grid-close-long");
+ FuturesOrder result = futuresApi.createFuturesOrder(SETTLE, closeOrder, null);
+ log.info("[GateGrid] 平多成功, id: {}, fillPrice: {}", result.getId(), result.getFillPrice());
+ } catch (Exception e) {
+ log.error("[GateGrid] 平多失败", e);
+ }
+ longEntryPrice = null;
+ longOrderId = null;
+ }
+
+ private void closeShortPosition() {
+ if (shortEntryPrice == null) return;
+ try {
+ FuturesOrder closeOrder = new FuturesOrder();
+ closeOrder.setContract(contract);
+ closeOrder.setSize(quantity);
+ closeOrder.setPrice("0");
+ closeOrder.setTif(FuturesOrder.TifEnum.IOC);
+ closeOrder.setReduceOnly(true);
+ closeOrder.setText("t-grid-close-short");
+ FuturesOrder result = futuresApi.createFuturesOrder(SETTLE, closeOrder, null);
+ log.info("[GateGrid] 平空成功, id: {}, fillPrice: {}", result.getId(), result.getFillPrice());
+ } catch (Exception e) {
+ log.error("[GateGrid] 平空失败", e);
+ }
+ shortEntryPrice = null;
+ shortOrderId = null;
+ }
+
+ private void closeAllPositions() {
+ closeLongPosition();
+ closeShortPosition();
+ }
+
+ private void printGridInfo() {
+ System.out.println("========== Gate 网格开仓 ==========");
+ System.out.println("合约: " + contract + " 杠杆: " + leverage + "x " + marginMode);
+ System.out.println("多头入场: " + longEntryPrice);
+ System.out.println("空头入场: " + shortEntryPrice);
+ System.out.println("数量: " + quantity + " 网格间距: " + gridRate.multiply(new BigDecimal("100")) + "%");
+ System.out.println("整体止盈: " + overallTp + " USDT 最大循环: " + maxCycles);
+ System.out.println("最大亏损: " + maxLoss + " USDT");
+ System.out.println("=====================================");
+ }
+
+ private String negateQuantity(String qty) {
+ if (qty.startsWith("-")) {
+ return qty.substring(1);
+ }
+ return "-" + qty;
+ }
+
+ private BigDecimal safeDecimal(String val) {
+ if (val == null || val.isEmpty()) {
+ return BigDecimal.ZERO;
+ }
+ return new BigDecimal(val);
+ }
+
+ public BigDecimal getLastClosePrice() {
+ return lastClosePrice;
+ }
+
+ public boolean isStrategyActive() {
+ return strategyActive;
+ }
+
+ public BigDecimal getTotalProfit() {
+ return totalProfit;
+ }
+
+ public int getCurrentCycle() {
+ return currentCycle;
+ }
+}
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 ca59116..828568c 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java
@@ -58,7 +58,9 @@
private static final String FUTURES_PING = "futures.ping";
private static final String FUTURES_PONG = "futures.pong";
private static final String GATE_INTERVAL = "1m";
- private static final String GATE_CONTRACT = "BTC_USDT";
+ private static final String GATE_CONTRACT = "XAU_USDT";
+
+ private GateGridTradeService gridTradeService;
// 心跳超时时间(秒),小于30秒
private static final int HEARTBEAT_TIMEOUT = 10;
@@ -77,6 +79,10 @@
this.caoZuoService = caoZuoService;
this.clientManager = clientManager;
this.wangGeListService = wangGeListService;
+ }
+
+ public void setGridTradeService(GateGridTradeService gridTradeService) {
+ this.gridTradeService = gridTradeService;
}
/**
@@ -366,85 +372,13 @@
System.out.println("成交额(a): " + baseVol);
System.out.println("K线完结(w): " + windowClosed);
System.out.println("==================================");
+
+ if (gridTradeService != null) {
+ gridTradeService.onKline(closePx, windowClosed);
+ }
} catch (Exception e) {
log.error("处理 K线频道推送数据失败", e);
}
- }
-
- private void doOpen(WebSocketClient webSocketClient, String accountName, MacdEmaStrategy.TradingOrder tradingOrderOpenOpen, BigDecimal closePx) {
- // 根据信号执行交易操作
- TradeRequestParam tradeRequestParam = new TradeRequestParam();
-
- String posSide = tradingOrderOpenOpen.getPosSide();
- tradeRequestParam.setPosSide(posSide);
- String currentPrice = String.valueOf(closePx);
- tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, currentPrice, posSide);
-
- String side = tradingOrderOpenOpen.getSide();
- tradeRequestParam.setSide(side);
-
- String clOrdId = WsParamBuild.getOrderNum(side);
- tradeRequestParam.setClOrdId(clOrdId);
-
- String sz = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
- tradeRequestParam.setSz(sz);
- TradeOrderWs.orderEvent(webSocketClient, tradeRequestParam);
- }
-
- private List<Kline> getKlineDataByInstIdAndBar(String instId, String bar) {
- List<Kline> klineList = new ArrayList<>();
- try {
- LinkedHashMap<String, Object> requestParam = new LinkedHashMap<>();
- requestParam.put("instId", instId);
- requestParam.put("bar", bar);
- requestParam.put("limit", "200");
- String result = ExchangeLoginService.getInstance(ExchangeInfoEnum.OKX_UAT.name()).lineHistory(requestParam);
- JSONObject json = JSON.parseObject(result);
- String data = json.getString("data");
-
- if (data != null) {
- List<String[]> klinesList = JSON.parseArray(data, String[].class);
- if (!CollUtil.isEmpty(klinesList)) {
- for (String[] s : klinesList) {
- // 确保数组有足够的元素
- if (s != null && s.length >= 9) {
- String s1 = s[8];
- try {
- if ("1".equals(s1)){
- Kline kline = new Kline();
- kline.setTs(s[0]);
- kline.setO(new BigDecimal(s[1]));
- kline.setH(new BigDecimal(s[2]));
- kline.setL(new BigDecimal(s[3]));
- kline.setC(new BigDecimal(s[4]));
- kline.setVol(new BigDecimal(s[5]));
- kline.setConfirm(s[8]);
- klineList.add(kline);
- }
- } catch (NumberFormatException e) {
- log.error("K线数据转换为BigDecimal失败: {}", Arrays.toString(s), e);
- }
- } else {
- log.warn("K线数据数组长度不足: {}", Arrays.toString(s));
- }
- }
- }
- } else {
- log.warn("K线数据为空");
- }
- } catch (JSONException e) {
- log.error("K线数据解析失败", e);
- } catch (Exception e) {
- log.error("获取K线数据异常", e);
- }
- return klineList;
- }
-
- /**
- * 构建 Redis Key
- */
- private String buildRedisKey(String instId) {
- return "PRICE_" + instId.replace("-", "");
}
/**
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 f020544..bc49fbe 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
@@ -1,24 +1,18 @@
package com.xcong.excoin.modules.gateApi;
-import com.xcong.excoin.modules.okxNewPrice.OkxKlineWebSocketClient;
-import com.xcong.excoin.modules.okxNewPrice.OkxQuantWebSocketClient;
import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.ExchangeInfoEnum;
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.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
-import java.util.Collection;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.math.BigDecimal;
/**
- * 管理多个OKX WebSocket客户端实例,每个实例对应一个账号
+ * 管理 Gate WebSocket 客户端和网格交易服务实例
*/
@Slf4j
@Component
@@ -31,53 +25,64 @@
private WangGeListService wangGeListService;
private GateKlineWebSocketClient klinePriceClient;
+ private GateGridTradeService gridTradeService;
+ private static final String API_KEY = "d90ca272391992b8e74f8f92cedb21ec";
+ private static final String API_SECRET = "1861e4f52de4bb53369ea3208d9ede38ece4777368030f96c77d27934c46c274";
- /**
- * 初始化方法,在Spring Bean构造完成后执行
- * 创建并初始化所有账号的WebSocket客户端实例
- */
@PostConstruct
public void init() {
- log.info("开始初始化OkxWebSocketClientManager");
-
- // 初始化价格WebSocket客户端
+ log.info("开始初始化GateWebSocketClientManager");
+
try {
+ gridTradeService = new GateGridTradeService(
+ API_KEY, API_SECRET,
+ "XAUT_USDT",
+ "100",
+ "cross",
+ new BigDecimal("0.0035"),
+ new BigDecimal("0.5"),
+ 3,
+ new BigDecimal("7.5"),
+ "1"
+ );
+ gridTradeService.init();
+
klinePriceClient = new GateKlineWebSocketClient(caoZuoService, this, wangGeListService);
+ klinePriceClient.setGridTradeService(gridTradeService);
klinePriceClient.init();
- log.info("已初始化OkxNewPriceWebSocketClient");
+ log.info("已初始化GateKlineWebSocketClient");
+
+ gridTradeService.startGrid();
} catch (Exception e) {
- log.error("初始化OkxNewPriceWebSocketClient失败", e);
+ log.error("初始化GateWebSocketClientManager失败", e);
}
-
}
- /**
- * 销毁方法,在Spring Bean销毁前执行
- * 关闭所有WebSocket客户端连接和相关资源
- */
@PreDestroy
public void destroy() {
- log.info("开始销毁OkxWebSocketClientManager");
-
- // 关闭价格WebSocket客户端
+ log.info("开始销毁GateWebSocketClientManager");
+
+ if (gridTradeService != null) {
+ gridTradeService.stopGrid();
+ }
if (klinePriceClient != null) {
try {
klinePriceClient.destroy();
- log.info("已销毁OkxNewPriceWebSocketClient");
+ log.info("已销毁GateKlineWebSocketClient");
} catch (Exception e) {
- log.error("销毁OkxNewPriceWebSocketClient失败", e);
+ log.error("销毁GateKlineWebSocketClient失败", e);
}
}
-
- log.info("OkxWebSocketClientManager销毁完成");
+
+ log.info("GateWebSocketClientManager销毁完成");
}
- /**
- * 获取OkxNewPriceWebSocketClient实例
- * @return 价格WebSocket客户端实例
- */
public GateKlineWebSocketClient getKlineWebSocketClient() {
return klinePriceClient;
}
+
+ public GateGridTradeService getGridTradeService() {
+ return gridTradeService;
+ }
}
\ No newline at end of file
--
Gitblit v1.9.1