From 2af15077fb2e66500e955ffebc44ecad42e2dd66 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Tue, 19 May 2026 11:18:56 +0800
Subject: [PATCH] feat(ws): 添加订单更新ID日志记录功能
---
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java | 782 +++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 615 insertions(+), 167 deletions(-)
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
index 00b09c6..ddbe3b9 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -1,5 +1,6 @@
package com.xcong.excoin.modules.gateApi;
+import cn.hutool.core.collection.CollUtil;
import io.gate.gateapi.ApiClient;
import io.gate.gateapi.ApiException;
import io.gate.gateapi.GateApiException;
@@ -54,10 +55,10 @@
* ├─ 每根K线 → 更新 unrealizedPnl → 方向判断
* │ ├─ closePrice > longPriceQueue[0] → processLongGrid
* │ └─ closePrice < shortPriceQueue[0] → processShortGrid
- * ├─ processShortGrid: 匹配空仓队列 → 队列转移→ 挂新空仓+多仓条件单
- * ├─ processLongGrid: 匹配多仓队列 → 队列转移→ 挂新多仓+空仓条件单
- * ├─ 订单推送(新) → onOrderUpdate → Map 匹配止盈价 → 挂止盈条件单
- * ├─ 仓位推送 → 更新均价/持仓量、处理反向单
+ * ├─ processShortGrid: 匹配空仓队列 → 本队补充 → 挂空仓+多仓条件单(止盈价存入Map)
+ * ├─ processLongGrid: 匹配多仓队列 → 本队补充 → 挂多仓+空仓条件单(止盈价存入Map)
+ * ├─ 订单推送(futures.orders) → onOrderUpdate → Map 匹配止盈价 → 挂止盈条件单
+ * ├─ 仓位推送 → 更新均价/持仓量、仓位减少时处理反向单
* ├─ 平仓推送 → 累加 cumulativePnl
* ├─ 保证金安全阀 → 超限跳过挂单,队列照常更新
* └─ cumulativePnl ≥ overallTp 或 ≤ -maxLoss → STOPPED
@@ -138,7 +139,7 @@
/** 基底空头入场价 */
private BigDecimal shortBaseEntryPrice;
- /** 基底多头入场价 */
+ /** 基底多头入场价(仅记录,当前未被业务逻辑消费,保留以备后续使用) */
private BigDecimal longBaseEntryPrice;
/** 基底多头是否已开 */
private volatile boolean baseLongOpened = false;
@@ -360,15 +361,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;
}
@@ -389,11 +398,14 @@
* <li><b>有仓位 (size ≠ 0)</b>:
* <ul>
* <li>首次开仓(基底):标记 baseOpened=true,记录基底入场价,双基底都成交后生成网格队列</li>
- * <li>仓位净增加(size > 之前记录值):说明网格触发了新开仓 → 取对应方向队列首元素为止盈价,设止盈条件单</li>
- * <li>仓位减少或不变(止盈平仓后):仅更新 positionSize,不重复设止盈</li>
+ * <li>仓位净减少(size.abs() < 之前记录值):止盈平仓后 → 检查反向条件单条件 →
+ * 满足时以 entryPrice ± step 为止盈价挂反向市价单(订单ID + 止盈价存入 Map)</li>
+ * <li>仓位净增加或不变:仅更新 positionSize,止盈由 {@link #onOrderUpdate} 通过订单订阅匹配处理</li>
* </ul>
* </li>
* <li><b>无仓位 (size = 0)</b>:清空活跃标记和持仓量</li>
+ * <li><b>Map 截断</b>:currentLongOrderIds / currentShortOrderIds 超过 5 个时,
+ * 从 LinkedHashMap 头部删除最旧条目,保留最新 5 个</li>
* </ul>
*
* @param contract 合约名称
@@ -419,32 +431,19 @@
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;
+ //取消多仓位线以上的开空仓挂单
+ List<GridElement> allShortOrders = GridElement.findAllShortOrders(longEntryPrice);
+ if (CollUtil.isNotEmpty(allShortOrders)){
+ for (GridElement e : allShortOrders) {
+ executor.cancelOrder(e.getShortOrderId());
+ }
+ }
}
} 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) {
@@ -456,32 +455,19 @@
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();
+ //取消多仓位线以上的开空仓挂单
+ List<GridElement> allLongOrders = GridElement.findAllLongOrders(shortEntryPrice);
+ if (CollUtil.isNotEmpty(allLongOrders)){
+ for (GridElement e : allLongOrders) {
+ executor.cancelOrder(e.getShortOrderId());
+ }
+ }
}
} 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();
- }
- }
}
}
}
@@ -533,19 +519,122 @@
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()));
+
+ /**
+ * 匹配止盈单止盈
+ */
+ GridElement byLongTakeProfitOrderId = GridElement.findByLongTakeProfitOrderId(orderId);
+ if (byLongTakeProfitOrderId != null){
+ longTakeProfitTraderIdParam(
+ byLongTakeProfitOrderId,
+ null,
+ false
+ );
+ longEntryTraderIdParam(
+ byLongTakeProfitOrderId,
+ null,
+ false
+ );
+ }
+ GridElement byShortTakeProfitOrderId = GridElement.findByShortTakeProfitOrderId(orderId);
+ if (byShortTakeProfitOrderId != null){
+ shortTakeProfitTraderIdParam(
+ byShortTakeProfitOrderId,
+ null,
+ false
+ );
+ shortEntryTraderIdParam(
+ byShortTakeProfitOrderId,
+ 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());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 用户私有成交回调。由 {@link com.xcong.excoin.modules.gateApi.wsHandler.handler.UserTradesChannelHandler}
+ * 在收到 {@code futures.usertrades} 推送时调用。
+ *
+ * @param contract 合约名称
+ * @param orderId 订单 ID
+ * @param price 成交价格
+ * @param size 成交数量
+ * @param role 用户角色(maker / taker)
+ * @param fee 手续费
+ */
+ public void onUserTrade(String contract, String orderId, BigDecimal price, String size, String role, BigDecimal fee) {
+ if (state == StrategyState.STOPPED) {
return;
}
- 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());
+ log.info("[Gate] 成交明细, 合约:{}, 订单ID:{}, 价格:{}, 数量:{}, 角色:{}, 手续费:{}",
+ contract, orderId, price, size, role, fee);
+ }
+
+ /**
+ * 自动订单(条件单)状态变更回调。
+ * 由 {@link com.xcong.excoin.modules.gateApi.wsHandler.handler.AutoOrdersChannelHandler}
+ * 在收到 {@code futures.autoorders} 推送时调用。
+ *
+ * @param orderId 条件单 ID
+ * @param status 订单状态(open / finished / cancelled)
+ * @param reason 变更原因
+ * @param orderType 订单类型(plan-close-long-position 等)
+ */
+ public void onAutoOrder(String orderId, String status, String reason, String orderType) {
+ if (state == StrategyState.STOPPED) {
+ return;
}
+ log.info("[Gate] 条件单状态变更, id:{}, status:{}, reason:{}, order_type:{}",
+ orderId, status, reason, orderType);
}
// ---- 网格队列处理 ----
@@ -554,42 +643,147 @@
* 尝试生成网格队列。双基底(多+空)都成交后才触发:
* <ol>
* <li>生成空仓价格队列(降序)和多仓价格队列(升序)</li>
- * <li>初始化止盈队列:多仓首元素 + step、空仓首元素 − step</li>
- * <li>挂初始多仓条件单(触发价 = 多仓队列首元素,rule=NUMBER_1 ≥触发价时开多)</li>
- * <li>挂初始空仓条件单(触发价 = 空仓队列首元素,rule=NUMBER_2 ≤触发价时开空)</li>
- * <li>条件单 ID 存入对应 currentXxxOrderIds 集合</li>
+ * <li>挂初始多仓条件单(触发价 = 多仓队列首元素,rule=NUMBER_1 ≥触发价时开多),
+ * 止盈价 = 触发价 + step,通过 onSuccess 回调将 orderId → 止盈价存入 currentLongOrderIds</li>
+ * <li>挂初始空仓条件单(触发价 = 空仓队列首元素,rule=NUMBER_2 ≤触发价时开空),
+ * 止盈价 = 触发价 − step,通过 onSuccess 回调将 orderId → 止盈价存入 currentShortOrderIds</li>
* <li>状态切换为 ACTIVE</li>
* </ol>
+ * 条件单成交后由 {@link #onOrderUpdate} 匹配止盈价并挂止盈条件单。
*/
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());
+ baseGridElement.setHasLongOrder(true);
+ //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());
+ baseGridElement.setHasShortOrder(true);
+ 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();
}
/**
@@ -600,13 +794,17 @@
*/
private void generateShortQueue() {
shortPriceQueue.clear();
- BigDecimal step = shortBaseEntryPrice.multiply(config.getGridRate()).setScale(1, RoundingMode.HALF_UP);
+ int prec = config.getPriceScale();
+ BigDecimal step = shortBaseEntryPrice.multiply(config.getGridRate()).setScale(prec, 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);
- }
+ BigDecimal elem = shortBaseEntryPrice.subtract(step).setScale(prec, RoundingMode.HALF_UP);
+ for (int i = 0; i < config.getGridQueueSize(); i++) {
+ shortPriceQueue.add(elem);
+ elem = elem.subtract(step).setScale(prec, RoundingMode.HALF_UP);
+ if (elem.compareTo(BigDecimal.ZERO) <= 0) {
+ break;
+ }
+ }
shortPriceQueue.sort((a, b) -> b.compareTo(a));
log.info("[Gate] 空队列:{}", shortPriceQueue);
}
@@ -618,14 +816,124 @@
*/
private void generateLongQueue() {
longPriceQueue.clear();
+ int prec = config.getPriceScale();
BigDecimal step = config.getStep();
- BigDecimal elem = shortBaseEntryPrice.add(step).setScale(1, RoundingMode.HALF_UP);
+ BigDecimal elem = shortBaseEntryPrice.add(step).setScale(prec, RoundingMode.HALF_UP);
for (int i = 0; i < config.getGridQueueSize(); i++) {
longPriceQueue.add(elem);
- elem = elem.add(step).setScale(1, RoundingMode.HALF_UP);
+ elem = elem.add(step).setScale(prec, RoundingMode.HALF_UP);
}
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();
+ //根据精度转换成小数
+ int prec = config.getPriceScale();
+ BigDecimal minTick = BigDecimal.ONE.scaleByPowerOfTen(-prec);
+ BigDecimal step = config.getStep().subtract(minTick);
+ 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(prec, RoundingMode.HALF_UP))
+ .quantity(qty)
+ .build();
+ TraderParam shortParam = TraderParam.builder()
+ .direction(TraderParam.Direction.SHORT)
+ .entryPrice(price)
+ .takeProfitPrice(price.subtract(step).setScale(prec, 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(prec, RoundingMode.HALF_UP))
+ .quantity(qty)
+ .build();
+ TraderParam shortParam = TraderParam.builder()
+ .direction(TraderParam.Direction.SHORT)
+ .entryPrice(price)
+ .takeProfitPrice(price.subtract(step).setScale(prec, 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 downId = (i == 0) ? 0 : id - 1;
+ Integer upId = (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(prec, RoundingMode.HALF_UP))
+ .quantity(qty)
+ .build();
+ TraderParam shortParam = TraderParam.builder()
+ .direction(TraderParam.Direction.SHORT)
+ .entryPrice(price)
+ .takeProfitPrice(price.subtract(step).setScale(prec, 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);
}
/**
@@ -638,28 +946,26 @@
* <h3>执行流程</h3>
* <ol>
* <li>匹配队列元素 → 为空则直接返回,不触发</li>
- * <li>空仓队列:移除 matched 元素,从尾部最小值递减 step 补充等量新元素,重新降序排序</li>
- * <li>多仓队列:以多仓首元素(最小价)为基准递减 step,生成 matched.size() 个新元素加入,
- * 升序排序,超限截尾</li>
- * <li>空仓止盈队列:始终加入新空仓首元素 − step(降序排序),不受守卫限制</li>
- * <li>保证金检查 → 不安全则跳过挂单(队列和止盈队列照常更新),安全则继续</li>
- * <li>挂新空仓条件单(触发价 = 新空仓首元素,rule=NUMBER_2 ≤触发价时开空,size=负)</li>
- * <li>多仓条件单守卫:newLongFirst < longEntryPrice 时才执行
- * → 取消所有旧多仓条件单(currentLongOrderIds) → 清空集合 →
- * 多仓止盈队列加入 newLongFirst + step →
- * 挂新多仓条件单(触发价 = newLongFirst,rule=NUMBER_1 ≥触发价时开多,size=正);
- * 不满足时保持旧多仓条件单不变,也不更新多仓止盈队列</li>
- * <li>反向开多判断:newShortFirst > shortEntryPrice 且 < longEntryPrice 且 longPositionSize < 3
- * → 挂反向条件多单(触发价 = newShortFirst),止盈价 = newShortFirst + step 加入多仓止盈队列</li>
+ * <li>空仓队列:移除 matched 元素,从尾部递减 step 补充等量新元素,重新降序排序</li>
+ * <li>多仓队列:<b>不再更新</b>(队列转移逻辑已移除)</li>
+ * <li>保证金检查 → 不安全则跳过挂单(队列照常更新),安全则继续</li>
+ * <li>挂新空仓条件单(触发价 = newShortFirst,rule=NUMBER_2,止盈 = newShortFirst − step,
+ * orderId → 止盈价存入 currentShortOrderIds)</li>
+ * <li>多仓条件单守卫:newLongFirst = newShortFirst + step × 2,
+ * 若 newLongFirst < longEntryPrice → 挂多仓条件单(止盈 = newLongFirst + step,
+ * orderId → 止盈价存入 currentLongOrderIds)</li>
* </ol>
+ * 条件单成交后由 {@link #onOrderUpdate} 匹配止盈价并挂止盈条件单。
+ * 反向条件单不再在此处理,改为在 {@link #onPositionUpdate} 仓位净减少时触发。
*
* @param currentPrice 当前 K 线收盘价(最新成交价)
*/
private void processShortGrid(BigDecimal currentPrice) {
+ int prec = config.getPriceScale();
List<BigDecimal> matched = new ArrayList<>();
synchronized (shortPriceQueue) {
for (BigDecimal p : shortPriceQueue) {
- if (p.compareTo(currentPrice) > 0) {
+ if (p.compareTo(currentPrice) >= 0) {
matched.add(p);
} else {
break;
@@ -676,32 +982,112 @@
BigDecimal min = shortPriceQueue.isEmpty() ? matched.get(matched.size() - 1) : shortPriceQueue.get(shortPriceQueue.size() - 1);
BigDecimal gridStep = config.getStep();
for (int i = 0; i < matched.size(); i++) {
- min = min.subtract(gridStep).setScale(1, RoundingMode.HALF_UP);
+ min = min.subtract(gridStep).setScale(prec, RoundingMode.HALF_UP);
shortPriceQueue.add(min);
}
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(prec, 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.isHasLongOrder()){
+ 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(currentPrice) < 0 &&
+ 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
+ );
+
+ }
}
}
@@ -718,28 +1104,26 @@
* <h3>执行流程</h3>
* <ol>
* <li>匹配队列元素 → 为空则直接返回,不触发</li>
- * <li>多仓队列:移除 matched 元素,从尾部最大值递增 step 补充等量新元素,重新升序排序</li>
- * <li>空仓队列:以空仓首元素(最高价)为基准递增 step,生成 matched.size() 个新元素加入,
- * 降序排序,超限截尾</li>
- * <li>多仓止盈队列:始终加入新多仓首元素 + step(升序排序),不受守卫限制</li>
- * <li>保证金检查 → 不安全则跳过挂单(队列和止盈队列照常更新),安全则继续</li>
- * <li>挂新多仓条件单(触发价 = 新多仓首元素,rule=NUMBER_1 ≥触发价时开多,size=正)</li>
- * <li>空仓条件单守卫:newShortFirst > shortEntryPrice 时才执行
- * → 取消所有旧空仓条件单(currentShortOrderIds) → 清空集合 →
- * 空仓止盈队列加入 newShortFirst − step →
- * 挂新空仓条件单(触发价 = newShortFirst,rule=NUMBER_2 ≤触发价时开空,size=负);
- * 不满足时保持旧空仓条件单不变,也不更新空仓止盈队列</li>
- * <li>反向开空判断:newLongFirst > shortEntryPrice 且 < longEntryPrice 且 shortPositionSize < 3
- * → 挂反向条件空单(触发价 = newLongFirst),止盈价 = newLongFirst − step 加入空仓止盈队列</li>
+ * <li>多仓队列:移除 matched 元素,从尾部递增 step 补充等量新元素,重新升序排序</li>
+ * <li>空仓队列:<b>不再更新</b>(队列转移逻辑已移除)</li>
+ * <li>保证金检查 → 不安全则跳过挂单(队列照常更新),安全则继续</li>
+ * <li>挂新多仓条件单(触发价 = newLongFirst,rule=NUMBER_1,止盈 = newLongFirst + step,
+ * orderId → 止盈价存入 currentLongOrderIds)</li>
+ * <li>空仓条件单守卫:newShortFirst = newLongFirst − step × 2,
+ * 若 newShortFirst > shortEntryPrice → 挂空仓条件单(止盈 = newShortFirst − step,
+ * orderId → 止盈价存入 currentShortOrderIds)</li>
* </ol>
+ * 条件单成交后由 {@link #onOrderUpdate} 匹配止盈价并挂止盈条件单。
+ * 反向条件单不再在此处理,改为在 {@link #onPositionUpdate} 仓位净减少时触发。
*
* @param currentPrice 当前 K 线收盘价(最新成交价)
*/
private void processLongGrid(BigDecimal currentPrice) {
+ int prec = config.getPriceScale();
List<BigDecimal> matched = new ArrayList<>();
synchronized (longPriceQueue) {
for (BigDecimal p : longPriceQueue) {
- if (p.compareTo(currentPrice) < 0) {
+ if (p.compareTo(currentPrice) <= 0) {
matched.add(p);
} else {
break;
@@ -752,56 +1136,120 @@
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);
BigDecimal gridStep = config.getStep();
for (int i = 0; i < matched.size(); i++) {
- max = max.add(gridStep).setScale(1, RoundingMode.HALF_UP);
+ max = max.add(gridStep).setScale(prec, RoundingMode.HALF_UP);
longPriceQueue.add(max);
}
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);
-// log.info("[Gate] 空队列增加:{}", elem);
-// }
-// shortPriceQueue.sort((a, b) -> b.compareTo(a));
-// while (shortPriceQueue.size() > config.getGridQueueSize()) {
-// shortPriceQueue.remove(shortPriceQueue.size() - 1);
-// }
-// log.info("[Gate] 现空队列:{}", shortPriceQueue);
-// }
-
-
+ 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(prec, 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(currentPrice) > 0 &&
+ downGridPrice.compareTo(longEntryPrice) <= 0 &&
+ downGridPrice.compareTo(shortEntryPrice) >= 0
+ ){
+ executor.placeConditionalEntryOrder(
+ downLongTraderParam.getEntryPrice(),
+ FuturesPriceTrigger.RuleEnum.NUMBER_1,
+ config.getQuantity(),
+ orderId ->
+ {
+ longEntryTraderIdParam(
+ downGridElement,
+ orderId,
+ true
+ );
+ },
+ null
+ );
+
+ }
}
}
--
Gitblit v1.9.1