| | |
| | | executor.openLong(config.getBaseQuantity(), (orderId) -> { |
| | | OkxTraderParam baseLongTp = OkxTraderParam.builder().entryOrderId(orderId).build(); |
| | | config.setBaseLongTraderParam(baseLongTp); |
| | | tryGenerateQueues(); // 异步回调到达后重试,防止 WS 推送先到导致 NPE |
| | | }, null); |
| | | executor.openShort(config.getBaseQuantity(), (orderId) -> { |
| | | OkxTraderParam baseShortTp = OkxTraderParam.builder().entryOrderId(orderId).build(); |
| | | config.setBaseShortTraderParam(baseShortTp); |
| | | tryGenerateQueues(); // 异步回调到达后重试,防止 WS 推送先到导致 NPE |
| | | }, null); |
| | | return; |
| | | } |
| | |
| | | baseLongOpened = true; |
| | | log.info("[OKX] 基底多成交价: {}", longBaseEntryPrice); |
| | | tryGenerateQueues(); |
| | | } else { |
| | | longPositionSize = posSize; |
| | | } |
| | | } else { |
| | | longPositionSize = posSize; |
| | | tryGenerateQueues(); // 后续 WS 推送触发重试,兜底此前 NPE 失败的情况 |
| | | } |
| | | } else { |
| | | if (longActive && state == StrategyState.ACTIVE) { |
| | | log.info("[OKX] 多仓持仓归零,重置策略"); |
| | |
| | | baseShortOpened = true; |
| | | log.info("[OKX] 基底空成交价: {}", shortBaseEntryPrice); |
| | | tryGenerateQueues(); |
| | | } else { |
| | | shortPositionSize = posSize; |
| | | } |
| | | } else { |
| | | shortPositionSize = posSize; |
| | | tryGenerateQueues(); // 后续 WS 推送触发重试,兜底此前 NPE 失败的情况 |
| | | } |
| | | } else { |
| | | if (shortActive && state == StrategyState.ACTIVE) { |
| | | log.info("[OKX] 空仓持仓归零,重置策略"); |
| | |
| | | // ---- 订单/条件单推送回调 ---- |
| | | |
| | | public void onOrderUpdate(String algoId, String state, String ordType) { |
| | | if (!"effective".equals(state) && !"canceled".equals(state)) { |
| | | if (!"filled".equals(state) && !"canceled".equals(state)) { |
| | | return; |
| | | } |
| | | |
| | |
| | | // ---- 网格队列处理 ---- |
| | | |
| | | private void tryGenerateQueues() { |
| | | // 防止重复执行:一旦已进入 ACTIVE 状态不再重复初始化 |
| | | if (state == StrategyState.ACTIVE) { |
| | | return; |
| | | } |
| | | if (baseLongOpened && baseShortOpened) { |
| | | // 防御异步竞态:openLong/openShort 的回调可能还未设置 trader param |
| | | OkxTraderParam baseLongTp = config.getBaseLongTraderParam(); |
| | | OkxTraderParam baseShortTp = config.getBaseShortTraderParam(); |
| | | if (baseLongTp == null || baseShortTp == null) { |
| | | log.warn("[OKX] tryGenerateQueues 等待异步回调: longTp={}, shortTp={}", |
| | | baseLongTp != null, baseShortTp != null); |
| | | return; |
| | | } |
| | | |
| | | generateShortQueue(); |
| | | generateLongQueue(); |
| | | updateGridElements(); |
| | | |
| | | // 标记基座挂单 |
| | | OkxGridElement baseGridElement = OkxGridElement.findById(0); |
| | | OkxTraderParam baseLongTp = config.getBaseLongTraderParam(); |
| | | baseGridElement.setLongOrderId(baseLongTp.getEntryOrderId()); |
| | | baseGridElement.setHasLongOrder(true); |
| | | OkxTraderParam baseShortTp = config.getBaseShortTraderParam(); |
| | | 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); |
| | | // 精度补偿:步长被setScale截断,priceDiff/step可能产生1.99998→Down截断为1的问题 |
| | | BigDecimal epsilon = new BigDecimal("0.00000001"); |
| | | int count = priceDiff.add(epsilon).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); |
| | | // 精度补偿:步长被setScale截断,priceDiff/step可能产生1.99998→Down截断为1的问题 |
| | | BigDecimal epsilon = new BigDecimal("0.00000001"); |
| | | int count = priceDiff.add(epsilon).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(); |