| | |
| | | * 根据加仓配置计算止损追单时的实际下单量。 |
| | | * <p>公式:</p> |
| | | * <pre> |
| | | * addMultiplier = floor(effectiveStopLossCount / addPositionInterval) |
| | | * divisor = addPositionInterval + 1 |
| | | * addMultiplier = floor(effectiveStopLossCount / divisor) |
| | | * addQty = addMultiplier × addPositionQuantity |
| | | * finalQty = min(baseQuantity + addQty, maxPositionPerSide > 0 ? maxPositionPerSide : ∞) |
| | | * </pre> |
| | |
| | | int maxPerSide = config.getMaxPositionPerSide(); |
| | | |
| | | int effectiveCount = getEffectiveStopLossCount(isLong); |
| | | int addMultiplier = interval > 0 ? effectiveCount / interval : 0; |
| | | int startThreshold = config.getAddPositionStartThreshold(); |
| | | if (startThreshold > 0) { |
| | | effectiveCount = Math.max(0, effectiveCount - startThreshold); |
| | | } |
| | | // divisor = interval + 1:interval=0→每次加仓, interval=1→每2次加仓(2,4,6...), interval=3→每4次加仓(4,8,12...) |
| | | int divisor = interval + 1; |
| | | int addMultiplier = interval >= 0 ? effectiveCount / divisor : 0; |
| | | int addQty = addMultiplier * addQtyPerUnit; |
| | | int totalQty = baseQty + addQty; |
| | | |
| | |
| | | }); |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | // ========== 止损触发操作表 ========== |
| | | /** |
| | | * value = int[2]: {对手满仓止盈%, 基础仓位阈值%} |
| | | * <ul> |
| | | * <li>[0] 对手满仓:挂对手仓位此%做止盈;0=不挂</li> |
| | | * <li>[1] 基础仓位阈值(占maxPos的%):对手/己方仓位超出此阈值部分做止盈;0=不挂</li> |
| | | * </ul> |
| | | */ |
| | | private static final Map<Integer, int[]> STOP_LOSS_RULES = new LinkedHashMap<>(); |
| | | private static final int DEFAULT_STOP_LOSS_PERCENT = 5; |
| | | |
| | | static { |
| | | // times {满仓%, 阈值%} |
| | | put(1, new int[]{0, 0}); |
| | | put(2, new int[]{0, 0}); |
| | | put(3, new int[]{30, 0}); |
| | | put(4, new int[]{0, 0}); |
| | | put(5, new int[]{0, 0}); |
| | | put(6, new int[]{40, 40}); |
| | | put(7, new int[]{0, 0}); |
| | | put(8, new int[]{0, 0}); |
| | | put(9, new int[]{50, 50}); |
| | | put(10, new int[]{0, 0}); |
| | | put(11, new int[]{0, 0}); |
| | | put(12, new int[]{55, 60}); |
| | | put(13, new int[]{0, 0}); |
| | | put(14, new int[]{0, 0}); |
| | | put(15, new int[]{60, 60}); |
| | | put(16, new int[]{0, 0}); |
| | | put(17, new int[]{0, 0}); |
| | | put(18, new int[]{65, 65}); |
| | | put(19, new int[]{0, 0}); |
| | | put(20, new int[]{0, 0}); |
| | | put(21, new int[]{70, 70}); |
| | | put(22, new int[]{0, 0}); |
| | | put(23, new int[]{0, 0}); |
| | | put(24, new int[]{75, 75}); |
| | | put(25, new int[]{0, 0}); |
| | | put(26, new int[]{0, 0}); |
| | | put(27, new int[]{80, 80}); |
| | | put(28, new int[]{0, 0}); |
| | | put(29, new int[]{0, 0}); |
| | | put(30, new int[]{85, 85}); |
| | | put(31, new int[]{0, 0}); |
| | | put(32, new int[]{0, 0}); |
| | | } |
| | | |
| | | private static void put(int times, int[] rule) { STOP_LOSS_RULES.put(times, rule); } |
| | | |
| | | private static int[] ruleOf(int times) { |
| | | int[] r = STOP_LOSS_RULES.get(times); |
| | | return r != null ? r : STOP_LOSS_RULES.get(32); |
| | | } |
| | | |
| | | /** |
| | | * 多仓止损触发后,查表处理对手盘(空仓)盈止 + 己方盈止 + 激活。 |
| | | */ |
| | | private void checkLongStopShortProfit(int times, int gridId) { |
| | | int[] r = ruleOf(times); |
| | | int maxPos = Integer.parseInt(config.getBaseQuantity()); |
| | | if (maxPos <= 0) { |
| | | return; |
| | | } |
| | | |
| | | int fullTpPct = r[0]; // 对手满仓 → 止盈% |
| | | int fullTpPctNum = maxPos * (100- fullTpPct)/ 100; |
| | | int thresholdPct = r[1]; // 基础仓位阈值% |
| | | int thresholdPosNum = maxPos * (100- thresholdPct)/ 100; |
| | | |
| | | // ---------- 对手盘(空仓)盈止 ---------- |
| | | int oppPos = Math.max(queryPositionSize(Position.ModeEnum.DUAL_SHORT), shortPositionSize.intValue()); |
| | | boolean isFullTp = oppPos > fullTpPctNum; |
| | | if (isFullTp) { |
| | | // 对手仓位超出阈值:超额整个挂一笔止盈,位置在触发格 - 1 |
| | | int excess = oppPos - fullTpPctNum; |
| | | GridElement tpElem = GridElement.findById(gridId); |
| | | if (tpElem != null) { |
| | | placeTakeProfitAtGrid(tpElem, false, excess, times); |
| | | } |
| | | } |
| | | |
| | | // ---------- 己方(多仓)盈止(≥6次) ---------- |
| | | // if (times >= 6 && thresholdPct > 0) { |
| | | // int selfPos = Math.max(queryPositionSize(Position.ModeEnum.DUAL_LONG), longPositionSize.intValue()); |
| | | // int excess = selfPos - thresholdPosNum; |
| | | // if (excess > 0) { |
| | | // int perOrderQty = maxPos * DEFAULT_STOP_LOSS_PERCENT / 100; |
| | | // if (perOrderQty <= 0) { |
| | | // perOrderQty = 1; |
| | | // } |
| | | // int totalOrders = excess / perOrderQty; |
| | | // if (totalOrders <= 0) { |
| | | // totalOrders = 1; |
| | | // } |
| | | // int entryGridId = 0; // 多仓建仓格 |
| | | // for (int i = 0; i < totalOrders; i++) { |
| | | // int tpGridId = entryGridId + 2 * (i + 1); // 朝盈利方向(向上)间隔2格 |
| | | // GridElement tpElem = GridElement.findById(tpGridId); |
| | | // if (tpElem == null) { |
| | | // break; |
| | | // } |
| | | // placeTakeProfitAtGrid(tpElem, true, perOrderQty, times); |
| | | // } |
| | | // log.info("[Gate] 止损{}→己方多仓止盈, excess:{}, 挂{}单, 每单{}张", |
| | | // times, excess, totalOrders, perOrderQty); |
| | | // } |
| | | // } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 空仓止损触发后,查表处理对手盘(多仓)盈止 + 己方盈止。 |
| | | */ |
| | | private void checkShortStopLongProfit(int times, int gridId) { |
| | | int[] r = ruleOf(times); |
| | | int maxPos = Integer.parseInt(config.getBaseQuantity()); |
| | | if (maxPos <= 0) { |
| | | return; |
| | | } |
| | | |
| | | int fullTpPct = r[0]; // 对手满仓 → 止盈% |
| | | int fullTpPctNum = maxPos * (100 - fullTpPct) / 100; |
| | | int thresholdPct = r[1]; // 基础仓位阈值% |
| | | int thresholdPosNum = maxPos * (100 - thresholdPct) / 100; |
| | | |
| | | // ---------- 对手盘(多仓)盈止 ---------- |
| | | int oppPos = Math.max(queryPositionSize(Position.ModeEnum.DUAL_LONG), longPositionSize.intValue()); |
| | | boolean isFullTp = oppPos > fullTpPctNum; |
| | | if (isFullTp) { |
| | | // 对手仓位超出阈值:超额整个挂一笔止盈,位置在触发格 |
| | | int excess = oppPos - fullTpPctNum; |
| | | GridElement tpElem = GridElement.findById(gridId); |
| | | if (tpElem != null) { |
| | | placeTakeProfitAtGrid(tpElem, true, excess, times); |
| | | } |
| | | } |
| | | |
| | | // ---------- 己方(空仓)盈止(≥6次) ---------- |
| | | // if (times >= 6 && thresholdPct > 0) { |
| | | // int selfPos = Math.max(queryPositionSize(Position.ModeEnum.DUAL_SHORT), shortPositionSize.intValue()); |
| | | // int excess = selfPos - thresholdPosNum; |
| | | // if (excess > 0) { |
| | | // int perOrderQty = maxPos * DEFAULT_STOP_LOSS_PERCENT / 100; |
| | | // if (perOrderQty <= 0) { |
| | | // perOrderQty = 1; |
| | | // } |
| | | // int totalOrders = excess / perOrderQty; |
| | | // if (totalOrders <= 0) { |
| | | // totalOrders = 1; |
| | | // } |
| | | // int entryGridId = 0; // 空仓建仓格 |
| | | // for (int i = 0; i < totalOrders; i++) { |
| | | // int tpGridId = entryGridId - 2 * (i + 1); // 朝盈利方向(向下)间隔2格 |
| | | // GridElement tpElem = GridElement.findById(tpGridId); |
| | | // if (tpElem == null) { |
| | | // break; |
| | | // } |
| | | // placeTakeProfitAtGrid(tpElem, false, perOrderQty, times); |
| | | // } |
| | | // log.info("[Gate] 止损{}→己方空仓止盈, excess:{}, 挂{}单, 每单{}张", |
| | | // times, excess, totalOrders, perOrderQty); |
| | | // } |
| | | // } |
| | | |
| | | } |
| | | |