mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 08:18:07 +00:00
Refactor order status handling in NadoGateway and MakerEngine. Introduce isOrderActiveStatus utility to streamline order filtering logic. Add tests for error handling and order status utilities.
This commit is contained in:
@@ -1275,13 +1275,14 @@ export class NadoGateway {
|
||||
const origQty = fromX18(new BigNumber(order.amountX18).abs()).toFixed();
|
||||
const remaining = fromX18(new BigNumber(order.unfilledAmountX18).abs()).toFixed();
|
||||
const executed = new BigNumber(origQty).minus(remaining);
|
||||
const status = executed.isFinite() && executed.gt(0) ? "PARTIALLY_FILLED" : "NEW";
|
||||
orders.push({
|
||||
orderId: order.digest,
|
||||
clientOrderId: order.clientId != null ? String(order.clientId) : order.digest,
|
||||
symbol: displaySymbol,
|
||||
side,
|
||||
type,
|
||||
status: "NEW",
|
||||
status,
|
||||
price: fromX18(order.priceX18).toFixed(),
|
||||
origQty,
|
||||
executedQty: executed.isFinite() ? executed.toFixed() : "0",
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
import { formatPriceToString } from "../utils/math";
|
||||
import { createTradeLog, type TradeLogEntry } from "../logging/trade-log";
|
||||
import { extractMessage, isInsufficientBalanceError, isUnknownOrderError, isRateLimitError } from "../utils/errors";
|
||||
import { isOrderActiveStatus } from "../utils/order-status";
|
||||
import { getPosition } from "../utils/strategy";
|
||||
import type { PositionSnapshot } from "../utils/strategy";
|
||||
import { computePositionPnl } from "../utils/pnl";
|
||||
@@ -183,7 +184,12 @@ export class MakerEngine {
|
||||
(orders) => {
|
||||
this.syncLocksWithOrders(orders);
|
||||
this.openOrders = Array.isArray(orders)
|
||||
? orders.filter((order) => order.type !== "MARKET" && order.symbol === this.config.symbol)
|
||||
? orders.filter(
|
||||
(order) =>
|
||||
order.type !== "MARKET" &&
|
||||
order.symbol === this.config.symbol &&
|
||||
isOrderActiveStatus(order.status)
|
||||
)
|
||||
: [];
|
||||
const currentIds = new Set(this.openOrders.map((order) => String(order.orderId)));
|
||||
for (const id of Array.from(this.pendingCancelOrders)) {
|
||||
@@ -386,10 +392,7 @@ export class MakerEngine {
|
||||
|
||||
private async syncOrders(targets: DesiredOrder[]): Promise<void> {
|
||||
const availableOrders = this.openOrders.filter((o) => !this.pendingCancelOrders.has(String(o.orderId)));
|
||||
const openOrders = availableOrders.filter((order) => {
|
||||
const status = (order.status ?? "").toUpperCase();
|
||||
return !status.includes("CLOSED") && !status.includes("FILLED") && !status.includes("CANCELED");
|
||||
});
|
||||
const openOrders = availableOrders.filter((order) => isOrderActiveStatus(order.status));
|
||||
const { toCancel, toPlace } = makeOrderPlan(openOrders, targets);
|
||||
|
||||
for (const order of toCancel) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
import { formatPriceToString } from "../utils/math";
|
||||
import { createTradeLog } from "../logging/trade-log";
|
||||
import { isUnknownOrderError, isRateLimitError } from "../utils/errors";
|
||||
import { isOrderActiveStatus } from "../utils/order-status";
|
||||
import { getPosition, parseSymbolParts } from "../utils/strategy";
|
||||
import type { PositionSnapshot } from "../utils/strategy";
|
||||
import { computeDepthStats } from "../utils/depth";
|
||||
@@ -212,7 +213,12 @@ export class OffsetMakerEngine {
|
||||
this.syncLocksWithOrders(orders);
|
||||
this.feedStatus.orders = true;
|
||||
this.openOrders = Array.isArray(orders)
|
||||
? orders.filter((order) => order.type !== "MARKET" && order.symbol === this.config.symbol)
|
||||
? orders.filter(
|
||||
(order) =>
|
||||
order.type !== "MARKET" &&
|
||||
order.symbol === this.config.symbol &&
|
||||
isOrderActiveStatus(order.status)
|
||||
)
|
||||
: [];
|
||||
const currentIds = new Set(this.openOrders.map((order) => String(order.orderId)));
|
||||
for (const id of Array.from(this.pendingCancelOrders)) {
|
||||
@@ -650,10 +656,7 @@ export class OffsetMakerEngine {
|
||||
|
||||
private async syncOrders(targets: DesiredOrder[]): Promise<void> {
|
||||
const availableOrders = this.openOrders.filter((o) => !this.pendingCancelOrders.has(String(o.orderId)));
|
||||
const openOrders = availableOrders.filter((order) => {
|
||||
const status = (order.status ?? "").toUpperCase();
|
||||
return !status.includes("CLOSED") && !status.includes("FILLED") && !status.includes("CANCELED");
|
||||
});
|
||||
const openOrders = availableOrders.filter((order) => isOrderActiveStatus(order.status));
|
||||
|
||||
// Coalesce reprices for entry orders: if within tick threshold or within dwell window, keep existing order
|
||||
const adjustedTargets: DesiredOrder[] = targets.map((t) => ({ ...t }));
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { isUnknownOrderError } from "./errors";
|
||||
|
||||
describe("isUnknownOrderError", () => {
|
||||
it("matches Nado digest-not-found by code", () => {
|
||||
const err = Object.assign(
|
||||
new Error("2020: Order with the provided digest (0xdeadbeef) could not be found."),
|
||||
{ code: 2020 }
|
||||
);
|
||||
expect(isUnknownOrderError(err)).toBe(true);
|
||||
});
|
||||
|
||||
it("matches Nado digest-not-found by message", () => {
|
||||
const err = new Error("Order with the provided digest (0xdeadbeef) could not be found.");
|
||||
expect(isUnknownOrderError(err)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not match unrelated errors", () => {
|
||||
expect(isUnknownOrderError(new Error("insufficient balance"))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
+9
-1
@@ -1,4 +1,10 @@
|
||||
export function isUnknownOrderError(error: unknown): boolean {
|
||||
if (!error) return false;
|
||||
if (typeof error === "object" && error !== null && "code" in error) {
|
||||
const code = Number((error as { code?: unknown }).code);
|
||||
// Nado: 2020: Order with the provided digest ... could not be found.
|
||||
if (Number.isFinite(code) && code === 2020) return true;
|
||||
}
|
||||
const message = extractMessage(error);
|
||||
if (!message) return false;
|
||||
const upper = message.toUpperCase();
|
||||
@@ -7,7 +13,9 @@ export function isUnknownOrderError(error: unknown): boolean {
|
||||
upper.includes("CODE\":-2011") ||
|
||||
upper.includes("ORDER_ID_NOT_FOUND") ||
|
||||
upper.includes("ORDER_IS_CLOSED") ||
|
||||
upper.includes("COULD NOT FIND ORDER")
|
||||
upper.includes("COULD NOT FIND ORDER") ||
|
||||
upper.includes("ORDER WITH THE PROVIDED DIGEST") ||
|
||||
(upper.includes("COULD NOT BE FOUND") && upper.includes("ORDER"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { isOrderActiveStatus } from "./order-status";
|
||||
|
||||
describe("isOrderActiveStatus", () => {
|
||||
it("treats empty status as active", () => {
|
||||
expect(isOrderActiveStatus(undefined)).toBe(true);
|
||||
expect(isOrderActiveStatus("")).toBe(true);
|
||||
expect(isOrderActiveStatus(" ")).toBe(true);
|
||||
});
|
||||
|
||||
it("treats typical active statuses as active", () => {
|
||||
expect(isOrderActiveStatus("NEW")).toBe(true);
|
||||
expect(isOrderActiveStatus("PARTIALLY_FILLED")).toBe(true);
|
||||
expect(isOrderActiveStatus("waiting_price")).toBe(true);
|
||||
});
|
||||
|
||||
it("treats final/non-open statuses as inactive", () => {
|
||||
expect(isOrderActiveStatus("FILLED")).toBe(false);
|
||||
expect(isOrderActiveStatus("CANCELED")).toBe(false);
|
||||
expect(isOrderActiveStatus("CANCELLED")).toBe(false);
|
||||
expect(isOrderActiveStatus("REJECTED")).toBe(false);
|
||||
expect(isOrderActiveStatus("EXPIRED")).toBe(false);
|
||||
expect(isOrderActiveStatus("TRIGGERED")).toBe(false);
|
||||
expect(isOrderActiveStatus("CLOSED")).toBe(false);
|
||||
expect(isOrderActiveStatus("closed_by_user")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export function isOrderActiveStatus(status: string | undefined): boolean {
|
||||
if (!status) return true;
|
||||
const normalized = status.trim().toUpperCase();
|
||||
if (!normalized) return true;
|
||||
if (normalized.includes("CLOSED")) return false;
|
||||
if (normalized === "FILLED") return false;
|
||||
if (normalized === "CANCELED" || normalized === "CANCELLED") return false;
|
||||
if (normalized === "REJECTED" || normalized === "EXPIRED") return false;
|
||||
if (normalized === "TRIGGERED") return false;
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user