| New file |
| | |
| | | # GateGridTradeService 代码审计报告 |
| | | |
| | | > 审计日期:2026-08-03 |
| | | > 文件:`src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java` |
| | | > 审查范围:全部 2147 行,含交叉验证 `GridElement` / `TraderParam` / `GateConfig` / `GateTradeExecutor` |
| | | |
| | | --- |
| | | |
| | | ## 一、总体评估 |
| | | |
| | | 代码结构清晰,多空双方向网格交易的主流程(K线驱动 → 条件单挂单 → 成交回调 → 止损追单 → 级联查表)完整且自洽。发现 **4 个 Bug**,其中 **1 个 Critical** 直接影响止损级联计数准确性。 |
| | | |
| | | --- |
| | | |
| | | ## 二、BUG 详细分析 |
| | | |
| | | ### Bug #1 🔴 Critical:sameGrid 阻止同网格多个止损单触发 |
| | | |
| | | **位置** |
| | | |
| | | - `handleLongStopLossTriggered` — 行 1283 ~ 1287 |
| | | - `handleShortStopLossTriggered` — 行 1494 ~ 1498 |
| | | |
| | | **问题代码** |
| | | |
| | | ```java |
| | | boolean sameGrid = (gridId == lastLongStopLossGridId); |
| | | if (sameGrid) { |
| | | log.info("[Gate] 多仓止损触发 同网格, 忽略"); |
| | | return; |
| | | } |
| | | lastLongStopLossGridId = gridId; |
| | | accumulatedLongLossCount++; |
| | | ``` |
| | | |
| | | **根因** |
| | | |
| | | `extendLongStopLoss` / `extendShortStopLoss` 中当 `gridCount == 1` 时,所有止损单挂在**同一个 gridId**: |
| | | |
| | | ```java |
| | | // 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** 的正常多单触发。 |
| | | |
| | | **修复方案** |
| | | |
| | | ```java |
| | | 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` 小),问题越严重(所有止损同格) |
| | | |
| | | --- |
| | | |
| | | ### Bug #2 🟡 Low:三处死代码 |
| | | |
| | | | 方法 | 行号 | 功能 | 调用情况 | |
| | | |---|---|---|---| |
| | | | `checkLastTakeProfitAndRestart()` | 1549 | 止盈全部清空后检查跨度重启 | 全项目检索 → **0 处调用** | |
| | | | `placeOpponentTakeProfit()` | 2125 | 旧版对手止盈挂单逻辑 | 全项目检索 → **0 处调用** | |
| | | | `cancelAllInitialTakeProfits()` | 1719 | 取消初始化阶段止盈单 | 全项目检索 → **0 处调用** | |
| | | |
| | | `checkLastTakeProfitAndRestart` 本身逻辑完整(跨度检查 → 清理条件单 → 平仓 → 重启),可能是某个版本的入口未被接上。 |
| | | |
| | | **影响**:仅代码整洁度,无功能影响。若跨度重启功能需要保留,需在合适回调点接入(如止盈触发后)。 |
| | | |
| | | --- |
| | | |
| | | ### Bug #3 🟡 Low-Medium:整数截断使小 maxPos 下多行规则不可区分 |
| | | |
| | | **位置**:行 1389、1391(`checkLongStopShortProfit`)及镜像 1445、1447(`checkShortStopLongProfit`) |
| | | |
| | | ```java |
| | | 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); |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ### Bug #4 🟢 Very Minor:perOrderQty 整数截断(已有兜底) |
| | | |
| | | **位置**:行 1410 ~ 1413(`checkLongStopShortProfit`)及镜像 1466 ~ 1469 |
| | | |
| | | ```java |
| | | 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 截断 | — | 已有兜底,不需处理 | |