feat: enhance order error handling in OffsetMakerEngine and update LighterGateway order status types for improved robustness

This commit is contained in:
discountry
2025-11-12 19:35:31 +08:00
parent e7f6341961
commit 870fe2b8d7
3 changed files with 55 additions and 4 deletions
+1 -1
View File
@@ -133,7 +133,7 @@ const RESOLUTION_MS: Record<string, number> = {
"1d": 86_400_000,
};
const TERMINAL_ORDER_STATUSES = new Set(["filled", "canceled", "cancelled", "expired"]);
const TERMINAL_ORDER_STATUSES = new Set(["filled", "canceled", "cancelled", "expired", "canceled-post-only"]);
export interface LighterGatewayOptions {
symbol: string; // display symbol used by strategy logging
+2 -2
View File
@@ -29,8 +29,8 @@ export interface LighterOrder {
time_in_force?: string;
trigger_price?: string;
reduce_only?: boolean;
status?: string;
trigger_status?: string;
status?: string | number;
trigger_status?: string | number;
trigger_time?: number;
updated_at?: number;
created_at?: number;
+52 -1
View File
@@ -546,7 +546,10 @@ export class OffsetMakerEngine {
this.lastEntryOrderBySide[target.side] = { price: target.price, ts: Date.now() };
}
} catch (error) {
this.tradeLog.push("error", `挂单失败(${target.side} ${target.price}): ${String(error)}`);
const dustClosed = await this.tryDustMarketClose(target, error);
if (!dustClosed) {
this.tradeLog.push("error", `挂单失败(${target.side} ${target.price}): ${String(error)}`);
}
}
}
}
@@ -713,4 +716,52 @@ export class OffsetMakerEngine {
private getReferencePrice(): number | null {
return getMidOrLast(this.depthSnapshot, this.tickerSnapshot);
}
private isInvalidAmountError(error: unknown): boolean {
const message =
typeof error === "string"
? error
: error instanceof Error
? error.message
: JSON.stringify(error);
if (!message) return false;
if (message.includes("\"code\":21706")) return true;
return message.toLowerCase().includes("invalid order base or quote amount");
}
private async tryDustMarketClose(target: DesiredOrder, error: unknown): Promise<boolean> {
if (!target.reduceOnly) return false;
if (!this.isInvalidAmountError(error)) return false;
const position = getPosition(this.accountSnapshot, this.config.symbol);
const absQty = Math.abs(target.amount);
if (absQty < EPS) return false;
const { topBid, topAsk } = getTopPrices(this.depthSnapshot);
try {
await marketClose(
this.exchange,
this.config.symbol,
this.openOrders,
this.locks,
this.timers,
this.pending,
target.side,
absQty,
(type, detail) => this.tradeLog.push(type, detail),
{
markPrice: position.markPrice,
expectedPrice:
target.side === "SELL"
? (topBid != null ? Number(topBid) : null)
: (topAsk != null ? Number(topAsk) : null),
maxPct: this.config.maxCloseSlippagePct,
},
{ qtyStep: this.qtyStep }
);
this.tradeLog.push("order", `小额仓位使用市价平仓 ${target.side} 数量 ${absQty.toFixed(6)}`);
return true;
} catch (closeError) {
this.tradeLog.push("error", `小额市价平仓失败: ${String(closeError)}`);
return false;
}
}
}