From e45e705c22df5bc979e72db6014dd1ff9637be42 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Wed, 24 Jun 2026 22:21:58 +0800
Subject: [PATCH] fix(okx): 修复网格交易成交日志记录问题

---
 src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/MarkPriceOkxChannelHandler.java |  188 +++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 188 insertions(+), 0 deletions(-)

diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/MarkPriceOkxChannelHandler.java b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/MarkPriceOkxChannelHandler.java
new file mode 100644
index 0000000..cdcd091
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/MarkPriceOkxChannelHandler.java
@@ -0,0 +1,188 @@
+package com.xcong.excoin.modules.okxApi.wsHandler.handler;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.xcong.excoin.modules.okxApi.OkxGridTradeService;
+import com.xcong.excoin.modules.okxApi.wsHandler.OkxChannelHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.client.WebSocketClient;
+
+import java.math.BigDecimal;
+
+/**
+ * OKX 标记价格频道处理器(mark-price)— 策略的价格驱动源。
+ *
+ * <h3>定位</h3>
+ * 这是一个<b>公开频道</b>,连接到 OKX 公开 WebSocket 端点,无需登录认证。
+ * 替代 candle1m K 线频道,作为策略唯一的实时价格来源。
+ * 标记价格变化时每 200ms 推送一次,无变化时每 10s 推送一次。
+ *
+ * <h3>双回调</h3>
+ * <ul>
+ *   <li>{@link OkxGridTradeService#onKline(BigDecimal)} — 驱动网格策略(处理开仓/止盈逻辑)</li>
+ *   <li>{@link OkxGridTradeService#setMarkPrice(BigDecimal)} — PnLPriceMode.MARK_PRICE 计算未实现盈亏</li>
+ * </ul>
+ *
+ * <h3>订阅格式</h3>
+ * <pre>
+ * {"op":"subscribe","args":[{"channel":"mark-price","instId":"ETH-USDT-SWAP"}]}
+ * </pre>
+ *
+ * <h3>数据推送格式</h3>
+ * <pre>
+ * {
+ *   "arg": {"channel":"mark-price","instId":"ETH-USDT-SWAP"},
+ *   "data": [{
+ *     "instType": "SWAP",
+ *     "instId": "ETH-USDT-SWAP",
+ *     "markPx": "42310.6",
+ *     "ts": "1630049139746"
+ *   }]
+ * }
+ * </pre>
+ *
+ * <h3>推送频率</h3>
+ * <ul>
+ *   <li>标记价格变化 → 每 200ms 推送一次</li>
+ *   <li>标记价格无变化 → 每 10s 推送一次</li>
+ * </ul>
+ *
+ * @author Administrator
+ */
+@Slf4j
+public class MarkPriceOkxChannelHandler implements OkxChannelHandler {
+
+    /** 频道名称 */
+    private static final String CHANNEL_NAME = "mark-price";
+
+    /** 交易对标识,如 "ETH-USDT-SWAP" */
+    private final String instId;
+
+    /** 网格交易服务,接收标记价格回调 */
+    private final OkxGridTradeService gridTradeService;
+
+    /** 订阅确认状态 */
+    private volatile boolean subscribed = false;
+
+    /**
+     * 构造标记价格频道处理器。
+     *
+     * @param instId           交易对标识(如 "ETH-USDT-SWAP")
+     * @param gridTradeService OKX 网格交易策略服务实例
+     */
+    public MarkPriceOkxChannelHandler(String instId, OkxGridTradeService gridTradeService) {
+        this.instId = instId;
+        this.gridTradeService = gridTradeService;
+    }
+
+    /**
+     * @return 频道名称 "mark-price"
+     */
+    @Override
+    public String getChannelName() {
+        return CHANNEL_NAME;
+    }
+
+    /**
+     * @return 交易对标识(如 "ETH-USDT-SWAP")
+     */
+    @Override
+    public String getInstId() {
+        return instId;
+    }
+
+    /**
+     * 发送标记价格频道订阅请求(公开频道,无需签名)。
+     *
+     * @param ws OKX 公开 WebSocket 客户端
+     */
+    @Override
+    public void subscribe(WebSocketClient ws) {
+        JSONObject msg = new JSONObject();
+        msg.put("op", "subscribe");
+        JSONArray args = new JSONArray();
+        JSONObject arg = new JSONObject();
+        arg.put("channel", CHANNEL_NAME);
+        arg.put("instId", instId);
+        args.add(arg);
+        msg.put("args", args);
+        ws.send(msg.toJSONString());
+        log.info("[OKX-WS] 订阅标记价格频道, instId: {}", instId);
+    }
+
+    /**
+     * 发送标记价格频道取消订阅请求。
+     *
+     * @param ws OKX 公开 WebSocket 客户端
+     */
+    @Override
+    public void unsubscribe(WebSocketClient ws) {
+        JSONObject msg = new JSONObject();
+        msg.put("op", "unsubscribe");
+        JSONArray args = new JSONArray();
+        JSONObject arg = new JSONObject();
+        arg.put("channel", CHANNEL_NAME);
+        arg.put("instId", instId);
+        args.add(arg);
+        msg.put("args", args);
+        ws.send(msg.toJSONString());
+        log.info("[OKX-WS] 取消订阅标记价格频道, instId: {}", instId);
+    }
+
+    /**
+     * 处理标记价格推送消息。
+     *
+     * <h3>数据提取</h3>
+     * 从 {@code data[0].markPx} 提取标记价格,同时回调:
+     * <ol>
+     *   <li>{@code gridTradeService.onKline(markPrice)} — 驱动网格策略</li>
+     *   <li>{@code gridTradeService.setMarkPrice(markPrice)} — PnL 计算用</li>
+     * </ol>
+     *
+     * @param response WebSocket 推送的完整 JSON
+     * @return true 表示已处理(匹配成功)
+     */
+    @Override
+    public boolean handleMessage(JSONObject response) {
+        JSONObject arg = response.getJSONObject("arg");
+        if (arg == null || !CHANNEL_NAME.equals(arg.getString("channel"))) {
+            return false;
+        }
+        try {
+            JSONArray dataArray = response.getJSONArray("data");
+            if (dataArray == null || dataArray.isEmpty()) {
+                return true;
+            }
+            // data[0] 是一个 JSONObject: {instType, instId, markPx, ts}
+            JSONObject markData = dataArray.getJSONObject(0);
+            if (markData == null) {
+                return true;
+            }
+            String markPxStr = markData.getString("markPx");
+            if (markPxStr == null || markPxStr.isEmpty()) {
+                return true;
+            }
+            BigDecimal markPrice = new BigDecimal(markPxStr);
+
+            if (gridTradeService != null) {
+                gridTradeService.setMarkPrice(markPrice);
+                gridTradeService.onKline(markPrice);
+            }
+        } catch (Exception e) {
+            log.error("[OKX-WS] 处理 mark-price 数据失败", e);
+        }
+        return true;
+    }
+
+    // ==================== 订阅状态 ====================
+
+    @Override
+    public boolean isSubscribed() {
+        return subscribed;
+    }
+
+    @Override
+    public void setSubscribed(boolean subscribed) {
+        this.subscribed = subscribed;
+    }
+}

--
Gitblit v1.9.1