| | |
| | | }); |
| | | } |
| | | |
| | | // ==================== 止盈条件单 ==================== |
| | | // ==================== 止盈/止损条件单 ==================== |
| | | |
| | | /** |
| | | * 异步创建止盈条件单(OKX 算法订单 — conditional 类型)。 |
| | | * 异步创建止盈条件单(OKX 算法订单 — conditional 类型,tpTriggerPx)。 |
| | | * |
| | | * <p>服务器监控价格,达到触发价后自动以市价平仓。 |
| | | * 使用 OKX 的 {@code order-algo} 接口,ordType=conditional。 |
| | | * |
| | | * <h3>止盈失败兜底</h3> |
| | | * 止盈单创建失败时立即调用 {@link #marketClose(String, String)} 市价平仓, |
| | | * 确保仓位不会因止损条件未挂上而无保护。 |
| | | * <p>止盈单创建失败时立即调用 {@link #marketClose(String, String)} 市价平仓兜底。 |
| | | * |
| | | * @param triggerPrice 触发价格 |
| | | * @param orderType 平仓类型:"close_long" 平多 / "close_short" 平空 |
| | |
| | | String orderType, |
| | | String size, |
| | | Consumer<String> onSuccess) { |
| | | placeConditionalClose(triggerPrice, orderType, size, onSuccess, false); |
| | | } |
| | | |
| | | /** |
| | | * 异步创建止损条件单(OKX 算法订单 — conditional 类型,slTriggerPx)。 |
| | | * |
| | | * <p>止损单创建失败时立即调用 {@link #marketClose(String, String)} 市价平仓兜底。 |
| | | * |
| | | * @param triggerPrice 触发价格 |
| | | * @param orderType 平仓类型:"close_long" 平多 / "close_short" 平空 |
| | | * @param size 平仓张数(正数,如 "15") |
| | | * @param onSuccess 成功回调,接收 algoId(可为 null) |
| | | */ |
| | | public void placeStopLoss(BigDecimal triggerPrice, |
| | | String orderType, |
| | | String size, |
| | | Consumer<String> onSuccess) { |
| | | placeConditionalClose(triggerPrice, orderType, size, onSuccess, true); |
| | | } |
| | | |
| | | /** |
| | | * 通用平仓条件单:isStopLoss=true 用 slTriggerPx/slOrdPx,false 用 tpTriggerPx/tpOrdPx。 |
| | | */ |
| | | private void placeConditionalClose(BigDecimal triggerPrice, |
| | | String orderType, |
| | | String size, |
| | | Consumer<String> onSuccess, |
| | | boolean isStopLoss) { |
| | | executor.execute(() -> { |
| | | String posSide = null; |
| | | try { |
| | |
| | | side = "buy"; |
| | | posSide = "short"; |
| | | } else { |
| | | log.error("[TradeExec-OKX] 未知止盈类型: {}", orderType); |
| | | log.error("[TradeExec-OKX] 未知平仓类型: {}", orderType); |
| | | return; |
| | | } |
| | | |
| | | // OKX conditional 止盈止损使用 tpTriggerPx/tpOrdPx 或 slTriggerPx/slOrdPx |
| | | String label = isStopLoss ? "止损" : "止盈"; |
| | | JSONObject body = new JSONObject(); |
| | | body.put("instId", contract); |
| | | body.put("tdMode", "cross"); |
| | |
| | | body.put("posSide", posSide); |
| | | body.put("ordType", "conditional"); |
| | | body.put("sz", size); |
| | | // "close_long"=平多(止盈), "close_short"=平空(止盈) |
| | | // 止盈用 tp 系列字段,止损用 sl 系列字段 |
| | | if (isStopLoss) { |
| | | body.put("slTriggerPx", triggerPrice.stripTrailingZeros().toPlainString()); |
| | | body.put("slTriggerPxType", "last"); |
| | | body.put("slOrdPx", "-1"); |
| | | } else { |
| | | body.put("tpTriggerPx", triggerPrice.stripTrailingZeros().toPlainString()); |
| | | body.put("tpTriggerPxType", "last"); |
| | | body.put("tpOrdPx", "-1"); |
| | | } |
| | | |
| | | JSONObject resp = okPost("/api/v5/trade/order-algo", body.toJSONString()); |
| | | String code = resp.getString("code"); |
| | | if (!"0".equals(code)) { |
| | | log.error("[TradeExec-OKX] 止盈单创建失败, code:{}, msg:{}, 立即市价止盈", |
| | | code, resp.getString("msg")); |
| | | log.error("[TradeExec-OKX] {}单创建失败, code:{}, msg:{}, 立即市价{}", |
| | | label, code, resp.getString("msg"), label); |
| | | marketClose(size, posSide); |
| | | return; |
| | | } |
| | | JSONArray data = resp.getJSONArray("data"); |
| | | String algoId = (data != null && !data.isEmpty()) |
| | | ? data.getJSONObject(0).getString("algoId") : null; |
| | | log.info("[TradeExec-OKX] 止盈单已创建, triggerPx:{}, type:{}, sz:{}, algoId:{}", |
| | | triggerPrice, orderType, size, algoId); |
| | | log.info("[TradeExec-OKX] {}单已创建, triggerPx:{}, type:{}, sz:{}, algoId:{}", |
| | | label, triggerPrice, orderType, size, algoId); |
| | | if (onSuccess != null) { |
| | | onSuccess.accept(algoId); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[TradeExec-OKX] 止盈单创建失败, triggerPx:{}, sz:{}, 立即市价止盈", |
| | | log.error("[TradeExec-OKX] 创建失败, triggerPx:{}, sz:{}, 立即市价{}", |
| | | triggerPrice, size, e); |
| | | if (posSide != null) { |
| | | marketClose(size, posSide); |
| | |
| | | for (int i = 0; i < data.size(); i++) { |
| | | JSONObject order = data.getJSONObject(i); |
| | | String algoId = order.getString("algoId"); |
| | | if (algoId == null) continue; |
| | | if (algoId == null) { |
| | | continue; |
| | | } |
| | | JSONObject item = new JSONObject(); |
| | | item.put("algoId", algoId); |
| | | item.put("instId", contract); |