From 801543488e9aa7897cac1602db362d3a68d40906 Mon Sep 17 00:00:00 2001 From: discountry Date: Wed, 8 Oct 2025 01:45:12 +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=E4=BC=98=E5=8C=96=E7=AD=89=E5=BE=85?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E6=9C=BA=E5=88=B6=EF=BC=8C=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E8=B4=A6=E6=88=B7=E5=BF=AB=E7=85=A7=E7=A1=AE=E8=AE=A4=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E4=BB=A5=E5=A4=84=E7=90=86=E6=B6=88=E5=A4=B1=E8=AE=A2?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/strategy/grid-engine.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/strategy/grid-engine.ts b/src/strategy/grid-engine.ts index fefe4bf..1ca5574 100644 --- a/src/strategy/grid-engine.ts +++ b/src/strategy/grid-engine.ts @@ -98,8 +98,8 @@ export class GridEngine { // temporarily block re-opening at that level to avoid immediate re-placement. private readonly levelReopenBlockUntil = new Map(); private static readonly DISAPPEAR_REOPEN_COOLDOWN_MS = 2000; - // Track levels awaiting classification after disappearance when neither fill nor cancel is confirmed - private readonly awaitingClassification = new Map(); + // Track levels awaiting classification after disappearance until next account snapshot confirms + private readonly awaitingClassification = new Map(); private static readonly AWAIT_CLASSIFICATION_TIMEOUT_MS = 2500; private sidesLocked = false; private startupCleaned = false; @@ -135,6 +135,7 @@ export class GridEngine { private running: boolean; private stopReason: string | null = null; private lastUpdated: number | null = null; + private accountVersion = 0; constructor(private readonly config: GridConfig, private readonly exchange: ExchangeAdapter, options: EngineOptions = {}) { this.tradeLog = createTradeLog(this.config.maxLogEntries); @@ -210,6 +211,7 @@ export class GridEngine { (snapshot) => { this.accountSnapshot = snapshot; this.position = getPosition(snapshot, this.config.symbol); + this.accountVersion += 1; this.lastAbsPositionAmt = Math.abs(this.position.positionAmt); if (!this.feedArrived.account) { this.feedArrived.account = true; @@ -529,9 +531,8 @@ export class GridEngine { this.awaitingClassification.delete(meta.level); this.levelReopenBlockUntil.delete(meta.level); } else { - // unknown: defer decision until position/account updates or timeout - const until = this.now() + GridEngine.AWAIT_CLASSIFICATION_TIMEOUT_MS; - this.awaitingClassification.set(meta.level, { until, absAtStart: this.lastAbsPositionAmt }); + // unknown: defer decision until we see a newer account snapshot + this.awaitingClassification.set(meta.level, { accountVerAtStart: this.accountVersion, absAtStart: this.lastAbsPositionAmt }); this.blockLevelReopen(meta.level); } } @@ -541,20 +542,19 @@ export class GridEngine { this.lastKeyMeta = keyToMeta; this.lastOrderIdsByKey = currentOrderIdsByKey; - // Resolve any awaiting classifications based on updated account snapshot or timeout + // Resolve awaiting classifications only after a new account snapshot if (this.awaitingClassification.size) { - const nowTs = this.now(); for (const [level, info] of Array.from(this.awaitingClassification.entries())) { const absNow = Math.abs(this.position.positionAmt); - if (absNow > info.absAtStart + EPSILON) { + if (this.accountVersion > info.accountVerAtStart && absNow > info.absAtStart + EPSILON) { // treat as filled const side = this.levelMeta[level]?.side === "BUY" ? "BUY" : "SELL"; if (side === "BUY") this.pendingLongLevels.add(level); else this.pendingShortLevels.add(level); this.awaitingClassification.delete(level); this.levelReopenBlockUntil.delete(level); - } else if (nowTs >= info.until) { - // timeout -> treat as canceled + } else if (this.accountVersion > info.accountVerAtStart && !(absNow > info.absAtStart + EPSILON)) { + // after new account snapshot without increased exposure -> canceled this.awaitingClassification.delete(level); this.levelReopenBlockUntil.delete(level); }