From f55640b503ec7d40c72f7943b9227ae60452e1c9 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Mon, 18 May 2026 16:40:11 +0800
Subject: [PATCH] 第二个版本
---
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java | 642 +++++++++++++++++++++++------
src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java | 372 ++++++++++++++++
src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java | 222 ++++++++++
src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java | 42
src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java | 32 +
src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java | 7
6 files changed, 1,161 insertions(+), 156 deletions(-)
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
index 094eaab..7153f7a 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
+++ b/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 */
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 409f59c..6ffb218 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/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
+ );
+
+ }
}
}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
index ee5151c..da78ba3 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
+++ b/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);
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
index e21b897..484d8ac 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
+++ b/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);
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java b/src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java
new file mode 100644
index 0000000..e34f80c
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java
@@ -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);
+ }
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java b/src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java
new file mode 100644
index 0000000..bce7886
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java
@@ -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);
+ }
+ }
+}
--
Gitblit v1.9.1