fix(okx): 修复账户余额获取和API请求处理问题
- 修改账户余额查询逻辑,改为获取USDT合约账户余额
- 更新WebSocket连接地址为业务地址(wss://.../ws/v5/business)
- 添加pong消息处理避免心跳异常
- 修改条件单取消接口调用方式,使用原始请求体格式
- 新增sendSignedRequestRaw方法支持原始请求体签名
| | |
| | | // ---- 订单/条件单推送回调 ---- |
| | | |
| | | public void onOrderUpdate(String algoId, String state, String ordType) { |
| | | if (!"effective".equals(state) && !"canceled".equals(state)) { |
| | | if (!"filled".equals(state) && !"canceled".equals(state)) { |
| | | return; |
| | | } |
| | | |
| | |
| | | baseGridElement.setShortOrderId(baseShortTp.getEntryOrderId()); |
| | | baseGridElement.setHasShortOrder(true); |
| | | |
| | | // 挂多仓止损 (id=-2 到 -11) |
| | | // 挂多仓止损 (id=-2 到 -11),每格 quantity 张 |
| | | for (int id = -2; id >= -11; id--) { |
| | | OkxGridElement elem = OkxGridElement.findById(id); |
| | | if (elem == null) continue; |
| | | BigDecimal triggerPrice = elem.getGridPrice(); |
| | | int finalId = id; |
| | | executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", "1", |
| | | executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(), |
| | | profitId -> { |
| | | elem.setLongStopLossOrderId(profitId); |
| | | OkxGridElement.refreshIndices(); |
| | | log.info("[OKX] 多仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", finalId, triggerPrice, profitId); |
| | | log.info("[OKX] 多仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}", |
| | | finalId, triggerPrice, config.getQuantity(), profitId); |
| | | }); |
| | | } |
| | | |
| | | // 挂空仓止损 (id=2 到 11) |
| | | // 挂空仓止损 (id=2 到 11),每格 quantity 张 |
| | | for (int id = 2; id <= 11; id++) { |
| | | OkxGridElement elem = OkxGridElement.findById(id); |
| | | if (elem == null) continue; |
| | | BigDecimal triggerPrice = elem.getGridPrice(); |
| | | int finalId = id; |
| | | executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", "1", |
| | | executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(), |
| | | profitId -> { |
| | | elem.setShortStopLossOrderId(profitId); |
| | | OkxGridElement.refreshIndices(); |
| | | log.info("[OKX] 空仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", finalId, triggerPrice, profitId); |
| | | log.info("[OKX] 空仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}", |
| | | finalId, triggerPrice, config.getQuantity(), profitId); |
| | | }); |
| | | } |
| | | |
| | |
| | | |
| | | BigDecimal triggerPrice = newEntryGrid.getGridPrice(); |
| | | BigDecimal priceDiff = longEntryPrice.subtract(triggerPrice).abs(); |
| | | int entryQty = priceDiff.divide(config.getStep(), 0, RoundingMode.DOWN).intValue(); |
| | | entryQty = Math.max(1, entryQty); |
| | | int count = priceDiff.divide(config.getStep(), 0, RoundingMode.DOWN).intValue(); |
| | | count = Math.max(1, count); |
| | | int entryQty = count * Integer.parseInt(config.getQuantity()); |
| | | String size = String.valueOf(entryQty); |
| | | log.info("[OKX] 多仓止损触发 gridId:{}, 在gridId:{}挂{}张多单", gridId, newEntryGridId, entryQty); |
| | | log.info("[OKX] 多仓止损触发 gridId:{}, 在gridId:{}挂{}张多单(价差:{},步长:{},count:{},qty:{})", |
| | | gridId, newEntryGridId, entryQty, priceDiff, config.getStep(), count, config.getQuantity()); |
| | | newEntryGrid.getLongTraderParam().setQuantity(size); |
| | | placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice, size); |
| | | } |
| | |
| | | |
| | | BigDecimal triggerPrice = newEntryGrid.getGridPrice(); |
| | | BigDecimal priceDiff = shortEntryPrice.subtract(triggerPrice).abs(); |
| | | int entryQty = priceDiff.divide(config.getStep(), 0, RoundingMode.DOWN).intValue(); |
| | | entryQty = Math.max(1, entryQty); |
| | | int count = priceDiff.divide(config.getStep(), 0, RoundingMode.DOWN).intValue(); |
| | | count = Math.max(1, count); |
| | | int entryQty = count * Integer.parseInt(config.getQuantity()); |
| | | String size = String.valueOf(entryQty); |
| | | log.info("[OKX] 空仓止损触发 gridId:{}, 在gridId:{}挂{}张空单", gridId, newEntryGridId, entryQty); |
| | | log.info("[OKX] 空仓止损触发 gridId:{}, 在gridId:{}挂{}张空单(价差:{},步长:{},count:{},qty:{})", |
| | | gridId, newEntryGridId, entryQty, priceDiff, config.getStep(), count, config.getQuantity()); |
| | | newEntryGrid.getShortTraderParam().setQuantity(size); |
| | | placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice, size); |
| | | } |
| | | |
| | | private void extendLongStopLoss(int filledQty) { |
| | | // filledQty 为本次新增止损张数 = count * quantity, 需要按 quantity 为粒度拆分为 count 个止损单 |
| | | int qty = Integer.parseInt(config.getQuantity()); |
| | | int stopLossCount = filledQty / qty; |
| | | int furthestSlId = 0; |
| | | for (OkxGridElement e : config.getGridElements()) { |
| | | if (e.getLongStopLossOrderId() != null && e.getId() < furthestSlId) { |
| | |
| | | } |
| | | } |
| | | if (furthestSlId == 0) furthestSlId = -11; |
| | | log.info("[OKX] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}张", furthestSlId, filledQty); |
| | | for (int i = 0; i < filledQty; i++) { |
| | | log.info("[OKX] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty); |
| | | for (int i = 0; i < stopLossCount; i++) { |
| | | int newSlId = furthestSlId - i - 1; |
| | | OkxGridElement elem = OkxGridElement.findById(newSlId); |
| | | if (elem == null) continue; |
| | | BigDecimal triggerPrice = elem.getGridPrice(); |
| | | int finalSlId = newSlId; |
| | | executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", "1", |
| | | executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(), |
| | | profitId -> { |
| | | elem.setLongStopLossOrderId(profitId); |
| | | OkxGridElement.refreshIndices(); |
| | |
| | | } |
| | | |
| | | private void extendShortStopLoss(int filledQty) { |
| | | int qty = Integer.parseInt(config.getQuantity()); |
| | | int stopLossCount = filledQty / qty; |
| | | int furthestSlId = 0; |
| | | for (OkxGridElement e : config.getGridElements()) { |
| | | if (e.getShortStopLossOrderId() != null && e.getId() > furthestSlId) { |
| | |
| | | } |
| | | } |
| | | if (furthestSlId == 0) furthestSlId = 11; |
| | | log.info("[OKX] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}张", furthestSlId, filledQty); |
| | | for (int i = 0; i < filledQty; i++) { |
| | | log.info("[OKX] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty); |
| | | for (int i = 0; i < stopLossCount; i++) { |
| | | int newSlId = furthestSlId + i + 1; |
| | | OkxGridElement elem = OkxGridElement.findById(newSlId); |
| | | if (elem == null) continue; |
| | | BigDecimal triggerPrice = elem.getGridPrice(); |
| | | int finalSlId = newSlId; |
| | | executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", "1", |
| | | executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(), |
| | | profitId -> { |
| | | elem.setShortStopLossOrderId(profitId); |
| | | OkxGridElement.refreshIndices(); |
| | |
| | | * @param onFailure 失败回调 |
| | | */ |
| | | public void openLong(String quantity, Consumer<String> onSuccess, Runnable onFailure) { |
| | | submitOrder("buy", "long", quantity, "market", null, false, "tGridLong", onSuccess, onFailure); |
| | | submitOrder("buy", "long", quantity, "market", null, false, null, onSuccess, onFailure); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param onFailure 失败回调 |
| | | */ |
| | | public void openShort(String quantity, Consumer<String> onSuccess, Runnable onFailure) { |
| | | submitOrder("sell", "short", quantity, "market", null, false, "tGridShort", onSuccess, onFailure); |
| | | submitOrder("sell", "short", quantity, "market", null, false, null, onSuccess, onFailure); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 异步取消所有未完成的 algo 订单。 |
| | | * 异步取消所有未完成的 algo 订单(best-effort,失败仅警告)。 |
| | | */ |
| | | public void cancelAllAlgoOrders() { |
| | | executor.execute(() -> { |
| | |
| | | okxAccount.baseUrl, "/api/v5/trade/cancel-algos", body, HttpMethod.POST, okxAccount.isSimluate()); |
| | | log.info("[OkxExec] 已尝试清除条件单, resp:{}", resp); |
| | | } catch (Exception e) { |
| | | log.error("[OkxExec] 清除条件单失败", e); |
| | | log.warn("[OkxExec] 清除条件单失败(若无挂单可忽略), msg:{}", e.getMessage()); |
| | | } |
| | | }); |
| | | } |
| | |
| | | private static final String CHANNEL_NAME = "orders-algo"; |
| | | |
| | | private final String instId; |
| | | private final String instFamily; |
| | | private final OkxGridTradeService gridTradeService; |
| | | |
| | | public OkxAlgoOrdersChannelHandler(String instId, OkxGridTradeService gridTradeService) { |
| | | this.instId = instId; |
| | | this.instFamily = instId.contains("-") ? instId.substring(0, instId.lastIndexOf("-")) : instId; |
| | | this.gridTradeService = gridTradeService; |
| | | } |
| | | |
| | |
| | | JSONObject arg = new JSONObject(); |
| | | arg.put("channel", CHANNEL_NAME); |
| | | arg.put("instType", "SWAP"); |
| | | arg.put("instFamily", instFamily); |
| | | msg.put("op", "subscribe"); |
| | | JSONArray args = new JSONArray(); |
| | | args.add(arg); |
| | | msg.put("args", args); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[OKX-WS] {} 订阅成功", CHANNEL_NAME); |
| | | log.info("[OKX-WS] {} 订阅成功, instFamily:{}", CHANNEL_NAME, instFamily); |
| | | } |
| | | |
| | | @Override |
| | |
| | | JSONObject arg = new JSONObject(); |
| | | arg.put("channel", CHANNEL_NAME); |
| | | arg.put("instType", "SWAP"); |
| | | arg.put("instFamily", instFamily); |
| | | msg.put("op", "unsubscribe"); |
| | | JSONArray args = new JSONArray(); |
| | | args.add(arg); |
| | |
| | | private static final String CHANNEL_NAME = "positions"; |
| | | |
| | | private final String instId; |
| | | private final String instFamily; |
| | | private final OkxGridTradeService gridTradeService; |
| | | |
| | | public OkxPositionsChannelHandler(String instId, OkxGridTradeService gridTradeService) { |
| | | this.instId = instId; |
| | | this.instFamily = instId.contains("-") ? instId.substring(0, instId.lastIndexOf("-")) : instId; |
| | | this.gridTradeService = gridTradeService; |
| | | } |
| | | |
| | |
| | | JSONObject arg = new JSONObject(); |
| | | arg.put("channel", CHANNEL_NAME); |
| | | arg.put("instType", "SWAP"); |
| | | arg.put("instFamily", instFamily); |
| | | msg.put("op", "subscribe"); |
| | | JSONArray args = new JSONArray(); |
| | | args.add(arg); |
| | | msg.put("args", args); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[OKX-WS] {} 订阅成功", CHANNEL_NAME); |
| | | log.info("[OKX-WS] {} 订阅成功, instFamily:{}", CHANNEL_NAME, instFamily); |
| | | } |
| | | |
| | | @Override |
| | |
| | | JSONObject arg = new JSONObject(); |
| | | arg.put("channel", CHANNEL_NAME); |
| | | arg.put("instType", "SWAP"); |
| | | arg.put("instFamily", instFamily); |
| | | msg.put("op", "unsubscribe"); |
| | | JSONArray args = new JSONArray(); |
| | | args.add(arg); |