From 2627fa9db83dc4f30b7da661a91ddc8ce1efcf0a Mon Sep 17 00:00:00 2001 From: discountry Date: Sat, 11 Oct 2025 21:41:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=8E=BB=E9=87=8D=E5=92=8C=E5=8F=96=E6=B6=88=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=9C=A8=E5=A4=84=E7=90=86STOP?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E6=97=B6=E9=81=BF=E5=85=8D=E7=B2=BE=E5=BA=A6?= =?UTF-8?q?=E6=8D=9F=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/order-coordinator.ts | 20 +++++++++----------- src/exchanges/lighter/adapter.ts | 5 +++-- src/exchanges/lighter/gateway.ts | 14 +++++++++++++- src/exchanges/lighter/mappers.ts | 3 ++- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/core/order-coordinator.ts b/src/core/order-coordinator.ts index 1bb36bf..a0caec4 100644 --- a/src/core/order-coordinator.ts +++ b/src/core/order-coordinator.ts @@ -89,7 +89,14 @@ export async function deduplicateOrders( side: string, log: LogHandler ): Promise { - const sameTypeOrders = openOrders.filter((o) => o.type === type && o.side === side); + // Treat STOP orders on some exchanges (e.g., Lighter) as LIMIT with stopPrice populated. + const sameTypeOrders = openOrders.filter((o) => { + const normalizedType = String(o.type).toUpperCase(); + const isStopLike = Number.isFinite(Number(o.stopPrice)) && Number(o.stopPrice) > 0; + const matchesStop = type === "STOP_MARKET" && isStopLike && o.side === side; + const exactMatch = normalizedType === type && o.side === side; + return exactMatch || matchesStop; + }); if (sameTypeOrders.length <= 1) return; sameTypeOrders.sort((a, b) => { const ta = b.updateTime || b.time || 0; @@ -241,7 +248,6 @@ export async function placeStopLossOrder( } const priceTick = opts?.priceTick ?? 0.1; const qtyStep = opts?.qtyStep ?? 0.001; - const isAster = String((adapter as any).id ?? "").toLowerCase() === "aster"; const params: CreateOrderParams = { symbol, side, @@ -249,14 +255,10 @@ export async function placeStopLossOrder( stopPrice: roundDownToTick(stopPrice, priceTick), closePosition: "true", timeInForce: "GTC", - // Do not round down stop quantity here to avoid underflow to exchange min; gateway will quantize precisely quantity, triggerType: "STOP_LOSS", }; - // For Aster futures, STOP orders must not include reduceOnly; omit it specifically. - if (!isAster) { - params.reduceOnly = "true"; - } + // 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); @@ -299,8 +301,6 @@ export async function placeTrailingStopOrder( symbol, side, type, - // 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), @@ -351,8 +351,6 @@ export async function marketClose( side, type, quantity: safeQty, - reduceOnly: "true", - // Hint exchanges (like Paradex) to close the whole position and tolerate omitted size closePosition: "true", }; await deduplicateOrders(adapter, symbol, openOrders, locks, timers, pendings, type, side, log); diff --git a/src/exchanges/lighter/adapter.ts b/src/exchanges/lighter/adapter.ts index 726432d..e00fe27 100644 --- a/src/exchanges/lighter/adapter.ts +++ b/src/exchanges/lighter/adapter.ts @@ -108,13 +108,14 @@ export class LighterExchangeAdapter implements ExchangeAdapter { async cancelOrder(params: { symbol: string; orderId: number | string }): Promise { await this.ensureInitialized("cancelOrder"); - await this.gateway.cancelOrder({ orderId: params.orderId }); + // Accept both clientOrderId and order_index as strings; forward as-is to preserve precision + await this.gateway.cancelOrder({ orderId: String(params.orderId) }); } async cancelOrders(params: { symbol: string; orderIdList: Array }): Promise { await this.ensureInitialized("cancelOrders"); for (const orderId of params.orderIdList) { - await this.gateway.cancelOrder({ orderId }); + await this.gateway.cancelOrder({ orderId: String(orderId) }); } } diff --git a/src/exchanges/lighter/gateway.ts b/src/exchanges/lighter/gateway.ts index 99a652c..e74967c 100644 --- a/src/exchanges/lighter/gateway.ts +++ b/src/exchanges/lighter/gateway.ts @@ -312,7 +312,14 @@ export class LighterGateway { await this.ensureInitialized(); const marketIndex = params.marketIndex ?? this.marketId; if (marketIndex == null) throw new Error("Market index unknown"); - const indexValue = BigInt(typeof params.orderId === "string" ? Number(params.orderId) : params.orderId); + // Parse order id to BigInt without precision loss; prefer string input + let indexValue: bigint; + if (typeof params.orderId === "string") { + indexValue = BigInt(params.orderId); + } else { + // Fallback for numeric ids (may be unsafe if beyond 2^53-1) + indexValue = BigInt(Math.trunc(params.orderId)); + } const { apiKeyIndex, nonce } = this.nonceManager.next(); try { const signed = await this.signer.signCancelOrder({ @@ -323,6 +330,11 @@ export class LighterGateway { }); const auth = await this.ensureAuthToken(); await this.http.sendTransaction(signed.txType, signed.txInfo, { authToken: auth }); + // Optimistically remove the order locally to avoid stale duplicates until WS confirms + const key = String(params.orderId); + this.orderMap.delete(key); + this.orders = Array.from(this.orderMap.values()); + this.emitOrders(); } catch (error) { this.nonceManager.acknowledgeFailure(apiKeyIndex); throw error; diff --git a/src/exchanges/lighter/mappers.ts b/src/exchanges/lighter/mappers.ts index 663ff11..5e4c21c 100644 --- a/src/exchanges/lighter/mappers.ts +++ b/src/exchanges/lighter/mappers.ts @@ -78,7 +78,8 @@ export function lighterOrderToAster(symbol: string, order: LighterOrder): AsterO ? "SELL" : "BUY"; return { - orderId: order.order_index, + // Use string order id to avoid precision loss; prefer on-chain order_index for cancellation + orderId: String(order.order_index ?? order.client_order_index ?? ""), clientOrderId: String(order.client_order_index ?? order.order_index ?? ""), symbol, side,