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)。
|
*
|
* <h3>定位</h3>
|
* 订阅用户条件订单(algo order)状态推送。当条件单状态变更(触发、取消等)时,
|
* 获得 algoId、状态、订单类型等信息。相当于 Gate 的 {@code futures.autoorders} 频道。
|
*
|
* <h3>订阅格式</h3>
|
* 私有频道,需要先登录认证。订阅时需要指定 instType 和 instId:
|
* <pre>
|
* {"op":"subscribe","args":[{"channel":"orders-algo","instType":"SWAP","instId":"ETH-USDT-SWAP"}]}
|
* </pre>
|
*
|
* <h3>数据推送格式(条件单触发/成交)</h3>
|
* <pre>
|
* {
|
* "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
|
* }]
|
* }
|
* </pre>
|
*
|
* <h3>状态映射</h3>
|
* <ul>
|
* <li>"effective" → status="finished"(已触发/已完成)</li>
|
* <li>"canceled" → status="cancelled"(已取消)</li>
|
* </ul>
|
*
|
* <h3>订单类型映射(orderType)</h3>
|
* 根据 posSide 和 actualSide 组合推断订单类型:
|
* <ul>
|
* <li>posSide=long, actualSide=sell → "plan-close-long-position"(平多仓)</li>
|
* <li>posSide=short, actualSide=buy → "plan-close-short-position"(平空仓)</li>
|
* <li>posSide=net, actualSide=buy → "entry-long"(开多仓)</li>
|
* <li>posSide=net, actualSide=sell → "entry-short"(开空仓)</li>
|
* </ul>
|
*
|
* @author Administrator
|
*/
|
@Slf4j
|
public class OrderAlgoOkxChannelHandler extends AbstractOkxPrivateChannelHandler {
|
|
/** OKX 条件订单频道名称 */
|
private static final String CHANNEL_NAME = "orders-algo";
|
|
/** OKX 配置 */
|
private final OkxConfig config;
|
|
/**
|
* 构造条件订单频道处理器。
|
*
|
* @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;
|
}
|
|
/**
|
* 发送订阅请求,需指定 instType 和 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("instId", getInstId());
|
args.add(arg);
|
msg.put("args", args);
|
ws.send(msg.toJSONString());
|
log.info("[OKX-WS] 订阅条件订单频道, instId: {}", getInstId());
|
}
|
|
/**
|
* 发送取消订阅请求。
|
*
|
* @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("instId", getInstId());
|
args.add(arg);
|
msg.put("args", args);
|
ws.send(msg.toJSONString());
|
log.info("[OKX-WS] 取消订阅条件订单频道, instId: {}", getInstId());
|
}
|
|
/**
|
* 处理条件订单推送消息。
|
*
|
* <h3>处理流程</h3>
|
* <ol>
|
* <li>检查 arg.channel 是否匹配 "orders-algo"</li>
|
* <li>遍历 data 数组,提取 algoId、state、actualSide、posSide、tradeId</li>
|
* <li>映射 state → status(effective→finished, canceled→cancelled)</li>
|
* <li>根据 posSide + actualSide 推断 orderType</li>
|
* <li>调用 gridTradeService.onAutoOrder(algoId, status, reason, 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);
|
|
// 按 instId 过滤
|
String dataInstId = orderData.getString("instId");
|
String instId = arg.getString("instId");
|
if (instId != null && dataInstId != null
|
&& !instId.equals(dataInstId)
|
&& !dataInstId.startsWith(contract)) {
|
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。
|
*
|
* <h3>映射规则</h3>
|
* <table>
|
* <tr><th>posSide</th><th>actualSide</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>net</td><td>buy</td><td>开多仓</td><td>entry-long</td></tr>
|
* <tr><td>net</td><td>sell</td><td>开空仓</td><td>entry-short</td></tr>
|
* </table>
|
*
|
* @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";
|
}
|
}
|