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-algo)。 * *

定位

* 订阅用户条件订单(algo order)状态推送。当条件单状态变更(触发、取消等)时, * 获得 algoId、状态、订单类型等信息。相当于 Gate 的 {@code futures.autoorders} 频道。 * *

订阅格式

* 私有频道,需要先登录认证。orders-algo 频道不支持 instId,需使用 instFamily: *
 * {"op":"subscribe","args":[{"channel":"orders-algo","instType":"SWAP","instFamily":"ETH-USDT"}]}
 * 
* *

数据推送格式(条件单触发/成交)

*
 * {
 *   "arg": {"channel":"orders-algo","instType":"SWAP","instId":"ETH-USDT-SWAP"},
 *   "data": [{
 *     "algoId": "1234567890",
 *     "state": "effective",          // 状态: "effective"=已触发, "canceled"=已取消
 *     "ordType": "conditional",       // 订单类型
 *     "actualSide": "buy",           // 实际买卖方向: "buy"/"sell"
 *     "posSide": "long",             // 持仓方向: "long"/"short"/"net"
 *     "sz": "1",                     // 委托数量
 *     "triggerPx": "3000",           // 触发价格
 *     "ordPx": "3000.5",             // 委托价格
 *     "tradeId": "9876543210"        // 关联交易ID
 *   }]
 * }
 * 
* *

状态映射

* * *

订单类型映射(orderType)

* 根据 posSide 和 actualSide 组合推断订单类型: * * * @author Administrator */ @Slf4j public class OrderAlgoOkxChannelHandler extends AbstractOkxPrivateChannelHandler { /** OKX 条件订单频道名称 */ private static final String CHANNEL_NAME = "orders-algo"; /** OKX 配置 */ private final OkxConfig config; /** 交易品种族(如 "ETH-USDT",从合约名 ETH-USDT-SWAP 派生) */ private final String instFamily; /** * 构造条件订单频道处理器。 * * @param config OKX 配置实例(提供合约名称等) * @param gridTradeService OKX 网格交易策略服务实例 */ public OrderAlgoOkxChannelHandler(OkxConfig config, OkxGridTradeService gridTradeService) { super(CHANNEL_NAME, config.getApiKey(), config.getApiSecret(), config.getPassphrase(), config.getContract(), gridTradeService); this.config = config; // orders-algo 频道不支持 instId,需用 instFamily:"ETH-USDT-SWAP" → "ETH-USDT" String contract = config.getContract(); int lastDash = contract.lastIndexOf('-'); this.instFamily = lastDash > 0 ? contract.substring(0, lastDash) : contract; } /** * 发送订阅请求,使用 instFamily 而非 instId。 * * @param ws 私有频道 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("instType", "SWAP"); arg.put("instFamily", instFamily); // orders-algo 频道用 instFamily,不能用 instId args.add(arg); msg.put("args", args); ws.send(msg.toJSONString()); log.info("[OKX-WS] 订阅条件订单频道, instFamily: {}", instFamily); } /** * 发送取消订阅请求。 * * @param ws 私有频道 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("instType", "SWAP"); arg.put("instFamily", instFamily); args.add(arg); msg.put("args", args); ws.send(msg.toJSONString()); log.info("[OKX-WS] 取消订阅条件订单频道, instFamily: {}", instFamily); } /** * 处理条件订单推送消息。 * *

处理流程

*
    *
  1. 检查 arg.channel 是否匹配 "orders-algo"
  2. *
  3. 遍历 data 数组,提取 algoId、state、actualSide、posSide、tradeId
  4. *
  5. 映射 state → status(effective→finished, canceled→cancelled)
  6. *
  7. 根据 posSide + actualSide 推断 orderType
  8. *
  9. 调用 gridTradeService.onAutoOrder(algoId, status, reason, 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); // 按合约名称精确过滤(instFamily 已做粗筛,这里精筛) String dataInstId = orderData.getString("instId"); if (dataInstId == null || !contract.equals(dataInstId)) { continue; } String algoId = orderData.getString("algoId"); String state = orderData.getString("state"); String actualSide = orderData.getString("actualSide"); String posSide = orderData.getString("posSide"); String tradeId = orderData.getString("tradeId"); String ordType = orderData.getString("ordType"); // 状态映射 String status; if ("effective".equals(state)) { status = "finished"; } else if ("canceled".equals(state)) { status = "cancelled"; } else { // 其他状态(如 "pending")暂不处理 log.debug("[OKX-WS] orders-algo 忽略状态: algoId:{}, state:{}", algoId, state); continue; } // 推断 orderType String orderType = mapOrderType(posSide, actualSide); log.info("[OKX-WS] orders-algo 状态变更, algoId:{}, state:{}, status:{}, orderType:{}, ordType:{}, actualSide:{}, posSide:{}, tradeId:{}", algoId, state, status, orderType, ordType, actualSide, posSide, tradeId); if (getGridTradeService() != null) { getGridTradeService().onAutoOrder(algoId, status, state, orderType, tradeId); } } } catch (Exception e) { log.error("[OKX-WS] 处理 orders-algo 数据失败", e); } return true; } /** * 根据 OKX 的 posSide 和 actualSide 映射到策略内部的 orderType。 * *

映射规则

* * * * * * *
posSideactualSide含义orderType
longsell平多仓plan-close-long-position
shortbuy平空仓plan-close-short-position
netbuy开多仓entry-long
netsell开空仓entry-short
* * @param posSide OKX 持仓方向("long"/"short"/"net") * @param actualSide OKX 实际买卖方向("buy"/"sell") * @return 策略内部的订单类型字符串 */ private String mapOrderType(String posSide, String actualSide) { // 平仓方向(止盈/止损触发) if ("long".equals(posSide) && "sell".equals(actualSide)) { return "plan-close-long-position"; } else if ("short".equals(posSide) && "buy".equals(actualSide)) { return "plan-close-short-position"; } // 开仓方向 — 覆盖 long_short_mode 和 net_mode 两种模式 if (("long".equals(posSide) || "net".equals(posSide)) && "buy".equals(actualSide)) { return "entry-long"; } else if (("short".equals(posSide) || "net".equals(posSide)) && "sell".equals(actualSide)) { return "entry-short"; } // 默认值 log.warn("[OKX-WS] 未知的 orderType 映射, posSide:{}, actualSide:{}", posSide, actualSide); return "unknown"; } }