| | |
| | | } else if (GateConfig.EXCESS_TP_DISPERSED.equals(config.getPlaceExcessTakeProfit())) { |
| | | placeDispersedTakeProfit(posSize, false, shortGridElement.getId()); |
| | | } |
| | | // [Gate] 基础仓位止盈挂单:与超额止盈并行,独立于止盈模式 |
| | | placeBaseTakeProfit(posSize, false, shortGridElement.getId()); |
| | | log.info("[Gate] 空单成交 gridId:{}, 当前持仓:{}张", filledQty, posSize); |
| | | |
| | | |
| | |
| | | } else if (GateConfig.EXCESS_TP_DISPERSED.equals(config.getPlaceExcessTakeProfit())) { |
| | | placeDispersedTakeProfit(posSize, true, longGridElement.getId()); |
| | | } |
| | | // [Gate] 基础仓位止盈挂单:与超额止盈并行,独立于止盈模式 |
| | | placeBaseTakeProfit(posSize, true, longGridElement.getId()); |
| | | log.info("[Gate] 多单成交 gridId:{}, 当前持仓:{}张", filledQty, posSize); |
| | | |
| | | } |
| | |
| | | private void handleLongTakeProfitTriggered(GridElement tpElem, String orderId) { |
| | | // 清理前判断:当前是否为该方向最后一个止盈单(最后一个不触发对向加仓) |
| | | boolean isLastTp = GridElement.getLongTakeProfitCount() <= 1; |
| | | // 清理止盈单状态(已成交) |
| | | longTakeProfitTraderIdParam(tpElem, null, false); |
| | | // 清理止盈单状态(移除已成交的这笔止盈单) |
| | | longTakeProfitTraderIdParam(tpElem, orderId, false); |
| | | log.info("[Gate] 多仓止盈触发 gridId:{}, orderId:{}, 剩余止盈:{}", tpElem.getId(), orderId, |
| | | GridElement.getLongTakeProfitCount()); |
| | | |
| | |
| | | private void handleShortTakeProfitTriggered(GridElement tpElem, String orderId) { |
| | | // 清理前判断:当前是否为该方向最后一个止盈单(最后一个不触发对向加仓) |
| | | boolean isLastTp = GridElement.getShortTakeProfitCount() <= 1; |
| | | // 清理止盈单状态(已成交) |
| | | shortTakeProfitTraderIdParam(tpElem, null, false); |
| | | // 清理止盈单状态(移除已成交的这笔止盈单) |
| | | shortTakeProfitTraderIdParam(tpElem, orderId, false); |
| | | log.info("[Gate] 空仓止盈触发 gridId:{}, orderId:{}, 剩余止盈:{}", tpElem.getId(), orderId, |
| | | GridElement.getShortTakeProfitCount()); |
| | | |
| | |
| | | GridElement baseElement,String profitId, boolean flag |
| | | ) { |
| | | TraderParam tp = baseElement.getLongTraderParam(); |
| | | if (flag && profitId != null) { |
| | | // 挂单成功:追加到止盈单列表(支持基础止盈 + 超额止盈并行挂单) |
| | | tp.setTakeProfitOrderId(profitId); |
| | | tp.setTakeProfitPlaced(flag); |
| | | baseElement.setLongTakeProfitOrderId(profitId); |
| | | tp.setTakeProfitPlaced(true); |
| | | baseElement.addLongTakeProfitOrderId(profitId); |
| | | } else { |
| | | // 清理:profitId 非空移除指定止盈单,为空清空该网格全部止盈单 |
| | | tp.setTakeProfitPlaced(false); |
| | | if (profitId != null) { |
| | | baseElement.removeLongTakeProfitOrderId(profitId); |
| | | if (!baseElement.hasLongTakeProfitOrders()) { |
| | | tp.setTakeProfitOrderId(null); |
| | | } |
| | | } else { |
| | | baseElement.clearLongTakeProfitOrderIds(); |
| | | tp.setTakeProfitOrderId(null); |
| | | } |
| | | } |
| | | GridElement.refreshIndices(); |
| | | } |
| | | private void shortTakeProfitTraderIdParam( |
| | | GridElement baseElement,String profitId, boolean flag |
| | | ) { |
| | | TraderParam tp = baseElement.getShortTraderParam(); |
| | | if (flag && profitId != null) { |
| | | // 挂单成功:追加到止盈单列表(支持基础止盈 + 超额止盈并行挂单) |
| | | tp.setTakeProfitOrderId(profitId); |
| | | tp.setTakeProfitPlaced(flag); |
| | | baseElement.setShortTakeProfitOrderId(profitId); |
| | | tp.setTakeProfitPlaced(true); |
| | | baseElement.addShortTakeProfitOrderId(profitId); |
| | | } else { |
| | | // 清理:profitId 非空移除指定止盈单,为空清空该网格全部止盈单 |
| | | tp.setTakeProfitPlaced(false); |
| | | if (profitId != null) { |
| | | baseElement.removeShortTakeProfitOrderId(profitId); |
| | | if (!baseElement.hasShortTakeProfitOrders()) { |
| | | tp.setTakeProfitOrderId(null); |
| | | } |
| | | } else { |
| | | baseElement.clearShortTakeProfitOrderIds(); |
| | | tp.setTakeProfitOrderId(null); |
| | | } |
| | | } |
| | | GridElement.refreshIndices(); |
| | | } |
| | | |
| | |
| | | */ |
| | | private void cancelAllLongTakeProfitsAndStopLosses() { |
| | | for (GridElement e : config.getGridElements()) { |
| | | String tpId = e.getLongTakeProfitOrderId(); |
| | | if (tpId != null) { |
| | | e.getLongTraderParam().setTakeProfitPlaced(false); |
| | | e.setLongTakeProfitOrderId(null); |
| | | for (String tpId : new ArrayList<>(e.getLongTakeProfitOrderIds())) { |
| | | executor.cancelConditionalOrder(tpId, oid -> {}); |
| | | } |
| | | e.clearLongTakeProfitOrderIds(); |
| | | if (e.getLongTraderParam() != null) { |
| | | e.getLongTraderParam().setTakeProfitPlaced(false); |
| | | e.getLongTraderParam().setTakeProfitOrderId(null); |
| | | } |
| | | for (String slId : new ArrayList<>(e.getLongStopLossOrderIds())) { |
| | | executor.cancelConditionalOrder(slId, oid -> {}); |
| | |
| | | */ |
| | | private void cancelAllShortTakeProfitsAndStopLosses() { |
| | | for (GridElement e : config.getGridElements()) { |
| | | String tpId = e.getShortTakeProfitOrderId(); |
| | | if (tpId != null) { |
| | | e.getShortTraderParam().setTakeProfitPlaced(false); |
| | | e.setShortTakeProfitOrderId(null); |
| | | for (String tpId : new ArrayList<>(e.getShortTakeProfitOrderIds())) { |
| | | executor.cancelConditionalOrder(tpId, oid -> {}); |
| | | } |
| | | e.clearShortTakeProfitOrderIds(); |
| | | if (e.getShortTraderParam() != null) { |
| | | e.getShortTraderParam().setTakeProfitPlaced(false); |
| | | e.getShortTraderParam().setTakeProfitOrderId(null); |
| | | } |
| | | for (String slId : new ArrayList<>(e.getShortStopLossOrderIds())) { |
| | | executor.cancelConditionalOrder(slId, oid -> {}); |
| | |
| | | * 首次挂单成交时,取消初始化阶段挂的所有止盈单(多+空),止损单保留不动。 |
| | | */ |
| | | private void cancelAllInitialTakeProfits() { |
| | | |
| | | |
| | | GridElement tpElemLong = GridElement.findById(3); |
| | | String longTpId = tpElemLong.getLongTakeProfitOrderId(); |
| | | if (longTpId != null) { |
| | | longTakeProfitTraderIdParam(tpElemLong, null, false); |
| | | executor.cancelConditionalOrder(longTpId, oid -> {}); |
| | | if (tpElemLong != null) { |
| | | for (String tpId : new ArrayList<>(tpElemLong.getLongTakeProfitOrderIds())) { |
| | | executor.cancelConditionalOrder(tpId, oid -> {}); |
| | | } |
| | | tpElemLong.clearLongTakeProfitOrderIds(); |
| | | if (tpElemLong.getLongTraderParam() != null) { |
| | | tpElemLong.getLongTraderParam().setTakeProfitPlaced(false); |
| | | tpElemLong.getLongTraderParam().setTakeProfitOrderId(null); |
| | | } |
| | | } |
| | | |
| | | GridElement tpElemShort = GridElement.findById(-3); |
| | | String shortTpId = tpElemShort.getShortTakeProfitOrderId(); |
| | | if (shortTpId != null) { |
| | | shortTakeProfitTraderIdParam(tpElemShort, null, false); |
| | | executor.cancelConditionalOrder(shortTpId, oid -> {}); |
| | | if (tpElemShort != null) { |
| | | for (String tpId : new ArrayList<>(tpElemShort.getShortTakeProfitOrderIds())) { |
| | | executor.cancelConditionalOrder(tpId, oid -> {}); |
| | | } |
| | | tpElemShort.clearShortTakeProfitOrderIds(); |
| | | if (tpElemShort.getShortTraderParam() != null) { |
| | | tpElemShort.getShortTraderParam().setTakeProfitPlaced(false); |
| | | tpElemShort.getShortTraderParam().setTakeProfitOrderId(null); |
| | | } |
| | | } |
| | | GridElement.refreshIndices(); |
| | | log.info("[Gate] 首次成交,取消所有初始化止盈单"); |
| | |
| | | } else if (GateConfig.EXCESS_TP_DISPERSED.equals(config.getPlaceExcessTakeProfit())) { |
| | | placeDispersedTakeProfit(posSize, true, 0); |
| | | } |
| | | placeBaseTakeProfit(posSize, true, 0); |
| | | log.info("[Gate] 生成多仓止盈止损完成, 仓位:{}张", posSize); |
| | | }); |
| | | } |
| | |
| | | } else if (GateConfig.EXCESS_TP_DISPERSED.equals(config.getPlaceExcessTakeProfit())) { |
| | | placeDispersedTakeProfit(posSize, false, 0); |
| | | } |
| | | placeBaseTakeProfit(posSize, false, 0); |
| | | log.info("[Gate] 生成空仓止盈止损完成, 仓位:{}张", posSize); |
| | | }); |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 基础止盈:对基础仓位(baseQuantity)也挂止盈单,与超额止盈并行。 |
| | | * |
| | | * <p>规则:从入场网格下一格(entryGridId ± 1)开始,每格 baseTakeProfitPerGrid 张, |
| | | * 最多挂 baseTakeProfitGridCount 格,挂到止损位的前一格截止(与分散止盈同起点/同终点)。 |
| | | * |
| | | * <p>边界:累计张数到 min(baseQuantity, 当前持仓) 即停(张数不足则最后一格挂剩余 = "挂几个算几个"); |
| | | * 网格不足时挂到停止位置结束。 |
| | | * |
| | | * @param posSize 当前总持仓张数 |
| | | * @param isLong true=多仓止盈,false=空仓止盈 |
| | | * @param entryGridId 入场网格 ID |
| | | */ |
| | | private void placeBaseTakeProfit(int posSize, boolean isLong, int entryGridId) { |
| | | int perGrid = config.getBaseTakeProfitPerGrid(); |
| | | int gridCount = config.getBaseTakeProfitGridCount(); |
| | | if (perGrid <= 0 || gridCount <= 0) { |
| | | return; |
| | | } |
| | | int baseQty = Integer.parseInt(config.getBaseQuantity()); |
| | | if (baseQty <= 0) { |
| | | return; |
| | | } |
| | | // 张数硬上限 = min(基础张数, 当前持仓),避免持仓不足 baseQuantity 时超挂 |
| | | int maxQty = Math.min(baseQty, posSize); |
| | | if (maxQty <= 0) { |
| | | return; |
| | | } |
| | | |
| | | GridElement endElem = isLong ? findFirstShortStopLossGrid() : findFirstLongStopLossGrid(); |
| | | if (endElem == null) { |
| | | log.warn("[Gate] {}基础止盈失败:未找到对手盘止损位", isLong ? "多仓" : "空仓"); |
| | | return; |
| | | } |
| | | int dir = isLong ? 1 : -1; |
| | | int startId = entryGridId + dir; |
| | | // 分配范围到止损位的前一格截止(与分散止盈一致) |
| | | int endId = endElem.getId() - dir; |
| | | |
| | | int remaining = maxQty; |
| | | int placed = 0; |
| | | int id = startId; |
| | | while (placed < gridCount && remaining > 0) { |
| | | if (isLong ? id > endId : id < endId) { |
| | | break; |
| | | } |
| | | GridElement elem = GridElement.findById(id); |
| | | if (elem == null) { |
| | | break; |
| | | } |
| | | int qty = Math.min(perGrid, remaining); |
| | | final GridElement fe = elem; |
| | | final int gid = id; |
| | | final BigDecimal triggerPrice = elem.getGridPrice(); |
| | | final String size = isLong ? negate(String.valueOf(qty)) : String.valueOf(qty); |
| | | final String orderType = isLong ? ORDER_TYPE_CLOSE_LONG : ORDER_TYPE_CLOSE_SHORT; |
| | | final FuturesPriceTrigger.RuleEnum rule = isLong ? FuturesPriceTrigger.RuleEnum.NUMBER_1 |
| | | : FuturesPriceTrigger.RuleEnum.NUMBER_2; |
| | | executor.placeTakeProfit(triggerPrice, rule, orderType, size, |
| | | profitId -> { |
| | | if (isLong) { |
| | | longTakeProfitTraderIdParam(fe, profitId, true); |
| | | } else { |
| | | shortTakeProfitTraderIdParam(fe, profitId, true); |
| | | } |
| | | log.info("[Gate] 基础止盈 {} gridId:{}, 触发价:{}, 数量:{}, tpId:{}", |
| | | isLong ? "多仓" : "空仓", gid, triggerPrice, size, profitId); |
| | | } |
| | | ); |
| | | remaining -= qty; |
| | | placed++; |
| | | id += dir; |
| | | } |
| | | log.info("[Gate] 基础止盈 {} 挂单完成, 入场gridId:{}, 挂{}格, 剩余{}张", |
| | | isLong ? "多仓" : "空仓", entryGridId, placed, remaining); |
| | | } |
| | | |
| | | /** |
| | | * 在指定网格挂一笔对手止盈单(非满仓超额止盈,挂在止损触发位的下一格)。 |
| | | */ |
| | | private void placeTakeProfitAtGrid(GridElement tpElem, boolean isLong, int qty, int times) { |