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
*
* - "filled" → status="finished" → 回调 onAutoOrder()
* - "canceled"/"live"/"partially_filled" → 忽略
*
*
* 订单类型映射(同 orders-algo 逻辑)
*
* | posSide | side | 含义 | orderType |
* | long | sell | 平多仓(止盈/止损) | plan-close-long-position |
* | short | buy | 平空仓(止盈/止损) | plan-close-short-position |
* | long | buy | 开多仓(条件单触发) | entry-long |
* | short | sell | 开空仓(条件单触发) | 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;
}
/**
* 处理订单推送消息。
*
* 处理流程
*
* - 按 instId 精确过滤出目标合约
* - 仅处理 state="filled"(成交),忽略其余状态
* - 过滤无 algoId 的订单(非条件单触发的普通订单)
* - 从 posSide + side 推断 orderType
* - 回调 gridTradeService.onAutoOrder(algoId, "finished", "filled", 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);
// 按合约名称精确过滤
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";
}
}