From 2e2990369b6ec1903add098f924f5fafdd1ccbf6 Mon Sep 17 00:00:00 2001 From: discountry Date: Tue, 7 Oct 2025 16:29:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E5=A5=97=E5=88=A9=E5=BC=95=E6=93=8E=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=B8=82=E5=9C=BA=E5=B0=B1=E7=BB=AA=E6=97=B6=E9=97=B4=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BF=A1=E5=8F=B7=E8=AF=84?= =?UTF-8?q?=E4=BC=B0=E4=BB=A5=E4=BD=BF=E7=94=A8=E5=BF=AB=E7=85=A7=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/strategy/basis-arb-engine.ts | 35 +++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/strategy/basis-arb-engine.ts b/src/strategy/basis-arb-engine.ts index 782dd23..5f4d28e 100644 --- a/src/strategy/basis-arb-engine.ts +++ b/src/strategy/basis-arb-engine.ts @@ -103,6 +103,7 @@ export class BasisArbEngine { private stopped = false; private lastEntrySignalAt = 0; private lastExitSignalAt = 0; + private marketReadyAt: number | null = null; constructor(config: BasisArbConfig, exchange: ExchangeAdapter, deps: BasisArbDependencies = {}) { this.config = config; @@ -180,6 +181,9 @@ export class BasisArbEngine { this.feedReady.futures = true; this.tradeLog.push("info", `期货深度已就绪 (${this.config.futuresSymbol})`); } + if (this.feedReady.futures && this.feedReady.spot && this.marketReadyAt == null) { + this.marketReadyAt = this.now(); + } this.emitUpdate(); } @@ -293,13 +297,17 @@ export class BasisArbEngine { this.feedReady.spot = true; this.tradeLog.push("info", `现货盘口已就绪 (${this.config.spotSymbol})`); } + if (this.feedReady.futures && this.feedReady.spot && this.marketReadyAt == null) { + this.marketReadyAt = this.now(); + } this.emitUpdate(); } private emitUpdate(): void { - // Evaluate entry/exit signals before emitting so the snapshot includes new log lines - this.evaluateSignals(); - this.events.emit("update", this.buildSnapshot(), (error) => { + // Build a single snapshot, evaluate signals against EXACTLY the same data, then emit that snapshot + const snapshot = this.buildSnapshot(); + this.evaluateSignals(snapshot); + this.events.emit("update", snapshot, (error) => { this.tradeLog.push("error", `推送订阅失败: ${String(error)}`); }); } @@ -380,16 +388,19 @@ export class BasisArbEngine { return sellFuturesNet - buySpotNet; } - private evaluateSignals(): void { - // Require both futures and spot feeds before computing signals - if (!this.feedReady.futures || !this.feedReady.spot) return; + private evaluateSignals(snapshot: BasisArbSnapshot): void { + // Require futures, spot, and funding feeds ready + if (!snapshot.feedStatus.futures || !snapshot.feedStatus.spot || !snapshot.feedStatus.funding) return; + // Require at least one refresh of both futures and spot AFTER initial readiness to avoid startup triggers + const readyAt = this.marketReadyAt; + if (readyAt == null) return; + const futTs = snapshot.futuresLastUpdate ?? 0; + const spotTs = snapshot.spotLastUpdate ?? 0; + if (futTs <= readyAt || spotTs <= readyAt) return; const now = this.now(); - const futuresBid = this.futures.bid; - const spotAsk = this.spot.ask; - const spread = this.computeSpread(futuresBid, spotAsk); - const spreadBps = this.computeSpreadBps(spread, spotAsk); - const fundingRate = this.funding.rate; - const nextFundingTime = this.funding.nextFundingTime; + const spreadBps = snapshot.spreadBps; + const fundingRate = snapshot.fundingRate; + const nextFundingTime = snapshot.nextFundingTime; const msUntilFunding = typeof nextFundingTime === "number" ? nextFundingTime - now : null; // Entry signal: positive bp and next funding >= 10 minutes away