| src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java | ●●●●● patch | view | raw | blame | history |
src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
@@ -1,6 +1,8 @@ package com.xcong.excoin.modules.gateApi; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; /** * Gate 模块统一配置。 @@ -92,6 +94,12 @@ private final PnLPriceMode unrealizedPnlPriceMode; /** 网格绝对步长(shortBaseEntryPrice × gridRate),运行时由队列生成逻辑设置 */ private BigDecimal step; /** 网格元素列表,由队列初始化时同步填充,包含完整的多空仓挂单状态 */ private volatile List<GridElement> gridElements = new ArrayList<>(); /** 基座多头挂单参数,在基座成交后由 tryGenerateQueues 填充 */ private TraderParam baseLongTraderParam; /** 基座空头挂单参数,在基座成交后由 tryGenerateQueues 填充 */ private TraderParam baseShortTraderParam; private GateConfig(Builder builder) { this.apiKey = builder.apiKey; @@ -195,6 +203,28 @@ /** 设置网格绝对步长(由 generateShortQueue 在运行时计算并注入) */ public void setStep(BigDecimal step) { this.step = step; } // ==================== 网格元素列表 ==================== /** @return 网格元素列表(队列初始化后填充),线程不安全,仅由策略线程单线程写入 */ public List<GridElement> getGridElements() { return gridElements; } /** 设置网格元素列表(由队列生成逻辑写入),同时重建全局 ID 索引 */ public void setGridElements(List<GridElement> gridElements) { this.gridElements = gridElements; GridElement.rebuildIndex(gridElements); } // ==================== 基座挂单参数 ==================== /** @return 基座多头挂单参数 */ public TraderParam getBaseLongTraderParam() { return baseLongTraderParam; } /** 设置基座多头挂单参数(由 tryGenerateQueues 在基座成交后填充) */ public void setBaseLongTraderParam(TraderParam baseLongTraderParam) { this.baseLongTraderParam = baseLongTraderParam; } /** @return 基座空头挂单参数 */ public TraderParam getBaseShortTraderParam() { return baseShortTraderParam; } /** 设置基座空头挂单参数(由 tryGenerateQueues 在基座成交后填充) */ public void setBaseShortTraderParam(TraderParam baseShortTraderParam) { this.baseShortTraderParam = baseShortTraderParam; } // ==================== 环境 ==================== /** @return 是否为生产环境(true=实盘生产网 / false=模拟盘测试网) */ @@ -240,7 +270,7 @@ /** 补仓最大重试次数,默认 3 */ private int reopenMaxRetries = 3; /** 网格队列容量,默认 50 */ private int gridQueueSize = 50; private int gridQueueSize = 300; /** 保证金占初始本金比例上限,默认 0.2(20%) */ private BigDecimal marginRatioLimit = new BigDecimal("0.2"); /** 合约乘数,默认 0.001 */ src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -360,15 +360,23 @@ return; } //初始化0位置的开仓,并且用空的开仓价格,作为价格基准来划分网格 if (state == StrategyState.WAITING_KLINE) { state = StrategyState.OPENING; log.info("[Gate] 首根K线到达,开基底仓位..."); executor.openLong(config.getQuantity(), (orderId) -> { log.info("[Gate] 基底多单已提交{}", orderId); TraderParam baseLongTp = TraderParam.builder() .entryOrderId(orderId) .build(); config.setBaseLongTraderParam(baseLongTp); }, null); executor.openShort(negate(config.getQuantity()), (orderId) -> { log.info("[Gate] 基底空单已提交{}",orderId); TraderParam baseShortTp = TraderParam.builder() .entryOrderId(orderId) .build(); config.setBaseShortTraderParam(baseShortTp); }, null); return; } @@ -422,32 +430,12 @@ baseLongOpened = true; log.info("[Gate] 基底多成交价: {}", longBaseEntryPrice); tryGenerateQueues(); } else if(size.compareTo(longPositionSize) < 0){ if (entryPrice.compareTo(shortEntryPrice) > 0 && entryPrice.compareTo(longEntryPrice) < 0 && shortPositionSize.compareTo(new BigDecimal("3")) < 0) { BigDecimal reverseShortTp = entryPrice.subtract(config.getStep()).setScale(1, RoundingMode.HALF_UP); executor.openShort(negate(config.getQuantity()), orderId -> { currentShortOrderIds.put(orderId, reverseShortTp);}, null); log.info("[Gate] 反向条件空单已挂, trigger:{}, size:{}, 止盈:{}", entryPrice, negate(config.getQuantity()), reverseShortTp); } } else { }else { longPositionSize = size; } } else { longActive = false; longPositionSize = BigDecimal.ZERO; } synchronized (currentLongOrderIds) { if (currentLongOrderIds.size() > 5) { Iterator<String> it = currentLongOrderIds.keySet().iterator(); for (int i = 0, remove = currentLongOrderIds.size() - 5; i < remove; i++) { it.next(); it.remove(); } } } } else if (Position.ModeEnum.DUAL_SHORT == mode) { if (hasPosition) { @@ -459,32 +447,12 @@ baseShortOpened = true; log.info("[Gate] 基底空成交价: {}", shortBaseEntryPrice); tryGenerateQueues(); } else if(size.abs().compareTo(shortPositionSize) < 0){ if (entryPrice.compareTo(shortEntryPrice) > 0 && entryPrice.compareTo(longEntryPrice) < 0 && longPositionSize.compareTo(new BigDecimal("3")) < 0) { BigDecimal reverseLongTp = entryPrice.add(config.getStep()).setScale(1, RoundingMode.HALF_UP); executor.openLong(config.getQuantity(), orderId -> { currentLongOrderIds.put(orderId, reverseLongTp);}, null); log.info("[Gate] 反向条件多单已挂, trigger:{}, size:{}, 止盈:{}", entryPrice, negate(config.getQuantity()), reverseLongTp); } } else { }else { shortPositionSize = size.abs(); } } else { shortActive = false; shortPositionSize = BigDecimal.ZERO; } synchronized (currentShortOrderIds) { if (currentShortOrderIds.size() > 5) { Iterator<String> it = currentShortOrderIds.keySet().iterator(); for (int i = 0, remove = currentShortOrderIds.size() - 5; i < remove; i++) { it.next(); it.remove(); } } } } } @@ -536,18 +504,84 @@ if (!"finished".equals(status) || !"filled".equals(finishAs)) { return; } BigDecimal longTp = currentLongOrderIds.remove(orderId); if (longTp != null) { executor.placeTakeProfit(longTp, FuturesPriceTrigger.RuleEnum.NUMBER_1, ORDER_TYPE_CLOSE_LONG, negate(config.getQuantity())); log.info("[Gate] 多单成交匹配止盈, orderId:{}, 止盈价:{}, size:{}", orderId, longTp, negate(config.getQuantity())); return; /** * 匹配止盈单止盈 */ GridElement byLongTakeProfitOrderId = GridElement.findByLongTakeProfitOrderId(orderId); if (byLongTakeProfitOrderId != null){ longTakeProfitTraderIdParam( byLongTakeProfitOrderId, null, false ); longEntryTraderIdParam( byLongTakeProfitOrderId, null, false ); } BigDecimal shortTp = currentShortOrderIds.remove(orderId); if (shortTp != null) { executor.placeTakeProfit(shortTp, FuturesPriceTrigger.RuleEnum.NUMBER_2, ORDER_TYPE_CLOSE_SHORT, config.getQuantity()); log.info("[Gate] 空单成交匹配止盈, orderId:{}, 止盈价:{}, size:{}", orderId, shortTp, config.getQuantity()); GridElement byShortTakeProfitOrderId = GridElement.findByShortTakeProfitOrderId(orderId); if (byShortTakeProfitOrderId != null){ shortTakeProfitTraderIdParam( byLongTakeProfitOrderId, null, false ); shortEntryTraderIdParam( byLongTakeProfitOrderId, null, false ); } /** * 匹配挂单 */ GridElement longGridElement = GridElement.findByLongOrderId(orderId); if (longGridElement != null) { if (longGridElement.isHasLongOrder()){ if (longGridElement.getLongTakeProfitOrderId() == null){ BigDecimal longTp = longGridElement.getLongTraderParam().getTakeProfitPrice(); if (longTp != null) { executor.placeTakeProfit(longTp, FuturesPriceTrigger.RuleEnum.NUMBER_1, ORDER_TYPE_CLOSE_LONG, negate(config.getQuantity()), (profitId) -> { longTakeProfitTraderIdParam( longGridElement, profitId, true ); }); log.info("[Gate] 多单成交匹配止盈, orderId:{}, 止盈价:{}, size:{}", orderId, longTp, negate(config.getQuantity())); return; } } } } GridElement shortGridElement = GridElement.findByShortOrderId(orderId); if (shortGridElement != null) { if (shortGridElement.isHasShortOrder()){ if (shortGridElement.getShortTakeProfitOrderId() == null){ BigDecimal shortTp = shortGridElement.getShortTraderParam().getTakeProfitPrice(); if (shortTp != null) { executor.placeTakeProfit(shortTp, FuturesPriceTrigger.RuleEnum.NUMBER_2, ORDER_TYPE_CLOSE_SHORT, config.getQuantity(), (profitId) -> { shortTakeProfitTraderIdParam( shortGridElement, profitId, true ); }); log.info("[Gate] 空单成交匹配止盈, orderId:{}, 止盈价:{}, size:{}", orderId, shortTp, config.getQuantity()); } } } } } @@ -567,33 +601,135 @@ */ private void tryGenerateQueues() { if (baseLongOpened && baseShortOpened) { //初始化空仓队列 generateShortQueue(); //初始化多仓队列 generateLongQueue(); //初始化网格数据 updateGridElements(); BigDecimal step = config.getStep(); /** * 挂初始位置多空仓条件单 * 0位置的多单止盈 * 0位置的空单止盈 */ GridElement baseGridElement = GridElement.findById(0); TraderParam baseLongTraderParam = config.getBaseLongTraderParam(); baseGridElement.setLongOrderId(baseLongTraderParam.getEntryOrderId()); //0位置的网格的多单止盈 BigDecimal upTakeProfitPrice = baseGridElement.getLongTraderParam().getTakeProfitPrice(); executor.placeTakeProfit( upTakeProfitPrice, FuturesPriceTrigger.RuleEnum.NUMBER_1, ORDER_TYPE_CLOSE_LONG, negate(config.getQuantity()), profitId -> { longTakeProfitTraderIdParam( baseGridElement, profitId, true ); } ); //0位置的网格的空单止盈 TraderParam baseShortTraderParam = config.getBaseShortTraderParam(); baseGridElement.setShortOrderId(baseShortTraderParam.getEntryOrderId()); BigDecimal downTakeProfitPrice = baseGridElement.getShortTraderParam().getTakeProfitPrice(); executor.placeTakeProfit( downTakeProfitPrice, FuturesPriceTrigger.RuleEnum.NUMBER_2, ORDER_TYPE_CLOSE_SHORT, config.getQuantity(), profitId -> { shortTakeProfitTraderIdParam( baseGridElement, profitId, true ); } ); BigDecimal longPriceQueueOne = longPriceQueue.get(0); BigDecimal longTp = longPriceQueueOne.add(step).setScale(1, RoundingMode.HALF_UP); executor.placeConditionalEntryOrder(longPriceQueueOne, FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(), orderId -> { currentLongOrderIds.put(orderId, longTp); log.info("[Gate] 初始条件多单已挂, id:{}, trigger:{}, 止盈:{}", orderId, longPriceQueue.get(0), longTp); }, /** * 挂初始位置的up位置的多单 * 挂初始位置的down位置的空单 */ Integer upId = baseGridElement.getUpId(); GridElement upGridElementOne = GridElement.findById(upId); BigDecimal longTp = upGridElementOne.getGridPrice(); executor.placeConditionalEntryOrder( longTp, FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(), orderId -> { longEntryTraderIdParam( upGridElementOne, orderId, true ); }, null); Integer downId = baseGridElement.getDownId(); GridElement downGridElementOne = GridElement.findById(downId); BigDecimal shortTp = downGridElementOne.getGridPrice(); executor.placeConditionalEntryOrder( shortTp, FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()), orderId -> { shortEntryTraderIdParam( downGridElementOne, orderId, true ); }, null); BigDecimal shortPriceQueueOne = shortPriceQueue.get(0); BigDecimal shortTp = shortPriceQueueOne.subtract(step).setScale(1, RoundingMode.HALF_UP); executor.placeConditionalEntryOrder(shortPriceQueueOne, FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()), orderId -> { currentShortOrderIds.put(orderId, shortTp); log.info("[Gate] 初始条件空单已挂, id:{}, trigger:{}, 止盈:{}", orderId, shortPriceQueue.get(0), shortTp); }, null); log.info("[Gate] 网格队列已生成, 空队首:{} → 尾:{}, 多队首:{} → 尾:{}, step:{}, 已激活", shortPriceQueueOne, shortPriceQueue.get(shortPriceQueue.size() - 1), longPriceQueueOne, longPriceQueue.get(longPriceQueue.size() - 1), step); state = StrategyState.ACTIVE; } } /** * 更新基座止盈信息,将止盈价、订单ID等写入 TraderParam 并回填到 ID=0 的网格元素中。 */ private void longTakeProfitTraderIdParam( GridElement baseElement,String profitId, boolean flag ) { TraderParam tp = baseElement.getLongTraderParam(); tp.setTakeProfitOrderId(profitId); tp.setTakeProfitPlaced(flag); baseElement.setLongTakeProfitOrderId(profitId); GridElement.refreshIndices(); } private void shortTakeProfitTraderIdParam( GridElement baseElement,String profitId, boolean flag ) { TraderParam tp = baseElement.getShortTraderParam(); tp.setTakeProfitOrderId(profitId); tp.setTakeProfitPlaced(flag); baseElement.setShortTakeProfitOrderId(profitId); GridElement.refreshIndices(); } private void longEntryTraderIdParam( GridElement baseElement,String entryId,boolean flag ) { TraderParam tp = baseElement.getLongTraderParam(); tp.setEntryOrderId(entryId); tp.setEntryOrderPlaced(flag); baseElement.setHasLongOrder(flag); baseElement.setLongOrderId(entryId); GridElement.refreshIndices(); } private void shortEntryTraderIdParam( GridElement baseElement, String entryId, boolean flag ) { TraderParam tp = baseElement.getShortTraderParam(); tp.setEntryOrderId(entryId); tp.setEntryOrderPlaced(flag); baseElement.setHasShortOrder(flag); baseElement.setShortOrderId(entryId); GridElement.refreshIndices(); } /** @@ -607,10 +743,13 @@ BigDecimal step = shortBaseEntryPrice.multiply(config.getGridRate()).setScale(1, RoundingMode.HALF_UP); config.setStep(step); BigDecimal elem = shortBaseEntryPrice.subtract(step).setScale(1, RoundingMode.HALF_UP); for (int i = 0; i < config.getGridQueueSize(); i++) { shortPriceQueue.add(elem); elem = elem.subtract(step).setScale(1, RoundingMode.HALF_UP); } for (int i = 0; i < config.getGridQueueSize(); i++) { shortPriceQueue.add(elem); elem = elem.subtract(step).setScale(1, RoundingMode.HALF_UP); if (elem.compareTo(BigDecimal.ZERO) <= 0) { break; } } shortPriceQueue.sort((a, b) -> b.compareTo(a)); log.info("[Gate] 空队列:{}", shortPriceQueue); } @@ -630,6 +769,112 @@ } longPriceQueue.sort(BigDecimal::compareTo); log.info("[Gate] 多队列:{}", longPriceQueue); } /** * 根据当前多空价格队列同步构建网格元素列表,写入 config。 * * <h3>ID 分配规则</h3> * <ul> * <li>空仓队列:id 从 -1 自减(-1, -2, -3...),第一个元素 upId=0,最后一个 downId=null</li> * <li>位置 0:gridPrice=shortBaseEntryPrice,upId=-1,downId=1,其数据在基座开仓时更新</li> * <li>多仓队列:id 从 1 自增(1, 2, 3...),第一个元素 upId=0,最后一个 downId=null</li> * </ul> * * <h3>链表关系</h3> * 所有元素通过 upId/downId 串成一条双向链表: * ... → -3 → -2 → -1 → 0 → 1 → 2 → 3 → ... */ private void updateGridElements() { List<GridElement> elements = new ArrayList<>(); int shortSize = shortPriceQueue.size(); int longSize = longPriceQueue.size(); BigDecimal step = config.getStep().subtract(config.getContractMultiplier()); String qty = config.getQuantity(); // 空仓队列:id 从 -1 自减, shortPriceQueue[i] → id=-(i+1) for (int i = 0; i < shortSize; i++) { int id = -(i + 1); Integer upId = (i == 0) ? 0 : id + 1; Integer downId = (i == shortSize - 1) ? null : id - 1; BigDecimal price = shortPriceQueue.get(i); TraderParam longParam = TraderParam.builder() .direction(TraderParam.Direction.LONG) .entryPrice(price) .takeProfitPrice(price.add(step).setScale(1, RoundingMode.HALF_UP)) .quantity(qty) .build(); TraderParam shortParam = TraderParam.builder() .direction(TraderParam.Direction.SHORT) .entryPrice(price) .takeProfitPrice(price.subtract(step).setScale(1, RoundingMode.HALF_UP)) .quantity(qty) .build(); elements.add(GridElement.builder() .id(id) .gridPrice(price) .upId(upId) .downId(downId) .longTraderParam(longParam) .shortTraderParam(shortParam) .build()); } // 位置 0:基底价格,数据在基座开仓时更新 { BigDecimal price = shortBaseEntryPrice; TraderParam longParam = TraderParam.builder() .direction(TraderParam.Direction.LONG) .entryPrice(price) .takeProfitPrice(price.add(step).setScale(1, RoundingMode.HALF_UP)) .quantity(qty) .build(); TraderParam shortParam = TraderParam.builder() .direction(TraderParam.Direction.SHORT) .entryPrice(price) .takeProfitPrice(price.subtract(step).setScale(1, RoundingMode.HALF_UP)) .quantity(qty) .build(); elements.add(GridElement.builder() .id(0) .gridPrice(price) .upId(shortSize > 0 ? -1 : null) .downId(longSize > 0 ? 1 : null) .longTraderParam(longParam) .shortTraderParam(shortParam) .build()); } // 多仓队列:id 从 1 自增, longPriceQueue[i] → id=i+1 for (int i = 0; i < longSize; i++) { int id = i + 1; Integer upId = (i == 0) ? 0 : id - 1; Integer downId = (i == longSize - 1) ? null : id + 1; BigDecimal price = longPriceQueue.get(i); TraderParam longParam = TraderParam.builder() .direction(TraderParam.Direction.LONG) .entryPrice(price) .takeProfitPrice(price.add(step).setScale(1, RoundingMode.HALF_UP)) .quantity(qty) .build(); TraderParam shortParam = TraderParam.builder() .direction(TraderParam.Direction.SHORT) .entryPrice(price) .takeProfitPrice(price.subtract(step).setScale(1, RoundingMode.HALF_UP)) .quantity(qty) .build(); elements.add(GridElement.builder() .id(id) .gridPrice(price) .upId(upId) .downId(downId) .longTraderParam(longParam) .shortTraderParam(shortParam) .build()); } config.setGridElements(elements); log.info("[Gate] 网格元素列表已构建, 共{}个元素 (空仓:{} 位置:0 多仓:{})", elements.size(), shortSize, longSize); } /** @@ -683,39 +928,105 @@ shortPriceQueue.sort((a, b) -> b.compareTo(a)); } // synchronized (longPriceQueue) { // BigDecimal first = longPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : longPriceQueue.get(0); // BigDecimal gridStep = config.getStep(); // for (int i = 1; i <= matched.size(); i++) { // BigDecimal elem = first.subtract(gridStep.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP); // longPriceQueue.add(elem); // } // longPriceQueue.sort(BigDecimal::compareTo); // while (longPriceQueue.size() > config.getGridQueueSize()) { // longPriceQueue.remove(longPriceQueue.size() - 1); // } // } synchronized (longPriceQueue) { BigDecimal first = longPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : longPriceQueue.get(0); BigDecimal gridStep = config.getStep(); for (int i = 1; i <= matched.size(); i++) { BigDecimal elem = first.subtract(gridStep.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP); longPriceQueue.add(elem); } longPriceQueue.sort(BigDecimal::compareTo); while (longPriceQueue.size() > config.getGridQueueSize()) { longPriceQueue.remove(longPriceQueue.size() - 1); } } if (!isMarginSafe()) { log.warn("[Gate] 保证金超限,跳过挂条件单"); } else { BigDecimal newShortFirst = shortPriceQueue.get(0); BigDecimal step = config.getStep(); BigDecimal stpElem = newShortFirst.subtract(step).setScale(1, RoundingMode.HALF_UP); executor.placeConditionalEntryOrder(newShortFirst, FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()), orderId -> { currentShortOrderIds.put(orderId, stpElem); log.info("[Gate] 新条件空单, id:{}, trigger:{}, 止盈:{}", orderId, newShortFirst, stpElem); }, null); /** * 下一个开仓位置 * 获取队列第一个元素的价格对应的网格 * 判断网格是否能开空仓,如果不能则跳过 * 前进方向挂空仓条件单 * 后置方向挂多空条件单 */ //下一个开仓位置 BigDecimal newLongFirst = shortPriceQueue.get(0); GridElement UpGridElement = GridElement.findByPrice(newLongFirst); BigDecimal newLongFirst = newShortFirst.add( step.multiply(new BigDecimal("2"))); if (newLongFirst.compareTo(longEntryPrice) < 0) { // 判断网格是否能开空仓,如果不能则跳过 if (UpGridElement != null) { BigDecimal ltpElem = newLongFirst.add(step).setScale(1, RoundingMode.HALF_UP); executor.placeConditionalEntryOrder(newLongFirst, FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(), orderId -> { currentLongOrderIds.put(orderId, ltpElem); log.info("[Gate] 新条件多单, id:{}, trigger:{}, 止盈:{}", orderId, newLongFirst, ltpElem); }, null); if (!UpGridElement.isHasShortOrder()) { //挂空仓条件单 TraderParam upShortTraderParam = UpGridElement.getShortTraderParam(); executor.placeConditionalEntryOrder( upShortTraderParam.getEntryPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(upShortTraderParam.getQuantity()), orderId -> { shortEntryTraderIdParam( UpGridElement, orderId, true ); }, null ); } } int i = UpGridElement.getId() + 2; GridElement downGridElement = GridElement.findById(i); if (downGridElement != null){ TraderParam downLongTraderParam = downGridElement.getLongTraderParam(); if (!downGridElement.isHasShortOrder()){ executor.placeConditionalEntryOrder( downLongTraderParam.getEntryPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_1, downLongTraderParam.getQuantity(), orderId -> { longEntryTraderIdParam( downGridElement, orderId, true ); }, null ); } TraderParam downShortTraderParam = downGridElement.getShortTraderParam(); BigDecimal downGridPrice = downGridElement.getGridPrice(); if ( !downGridElement.isHasShortOrder() && downGridPrice.compareTo(longEntryPrice) <= 0 && downGridPrice.compareTo(shortEntryPrice) >= 0 ){ executor.placeConditionalEntryOrder( downShortTraderParam.getEntryPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(downShortTraderParam.getQuantity()), orderId -> { shortEntryTraderIdParam( downGridElement, orderId, true ); }, null ); } } } @@ -763,6 +1074,11 @@ log.info("[Gate] 多仓队列触发, 匹配{}个元素, 当前价:{}", matched.size(), currentPrice); /** * 匹配到元素后, * 多仓队列更新 * 空仓队列更新 */ synchronized (longPriceQueue) { longPriceQueue.removeAll(matched); BigDecimal max = longPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : longPriceQueue.get(longPriceQueue.size() - 1); @@ -773,44 +1089,104 @@ } longPriceQueue.sort(BigDecimal::compareTo); } // synchronized (shortPriceQueue) { // BigDecimal first = shortPriceQueue.isEmpty() ? matched.get(0) : shortPriceQueue.get(0); // BigDecimal gridStep = config.getStep(); // for (int i = 1; i <= matched.size(); i++) { // BigDecimal elem = first.add(gridStep.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP); // shortPriceQueue.add(elem); // } // shortPriceQueue.sort((a, b) -> b.compareTo(a)); // while (shortPriceQueue.size() > config.getGridQueueSize()) { // shortPriceQueue.remove(shortPriceQueue.size() - 1); // } // } synchronized (shortPriceQueue) { BigDecimal first = shortPriceQueue.isEmpty() ? matched.get(0) : shortPriceQueue.get(0); BigDecimal gridStep = config.getStep(); for (int i = 1; i <= matched.size(); i++) { BigDecimal elem = first.add(gridStep.multiply(BigDecimal.valueOf(i))).setScale(1, RoundingMode.HALF_UP); shortPriceQueue.add(elem); } shortPriceQueue.sort((a, b) -> b.compareTo(a)); while (shortPriceQueue.size() > config.getGridQueueSize()) { shortPriceQueue.remove(shortPriceQueue.size() - 1); } } if (!isMarginSafe()) { log.warn("[Gate] 保证金超限,跳过挂条件单"); } else { BigDecimal step = config.getStep(); /** * 下一个开仓位置 * 获取队列第一个元素的价格对应的网格 * 判断网格是否能开多仓,如果不能则跳过 * 前进方向挂多仓条件单 * 后置方向挂多空条件单 */ //下一个开仓位置 BigDecimal newLongFirst = longPriceQueue.get(0); BigDecimal ltpElem = newLongFirst.add(step).setScale(1, RoundingMode.HALF_UP); executor.placeConditionalEntryOrder(newLongFirst, FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(), orderId -> { currentLongOrderIds.put(orderId, ltpElem); log.info("[Gate] 新条件多单, id:{}, trigger:{}, 止盈:{}", orderId, newLongFirst, ltpElem); }, null); GridElement UpGridElement = GridElement.findByPrice(newLongFirst); // 判断网格是否能开多仓,如果不能则跳过 if (UpGridElement != null) { if (!UpGridElement.isHasLongOrder()) { //挂多仓条件单 TraderParam upLongTraderParam = UpGridElement.getLongTraderParam(); executor.placeConditionalEntryOrder( upLongTraderParam.getEntryPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(), orderId -> { longEntryTraderIdParam( UpGridElement, orderId, true ); }, null ); } } BigDecimal newShortFirst = newLongFirst.subtract( step.multiply(new BigDecimal("2"))); if (newShortFirst.compareTo(shortEntryPrice) > 0){ BigDecimal stpElem = newShortFirst.subtract(step).setScale(1, RoundingMode.HALF_UP); executor.placeConditionalEntryOrder(newShortFirst, FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()), orderId -> { currentShortOrderIds.put(orderId, stpElem); log.info("[Gate] 新条件空单, id:{}, trigger:{}, 止盈:{}", orderId, newShortFirst, stpElem); }, null); int i = UpGridElement.getId() - 2; GridElement downGridElement = GridElement.findById(i); if (downGridElement != null){ TraderParam shortTraderParam = downGridElement.getShortTraderParam(); if (!downGridElement.isHasShortOrder()){ executor.placeConditionalEntryOrder( shortTraderParam.getEntryPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()), orderId -> { shortEntryTraderIdParam( downGridElement, orderId, true ); }, null ); } TraderParam downLongTraderParam = downGridElement.getLongTraderParam(); BigDecimal downGridPrice = downGridElement.getGridPrice(); if ( !downGridElement.isHasLongOrder() && downGridPrice.compareTo(longEntryPrice) <= 0 && downGridPrice.compareTo(shortEntryPrice) >= 0 ){ executor.placeConditionalEntryOrder( downLongTraderParam.getEntryPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(), orderId -> { longEntryTraderIdParam( downGridElement, orderId, true ); }, null ); } } } src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
@@ -176,13 +176,18 @@ public void placeTakeProfit(BigDecimal triggerPrice, FuturesPriceTrigger.RuleEnum rule, String orderType, String size) { String size, Consumer<String> onSuccess) { executor.execute(() -> { FuturesPriceTriggeredOrder order = buildTriggeredOrder(triggerPrice, rule, orderType, size); try { TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order); log.info("[TradeExec] 止盈单已创建, 触发价:{}, 类型:{}, size:{}, id:{}", triggerPrice, orderType, size, response.getId()); String orderId = String.valueOf(response.getId()); if (onSuccess != null) { onSuccess.accept(orderId); } } catch (Exception e) { log.error("[TradeExec] 止盈单创建失败, 触发价:{}, size:{}, 立即市价止盈", triggerPrice, size, e); marketClose(size); src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
@@ -50,39 +50,39 @@ try { //测试盘 // config = GateConfig.builder() // .apiKey("d90ca272391992b8e74f8f92cedb21ec") // .apiSecret("1861e4f52de4bb53369ea3208d9ede38ece4777368030f96c77d27934c46c274") // .contract("ETH_USDT") // .leverage("100") // .marginMode("CROSS") // .positionMode("dual") // .gridRate(new BigDecimal("0.003")) // .overallTp(new BigDecimal("5")) // .maxLoss(new BigDecimal("15")) // .quantity("1") // .contractMultiplier(new BigDecimal("0.01")) // .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.LAST_PRICE) // .isProduction(false) // .reopenMaxRetries(3) // .build(); //实盘 config = GateConfig.builder() .apiKey("865371cdaccd5d238aceb06a55f0143a") .apiSecret("49589c30dfdc3acba007eed445a94990c4b0aa5faac9843e32defdd7371f5a50") .apiKey("d90ca272391992b8e74f8f92cedb21ec") .apiSecret("1861e4f52de4bb53369ea3208d9ede38ece4777368030f96c77d27934c46c274") .contract("ETH_USDT") .leverage("100") .marginMode("CROSS") .positionMode("dual") .gridRate(new BigDecimal("0.005")) .gridRate(new BigDecimal("0.002")) .overallTp(new BigDecimal("5")) .maxLoss(new BigDecimal("15")) .quantity("1") .contractMultiplier(new BigDecimal("0.01")) .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.LAST_PRICE) .isProduction(true) .isProduction(false) .reopenMaxRetries(3) .build(); //实盘 // config = GateConfig.builder() // .apiKey("865371cdaccd5d238aceb06a55f0143a") // .apiSecret("49589c30dfdc3acba007eed445a94990c4b0aa5faac9843e32defdd7371f5a50") // .contract("ETH_USDT") // .leverage("100") // .marginMode("CROSS") // .positionMode("dual") // .gridRate(new BigDecimal("0.005")) // .overallTp(new BigDecimal("5")) // .maxLoss(new BigDecimal("15")) // .quantity("1") // .contractMultiplier(new BigDecimal("0.01")) // .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.LAST_PRICE) // .isProduction(true) // .reopenMaxRetries(3) // .build(); // 1. 初始化交易服务:查用户ID → 切持仓模式 → 清条件单 → 平已有仓位 → 设杠杆 gridTradeService = new GateGridTradeService(config); src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java
New file @@ -0,0 +1,372 @@ package com.xcong.excoin.modules.gateApi; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 网格元素,表示网格交易策略中的一个价格层级。 * * <p>每个网格层级包含该价格位置上的多仓和空仓挂单信息, * 通过编号和网格价格唯一标识一个网格层级。 * * <h3>关键字段</h3> * <ul> * <li><b>编号</b>:网格层级的唯一标识</li> * <li><b>网格价格</b>:该层级对应的触发价格</li> * <li><b>多仓挂单</b>:是否已有多头挂单、多头挂单参数</li> * <li><b>空仓挂单</b>:是否有空头挂单、空头挂单参数</li> * </ul> * * <h3>使用示例</h3> * <pre> * GridElement element = GridElement.builder() * .id(1) * .gridPrice(new BigDecimal("2300.0")) * .build(); * * // 挂多仓单 * TraderParam longParam = TraderParam.builder() * .direction(TraderParam.Direction.LONG) * .entryPrice(new BigDecimal("2293.7")) * .takeProfitPrice(new BigDecimal("2301.6")) * .build(); * element.setHasLongOrder(true); * element.setLongTraderParam(longParam); * </pre> * * @author Administrator */ public class GridElement { /** 网格层级编号 */ private int id; /** 网格触发价格 */ private BigDecimal gridPrice; /** 是否存在多仓挂单 */ private boolean hasLongOrder; /** 是否存在空仓挂单 */ private boolean hasShortOrder; /** 多仓挂单参数 */ private TraderParam longTraderParam; /** 空仓挂单参数 */ private TraderParam shortTraderParam; /** 上一个网格编号,null 表示无上一级 */ private Integer upId; /** 下一个网格编号,null 表示无下一级 */ private Integer downId; /** 多仓挂单订单 ID */ private String longOrderId; /** 空仓挂单订单 ID */ private String shortOrderId; /** 多仓止盈订单 ID */ private String longTakeProfitOrderId; /** 空仓止盈订单 ID */ private String shortTakeProfitOrderId; /** 全局 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */ private static final Map<Integer, GridElement> INDEX = new ConcurrentHashMap<>(); /** 全局价格索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */ private static final Map<BigDecimal, GridElement> PRICE_INDEX = new ConcurrentHashMap<>(); /** 全局多仓订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */ private static final Map<String, GridElement> LONG_ORDER_ID_INDEX = new ConcurrentHashMap<>(); /** 全局空仓订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */ private static final Map<String, GridElement> SHORT_ORDER_ID_INDEX = new ConcurrentHashMap<>(); /** 全局多仓止盈订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */ private static final Map<String, GridElement> LONG_TP_ORDER_ID_INDEX = new ConcurrentHashMap<>(); /** 全局空仓止盈订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */ private static final Map<String, GridElement> SHORT_TP_ORDER_ID_INDEX = new ConcurrentHashMap<>(); /** * 根据 ID 快速查找网格元素(O(1))。 * * @param id 网格层级编号 * @return 对应的 GridElement,不存在则返回 null */ public static GridElement findById(int id) { return INDEX.get(id); } /** * 根据价格快速查找网格元素(O(1))。 * * @param price 网格价格 * @return 对应的 GridElement,不存在则返回 null */ public static GridElement findByPrice(BigDecimal price) { return PRICE_INDEX.get(price); } /** * 根据多仓挂单订单 ID 快速查找网格元素(O(1))。 * * @param orderId 多仓挂单订单 ID * @return 对应的 GridElement,不存在则返回 null */ public static GridElement findByLongOrderId(String orderId) { return LONG_ORDER_ID_INDEX.get(orderId); } /** * 根据空仓挂单订单 ID 快速查找网格元素(O(1))。 * * @param orderId 空仓挂单订单 ID * @return 对应的 GridElement,不存在则返回 null */ public static GridElement findByShortOrderId(String orderId) { return SHORT_ORDER_ID_INDEX.get(orderId); } /** * 根据多仓止盈订单 ID 快速查找网格元素(O(1))。 * * @param orderId 多仓止盈订单 ID * @return 对应的 GridElement,不存在则返回 null */ public static GridElement findByLongTakeProfitOrderId(String orderId) { return LONG_TP_ORDER_ID_INDEX.get(orderId); } /** * 根据空仓止盈订单 ID 快速查找网格元素(O(1))。 * * @param orderId 空仓止盈订单 ID * @return 对应的 GridElement,不存在则返回 null */ public static GridElement findByShortTakeProfitOrderId(String orderId) { return SHORT_TP_ORDER_ID_INDEX.get(orderId); } /** * 从列表中重建全局 ID 索引和价格索引。 * 由 {@link GateConfig#setGridElements(List)} 在每次列表变更后调用。 */ public static void rebuildIndex(List<GridElement> elements) { INDEX.clear(); PRICE_INDEX.clear(); LONG_ORDER_ID_INDEX.clear(); SHORT_ORDER_ID_INDEX.clear(); LONG_TP_ORDER_ID_INDEX.clear(); SHORT_TP_ORDER_ID_INDEX.clear(); for (GridElement e : elements) { INDEX.put(e.getId(), e); putDynamicIndices(e); } } /** * 刷新动态索引(价格索引、多仓/空仓订单 ID 索引)。 * 在对 GridElement 的 {@link #longOrderId}、{@link #shortOrderId}、 * {@link #longTraderParam}、{@link #shortTraderParam} 等字段修改后调用, * 使快速查找方法获取到最新数据。 */ public static void refreshIndices() { PRICE_INDEX.clear(); LONG_ORDER_ID_INDEX.clear(); SHORT_ORDER_ID_INDEX.clear(); LONG_TP_ORDER_ID_INDEX.clear(); SHORT_TP_ORDER_ID_INDEX.clear(); for (GridElement e : INDEX.values()) { putDynamicIndices(e); } } private static void putDynamicIndices(GridElement e) { PRICE_INDEX.put(e.getGridPrice(), e); if (e.getLongOrderId() != null) { LONG_ORDER_ID_INDEX.put(e.getLongOrderId(), e); } if (e.getShortOrderId() != null) { SHORT_ORDER_ID_INDEX.put(e.getShortOrderId(), e); } if (e.getLongTakeProfitOrderId() != null) { LONG_TP_ORDER_ID_INDEX.put(e.getLongTakeProfitOrderId(), e); } if (e.getShortTakeProfitOrderId() != null) { SHORT_TP_ORDER_ID_INDEX.put(e.getShortTakeProfitOrderId(), e); } } /** * @return 根据 upId 获取上一个网格元素,无上一级则返回 null */ public GridElement getUp() { return upId != null ? INDEX.get(upId) : null; } /** * @return 根据 downId 获取下一个网格元素,无下一级则返回 null */ public GridElement getDown() { return downId != null ? INDEX.get(downId) : null; } private GridElement(Builder builder) { this.id = builder.id; this.gridPrice = builder.gridPrice; this.hasLongOrder = builder.hasLongOrder; this.hasShortOrder = builder.hasShortOrder; this.longTraderParam = builder.longTraderParam; this.shortTraderParam = builder.shortTraderParam; this.upId = builder.upId; this.downId = builder.downId; this.longOrderId = builder.longOrderId; this.shortOrderId = builder.shortOrderId; this.longTakeProfitOrderId = builder.longTakeProfitOrderId; this.shortTakeProfitOrderId = builder.shortTakeProfitOrderId; } // ==================== 网格层级编号 ==================== /** @return 网格层级编号 */ public int getId() { return id; } /** 设置网格层级编号 */ public void setId(int id) { this.id = id; } // ==================== 网格价格 ==================== /** @return 网格触发价格 */ public BigDecimal getGridPrice() { return gridPrice; } /** 设置网格触发价格 */ public void setGridPrice(BigDecimal gridPrice) { this.gridPrice = gridPrice; } // ==================== 多仓挂单标记 ==================== /** @return 是否存在多仓挂单 */ public boolean isHasLongOrder() { return hasLongOrder; } /** 标记是否存在多仓挂单 */ public void setHasLongOrder(boolean hasLongOrder) { this.hasLongOrder = hasLongOrder; } // ==================== 空仓挂单标记 ==================== /** @return 是否存在空仓挂单 */ public boolean isHasShortOrder() { return hasShortOrder; } /** 标记是否存在空仓挂单 */ public void setHasShortOrder(boolean hasShortOrder) { this.hasShortOrder = hasShortOrder; } // ==================== 多仓挂单参数 ==================== /** @return 多仓挂单参数 */ public TraderParam getLongTraderParam() { return longTraderParam; } /** 设置多仓挂单参数 */ public void setLongTraderParam(TraderParam longTraderParam) { this.longTraderParam = longTraderParam; } // ==================== 空仓挂单参数 ==================== /** @return 空仓挂单参数 */ public TraderParam getShortTraderParam() { return shortTraderParam; } /** 设置空仓挂单参数 */ public void setShortTraderParam(TraderParam shortTraderParam) { this.shortTraderParam = shortTraderParam; } // ==================== 上一个网格编号 ==================== /** @return 上一个网格编号,null 表示无上一级 */ public Integer getUpId() { return upId; } /** 设置上一个网格编号 */ public void setUpId(Integer upId) { this.upId = upId; } // ==================== 下一个网格编号 ==================== /** @return 下一个网格编号,null 表示无下一级 */ public Integer getDownId() { return downId; } /** 设置下一个网格编号 */ public void setDownId(Integer downId) { this.downId = downId; } // ==================== 多仓挂单订单 ID ==================== /** @return 多仓挂单订单 ID */ public String getLongOrderId() { return longOrderId; } /** 设置多仓挂单订单 ID */ public void setLongOrderId(String longOrderId) { this.longOrderId = longOrderId; } // ==================== 空仓挂单订单 ID ==================== /** @return 空仓挂单订单 ID */ public String getShortOrderId() { return shortOrderId; } /** 设置空仓挂单订单 ID */ public void setShortOrderId(String shortOrderId) { this.shortOrderId = shortOrderId; } // ==================== 多仓止盈订单 ID ==================== /** @return 多仓止盈订单 ID */ public String getLongTakeProfitOrderId() { return longTakeProfitOrderId; } /** 设置多仓止盈订单 ID */ public void setLongTakeProfitOrderId(String longTakeProfitOrderId) { this.longTakeProfitOrderId = longTakeProfitOrderId; } // ==================== 空仓止盈订单 ID ==================== /** @return 空仓止盈订单 ID */ public String getShortTakeProfitOrderId() { return shortTakeProfitOrderId; } /** 设置空仓止盈订单 ID */ public void setShortTakeProfitOrderId(String shortTakeProfitOrderId) { this.shortTakeProfitOrderId = shortTakeProfitOrderId; } public static Builder builder() { return new Builder(); } /** * GridElement 的流式构造器。 * * <h3>必填项</h3> * {@code id}、{@code gridPrice} 必须设置。 * * <h3>默认值</h3> * hasLongOrder/hasShortOrder 默认为 false,upId/downId/TraderParam 默认为 null */ public static class Builder { /** 网格层级编号(必填) */ private int id; /** 网格触发价格(必填) */ private BigDecimal gridPrice; /** 是否有多仓挂单,默认 false */ private boolean hasLongOrder = false; /** 是否有空仓挂单,默认 false */ private boolean hasShortOrder = false; /** 多仓挂单参数 */ private TraderParam longTraderParam; /** 空仓挂单参数 */ private TraderParam shortTraderParam; /** 上一个网格编号,默认 null(无上一级) */ private Integer upId; /** 下一个网格编号,默认 null(无下一级) */ private Integer downId; /** 多仓挂单订单 ID */ private String longOrderId; /** 空仓挂单订单 ID */ private String shortOrderId; /** 多仓止盈订单 ID */ private String longTakeProfitOrderId; /** 空仓止盈订单 ID */ private String shortTakeProfitOrderId; /** 设置网格层级编号 */ public Builder id(int id) { this.id = id; return this; } /** 设置网格触发价格 */ public Builder gridPrice(BigDecimal gridPrice) { this.gridPrice = gridPrice; return this; } /** 设置是否存在多仓挂单 */ public Builder hasLongOrder(boolean hasLongOrder) { this.hasLongOrder = hasLongOrder; return this; } /** 设置是否存在空仓挂单 */ public Builder hasShortOrder(boolean hasShortOrder) { this.hasShortOrder = hasShortOrder; return this; } /** 设置多仓挂单参数 */ public Builder longTraderParam(TraderParam longTraderParam) { this.longTraderParam = longTraderParam; return this; } /** 设置空仓挂单参数 */ public Builder shortTraderParam(TraderParam shortTraderParam) { this.shortTraderParam = shortTraderParam; return this; } /** 设置上一个网格编号 */ public Builder upId(Integer upId) { this.upId = upId; return this; } /** 设置下一个网格编号 */ public Builder downId(Integer downId) { this.downId = downId; return this; } /** 设置多仓挂单订单 ID */ public Builder longOrderId(String longOrderId) { this.longOrderId = longOrderId; return this; } /** 设置空仓挂单订单 ID */ public Builder shortOrderId(String shortOrderId) { this.shortOrderId = shortOrderId; return this; } /** 设置多仓止盈订单 ID */ public Builder longTakeProfitOrderId(String longTakeProfitOrderId) { this.longTakeProfitOrderId = longTakeProfitOrderId; return this; } /** 设置空仓止盈订单 ID */ public Builder shortTakeProfitOrderId(String shortTakeProfitOrderId) { this.shortTakeProfitOrderId = shortTakeProfitOrderId; return this; } public GridElement build() { return new GridElement(this); } } } src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java
New file @@ -0,0 +1,222 @@ package com.xcong.excoin.modules.gateApi; import java.math.BigDecimal; /** * 网格交易单参数,封装单笔条件开仓单及其止盈单的完整状态。 * * <p>每笔挂单对应的参数独立存储为一个 TraderParam 实例, * 用于追踪条件开仓单和止盈条件单的挂单状态、价格位置及订单 ID。 * * <h3>关键字段</h3> * <ul> * <li><b>方向</b>:多 / 空,决定挂单和止盈的触发方向</li> * <li><b>价格</b>:挂单价(条件开仓触发价)、止盈价(止盈触发价)</li> * <li><b>挂单状态</b>:挂单价是否挂成功、止盈价是否挂成功</li> * <li><b>网格位置</b>:挂单价网格位置、止盈价网格位置(用于定位在价格队列中的索引)</li> * <li><b>订单ID</b>:挂单订单 ID、止盈订单 ID(用于取消和匹配)</li> * </ul> * * <h3>使用示例</h3> * <pre> * TraderParam param = TraderParam.builder() * .direction(Direction.LONG) * .entryPrice(new BigDecimal("2293.7")) * .takeProfitPrice(new BigDecimal("2301.6")) * .quantity("1") * .entryGridPosition(3) * .takeProfitGridPosition(4) * .build(); * * // 挂单成功后更新 * param.setEntryOrderPlaced(true); * param.setEntryOrderId("12345"); * * // 止盈单挂成功后更新 * param.setTakeProfitPlaced(true); * param.setTakeProfitOrderId("12346"); * </pre> * * @author Administrator */ public class TraderParam { /** * 交易方向。 * <ul> * <li>{@link #LONG} — 多头</li> * <li>{@link #SHORT} — 空头</li> * </ul> */ public enum Direction { /** 多头方向 */ LONG, /** 空头方向 */ SHORT } /** 交易方向(多 / 空) */ private Direction direction; /** 下单数量(合约张数) */ private String quantity; /** 挂单价在网格队列中的位置(索引,-1 表示未定位) */ private int entryGridPosition; /** 条件开仓触发价 */ private BigDecimal entryPrice; /** 挂单(条件开仓单)订单 ID */ private String entryOrderId; /** 挂单价(条件开仓单)是否挂成功 */ private boolean entryOrderPlaced; /** 止盈价在网格队列中的位置(索引,-1 表示未定位) */ private int takeProfitGridPosition; /** 止盈触发价 */ private BigDecimal takeProfitPrice; /** 止盈条件单订单 ID */ private String takeProfitOrderId; /** 止盈价是否挂成功 */ private boolean takeProfitPlaced; private TraderParam(Builder builder) { this.direction = builder.direction; this.entryPrice = builder.entryPrice; this.takeProfitPrice = builder.takeProfitPrice; this.quantity = builder.quantity; this.takeProfitPlaced = builder.takeProfitPlaced; this.entryOrderPlaced = builder.entryOrderPlaced; this.entryGridPosition = builder.entryGridPosition; this.takeProfitGridPosition = builder.takeProfitGridPosition; this.takeProfitOrderId = builder.takeProfitOrderId; this.entryOrderId = builder.entryOrderId; } // ==================== 交易方向 ==================== /** @return 交易方向(多 / 空) */ public Direction getDirection() { return direction; } /** 设置交易方向 */ public void setDirection(Direction direction) { this.direction = direction; } // ==================== 条件开仓触发价 ==================== /** @return 条件开仓触发价 */ public BigDecimal getEntryPrice() { return entryPrice; } /** 设置条件开仓触发价 */ public void setEntryPrice(BigDecimal entryPrice) { this.entryPrice = entryPrice; } // ==================== 止盈触发价 ==================== /** @return 止盈触发价 */ public BigDecimal getTakeProfitPrice() { return takeProfitPrice; } /** 设置止盈触发价 */ public void setTakeProfitPrice(BigDecimal takeProfitPrice) { this.takeProfitPrice = takeProfitPrice; } // ==================== 下单数量 ==================== /** @return 下单数量(合约张数) */ public String getQuantity() { return quantity; } /** 设置下单数量 */ public void setQuantity(String quantity) { this.quantity = quantity; } // ==================== 挂单价网格位置 ==================== /** @return 挂单价在网格队列中的索引位置,-1 表示未定位 */ public int getEntryGridPosition() { return entryGridPosition; } /** 设置挂单价在网格队列中的索引位置 */ public void setEntryGridPosition(int entryGridPosition) { this.entryGridPosition = entryGridPosition; } // ==================== 止盈价网格位置 ==================== /** @return 止盈价在网格队列中的索引位置,-1 表示未定位 */ public int getTakeProfitGridPosition() { return takeProfitGridPosition; } /** 设置止盈价在网格队列中的索引位置 */ public void setTakeProfitGridPosition(int takeProfitGridPosition) { this.takeProfitGridPosition = takeProfitGridPosition; } // ==================== 止盈价是否挂成功 ==================== /** @return 止盈条件单是否已挂成功 */ public boolean isTakeProfitPlaced() { return takeProfitPlaced; } /** 标记止盈条件单已挂成功 */ public void setTakeProfitPlaced(boolean takeProfitPlaced) { this.takeProfitPlaced = takeProfitPlaced; } // ==================== 挂单价是否挂成功 ==================== /** @return 条件开仓单是否已挂成功 */ public boolean isEntryOrderPlaced() { return entryOrderPlaced; } /** 标记条件开仓单已挂成功 */ public void setEntryOrderPlaced(boolean entryOrderPlaced) { this.entryOrderPlaced = entryOrderPlaced; } // ==================== 止盈订单 ID ==================== /** @return 止盈条件单订单 ID(挂成功后由交易所返回) */ public String getTakeProfitOrderId() { return takeProfitOrderId; } /** 记录止盈条件单订单 ID */ public void setTakeProfitOrderId(String takeProfitOrderId) { this.takeProfitOrderId = takeProfitOrderId; } // ==================== 挂单订单 ID ==================== /** @return 挂单(条件开仓单)订单 ID(挂成功后由交易所返回) */ public String getEntryOrderId() { return entryOrderId; } /** 记录条件开仓单订单 ID */ public void setEntryOrderId(String entryOrderId) { this.entryOrderId = entryOrderId; } public static Builder builder() { return new Builder(); } /** * TraderParam 的流式构造器。 * * <h3>必填项</h3> * {@code direction}、{@code entryPrice}、{@code takeProfitPrice} 必须设置。 * * <h3>默认值</h3> * quantity=1 / entryGridPosition=-1 / takeProfitGridPosition=-1 / 挂单状态均为 false */ public static class Builder { /** 交易方向(必填) */ private Direction direction; /** 条件开仓触发价(必填) */ private BigDecimal entryPrice; /** 止盈触发价(必填) */ private BigDecimal takeProfitPrice; /** 下单数量,默认 "1" */ private String quantity = "1"; /** 止盈价是否挂成功,默认 false */ private boolean takeProfitPlaced = false; /** 挂单价是否挂成功,默认 false */ private boolean entryOrderPlaced = false; /** 挂单价网格位置,默认 -1(未定位) */ private int entryGridPosition = -1; /** 止盈价网格位置,默认 -1(未定位) */ private int takeProfitGridPosition = -1; /** 止盈订单 ID */ private String takeProfitOrderId; /** 挂单订单 ID */ private String entryOrderId; /** 设置交易方向(多 / 空) */ public Builder direction(Direction direction) { this.direction = direction; return this; } /** 设置条件开仓触发价 */ public Builder entryPrice(BigDecimal entryPrice) { this.entryPrice = entryPrice; return this; } /** 设置止盈触发价 */ public Builder takeProfitPrice(BigDecimal takeProfitPrice) { this.takeProfitPrice = takeProfitPrice; return this; } /** 设置下单数量(合约张数) */ public Builder quantity(String quantity) { this.quantity = quantity; return this; } /** 设置止盈价是否已挂成功 */ public Builder takeProfitPlaced(boolean takeProfitPlaced) { this.takeProfitPlaced = takeProfitPlaced; return this; } /** 设置挂单价是否已挂成功 */ public Builder entryOrderPlaced(boolean entryOrderPlaced) { this.entryOrderPlaced = entryOrderPlaced; return this; } /** 设置挂单价在网格队列中的索引位置 */ public Builder entryGridPosition(int entryGridPosition) { this.entryGridPosition = entryGridPosition; return this; } /** 设置止盈价在网格队列中的索引位置 */ public Builder takeProfitGridPosition(int takeProfitGridPosition) { this.takeProfitGridPosition = takeProfitGridPosition; return this; } /** 设置止盈条件单订单 ID */ public Builder takeProfitOrderId(String takeProfitOrderId) { this.takeProfitOrderId = takeProfitOrderId; return this; } /** 设置挂单(条件开仓单)订单 ID */ public Builder entryOrderId(String entryOrderId) { this.entryOrderId = entryOrderId; return this; } public TraderParam build() { return new TraderParam(this); } } }