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 追踪条件单成交。
|
*
|
* <h3>为什么用 orders 替代 orders-algo</h3>
|
* 当 algo 订单(conditional/trigger)触发后,OKX 会生成对应的普通市价单并成交。
|
* orders 频道的 fill 数据中包含 {@code algoId} 字段,可直接关联到原始条件单 ID。
|
* 相比 orders-algo(只推送状态变更),orders 频道能拿到真实的 {@code fillPx/fillSz/tradeId}。
|
*
|
* <h3>订阅格式</h3>
|
* 私有频道,标准格式无需 instFamily:
|
* <pre>
|
* {"op":"subscribe","args":[{"channel":"orders","instType":"SWAP"}]}
|
* </pre>
|
*
|
* <h3>数据推送格式(含 algoId 的 fill)</h3>
|
* <pre>
|
* {
|
* "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"
|
* }]
|
* }
|
* </pre>
|
*
|
* <h3>状态映射 — 仅处理 filled</h3>
|
* <ul>
|
* <li>"filled" → status="finished" → 回调 onAutoOrder()</li>
|
* <li>"canceled"/"live"/"partially_filled" → 忽略</li>
|
* </ul>
|
*
|
* <h3>订单类型映射(同 orders-algo 逻辑)</h3>
|
* <table>
|
* <tr><th>posSide</th><th>side</th><th>含义</th><th>orderType</th></tr>
|
* <tr><td>long</td><td>sell</td><td>平多仓(止盈/止损)</td><td>plan-close-long-position</td></tr>
|
* <tr><td>short</td><td>buy</td><td>平空仓(止盈/止损)</td><td>plan-close-short-position</td></tr>
|
* <tr><td>long</td><td>buy</td><td>开多仓(条件单触发)</td><td>entry-long</td></tr>
|
* <tr><td>short</td><td>sell</td><td>开空仓(条件单触发)</td><td>entry-short</td></tr>
|
* </table>
|
*
|
* @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;
|
}
|
|
/**
|
* 处理订单推送消息。
|
*
|
* <h3>处理流程</h3>
|
* <ol>
|
* <li>按 instId 精确过滤出目标合约</li>
|
* <li>仅处理 state="filled"(成交),忽略其余状态</li>
|
* <li>过滤无 algoId 的订单(非条件单触发的普通订单)</li>
|
* <li>从 posSide + side 推断 orderType</li>
|
* <li>回调 gridTradeService.onAutoOrder(algoId, "finished", "filled", orderType, tradeId)</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;
|
}
|
|
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";
|
}
|
}
|