From fef7604490c242f30890b9462a3808aeaff9612b Mon Sep 17 00:00:00 2001 From: discountry Date: Tue, 7 Oct 2025 22:26:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E7=BD=91=E6=A0=BC?= =?UTF-8?q?=E5=BC=95=E6=93=8E=EF=BC=8C=E5=A2=9E=E5=BC=BA=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E6=B6=88=E5=A4=B1=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=B9=B3=E4=BB=93=E8=AE=A2=E5=8D=95=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E4=B8=8E=E7=9B=AE=E6=A0=87=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/strategy/grid-engine.ts | 41 +++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/strategy/grid-engine.ts b/src/strategy/grid-engine.ts index 15eb2c7..72398a9 100644 --- a/src/strategy/grid-engine.ts +++ b/src/strategy/grid-engine.ts @@ -490,10 +490,15 @@ export class GridEngine { } } if (handledByCloseTracking) continue; - // Otherwise, treat as an open order disappearance and mark pending + // Otherwise, treat disappearance as potential open fill: confirm by position delta increase if (meta) { - if (meta.side === "BUY") this.pendingLongLevels.add(meta.level); - else this.pendingShortLevels.add(meta.level); + const absNow = Math.abs(this.position.positionAmt); + if (absNow > this.lastAbsPositionAmt + EPSILON) { + if (meta.side === "BUY") this.pendingLongLevels.add(meta.level); + else this.pendingShortLevels.add(meta.level); + } else { + // Likely canceled or expired; do not mark pending + } } } this.lastOpenOrderKeys = currentKeys; @@ -511,6 +516,8 @@ export class GridEngine { for (const level of this.buyLevelIndices) { const levelPrice = this.gridLevels[level]!; if (levelPrice >= price - halfTick) continue; + // do not open if this level is currently a close target for any pending source + if (this.isTargetOfPendingShort(level) || this.isTargetOfPendingLong(level)) continue; if (this.pendingLongLevels.has(level)) continue; // wait until close filled const key = this.getOrderKey("BUY", this.formatPrice(levelPrice), false); if (activeKeySet.has(key)) continue; // already has open order @@ -521,6 +528,7 @@ export class GridEngine { for (const level of this.sellLevelIndices) { const levelPrice = this.gridLevels[level]!; if (levelPrice <= price + halfTick) continue; + if (this.isTargetOfPendingShort(level) || this.isTargetOfPendingLong(level)) continue; if (this.pendingShortLevels.has(level)) continue; const key = this.getOrderKey("SELL", this.formatPrice(levelPrice), false); if (activeKeySet.has(key)) continue; @@ -532,24 +540,35 @@ export class GridEngine { const target = this.levelMeta[source]?.closeTarget; if (target == null) continue; const priceStr = this.formatPrice(this.gridLevels[target]!); - // always desire a close order; allow placement even if an identical open exists - desired.push({ level: target, side: "SELL", price: priceStr, amount: this.config.orderSize, reduceOnly: false }); + const closeKey = this.getOrderKey("SELL", priceStr, false); + if (!activeKeySet.has(closeKey)) { + desired.push({ level: target, side: "SELL", price: priceStr, amount: this.config.orderSize, reduceOnly: false }); + } else { + // Map existing identical open as the close so disappearance clears pending + if (!this.closeKeyBySourceLevel.has(source)) { + this.closeKeyBySourceLevel.set(source, closeKey); + } + } } for (const source of this.pendingShortLevels) { const target = this.levelMeta[source]?.closeTarget; if (target == null) continue; const priceStr = this.formatPrice(this.gridLevels[target]!); - desired.push({ level: target, side: "BUY", price: priceStr, amount: this.config.orderSize, reduceOnly: false }); + const closeKey = this.getOrderKey("BUY", priceStr, false); + if (!activeKeySet.has(closeKey)) { + desired.push({ level: target, side: "BUY", price: priceStr, amount: this.config.orderSize, reduceOnly: false }); + } else { + if (!this.closeKeyBySourceLevel.has(source)) { + this.closeKeyBySourceLevel.set(source, closeKey); + } + } } // Place desired orders this.desiredOrders = desired; for (const d of desired) { const key = this.getOrderKey(d.side, d.price, d.reduceOnly); - const isCloseDesired = - (d.side === "SELL" && this.isTargetOfPendingLong(d.level)) || - (d.side === "BUY" && this.isTargetOfPendingShort(d.level)); - if (activeKeySet.has(key) && !isCloseDesired) continue; + if (activeKeySet.has(key)) continue; try { const placed = await placeOrder( this.exchange, @@ -566,7 +585,7 @@ export class GridEngine { undefined, { priceTick: this.config.priceTick, qtyStep: this.config.qtyStep, skipDedupe: true } ); - if (placed && isCloseDesired) { + if (placed && ((d.side === "SELL" && this.isTargetOfPendingLong(d.level)) || (d.side === "BUY" && this.isTargetOfPendingShort(d.level)))) { this.closeKeyBySourceLevel.set( this.findSourceForCloseTarget(d.level, d.side), key