审计日期:2026-08-03
文件:src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
审查范围:全部 2147 行,含交叉验证GridElement/TraderParam/GateConfig/GateTradeExecutor
代码结构清晰,多空双方向网格交易的主流程(K线驱动 → 条件单挂单 → 成交回调 → 止损追单 → 级联查表)完整且自洽。发现 4 个 Bug,其中 1 个 Critical 直接影响止损级联计数准确性。
位置
handleLongStopLossTriggered — 行 1283 ~ 1287handleShortStopLossTriggered — 行 1494 ~ 1498问题代码
boolean sameGrid = (gridId == lastLongStopLossGridId);
if (sameGrid) {
log.info("[Gate] 多仓止损触发 同网格, 忽略");
return;
}
lastLongStopLossGridId = gridId;
accumulatedLongLossCount++;
根因
extendLongStopLoss / extendShortStopLoss 中当 gridCount == 1 时,所有止损单挂在**同一个 gridId**:
// extendLongStopLoss,行 1759-1761
if (gridCount == 1) {
orders[0] = totalOrders; // 多张止损单 → 同一个 gridId
}
// 行 1789:gridId - 2 - 0 = gridId - 2,同一个位置
以 baseQty=2, qty=2, filledQty=4 为例:
- gridCount = 2/2 = 1,totalOrders = 4/2 = 2
- 2 单止损全部挂在 gridId-2
- 第 1 单触发 → lastLongStopLossGridId = gridId,count = 1
- 第 2 单触发 → sameGrid = true → 被丢弃,count 少 1
为什么可以用 removeXxxStopLossOrderId 替代
handleLongStopLossTriggered 第一步就是 gridElement.removeLongStopLossOrderId(orderId),从列表中删除该 orderId。refreshIndices() 会重建全局索引,确保同一 orderId 不会二次匹配。
重复推送的防线:若 WS 对**同一订单**推送两次,第二次
findByLongStopLossOrderId找不到(已从索引清除),直接落到后续匹配逻辑,无副作用。
sameGrid 是**按 gridId 去重**,粒度太粗,误杀了**不同 orderId 但同一个 gridId** 的正常多单触发。
修复方案
private void handleLongStopLossTriggered(GridElement gridElement, String orderId) {
// 用 remove 返回值判断是否重复推送,代替 sameGrid
if (!gridElement.removeLongStopLossOrderId(orderId)) {
log.info("[Gate] 多仓止损重复推送 orderId:{}, 忽略", orderId);
return;
}
int gridId = gridElement.getId();
lastLongStopLossGridId = gridId;
accumulatedLongLossCount++;
log.info("[Gate] 多仓止损触发 gridId:{}, 止损次数:{}", gridId, accumulatedLongLossCount);
int newEntryGridId = gridId + 1;
// ... 后续追单逻辑不变 ...
}
handleShortStopLossTriggered 同理(gridId - 1)。
影响范围
accumulatedLongLossCount / accumulatedShortLossCount 少计数STOP_LOSS_RULES 级联规则表推进不到应有行数baseQty/qty 小),问题越严重(所有止损同格)| 方法 | 行号 | 功能 | 调用情况 |
|---|---|---|---|
checkLastTakeProfitAndRestart() |
1549 | 止盈全部清空后检查跨度重启 | 全项目检索 → 0 处调用 |
placeOpponentTakeProfit() |
2125 | 旧版对手止盈挂单逻辑 | 全项目检索 → 0 处调用 |
cancelAllInitialTakeProfits() |
1719 | 取消初始化阶段止盈单 | 全项目检索 → 0 处调用 |
checkLastTakeProfitAndRestart 本身逻辑完整(跨度检查 → 清理条件单 → 平仓 → 重启),可能是某个版本的入口未被接上。
影响:仅代码整洁度,无功能影响。若跨度重启功能需要保留,需在合适回调点接入(如止盈触发后)。
位置:行 1389、1391(checkLongStopShortProfit)及镜像 1445、1447(checkShortStopLongProfit)
int fullTpPctNum = maxPos * (100 - fullTpPct) / 100; // Java int 除法截断
int thresholdPosNum = maxPos * (100 - thresholdPct) / 100;
以 maxPos = 4 为例
| STOP_LOSS_RULES 行 | fullTpPct | fullTpPctNum | 触发条件 oppPos > N |
|---|---|---|---|
| 第 3 行 (times=3) | 30 | 4*70/100 = 2 |
oppPos ≥ 3 |
| 第 6 行 (times=6) | 40 | 4*60/100 = 2 |
oppPos ≥ 3 ⚠️ 与第3行相同 |
| 第 9 行 (times=9) | 50 | 4*50/100 = 2 |
oppPos ≥ 3 ⚠️ 与第3/6行相同 |
前三行规则对于对手止盈**完全不可区分**,实际触发门槛都是 maxPos 的 75%。
同样 thresholdPosNum 也有精度损失:
| thresholdPct | thresholdPosNum (maxPos=4) |
实际百分比 |
|---|---|---|
| 40 | 4*60/100 = 2 |
excess = selfPos - 2 |
| 50 | 4*50/100 = 2 |
同上 ⚠️ |
| 60 | 4*40/100 = 1 |
excess = selfPos - 1 |
影响
maxPos < 10:规则表精度差,多行等价maxPos ≥ 20:每 5% 差 1 张,基本可区分maxPos 精度,可改用:java int fullTpPctNum = (int) Math.ceil(maxPos * (100.0 - fullTpPct) / 100.0); 位置:行 1410 ~ 1413(checkLongStopShortProfit)及镜像 1466 ~ 1469
int perOrderQty = maxPos * DEFAULT_STOP_LOSS_PERCENT / 100; // DEFAULT_STOP_LOSS_PERCENT = 5
if (perOrderQty <= 0) {
perOrderQty = 1; // 兜底
}
maxPos = 4 → 4 * 5 / 100 = 0 → 兜底为 1。
影响:几乎无。perOrderQty 从设计的 "maxPos 的 5%" 降级为固定 1 张,但兜底保证了逻辑不崩溃。maxPos ≥ 20 后恢复正常。
以下模块经逐行审查,确认无 Bug:
| 模块 | 验证点 | 结论 |
|---|---|---|
checkLongStopShortProfit |
对手(空)盈止位 gridId,己方(多)盈止位 0+2,4,6... |
✅ 正确 |
checkShortStopLongProfit |
对手(多)盈止位 gridId,己方(空)盈止位 0-2,-4,-6... |
✅ 正确 |
cancelAllXxxTakeProfitsAndStopLosses |
先清后建:取消 → extend → checkXxxProfit 重挂 | ✅ 无遗漏 |
onAutoOrder 入口匹配 |
止损单 → 入口单 的顺序匹配 | ✅ 正确 |
extendStopLossInProgress 防重入 |
同格多成交 → pendingReExtend → 重挂 | ✅ 正确 |
processShortGrid/processLongGrid |
动量方向、队列匹配、下行守卫 | ✅ 正确 |
checkProfitAndReset |
估算手续费、轮次控制 | ✅ 正确 |
onKline 单边归零触发 |
longActive==false 时触发 processShortGrid |
✅ 正确 |
| 优先级 | Bug | 修复难度 | 建议 |
|---|---|---|---|
| 🔴 P0 | #1 sameGrid 丢止损 | 低(删 3 行加 4 行) | 立即修复 |
| 🟡 P2 | #2 死代码 | — | 清理或恢复接入 |
| 🟡 P3 | #3 小 maxPos 精度 | 中(需改计算逻辑) | maxPos ≥ 10 可暂缓 |
| 🟢 P4 | #4 perOrderQty 截断 | — | 已有兜底,不需处理 |