diff --git a/src/core/order-coordinator.ts b/src/core/order-coordinator.ts index aa8b37e..9b0ddbd 100644 --- a/src/core/order-coordinator.ts +++ b/src/core/order-coordinator.ts @@ -253,8 +253,7 @@ export async function placeStopLossOrder( quantity, triggerType: "STOP_LOSS", }; - // 部分交易所(例如 Paradex)要求 STOP_MARKET 同时提供 price 字段 - params.price = params.stopPrice; + // Avoid forcing price for STOP_MARKET globally; keep this exchange-specific in gateways await deduplicateOrders(adapter, symbol, openOrders, locks, timers, pendings, type, side, log); lockOperating(locks, timers, pendings, type, log); try { @@ -296,7 +295,9 @@ export async function placeTrailingStopOrder( symbol, side, type, - quantity: roundQtyDownToStep(quantity, qtyStep), + // Do not round down trailing-stop quantity to avoid underflowing small positions to zero; + // let the exchange adapter handle precise quantization. + quantity, reduceOnly: "true", activationPrice: roundDownToTick(activationPrice, priceTick), callbackRate, diff --git a/src/exchanges/aster/client.ts b/src/exchanges/aster/client.ts index 71d3204..b4c172b 100644 --- a/src/exchanges/aster/client.ts +++ b/src/exchanges/aster/client.ts @@ -778,7 +778,28 @@ export class AsterRestClient { } async createOrder(params: CreateOrderParams): Promise { - const payload: Record = { ...params }; + // Sanitize and normalize params for Aster futures API. Paradex-specific flags + // like reduceOnly/closePosition on STOP/TRAILING should not leak here. + const payload: Record = {}; + payload.symbol = String(params.symbol).toUpperCase(); + payload.side = params.side; + payload.type = params.type; + if (params.timeInForce !== undefined) payload.timeInForce = params.timeInForce; + if (params.price !== undefined) payload.price = params.price; + if (params.stopPrice !== undefined) payload.stopPrice = params.stopPrice; + if (params.activationPrice !== undefined) payload.activationPrice = params.activationPrice; + if (params.callbackRate !== undefined) payload.callbackRate = params.callbackRate; + if (params.quantity !== undefined) payload.quantity = Math.abs(params.quantity); + + // Aster rejects reduceOnly/closePosition for certain order types (e.g. STOP/TRAILING). + // Keep the behavior exchange-specific by stripping them here for Aster. + const type = String(params.type).toUpperCase(); + const isStopOrTrailing = type === "STOP_MARKET" || type === "TRAILING_STOP_MARKET"; + if (!isStopOrTrailing) { + if (params.reduceOnly !== undefined) payload.reduceOnly = params.reduceOnly; + if (params.closePosition !== undefined) payload.closePosition = params.closePosition; + } + const response = await this.signedRequest({ path: "/fapi/v1/order", method: "POST", params: payload }); return toOrderFromRest(response); }