package com.xcong.excoin.modules.okxApi.wsHandler.handler; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.xcong.excoin.modules.okxApi.OkxConfig; import com.xcong.excoin.modules.okxApi.OkxGridTradeService; import com.xcong.excoin.modules.okxApi.wsHandler.AbstractOkxPrivateChannelHandler; import lombok.extern.slf4j.Slf4j; import org.java_websocket.client.WebSocketClient; /** * OKX 订单频道处理器(orders),替代 orders-algo 追踪条件单成交。 * *

为什么用 orders 替代 orders-algo

* 当 algo 订单(conditional/trigger)触发后,OKX 会生成对应的普通市价单并成交。 * orders 频道的 fill 数据中包含 {@code algoId} 字段,可直接关联到原始条件单 ID。 * 相比 orders-algo(只推送状态变更),orders 频道能拿到真实的 {@code fillPx/fillSz/tradeId}。 * *

订阅格式

* 私有频道,标准格式无需 instFamily: *
 * {"op":"subscribe","args":[{"channel":"orders","instType":"SWAP"}]}
 * 
* *

数据推送格式(含 algoId 的 fill)

*
 * {
 *   "arg": {"channel":"orders","instType":"SWAP"},
 *   "data": [{
 *     "instId": "ETH-USDT-SWAP",
 *     "ordId": "...",
 *     "algoId": "...",          // 链接回原始条件单
 *     "state": "filled",        // 只处理 filled 状态
 *     "side": "sell",
 *     "posSide": "long",
 *     "fillPx": "3000",
 *     "fillSz": "1",
 *     "tradeId": "...",
 *     "ordType": "market"
 *   }]
 * }
 * 
* *

状态映射 — 仅处理 filled

* * *

订单类型映射(同 orders-algo 逻辑)

* * * * * * *
posSideside含义orderType
longsell平多仓(止盈/止损)plan-close-long-position
shortbuy平空仓(止盈/止损)plan-close-short-position
longbuy开多仓(条件单触发)entry-long
shortsell开空仓(条件单触发)entry-short
* * @author Administrator */ @Slf4j public class OrdersOkxChannelHandler extends AbstractOkxPrivateChannelHandler { /** OKX 订单频道名称 */ private static final String CHANNEL_NAME = "orders"; /** OKX 配置,用于按合约名称过滤 */ private final OkxConfig config; /** * 构造订单频道处理器。 * * @param config OKX 配置实例 * @param gridTradeService OKX 网格交易策略服务实例 */ public OrdersOkxChannelHandler(OkxConfig config, OkxGridTradeService gridTradeService) { super(CHANNEL_NAME, config.getApiKey(), config.getApiSecret(), config.getPassphrase(), config.getContract(), gridTradeService); this.config = config; } /** * 处理订单推送消息。 * *

处理流程

*
    *
  1. 按 instId 精确过滤出目标合约
  2. *
  3. 仅处理 state="filled"(成交),忽略其余状态
  4. *
  5. 过滤无 algoId 的订单(非条件单触发的普通订单)
  6. *
  7. 从 posSide + side 推断 orderType
  8. *
  9. 回调 gridTradeService.onAutoOrder(algoId, "finished", "filled", orderType, tradeId)
  10. *
* * @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; } String contract = config.getContract(); for (int i = 0; i < dataArray.size(); i++) { JSONObject orderData = dataArray.getJSONObject(i); // 按合约名称精确过滤 String dataInstId = orderData.getString("instId"); if (dataInstId == null || !contract.equals(dataInstId)) { continue; } // 仅处理已成交 String state = orderData.getString("state"); if (!"filled".equals(state)) { continue; } // 必须有 algoId(否则不是条件单触发的订单) String algoId = orderData.getString("algoId"); if (algoId == null || algoId.isEmpty()) { continue; } String posSide = orderData.getString("posSide"); String side = orderData.getString("side"); String tradeId = orderData.getString("tradeId"); String fillSz = orderData.getString("fillSz"); // 推断订单类型 String orderType = mapOrderType(posSide, side); log.info("[OKX-WS] orders fill, algoId:{}, state:filled, orderType:{}, side:{}, posSide:{}, fillSz:{}, tradeId:{}", algoId, orderType, side, posSide, fillSz, tradeId); if (getGridTradeService() != null) { getGridTradeService().onAutoOrder(algoId, state, orderType, tradeId); } } } catch (Exception e) { log.error("[OKX-WS] 处理 orders 数据失败", e); } return true; } /** * 根据 posSide 和 side 推断订单类型。 */ private String mapOrderType(String posSide, String side) { // 平仓方向(止盈/止损触发) if ("long".equals(posSide) && "sell".equals(side)) { return "plan-close-long-position"; } else if ("short".equals(posSide) && "buy".equals(side)) { return "plan-close-short-position"; } // 开仓方向 — 覆盖 long_short_mode 和 net_mode if (("long".equals(posSide) || "net".equals(posSide)) && "buy".equals(side)) { return "entry-long"; } else if (("short".equals(posSide) || "net".equals(posSide)) && "sell".equals(side)) { return "entry-short"; } log.warn("[OKX-WS] orders 未知订单类型, posSide:{}, side:{}", posSide, side); return "unknown"; } }