| | |
| | | .entryOrderId(orderId) |
| | | .build(); |
| | | config.setBaseLongTraderParam(baseLongTp); |
| | | baseLongOpened = true; |
| | | }, null); |
| | | executor.openShort(size, (orderId) -> { |
| | | TraderParam baseShortTp = TraderParam.builder() |
| | | .entryOrderId(orderId) |
| | | .build(); |
| | | config.setBaseShortTraderParam(baseShortTp); |
| | | baseShortOpened = true; |
| | | }, null); |
| | | return; |
| | | } |
| | | |
| | | // OPENING 状态下若 WS 仓位已确认但 REST 回调尚未完成,等标记价格推送时重试队列生成 |
| | | if (state == StrategyState.OPENING && |
| | | baseLongOpened && baseShortOpened) { |
| | | tryGenerateQueues(); |
| | | } |
| | | |
| | | |
| | | if (state == StrategyState.ACTIVE && |
| | | !longActive && |
| | |
| | | boolean isLong = (direction == TraderParam.Direction.LONG); |
| | | |
| | | if (state == StrategyState.OPENING) { |
| | | if (isLong && hasPosition && !baseLongOpened) { |
| | | // 基底成交通知仅记录价格/数量,flag 在 REST 回调中设置 |
| | | if (isLong && hasPosition) { |
| | | longActive = true; |
| | | longPositionSize = size; |
| | | longEntryPrice = entryPrice; |
| | | longBaseEntryPrice = entryPrice; |
| | | baseLongOpened = true; |
| | | log.info("[OKX] 基底多成交价: {}", longBaseEntryPrice); |
| | | tryGenerateQueues(); |
| | | } else if (!isLong && hasPosition && !baseShortOpened) { |
| | | } else if (!isLong && hasPosition) { |
| | | shortActive = true; |
| | | shortPositionSize = size.abs(); |
| | | shortEntryPrice = entryPrice; |
| | | shortBaseEntryPrice = entryPrice; |
| | | baseShortOpened = true; |
| | | log.info("[OKX] 基底空成交价: {}", shortBaseEntryPrice); |
| | | tryGenerateQueues(); |
| | | } |
| | |
| | | // ---- 自动订单(条件单)状态变更回调 ---- |
| | | |
| | | /** |
| | | * 自动订单状态变更回调。由 OrderAlgoOkxChannelHandler 调用。 |
| | | * 自动订单状态变更回调。由 OrdersOkxChannelHandler 调用。 |
| | | */ |
| | | public void onAutoOrder(String orderId, String status, String reason, String orderType, String tradeId) { |
| | | if (state == StrategyState.STOPPED) { |
| | |
| | | int posSize = queryPositionSize(OkxPosMode.SHORT); |
| | | extendShortStopLoss(posSize, shortGridElement.getId()); |
| | | accumulatedShortLossCount = 0; |
| | | log.info("[OKX] 空单成交 gridId:{}, 当前持仓:{}张", filledQty, posSize); |
| | | log.info("[OKX] 空单成交 gridId:{}, 成交{}张, 当前持仓:{}张", |
| | | shortGridElement.getId(), filledQty, posSize); |
| | | |
| | | BigDecimal shortBaseQty = new BigDecimal(config.getBaseQuantity()); |
| | | BigDecimal shortGridQty = new BigDecimal(config.getQuantity()); |
| | |
| | | BigDecimal shortExcess = BigDecimal.valueOf(posSize).subtract(shortBaseQty); |
| | | int shortExcessCount = shortExcess.divide(shortGridQty, 0, RoundingMode.DOWN).intValue(); |
| | | for (int i = 0; i < shortExcessCount; i++) { |
| | | int tpGridId = shortGridElement.getId() - 2 - i; |
| | | if (i > 0) { tpGridId = tpGridId - 1; } |
| | | int tpGridId = shortGridElement.getId() - 2 * (i + 1); |
| | | GridElement tpElem = GridElement.findById(tpGridId); |
| | | if (tpElem == null || tpElem.getShortTakeProfitOrderId() != null) { |
| | | continue; |
| | |
| | | int posSize = queryPositionSize(OkxPosMode.LONG); |
| | | extendLongStopLoss(posSize, longGridElement.getId()); |
| | | accumulatedLongLossCount = 0; |
| | | log.info("[OKX] 多单成交 gridId:{}, 当前持仓:{}张", filledQty, posSize); |
| | | log.info("[OKX] 多单成交 gridId:{}, 成交{}张, 当前持仓:{}张", |
| | | longGridElement.getId(), filledQty, posSize); |
| | | |
| | | BigDecimal longBaseQty = new BigDecimal(config.getBaseQuantity()); |
| | | BigDecimal longGridQty = new BigDecimal(config.getQuantity()); |
| | |
| | | BigDecimal longExcess = BigDecimal.valueOf(posSize).subtract(longBaseQty); |
| | | int longExcessCount = longExcess.divide(longGridQty, 0, RoundingMode.DOWN).intValue(); |
| | | for (int i = 0; i < longExcessCount; i++) { |
| | | int tpGridId = longGridElement.getId() + 2 + i; |
| | | if (i > 0) { tpGridId = tpGridId + 1; } |
| | | int tpGridId = longGridElement.getId() + 2 * (i + 1); |
| | | GridElement tpElem = GridElement.findById(tpGridId); |
| | | if (tpElem == null || tpElem.getLongTakeProfitOrderId() != null) { |
| | | continue; |
| | |
| | | // ---- 网格队列处理 ---- |
| | | |
| | | private void tryGenerateQueues() { |
| | | if (baseLongOpened && baseShortOpened) { |
| | | // OPENING 状态下若 WS 仓位已确认但 REST 回调尚未完成,等标记价格推送时重试队列生成 |
| | | if (state == StrategyState.OPENING && baseLongOpened && baseShortOpened) { |
| | | // 确保 openLong/openShort 的 REST 回调已完成(WS 推送可能比回调更快到达) |
| | | if (config.getBaseLongTraderParam() == null || config.getBaseShortTraderParam() == null) { |
| | | log.warn("[OKX] 基底REST回调尚未完成, 延后队列生成"); |
| | |
| | | baseGridElement.setShortOrderId(baseShortTraderParam.getEntryOrderId()); |
| | | baseGridElement.setHasShortOrder(true); |
| | | |
| | | // 挂基座止盈 |
| | | { |
| | | BigDecimal tpPrice = baseGridElement.getGridPrice().add(config.getStep()); |
| | | executor.placeTakeProfit(tpPrice, "close_long", config.getBaseQuantity(), |
| | | profitId -> { |
| | | longTakeProfitTraderIdParam(baseGridElement, profitId, true); |
| | | log.info("[OKX] 基座多仓止盈已挂, gridId:0, 触发价:{}, tpId:{}", tpPrice, profitId); |
| | | }); |
| | | } |
| | | { |
| | | BigDecimal tpPrice = baseGridElement.getGridPrice().subtract(config.getStep()); |
| | | executor.placeTakeProfit(tpPrice, "close_short", config.getBaseQuantity(), |
| | | profitId -> { |
| | | shortTakeProfitTraderIdParam(baseGridElement, profitId, true); |
| | | log.info("[OKX] 基座空仓止盈已挂, gridId:0, 触发价:{}, tpId:{}", tpPrice, profitId); |
| | | }); |
| | | } |
| | | |
| | | // 挂初始止损 |
| | | int stopCount = Integer.parseInt(config.getBaseQuantity()) / Integer.parseInt(config.getQuantity()) + 1; |
| | | for (int id = 2; id <= stopCount; id++) { |
| | |
| | | } |
| | | BigDecimal triggerPrice = elem.getGridPrice(); |
| | | int finalId = id; |
| | | executor.placeTakeProfit(triggerPrice, "close_short", config.getQuantity(), |
| | | executor.placeStopLoss(triggerPrice, "close_short", config.getQuantity(), |
| | | profitId -> { |
| | | elem.setShortStopLossOrderId(profitId); |
| | | GridElement.refreshIndices(); |
| | |
| | | } |
| | | BigDecimal triggerPrice = elem.getGridPrice(); |
| | | int finalId = id; |
| | | executor.placeTakeProfit(triggerPrice, "close_long", config.getQuantity(), |
| | | executor.placeStopLoss(triggerPrice, "close_long", config.getQuantity(), |
| | | profitId -> { |
| | | elem.setLongStopLossOrderId(profitId); |
| | | GridElement.refreshIndices(); |
| | |
| | | }); |
| | | } |
| | | log.info("[OKX] 止损单已全部挂完, 空仓止损: 2~{}, 多仓止损: -2~-{}", stopCount, stopCount); |
| | | |
| | | // 挂初始条件开仓单 |
| | | GridElement longFirst = GridElement.findById(1); |
| | | if (longFirst != null && !longFirst.isHasLongOrder()) { |
| | | BigDecimal triggerPrice = longFirst.getGridPrice(); |
| | | log.info("[OKX] 挂初始多仓条件单, gridId:1, trigger:{}", triggerPrice); |
| | | placeEntryOrderWithPreFlag(longFirst, true, triggerPrice, config.getBaseQuantity()); |
| | | } |
| | | GridElement shortFirst = GridElement.findById(-1); |
| | | if (shortFirst != null && !shortFirst.isHasShortOrder()) { |
| | | BigDecimal triggerPrice = shortFirst.getGridPrice(); |
| | | log.info("[OKX] 挂初始空仓条件单, gridId:-1, trigger:{}", triggerPrice); |
| | | placeEntryOrderWithPreFlag(shortFirst, false, triggerPrice, negate(config.getBaseQuantity())); |
| | | } |
| | | |
| | | state = StrategyState.ACTIVE; |
| | | } |
| | |
| | | } |
| | | BigDecimal triggerPrice = elem.getGridPrice(); |
| | | int finalSlId = newSlId; |
| | | executor.placeTakeProfit(triggerPrice, "close_long", config.getQuantity(), |
| | | executor.placeStopLoss(triggerPrice, "close_long", config.getQuantity(), |
| | | profitId -> { |
| | | elem.setLongStopLossOrderId(profitId); |
| | | GridElement.refreshIndices(); |
| | |
| | | } |
| | | BigDecimal triggerPrice = elem.getGridPrice(); |
| | | int finalSlId = newSlId; |
| | | executor.placeTakeProfit(triggerPrice, "close_short", config.getQuantity(), |
| | | executor.placeStopLoss(triggerPrice, "close_short", config.getQuantity(), |
| | | profitId -> { |
| | | elem.setShortStopLossOrderId(profitId); |
| | | GridElement.refreshIndices(); |