From c846827b1024f3b6f5a33663f219827c67489d21 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Fri, 22 May 2026 21:31:35 +0800
Subject: [PATCH] refactor(gateApi): 优化条件单挂单逻辑并解决竞态问题
---
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java | 507 ++++++++++++++++++++++++++++++++++++--------------------
1 files changed, 325 insertions(+), 182 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 b1d7814..9ac5617 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -14,7 +14,6 @@
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -24,79 +23,63 @@
import com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionsChannelHandler;
/**
- * Gate 网格交易服务 — 策略核心。
+ * 网格交易策略引擎 — 多空对冲网格。
*
- * <h3>策略概述</h3>
- * 多空双开基底 → 生成价格网格队列 → 条件单监控 → 触发成交后队列动态转移。
- * 每根 K 线更新未实现盈亏(unrealizedPnl),平仓后累加已实现盈亏(cumulativePnl)。
+ * <h3>策略原理</h3>
+ * 以空仓基底入场价(shortBaseEntryPrice)为价格基准,向上/向下各生成一个价格网格队列。
+ * 价格触发网格层级时挂条件单,成交后自动挂止盈单。每笔止盈盈利 = step - minTick。
*
- * <h3>核心机制</h3>
- * <ul>
- * <li><b>条件开仓单</b>:使用 Gate API {@code FuturesPriceTriggeredOrder},服务器监控价格,
- * 达到触发价后以市价 IOC 开仓。相比限价单,条件单仅在触发价到达时才执行,避免提前成交。</li>
- * <li><b>条件单 ID 映射</b>(currentLongOrderIds / currentShortOrderIds):
- * 用同步 Map 管理所有活跃的条件单(订单ID → 止盈价格)。挂条件单时通过回调存入,
- * 订单成交后通过 {@code futures.orders} 推送匹配止盈价并挂止盈单。</li>
- * <li><b>订单订阅(futures.orders)</b>:订单成交(status=finished, finish_as=filled)时,
- * 通过 {@link #onOrderUpdate(String, String, String)} 从 Map 中取出止盈价,
- * 调用 {@code executor.placeTakeProfit} 创建止盈条件单。</li>
- * <li><b>反向条件单</b>:当新网格首元素价格夹在多/空持仓均价之间,
- * 且反向持仓张数不超过 3 张时,额外挂一张反向市价单,通过订单订阅自动挂止盈。</li>
- * </ul>
- *
- * <h3>状态机</h3>
+ * <h3>完整生命周期</h3>
* <pre>
- * WAITING_KLINE → (首K线) → 异步双开基底
- *
- * 仓位推送(dual_long/dual_short) → 基底成交 → 记录入场价
- * → 双基底都成交 → 生成队列 + 初始条件单 + 止盈队列 → ACTIVE
- *
- * ACTIVE:
- * ├─ 每根K线 → 更新 unrealizedPnl → 方向判断
- * │ ├─ closePrice > longPriceQueue[0] → processLongGrid
- * │ └─ closePrice < shortPriceQueue[0] → processShortGrid
- * ├─ processShortGrid: 匹配空仓队列 → 本队补充 → 挂空仓+多仓条件单(止盈价存入Map)
- * ├─ processLongGrid: 匹配多仓队列 → 本队补充 → 挂多仓+空仓条件单(止盈价存入Map)
- * ├─ 订单推送(futures.orders) → onOrderUpdate → Map 匹配止盈价 → 挂止盈条件单
- * ├─ 仓位推送 → 更新均价/持仓量、仓位减少时处理反向单
- * ├─ 平仓推送 → 累加 cumulativePnl
- * ├─ 保证金安全阀 → 超限跳过挂单,队列照常更新
- * └─ cumulativePnl ≥ overallTp 或 ≤ -maxLoss → STOPPED
+ * init() → startGrid() → WAITING_KLINE
+ * ↓
+ * onKline(首根K线) → OPENING → 异步市价双开基底(开多+开空)
+ * ↓
+ * onPositionUpdate() → 基底成交 → baseLongOpened && baseShortOpened
+ * ↓
+ * tryGenerateQueues()
+ * ├── generateShortQueue() ← 空仓价格队列(降序,从 shortBaseEntryPrice-step 向下)
+ * ├── generateLongQueue() ← 多仓价格队列(升序,从 shortBaseEntryPrice+step 向上)
+ * ├── updateGridElements() ← 构建 GridElement 列表 + TraderParam + 全局索引
+ * ├── 挂基座止盈单(ID=0 的 long/short takeProfit)
+ * └── 挂初始条件单(up=-1 多单, down=1 空单)
+ * ↓
+ * state = ACTIVE(每根K线反复执行以下循环)
+ * ↓
+ * onKline() → processLongGrid() + processShortGrid()
+ * ├── 匹配队列元素 → 队列补偿 → 保证金检查
+ * ├── 首元素方向:挂条件开仓单 → 订单ID + GridElement状态同步
+ * └── 反向守卫:在 downGrid 位置挂对向单(价格区间+trigger方向校验)
+ * ↓
+ * onOrderUpdate() ← futures.orders / futures.autoorders 推送
+ * ├── 匹配止盈单ID → 清空止盈状态(已成交)
+ * └── 匹配挂单ID → 挂止盈条件单 → 止盈ID + GridElement状态同步
+ * ↓
+ * onPositionClose() → cumulativePnl 累加
+ * ├── ≥ overallTp → STOPPED
+ * └── ≤ -maxLoss → STOPPED
* </pre>
*
- * <h3>队列转移规则</h3>
- * <ul>
- * <li><b>空仓队列触发</b>(processShortGrid):matched 元素从空仓队列移除,
- * 尾部递减 step 补充新元素;多仓队列以首元素(最小价)递减 step 生成新元素加入。</li>
- * <li><b>多仓队列触发</b>(processLongGrid):matched 元素从多仓队列移除,
- * 尾部递增 step 补充新元素;空仓队列以首元素(最高价)递增 step 生成新元素加入。</li>
- * <li>队列容量超限时截断尾部,保持固定容量。</li>
- * </ul>
- *
- * <h3>止盈机制</h3>
- * <ul>
- * <li>网格触发时,挂条件单的回调中将订单 ID 和止盈价存入 currentLongOrderIds / currentShortOrderIds Map。</li>
- * <li>条件单成交后,{@code futures.orders} 推送触发 {@link #onOrderUpdate},
- * 通过订单 ID 取出止盈价,创建止盈条件单(plan-close-*-position)。</li>
- * <li>止盈条件单:以触发价监控(price_type=最新价,strategy_type=价格触发),
- * 到达后以市价 IOC 平仓(reduce_only=true,price="0")。</li>
- * </ul>
- *
- * <h3>反向条件单条件</h3>
+ * <h3>仓位线动态调整</h3>
* <pre>
- * newFirstPrice > shortEntryPrice AND newFirstPrice < longEntryPrice
- * AND 反向持仓张数 < 3
+ * onPositionUpdate() 中仓位均价变化后:
+ * longEntryPrice ↑ → 取消 高于 longEntryPrice 的空仓挂单(避免逆势空单)
+ * shortEntryPrice ↓ → 取消 低于 shortEntryPrice 的多仓挂单(避免逆势多单)
* </pre>
- * 满足条件时以 newFirstPrice ± step 为止盈价直接挂市价单,通过订单订阅自动挂止盈。
*
- * <h3>未实现盈亏公式(正向合约)</h3>
+ * <h3>关键公式</h3>
* <pre>
- * 多仓: 持仓量 × 合约乘数 × (计价价格 − 开仓均价)
- * 空仓: 持仓量 × 合约乘数 × (开仓均价 − 计价价格)
+ * step = shortBaseEntryPrice × gridRate ← 网格绝对步长
+ * minTick = 10^(-priceScale) ← 交易所最小价格单位
+ * 多止盈 = gridPrice + (step - minTick) ← 多仓止盈价
+ * 空止盈 = gridPrice - (step - minTick) ← 空仓止盈价
+ * 单笔盈利 = (step - minTick) × contractMultiplier × quantity ← USDT
* </pre>
- * 计价价格支持切换:{@link GateConfig.PnLPriceMode#LAST_PRICE 最新成交价} 或
- * {@link GateConfig.PnLPriceMode#MARK_PRICE 标记价格}(通过 {@link #setMarkPrice(BigDecimal)} 注入)。
- * 入场价和持仓量由 {@link #onPositionUpdate(String, Position.ModeEnum, BigDecimal, BigDecimal)} 实时更新。
+ *
+ * <h3>线程模型</h3>
+ * 所有 WS 回调(onKline/onPositionUpdate/onOrderUpdate 等)在 WS 回调线程中串行执行。
+ * 下单/撤单操作提交到 GateTradeExecutor 的单线程池异步执行,避免阻塞 WS 线程。
+ * stopGrid() 会将 state 设为 STOPPED,后续所有 WS 回调直接返回不再处理。
*
* @author Administrator
*/
@@ -358,6 +341,16 @@
lastKlinePrice = closePrice;
updateUnrealizedPnl();
if (state == StrategyState.STOPPED) {
+ try {
+ futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract());
+ } catch (ApiException e) {
+ e.printStackTrace();
+ }
+ closeExistingPositions();
+
+ BigDecimal totalPnl = cumulativePnl.add(unrealizedPnl);
+ log.info("[Gate] 已实现:{}, 未实现:{}, 合计:{}",
+ cumulativePnl, unrealizedPnl, totalPnl);
return;
}
@@ -437,7 +430,29 @@
List<GridElement> allShortOrders = GridElement.findAllShortOrders(longEntryPrice);
if (CollUtil.isNotEmpty(allShortOrders)){
for (GridElement e : allShortOrders) {
- executor.cancelOrder(e.getShortOrderId());
+ executor.cancelConditionalOrder(
+ e.getShortOrderId(),
+ orderId -> {
+ shortEntryTraderIdParam(
+ e,
+ null,
+ false
+ );
+ }
+ );
+
+ if (e.getShortTakeProfitOrderId() != null){
+ executor.cancelConditionalOrder(
+ e.getShortTakeProfitOrderId(),
+ orderId -> {
+ shortTakeProfitTraderIdParam(
+ e,
+ null,
+ false
+ );
+ }
+ );
+ }
}
}
}
@@ -461,7 +476,28 @@
List<GridElement> allLongOrders = GridElement.findAllLongOrders(shortEntryPrice);
if (CollUtil.isNotEmpty(allLongOrders)){
for (GridElement e : allLongOrders) {
- executor.cancelOrder(e.getShortOrderId());
+ executor.cancelConditionalOrder(
+ e.getLongOrderId(),
+ orderId -> {
+ longEntryTraderIdParam(
+ e,
+ null,
+ false
+ );
+ }
+ );
+ if (e.getLongTakeProfitOrderId() != null){
+ executor.cancelConditionalOrder(
+ e.getLongTakeProfitOrderId(),
+ orderId -> {
+ longTakeProfitTraderIdParam(
+ e,
+ null,
+ false
+ );
+ }
+ );
+ }
}
}
}
@@ -490,13 +526,17 @@
return;
}
cumulativePnl = cumulativePnl.add(pnl);
- log.info("[Gate] 盈亏累加:{}, 方向:{}, 累计:{}", pnl, side, cumulativePnl);
+ BigDecimal totalPnl = cumulativePnl.add(unrealizedPnl);
+ log.info("[Gate] 已实现:{}, 未实现:{}, 合计:{}",
+ cumulativePnl, unrealizedPnl, totalPnl);
- if (cumulativePnl.compareTo(config.getOverallTp()) >= 0) {
- log.info("[Gate] 已达止盈目标 {}→已停止", cumulativePnl);
+ if (totalPnl.compareTo(config.getOverallTp()) >= 0) {
+ log.info("[Gate] 已达止盈目标(合计{})→已停止, 已实现:{}, 未实现:{}",
+ totalPnl, cumulativePnl, unrealizedPnl);
state = StrategyState.STOPPED;
- } else if (cumulativePnl.compareTo(config.getMaxLoss().negate()) <= 0) {
- log.info("[Gate] 已达亏损上限 {}→已停止", cumulativePnl);
+ } else if (totalPnl.compareTo(config.getMaxLoss().negate()) <= 0) {
+ log.info("[Gate] 已达亏损上限(合计{})→已停止, 已实现:{}, 未实现:{}",
+ totalPnl, cumulativePnl, unrealizedPnl);
state = StrategyState.STOPPED;
}
}
@@ -530,11 +570,11 @@
null,
false
);
- longEntryTraderIdParam(
- byLongTakeProfitOrderId,
- null,
- false
- );
+// longEntryTraderIdParam(
+// byLongTakeProfitOrderId,
+// null,
+// false
+// );
}
GridElement byShortTakeProfitOrderId = GridElement.findByShortTakeProfitOrderId(orderId);
if (byShortTakeProfitOrderId != null){
@@ -543,11 +583,11 @@
null,
false
);
- shortEntryTraderIdParam(
- byShortTakeProfitOrderId,
- null,
- false
- );
+// shortEntryTraderIdParam(
+// byShortTakeProfitOrderId,
+// null,
+// false
+// );
}
/**
@@ -556,6 +596,11 @@
GridElement longGridElement = GridElement.findByLongOrderId(orderId);
if (longGridElement != null) {
if (longGridElement.isHasLongOrder()){
+ longEntryTraderIdParam(
+ longGridElement,
+ null,
+ false
+ );
if (longGridElement.getLongTakeProfitOrderId() == null){
BigDecimal longTp = longGridElement.getLongTraderParam().getTakeProfitPrice();
if (longTp != null) {
@@ -579,6 +624,11 @@
GridElement shortGridElement = GridElement.findByShortOrderId(orderId);
if (shortGridElement != null) {
if (shortGridElement.isHasShortOrder()){
+ shortEntryTraderIdParam(
+ shortGridElement,
+ null,
+ false
+ );
if (shortGridElement.getShortTakeProfitOrderId() == null){
BigDecimal shortTp = shortGridElement.getShortTraderParam().getTakeProfitPrice();
if (shortTp != null) {
@@ -629,7 +679,7 @@
* @param reason 变更原因
* @param orderType 订单类型(plan-close-long-position 等)
*/
- public void onAutoOrder(String orderId, String status, String reason, String orderType) {
+ public void onAutoOrder(String orderId, String status, String reason, String orderType, String tradeId) {
if (state == StrategyState.STOPPED) {
return;
}
@@ -674,7 +724,9 @@
*/
GridElement longGridElement = GridElement.findByLongOrderId(orderId);
if (longGridElement != null) {
- if (longGridElement.isHasLongOrder()){
+ if (longGridElement.isHasLongOrder() && !tradeId.equals("0")){
+
+ onUserTradeLongEntry(longGridElement);
if (longGridElement.getLongTakeProfitOrderId() == null){
BigDecimal longTp = longGridElement.getLongTraderParam().getTakeProfitPrice();
if (longTp != null) {
@@ -697,7 +749,9 @@
}
GridElement shortGridElement = GridElement.findByShortOrderId(orderId);
if (shortGridElement != null) {
- if (shortGridElement.isHasShortOrder()){
+ if (shortGridElement.isHasShortOrder() && !tradeId.equals("0")){
+
+ onUserTradeShortEntry(shortGridElement);
if (shortGridElement.getShortTakeProfitOrderId() == null){
BigDecimal shortTp = shortGridElement.getShortTraderParam().getTakeProfitPrice();
if (shortTp != null) {
@@ -713,6 +767,129 @@
);
});
log.info("[Gate] 空单成交匹配止盈, orderId:{}, 止盈价:{}, size:{}", orderId, shortTp, config.getQuantity());
+ }
+ }
+ }
+ }
+ }
+
+ private void onUserTradeShortEntry(GridElement gridElement) {
+ if (!isMarginSafe()) {
+ log.warn("[Gate] 保证金超限,跳过挂条件单");
+ } else {
+
+ /**
+ * 下一个开仓位置
+ * 获取队列第一个元素的价格对应的网格
+ * 判断网格是否能开空仓,如果不能则跳过
+ * 前进方向挂空仓条件单
+ * 后置方向挂多空条件单
+ */
+ //下一个开仓位置
+ GridElement UpGridElement = GridElement.findById(gridElement.getDownId());
+ BigDecimal newLongFirst = UpGridElement.getGridPrice();
+
+ // 判断网格是否能开空仓,如果不能则跳过
+ if (UpGridElement != null) {
+
+ if (!UpGridElement.isHasShortOrder() && shortEntryPrice.compareTo(newLongFirst) > 0) {
+
+ TraderParam upShortTraderParam = UpGridElement.getShortTraderParam();
+ placeEntryOrderWithPreFlag(UpGridElement, false,
+ upShortTraderParam.getEntryPrice(),
+ FuturesPriceTrigger.RuleEnum.NUMBER_2,
+ negate(upShortTraderParam.getQuantity()));
+ }
+ int i = gridElement.getUpId();
+ GridElement downGridElement = GridElement.findById(i);
+ if (downGridElement != null){
+
+ BigDecimal downGridPrice = downGridElement.getGridPrice();
+
+ TraderParam downShortTraderParam = downGridElement.getShortTraderParam();
+ if (
+ !downGridElement.isHasShortOrder() &&
+ downGridPrice.compareTo(longEntryPrice) <= 0 &&
+ downGridPrice.compareTo(shortEntryPrice) >= 0
+ ){
+ placeEntryOrderWithPreFlag(downGridElement, false,
+ downShortTraderParam.getEntryPrice(),
+ FuturesPriceTrigger.RuleEnum.NUMBER_1,
+ negate(downShortTraderParam.getQuantity()));
+
+ }
+
+ TraderParam downLongTraderParam = downGridElement.getLongTraderParam();
+ if (
+ !downGridElement.isHasLongOrder() &&
+ downGridPrice.compareTo(longEntryPrice) <= 0
+ ){
+ placeEntryOrderWithPreFlag(downGridElement, true,
+ downLongTraderParam.getEntryPrice(),
+ FuturesPriceTrigger.RuleEnum.NUMBER_1,
+ downLongTraderParam.getQuantity());
+ }
+ }
+ }
+ }
+ }
+
+ private void onUserTradeLongEntry(GridElement gridElement) {
+ if (!isMarginSafe()) {
+ log.warn("[Gate] 保证金超限,跳过挂条件单");
+ } else {
+
+ /**
+ * 下一个开仓位置
+ * 获取队列第一个元素的价格对应的网格
+ * 判断网格是否能开多仓,如果不能则跳过
+ * 前进方向挂多仓条件单
+ * 后置方向挂多空条件单
+ */
+ //下一个开仓位置
+ GridElement UpGridElement = GridElement.findById(gridElement.getUpId());
+ BigDecimal newLongFirst = UpGridElement.getGridPrice() ;
+
+ // 判断网格是否能开多仓,如果不能则跳过
+ if (UpGridElement != null) {
+
+ if (!UpGridElement.isHasLongOrder() && longEntryPrice.compareTo(newLongFirst) < 0) {
+ TraderParam upLongTraderParam = UpGridElement.getLongTraderParam();
+ placeEntryOrderWithPreFlag(UpGridElement, true,
+ upLongTraderParam.getEntryPrice(),
+ FuturesPriceTrigger.RuleEnum.NUMBER_1,
+ config.getQuantity());
+ }
+
+ int i = gridElement.getDownId();
+ GridElement downGridElement = GridElement.findById(i);
+ if (downGridElement != null){
+
+ BigDecimal downGridPrice = downGridElement.getGridPrice();
+
+ TraderParam downLongTraderParam = downGridElement.getLongTraderParam();
+ if (
+ !downGridElement.isHasLongOrder() &&
+ downGridPrice.compareTo(shortEntryPrice) >= 0 &&
+ downGridPrice.compareTo(longEntryPrice) <= 0
+ ){
+ placeEntryOrderWithPreFlag(downGridElement, true,
+ downLongTraderParam.getEntryPrice(),
+ FuturesPriceTrigger.RuleEnum.NUMBER_2,
+ config.getQuantity());
+
+ }
+
+ TraderParam shortTraderParam = downGridElement.getShortTraderParam();
+ if (
+ !downGridElement.isHasShortOrder() &&
+ downGridPrice.compareTo(shortEntryPrice) >= 0
+ ){
+
+ placeEntryOrderWithPreFlag(downGridElement, false,
+ shortTraderParam.getEntryPrice(),
+ FuturesPriceTrigger.RuleEnum.NUMBER_2,
+ negate(config.getQuantity()));
}
}
}
@@ -792,33 +969,17 @@
Integer upId = baseGridElement.getUpId();
GridElement upGridElementOne = GridElement.findById(upId);
BigDecimal longTp = upGridElementOne.getGridPrice();
- executor.placeConditionalEntryOrder(
+ placeEntryOrderWithPreFlag(upGridElementOne, true,
longTp,
FuturesPriceTrigger.RuleEnum.NUMBER_1,
- config.getQuantity(),
- orderId -> {
- longEntryTraderIdParam(
- upGridElementOne,
- orderId,
- true
- );
- },
- null);
+ config.getQuantity());
Integer downId = baseGridElement.getDownId();
GridElement downGridElementOne = GridElement.findById(downId);
BigDecimal shortTp = downGridElementOne.getGridPrice();
- executor.placeConditionalEntryOrder(
+ placeEntryOrderWithPreFlag(downGridElementOne, false,
shortTp,
FuturesPriceTrigger.RuleEnum.NUMBER_2,
- negate(config.getQuantity()),
- orderId -> {
- shortEntryTraderIdParam(
- downGridElementOne,
- orderId,
- true
- );
- },
- null);
+ negate(config.getQuantity()));
state = StrategyState.ACTIVE;
}
@@ -1077,24 +1238,13 @@
// 判断网格是否能开空仓,如果不能则跳过
if (UpGridElement != null) {
- if (!UpGridElement.isHasShortOrder()) {
+ if (!UpGridElement.isHasShortOrder() && shortEntryPrice.compareTo(newLongFirst) > 0) {
- //挂空仓条件单
TraderParam upShortTraderParam = UpGridElement.getShortTraderParam();
- executor.placeConditionalEntryOrder(
+ placeEntryOrderWithPreFlag(UpGridElement, false,
upShortTraderParam.getEntryPrice(),
FuturesPriceTrigger.RuleEnum.NUMBER_2,
- negate(upShortTraderParam.getQuantity()),
- orderId ->
- {
- shortEntryTraderIdParam(
- UpGridElement,
- orderId,
- true
- );
- },
- null
- );
+ negate(upShortTraderParam.getQuantity()));
}
int i = UpGridElement.getId() + 2;
GridElement downGridElement = GridElement.findById(i);
@@ -1108,20 +1258,10 @@
downGridPrice.compareTo(longEntryPrice) <= 0 &&
downGridPrice.compareTo(shortEntryPrice) >= 0
){
- executor.placeConditionalEntryOrder(
+ placeEntryOrderWithPreFlag(downGridElement, false,
downShortTraderParam.getEntryPrice(),
- FuturesPriceTrigger.RuleEnum.NUMBER_2,
- negate(downShortTraderParam.getQuantity()),
- orderId ->
- {
- shortEntryTraderIdParam(
- downGridElement,
- orderId,
- true
- );
- },
- null
- );
+ FuturesPriceTrigger.RuleEnum.NUMBER_1,
+ negate(downShortTraderParam.getQuantity()));
}
@@ -1130,20 +1270,10 @@
!downGridElement.isHasLongOrder() &&
downGridPrice.compareTo(longEntryPrice) <= 0
){
- executor.placeConditionalEntryOrder(
+ placeEntryOrderWithPreFlag(downGridElement, true,
downLongTraderParam.getEntryPrice(),
FuturesPriceTrigger.RuleEnum.NUMBER_1,
- downLongTraderParam.getQuantity(),
- orderId ->
- {
- longEntryTraderIdParam(
- downGridElement,
- orderId,
- true
- );
- },
- null
- );
+ downLongTraderParam.getQuantity());
}
}
}
@@ -1214,23 +1344,12 @@
// 判断网格是否能开多仓,如果不能则跳过
if (UpGridElement != null) {
- if (!UpGridElement.isHasLongOrder()) {
- //挂多仓条件单
+ if (!UpGridElement.isHasLongOrder() && longEntryPrice.compareTo(newLongFirst) < 0) {
TraderParam upLongTraderParam = UpGridElement.getLongTraderParam();
- executor.placeConditionalEntryOrder(
+ placeEntryOrderWithPreFlag(UpGridElement, true,
upLongTraderParam.getEntryPrice(),
FuturesPriceTrigger.RuleEnum.NUMBER_1,
- config.getQuantity(),
- orderId ->
- {
- longEntryTraderIdParam(
- UpGridElement,
- orderId,
- true
- );
- },
- null
- );
+ config.getQuantity());
}
int i = UpGridElement.getId() - 2;
@@ -1245,20 +1364,10 @@
downGridPrice.compareTo(shortEntryPrice) >= 0 &&
downGridPrice.compareTo(longEntryPrice) <= 0
){
- executor.placeConditionalEntryOrder(
+ placeEntryOrderWithPreFlag(downGridElement, true,
downLongTraderParam.getEntryPrice(),
- FuturesPriceTrigger.RuleEnum.NUMBER_1,
- config.getQuantity(),
- orderId ->
- {
- longEntryTraderIdParam(
- downGridElement,
- orderId,
- true
- );
- },
- null
- );
+ FuturesPriceTrigger.RuleEnum.NUMBER_2,
+ config.getQuantity());
}
@@ -1268,20 +1377,10 @@
downGridPrice.compareTo(shortEntryPrice) >= 0
){
- executor.placeConditionalEntryOrder(
+ placeEntryOrderWithPreFlag(downGridElement, false,
shortTraderParam.getEntryPrice(),
FuturesPriceTrigger.RuleEnum.NUMBER_2,
- negate(config.getQuantity()),
- orderId ->
- {
- shortEntryTraderIdParam(
- downGridElement,
- orderId,
- true
- );
- },
- null
- );
+ negate(config.getQuantity()));
}
}
}
@@ -1324,6 +1423,50 @@
}
/**
+ * 预设标志位后提交条件开仓单,防止异步回调导致的竞态重复挂单。
+ *
+ * <p>在调用 {@link GateTradeExecutor#placeConditionalEntryOrder} 之前同步设置
+ * {@code isHasLongOrder / isHasShortOrder},关闭 WS 线程与 Executor 线程之间的
+ * 检查-下单时间窗口。API 失败时自动回滚标志位。
+ *
+ * @param gridElement 目标网格元素
+ * @param isLong true=多仓下单,false=空仓下单
+ * @param triggerPrice 触发价
+ * @param rule 触发规则
+ * @param size 开仓张数
+ */
+ private void placeEntryOrderWithPreFlag(GridElement gridElement, boolean isLong,
+ BigDecimal triggerPrice,
+ FuturesPriceTrigger.RuleEnum rule,
+ String size) {
+ if (isLong) {
+ gridElement.setHasLongOrder(true);
+ } else {
+ gridElement.setHasShortOrder(true);
+ }
+ executor.placeConditionalEntryOrder(triggerPrice, rule, size,
+ orderId -> {
+ if (isLong) {
+ longEntryTraderIdParam(gridElement, orderId, true);
+ } else {
+ shortEntryTraderIdParam(gridElement, orderId, true);
+ }
+ },
+ () -> {
+ if (isLong) {
+ gridElement.setHasLongOrder(false);
+ gridElement.setLongOrderId(null);
+ } else {
+ gridElement.setHasShortOrder(false);
+ gridElement.setShortOrderId(null);
+ }
+ GridElement.refreshIndices();
+ log.warn("[Gate] 条件单创建失败,回滚标志位 gridId:{}, isLong:{}", gridElement.getId(), isLong);
+ }
+ );
+ }
+
+ /**
* 根据持仓和当前价格计算未实现盈亏。
*
* <h3>正向合约公式</h3>
--
Gitblit v1.9.1