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
* }]
* }
*
*
* 状态映射
*
* - "effective" → status="finished"(已触发/已完成)
* - "canceled" → status="cancelled"(已取消)
*
*
* 订单类型映射(orderType)
* 根据 posSide 和 actualSide 组合推断订单类型:
*
* - posSide=long, actualSide=sell → "plan-close-long-position"(平多仓)
* - posSide=short, actualSide=buy → "plan-close-short-position"(平空仓)
* - posSide=net, actualSide=buy → "entry-long"(开多仓)
* - posSide=net, actualSide=sell → "entry-short"(开空仓)
*
*
* @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);
}
/**
* 处理条件订单推送消息。
*
* 处理流程
*
* - 检查 arg.channel 是否匹配 "orders-algo"
* - 遍历 data 数组,提取 algoId、state、actualSide、posSide、tradeId
* - 映射 state → status(effective→finished, canceled→cancelled)
* - 根据 posSide + actualSide 推断 orderType
* - 调用 gridTradeService.onAutoOrder(algoId, status, reason, orderType, tradeId)
*
*
* @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。
*
* 映射规则
*
* | posSide | actualSide | 含义 | orderType |
* | long | sell | 平多仓 | plan-close-long-position |
* | short | buy | 平空仓 | plan-close-short-position |
* | net | buy | 开多仓 | entry-long |
* | net | sell | 开空仓 | 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";
}
}