diff --git a/src/core/order-coordinator.ts b/src/core/order-coordinator.ts index e56e603..bff87cc 100644 --- a/src/core/order-coordinator.ts +++ b/src/core/order-coordinator.ts @@ -242,6 +242,7 @@ export async function placeStopLossOrder( closePosition: "true", timeInForce: "GTC", quantity: roundQtyDownToStep(quantity, qtyStep), + triggerType: "STOP_LOSS", }; // 部分交易所(例如 Paradex)要求 STOP_MARKET 同时提供 price 字段 params.price = params.stopPrice; diff --git a/src/exchanges/grvt/gateway.ts b/src/exchanges/grvt/gateway.ts index cd4fff1..2412c1f 100644 --- a/src/exchanges/grvt/gateway.ts +++ b/src/exchanges/grvt/gateway.ts @@ -1337,7 +1337,7 @@ function buildUnsignedOrder(params: { function buildTriggerMetadata(params: CreateOrderParams): GrvtUnsignedOrder["metadata"]["trigger"] | undefined { if (params.type === "STOP_MARKET") { - const triggerType = params.side === "BUY" ? "TAKE_PROFIT" : "STOP_LOSS"; + const triggerType = params.triggerType ?? (params.side === "BUY" ? "TAKE_PROFIT" : "STOP_LOSS"); const stopPrice = params.stopPrice ?? params.activationPrice; if (!stopPrice) { throw new Error("GRVT stop orders require a stopPrice or activationPrice"); diff --git a/src/exchanges/types.ts b/src/exchanges/types.ts index 6ff56c4..f41eeae 100644 --- a/src/exchanges/types.ts +++ b/src/exchanges/types.ts @@ -21,6 +21,7 @@ export interface CreateOrderParams { timeInForce?: TimeInForce; reduceOnly?: StringBoolean; closePosition?: StringBoolean; + triggerType?: "UNSPECIFIED" | "TAKE_PROFIT" | "STOP_LOSS"; } export interface AsterAccountPosition { diff --git a/src/strategy/trend-engine.ts b/src/strategy/trend-engine.ts index 6c7254c..3364d11 100644 --- a/src/strategy/trend-engine.ts +++ b/src/strategy/trend-engine.ts @@ -509,10 +509,21 @@ export class TrendEngine { } this.entryPricePendingLogged = false; const direction = position.positionAmt > 0 ? "long" : "short"; + const qtyAbs = Math.abs(position.positionAmt); + const depthBid = Number(this.depthSnapshot?.bids?.[0]?.[0]); + const depthAsk = Number(this.depthSnapshot?.asks?.[0]?.[0]); + const closeSidePriceRaw = direction === "long" ? depthBid : depthAsk; + const effectiveClosePrice = Number.isFinite(closeSidePriceRaw) + ? closeSidePriceRaw + : Number.isFinite(price) + ? price + : position.entryPrice; const pnl = - (direction === "long" - ? price - position.entryPrice - : position.entryPrice - price) * Math.abs(position.positionAmt); + qtyAbs > 0 + ? (direction === "long" + ? effectiveClosePrice - position.entryPrice + : position.entryPrice - effectiveClosePrice) * qtyAbs + : 0; const unrealized = Number.isFinite(position.unrealizedProfit) ? position.unrealizedProfit : null; @@ -671,11 +682,7 @@ export class TrendEngine { } const derivedLoss = pnl < -this.config.lossLimit; - const snapshotLoss = Boolean( - unrealized != null && - unrealized < -this.config.lossLimit && - pnl <= 0 - ); + const snapshotLoss = derivedLoss; if (derivedLoss || snapshotLoss) { const result = { closed: false, pnl };