| | |
| | | private final Map<String, Integer> tpOppositeShortOrderGridMap = new ConcurrentHashMap<>(); |
| | | /** 止盈触发对向加仓:空仓止盈→多仓开仓单ID → 网格ID(成交后在该网格-1挂多仓止损) */ |
| | | private final Map<String, Integer> tpOppositeLongOrderGridMap = new ConcurrentHashMap<>(); |
| | | /** 仓位止盈补仓:多仓止盈→多仓开仓单ID → 张数(成交后按原多仓止损位重挂全持仓止损) */ |
| | | private final Map<String, Integer> tpReplenishLongOrderMap = new ConcurrentHashMap<>(); |
| | | /** 仓位止盈补仓:空仓止盈→空仓开仓单ID → 张数(成交后按原空仓止损位重挂全持仓止损) */ |
| | | private final Map<String, Integer> tpReplenishShortOrderMap = new ConcurrentHashMap<>(); |
| | | |
| | | private volatile BigDecimal lastKlinePrice; |
| | | private volatile BigDecimal markPrice = BigDecimal.ZERO; |
| | |
| | | lastLongStopLossGridId = Integer.MAX_VALUE; |
| | | lastShortStopLossGridId = Integer.MAX_VALUE; |
| | | dualStopLossModeActive.set(false); |
| | | tpReplenishLongOrderMap.clear(); |
| | | tpReplenishShortOrderMap.clear(); |
| | | shortPriceQueue.clear(); |
| | | longPriceQueue.clear(); |
| | | totalShortPriceQueue.clear(); |
| | |
| | | handleTpOppositeShortAddFilled(shortGridElement, tpOppositeGridId); |
| | | return; |
| | | } |
| | | // 仓位止盈补仓的空仓开仓单成交 → 取消原空仓止损并在原位置按最新持仓重挂 |
| | | Integer replenishQty = tpReplenishShortOrderMap.remove(orderId); |
| | | if (replenishQty != null) { |
| | | handleReplenishShortAddFilled(shortGridElement, orderId, replenishQty); |
| | | return; |
| | | } |
| | | int filledQty = Integer.parseInt(shortGridElement.getShortTraderParam().getQuantity()); |
| | | shortEntryTraderIdParam(shortGridElement, orderId, false); |
| | | |
| | |
| | | Integer tpOppositeGridId = tpOppositeLongOrderGridMap.remove(orderId); |
| | | if (tpOppositeGridId != null) { |
| | | handleTpOppositeLongAddFilled(longGridElement, tpOppositeGridId); |
| | | return; |
| | | } |
| | | // 仓位止盈补仓的多仓开仓单成交 → 取消原多仓止损并在原位置按最新持仓重挂 |
| | | Integer replenishQty = tpReplenishLongOrderMap.remove(orderId); |
| | | if (replenishQty != null) { |
| | | handleReplenishLongAddFilled(longGridElement, orderId, replenishQty); |
| | | return; |
| | | } |
| | | int filledQty = Integer.parseInt(longGridElement.getLongTraderParam().getQuantity()); |
| | |
| | | |
| | | /** |
| | | * 仓位止盈补仓:任意方向止盈触发时,在对侧止损位挂与止盈方向一致、张数 = 本次止盈单张数的条件开仓单。 |
| | | * <p>开仓单走标准入场流程(placeEntryOrderWithPreFlag 预置标记),成交后进入 ENTRY_FILLED 常规分支, |
| | | * extend 会按最新全持仓重挂止损,保证补仓仓位有完整止损覆盖。</p> |
| | | * <p>开仓单走<b>专用分支</b>:成交后取消补仓方向原有的止损单,并在与原来相同的位置 |
| | | * 按最新总持仓重挂止损(不动止盈),详见 {@link #handleReplenishLongAddFilled} / |
| | | * {@link #handleReplenishShortAddFilled}。</p> |
| | | * |
| | | * @param isLong true=多仓止盈(挂多仓开仓单),false=空仓止盈(挂空仓开仓单) |
| | | * @param tpSize 本次触发止盈单的张数(null 或 <=0 则跳过) |
| | |
| | | log.warn("[Gate] 仓位止盈补仓: 未找到对侧止损位, isLong:{}", isLong); |
| | | return; |
| | | } |
| | | BigDecimal triggerPrice = slElem.getGridPrice(); |
| | | // 方向与止盈方向一致:多仓止盈 → 多开仓(价格≥触发价 NUMBER_1);空仓止盈 → 空开仓(价格≤触发价 NUMBER_2) |
| | | FuturesPriceTrigger.RuleEnum rule = isLong ? FuturesPriceTrigger.RuleEnum.NUMBER_1 |
| | | : FuturesPriceTrigger.RuleEnum.NUMBER_2; |
| | | String size = isLong ? String.valueOf(tpSize) : negate(String.valueOf(tpSize)); |
| | | // 记录本网格该方向的开仓张数(供 ENTRY_FILLED 分支读取成交张数) |
| | | if (isLong) { |
| | | slElem.getLongTraderParam().setQuantity(String.valueOf(tpSize)); |
| | | placeReplenishLongEntry(slElem, tpSize); |
| | | } else { |
| | | slElem.getShortTraderParam().setQuantity(String.valueOf(tpSize)); |
| | | placeReplenishShortEntry(slElem, tpSize); |
| | | } |
| | | placeEntryOrderWithPreFlag(slElem, isLong, triggerPrice, rule, size); |
| | | log.info("[Gate] 仓位止盈补仓: {}止盈触发, 对侧止损位 gridId:{}, 挂{}开仓{}张", |
| | | isLong ? "多仓" : "空仓", slElem.getId(), isLong ? "多" : "空", tpSize); |
| | | } |
| | | |
| | | /** |
| | | * 仓位止盈补仓:在对侧止损位挂多仓条件开仓单(价格≥触发价),成交走专用分支 {@link #handleReplenishLongAddFilled}。 |
| | | */ |
| | | private void placeReplenishLongEntry(GridElement target, int qty) { |
| | | target.setHasLongOrder(true); |
| | | executor.placeConditionalEntryOrder( |
| | | target.getGridPrice(), |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_1, |
| | | String.valueOf(qty), |
| | | orderId -> { |
| | | tpReplenishLongOrderMap.put(orderId, qty); |
| | | target.getLongTraderParam().setQuantity(String.valueOf(qty)); |
| | | longEntryTraderIdParam(target, orderId, true); |
| | | log.info("[Gate] 仓位止盈补仓多开仓单已挂, 目标gridId:{}, 数量:{}张, orderId:{}", |
| | | target.getId(), qty, orderId); |
| | | }, |
| | | () -> { |
| | | if (!target.hasLongOrderIds()) { |
| | | target.setHasLongOrder(false); |
| | | } |
| | | GridElement.refreshIndices(); |
| | | log.warn("[Gate] 仓位止盈补仓多开仓单挂单失败, 目标gridId:{}", target.getId()); |
| | | } |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * 仓位止盈补仓:在对侧止损位挂空仓条件开仓单(价格≤触发价),成交走专用分支 {@link #handleReplenishShortAddFilled}。 |
| | | */ |
| | | private void placeReplenishShortEntry(GridElement target, int qty) { |
| | | target.setHasShortOrder(true); |
| | | executor.placeConditionalEntryOrder( |
| | | target.getGridPrice(), |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_2, |
| | | negate(String.valueOf(qty)), |
| | | orderId -> { |
| | | tpReplenishShortOrderMap.put(orderId, qty); |
| | | target.getShortTraderParam().setQuantity(String.valueOf(qty)); |
| | | shortEntryTraderIdParam(target, orderId, true); |
| | | log.info("[Gate] 仓位止盈补仓空开仓单已挂, 目标gridId:{}, 数量:{}张, orderId:{}", |
| | | target.getId(), qty, orderId); |
| | | }, |
| | | () -> { |
| | | if (!target.hasShortOrderIds()) { |
| | | target.setHasShortOrder(false); |
| | | } |
| | | GridElement.refreshIndices(); |
| | | log.warn("[Gate] 仓位止盈补仓空开仓单挂单失败, 目标gridId:{}", target.getId()); |
| | | } |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * 仓位止盈补仓:多仓开仓单成交(专用分支)。 |
| | | * 先取消多仓原有全部止损单(保留止盈),再在与原来相同的位置按最新总持仓重挂止损。 |
| | | * |
| | | * @param elem 补仓开仓单所在的网格元素 |
| | | * @param orderId 已成交的开仓单 ID |
| | | * @param filledQty 本次成交张数(挂单时记录) |
| | | */ |
| | | private void handleReplenishLongAddFilled(GridElement elem, String orderId, int filledQty) { |
| | | longEntryTraderIdParam(elem, orderId, false); |
| | | |
| | | // 最新总持仓:REST/本地/本次成交取最大,规避时序竞态 |
| | | int posSize = Math.max(Math.max(queryPositionSize(Position.ModeEnum.DUAL_LONG), longPositionSize.intValue()), filledQty); |
| | | // 原多仓止损位(最靠近入场的那个) |
| | | GridElement anchorSl = findNearestLongStopLossGrid(); |
| | | if (anchorSl == null) { |
| | | log.warn("[Gate] 仓位止盈补仓: 未找到原多仓止损位, 跳过重挂, 当前持仓:{}张", posSize); |
| | | return; |
| | | } |
| | | // 反推原入场网格:止损挂在 entry-2-i,最近止损位 = entry-2 → entry = 最近止损位 + 2 |
| | | int anchorEntryGridId = anchorSl.getId() + 2; |
| | | // 取消原多仓止损(保留止盈),并在原位置重挂全持仓止损 |
| | | cancelAllLongStopLossesOnly(); |
| | | lastLongStopLossGridId = Integer.MAX_VALUE; |
| | | extendLongStopLoss(posSize, anchorEntryGridId); |
| | | |
| | | // 埋点: ENTRY_FILLED(补仓成交) |
| | | emitStats("ENTRY_FILLED", mapOf( |
| | | "direction", "long", |
| | | "gridId", elem.getId(), |
| | | "filledQty", filledQty, |
| | | "source", "tp_replenish" |
| | | )); |
| | | log.info("[Gate] 仓位止盈补仓多开仓成交, gridId:{}, 当前持仓:{}张, 止损按原位置gridId:{}重挂", |
| | | elem.getId(), posSize, anchorSl.getId()); |
| | | } |
| | | |
| | | /** |
| | | * 仓位止盈补仓:空仓开仓单成交(专用分支)。 |
| | | * 先取消空仓原有全部止损单(保留止盈),再在与原来相同的位置按最新总持仓重挂止损。 |
| | | * |
| | | * @param elem 补仓开仓单所在的网格元素 |
| | | * @param orderId 已成交的开仓单 ID |
| | | * @param filledQty 本次成交张数(挂单时记录) |
| | | */ |
| | | private void handleReplenishShortAddFilled(GridElement elem, String orderId, int filledQty) { |
| | | shortEntryTraderIdParam(elem, orderId, false); |
| | | |
| | | // 最新总持仓:REST/本地/本次成交取最大,规避时序竞态 |
| | | int posSize = Math.max(Math.max(queryPositionSize(Position.ModeEnum.DUAL_SHORT), shortPositionSize.intValue()), filledQty); |
| | | // 原空仓止损位(最靠近入场的那个) |
| | | GridElement anchorSl = findNearestShortStopLossGrid(); |
| | | if (anchorSl == null) { |
| | | log.warn("[Gate] 仓位止盈补仓: 未找到原空仓止损位, 跳过重挂, 当前持仓:{}张", posSize); |
| | | return; |
| | | } |
| | | // 反推原入场网格:止损挂在 entry+2+i,最近止损位 = entry+2 → entry = 最近止损位 - 2 |
| | | int anchorEntryGridId = anchorSl.getId() - 2; |
| | | // 取消原空仓止损(保留止盈),并在原位置重挂全持仓止损 |
| | | cancelAllShortStopLossesOnly(); |
| | | lastShortStopLossGridId = Integer.MAX_VALUE; |
| | | extendShortStopLoss(posSize, anchorEntryGridId); |
| | | |
| | | // 埋点: ENTRY_FILLED(补仓成交) |
| | | emitStats("ENTRY_FILLED", mapOf( |
| | | "direction", "short", |
| | | "gridId", elem.getId(), |
| | | "filledQty", filledQty, |
| | | "source", "tp_replenish" |
| | | )); |
| | | log.info("[Gate] 仓位止盈补仓空开仓成交, gridId:{}, 当前持仓:{}张, 止损按原位置gridId:{}重挂", |
| | | elem.getId(), posSize, anchorSl.getId()); |
| | | } |
| | | |
| | | /** |
| | | * 找到最靠近入场方向的多仓止损网格(ID 最大者)。 |
| | | */ |
| | | private GridElement findNearestLongStopLossGrid() { |
| | | GridElement nearest = null; |
| | | for (GridElement e : config.getGridElements()) { |
| | | if (e.hasLongStopLossOrders()) { |
| | | if (nearest == null || e.getId() > nearest.getId()) { |
| | | nearest = e; |
| | | } |
| | | } |
| | | } |
| | | return nearest; |
| | | } |
| | | |
| | | /** |
| | | * 找到最靠近入场方向的空仓止损网格(ID 最小者)。 |
| | | */ |
| | | private GridElement findNearestShortStopLossGrid() { |
| | | GridElement nearest = null; |
| | | for (GridElement e : config.getGridElements()) { |
| | | if (e.hasShortStopLossOrders()) { |
| | | if (nearest == null || e.getId() < nearest.getId()) { |
| | | nearest = e; |
| | | } |
| | | } |
| | | } |
| | | return nearest; |
| | | } |
| | | |
| | | /** |
| | | * 取消所有已挂未成交的对向空仓开仓单(止盈依次上移时,取消旧位置的开仓单)。 |
| | | */ |
| | | private void cancelTpOppositeShortEntries() { |