diff --git a/README.md b/README.md index 32bd4b8..d83b555 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ A Bun-powered trading workstation for Aster perpetual contracts. The project shi - **Live data over websockets** with REST fallbacks and automatic re-sync after reconnects. - **Trend strategy**: SMA30 crossover entries, automated stop-loss / trailing-stop, and P&L tracking. - **Maker strategy**: adaptive bid/ask chasing, risk stops, and target order introspection. -- **State persistence**: positions, open orders, and logs mirrored under `data/` so restarts continue where you left off. - **Extensibility**: exchange gateway, engines, and UI components are modular for new venues or strategies. ## Requirements @@ -34,7 +33,7 @@ Additional maker-specific knobs (`MAKER_*`) live in `src/config.ts` and may be o ```bash bun run index.ts # or: bun run dev / bun run start ``` -Pick a strategy with the arrow keys. Press `Esc` to return to the menu. The dashboard shows live order books, holdings, pending orders, and recent events. All state is mirrored in `data/trend-*.json` and `data/maker-*.json` so the bot can resume after crashes or manual stops. +Pick a strategy with the arrow keys. Press `Esc` to return to the menu. The dashboard shows live order books, holdings, pending orders, and recent events. 状态完全以交易所数据为准,重新启动时会自动同步账户和挂单。 ## Testing ```bash @@ -51,10 +50,6 @@ Current tests cover the order coordinator utilities and strategy helpers; add un - `src/utils/` – math helpers, persistence, strategy utilities - `tests/` – Vitest suites for critical modules -## Persistence & Recovery Notes -- Important state is stored in `./data` (git-ignored). Deleting those files forces a fresh start. -- On reconnect or restart the bot re-pulls account/position/order snapshots and reconciles against local state. Orders on other symbols are ignored so you can trade manually without interference. - ## Troubleshooting - **Websocket reconnect loops**: ensure outbound access to `wss://fstream.asterdex.com/ws` and REST endpoints. - **429 or 5xx responses**: the gateway backs off automatically, but check your rate limits and credentials. diff --git a/src/core/maker-engine.ts b/src/core/maker-engine.ts index 879fc67..0fb1218 100644 --- a/src/core/maker-engine.ts +++ b/src/core/maker-engine.ts @@ -8,7 +8,7 @@ import type { } from "../exchanges/types"; import { toPrice1Decimal } from "../utils/math"; import { createTradeLog, type TradeLogEntry } from "../state/trade-log"; -import { loadState, saveState } from "../utils/persistence"; +import { isUnknownOrderError } from "../utils/errors"; import { getPosition, type PositionSnapshot } from "../utils/strategy"; import { marketClose, @@ -33,6 +33,7 @@ export interface MakerEngineSnapshot { position: PositionSnapshot; pnl: number; accountUnrealized: number; + sessionVolume: number; openOrders: AsterOrder[]; desiredOrders: DesiredOrder[]; tradeLog: TradeLogEntry[]; @@ -56,21 +57,18 @@ export class MakerEngine { private readonly tradeLog: ReturnType; private readonly listeners = new Map>(); - private readonly stateFile: string; - private savedStateApplied = false; - private lastPersistedAt = 0; - private savedOpenOrders: AsterOrder[] = []; private timer: ReturnType | null = null; private processing = false; private desiredOrders: DesiredOrder[] = []; private accountUnrealized = 0; + private sessionQuoteVolume = 0; + private prevPositionAmt = 0; + private initializedPosition = false; constructor(private readonly config: MakerConfig, private readonly exchange: ExchangeAdapter) { this.tradeLog = createTradeLog(this.config.maxLogEntries); - this.stateFile = `maker-${this.config.symbol}.json`; this.bootstrap(); - void this.restoreState(); } start(): void { @@ -113,6 +111,8 @@ export class MakerEngine { if (Number.isFinite(totalUnrealized)) { this.accountUnrealized = totalUnrealized; } + const position = getPosition(snapshot, this.config.symbol); + this.updateSessionVolume(position); this.emitUpdate(); }); @@ -121,7 +121,6 @@ export class MakerEngine { this.openOrders = Array.isArray(orders) ? orders.filter((order) => order.type !== "MARKET" && order.symbol === this.config.symbol) : []; - this.reconcileSavedOpenOrders(); this.emitUpdate(); }); @@ -191,6 +190,7 @@ export class MakerEngine { } this.desiredOrders = desired; + this.updateSessionVolume(position); await this.syncOrders(desired); await this.checkRisk(position, bidPrice, askPrice); this.emitUpdate(); @@ -232,7 +232,11 @@ export class MakerEngine { await this.exchange.cancelOrder({ symbol: this.config.symbol, orderId: order.orderId }); this.tradeLog.push("order", `撤销不匹配订单 ${order.side} @ ${order.price} reduceOnly=${order.reduceOnly}`); } catch (error) { - this.tradeLog.push("error", `撤销订单失败: ${String(error)}`); + if (isUnknownOrderError(error)) { + this.tradeLog.push("order", "撤销时发现订单已被成交/取消,忽略"); + } else { + this.tradeLog.push("error", `撤销订单失败: ${String(error)}`); + } } } @@ -287,7 +291,11 @@ export class MakerEngine { (type, detail) => this.tradeLog.push(type, detail) ); } catch (error) { - this.tradeLog.push("error", `止损平仓失败: ${String(error)}`); + if (isUnknownOrderError(error)) { + this.tradeLog.push("order", "止损平仓时订单已不存在"); + } else { + this.tradeLog.push("error", `止损平仓失败: ${String(error)}`); + } } } } @@ -298,7 +306,11 @@ export class MakerEngine { try { await this.exchange.cancelOrder({ symbol: this.config.symbol, orderId: order.orderId }); } catch (error) { - this.tradeLog.push("error", `撤销订单失败: ${String(error)}`); + if (isUnknownOrderError(error)) { + this.tradeLog.push("order", "订单已不存在,撤销跳过"); + } else { + this.tradeLog.push("error", `撤销订单失败: ${String(error)}`); + } } } } @@ -309,7 +321,6 @@ export class MakerEngine { if (handlers) { handlers.forEach((handler) => handler(snapshot)); } - void this.persistSnapshot(snapshot); } private buildSnapshot(): MakerEngineSnapshot { @@ -335,6 +346,7 @@ export class MakerEngine { position, pnl, accountUnrealized: this.accountUnrealized, + sessionVolume: this.sessionQuoteVolume, openOrders: this.openOrders, desiredOrders: this.desiredOrders, tradeLog: this.tradeLog.all(), @@ -342,41 +354,33 @@ export class MakerEngine { }; } - private async restoreState(): Promise { - if (this.savedStateApplied) return; - this.savedStateApplied = true; - const state = await loadState<{ - tradeLog?: TradeLogEntry[]; - accountUnrealized?: number; - openOrders?: AsterOrder[]; - }>(this.stateFile); - if (!state) return; - if (Array.isArray(state.tradeLog)) this.tradeLog.replace(state.tradeLog); - if (typeof state.accountUnrealized === "number") this.accountUnrealized = state.accountUnrealized; - if (Array.isArray(state.openOrders)) this.savedOpenOrders = state.openOrders; - } - - private reconcileSavedOpenOrders(): void { - if (!this.savedOpenOrders.length) return; - const currentIds = new Set(this.openOrders.map((order) => order.orderId)); - const missing = this.savedOpenOrders.filter((order) => !currentIds.has(order.orderId)); - if (missing.length) { - this.tradeLog.push("order", `检测到 ${missing.length} 个历史挂单与当前状态不一致,将重新同步`); + private updateSessionVolume(position: PositionSnapshot): void { + const price = this.getReferencePrice(); + if (!this.initializedPosition) { + this.prevPositionAmt = position.positionAmt; + this.initializedPosition = true; + return; } - this.savedOpenOrders = []; + if (price == null) { + this.prevPositionAmt = position.positionAmt; + return; + } + const delta = Math.abs(position.positionAmt - this.prevPositionAmt); + if (delta > 0) { + this.sessionQuoteVolume += delta * price; + } + this.prevPositionAmt = position.positionAmt; } - private async persistSnapshot(snapshot?: MakerEngineSnapshot): Promise { - const now = Date.now(); - if (now - this.lastPersistedAt < 1000) return; - this.lastPersistedAt = now; - const current = snapshot ?? this.buildSnapshot(); - await saveState(this.stateFile, { - tradeLog: current.tradeLog, - accountUnrealized: current.accountUnrealized, - openOrders: current.openOrders.filter((order) => order.symbol === this.config.symbol), - position: current.position, - timestamp: current.lastUpdated, - }); + private getReferencePrice(): number | null { + const bid = Number(this.depthSnapshot?.bids?.[0]?.[0]); + const ask = Number(this.depthSnapshot?.asks?.[0]?.[0]); + if (Number.isFinite(bid) && Number.isFinite(ask)) return (bid + ask) / 2; + if (this.tickerSnapshot) { + const last = Number(this.tickerSnapshot.lastPrice); + if (Number.isFinite(last)) return last; + } + return null; } + } diff --git a/src/core/order-coordinator.ts b/src/core/order-coordinator.ts index 66ac1f8..351a57d 100644 --- a/src/core/order-coordinator.ts +++ b/src/core/order-coordinator.ts @@ -1,6 +1,7 @@ import type { ExchangeAdapter } from "../exchanges/adapter"; import type { AsterOrder, CreateOrderParams } from "../exchanges/types"; import { toPrice1Decimal, toQty3Decimal } from "../utils/math"; +import { isUnknownOrderError } from "../utils/errors"; export type OrderLockMap = Record; export type OrderTimerMap = Record | null>; @@ -70,7 +71,11 @@ export async function deduplicateOrders( await adapter.cancelOrders({ symbol, orderIdList }); log("order", `去重撤销重复 ${type} 单: ${orderIdList.join(",")}`); } catch (err) { - log("error", `去重撤单失败: ${String(err)}`); + if (isUnknownOrderError(err)) { + log("order", "去重时发现订单已不存在,跳过删除"); + } else { + log("error", `去重撤单失败: ${String(err)}`); + } } finally { unlockOperating(locks, timers, pendings, type); } @@ -109,6 +114,10 @@ export async function placeOrder( return order; } catch (err) { unlockOperating(locks, timers, pendings, type); + if (isUnknownOrderError(err)) { + log("order", "订单已成交或被撤销,跳过新单"); + return undefined; + } throw err; } } @@ -143,6 +152,10 @@ export async function placeMarketOrder( return order; } catch (err) { unlockOperating(locks, timers, pendings, type); + if (isUnknownOrderError(err)) { + log("order", "市价单失败但订单已不存在,忽略"); + return undefined; + } throw err; } } @@ -190,6 +203,10 @@ export async function placeStopLossOrder( return order; } catch (err) { unlockOperating(locks, timers, pendings, type); + if (isUnknownOrderError(err)) { + log("order", "止损单已失效,跳过"); + return undefined; + } throw err; } } @@ -231,6 +248,10 @@ export async function placeTrailingStopOrder( return order; } catch (err) { unlockOperating(locks, timers, pendings, type); + if (isUnknownOrderError(err)) { + log("order", "动态止盈单已失效,跳过"); + return undefined; + } throw err; } } @@ -263,6 +284,10 @@ export async function marketClose( log("close", `市价平仓: ${side}`); } catch (err) { unlockOperating(locks, timers, pendings, type); + if (isUnknownOrderError(err)) { + log("order", "市场平仓时订单已不存在"); + return; + } throw err; } } diff --git a/src/core/trend-engine.ts b/src/core/trend-engine.ts index 6a885ba..5cb5289 100644 --- a/src/core/trend-engine.ts +++ b/src/core/trend-engine.ts @@ -22,9 +22,9 @@ import { unlockOperating, } from "./order-coordinator"; import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "./order-coordinator"; +import { isUnknownOrderError } from "../utils/errors"; import { toPrice1Decimal } from "../utils/math"; import { createTradeLog, type TradeLogEntry } from "../state/trade-log"; -import { loadState, saveState } from "../utils/persistence"; export interface TrendEngineSnapshot { ready: boolean; @@ -37,6 +37,7 @@ export interface TrendEngineSnapshot { unrealized: number; totalProfit: number; totalTrades: number; + sessionVolume: number; tradeLog: TradeLogEntry[]; openOrders: AsterOrder[]; depth: AsterDepth | null; @@ -74,18 +75,15 @@ export class TrendEngine { private totalProfit = 0; private totalTrades = 0; private lastOpenPlan: OpenOrderPlan = { side: null, price: null }; - private savedStateApplied = false; - private readonly stateFile: string; - private lastPersistedAt = 0; - private savedOpenOrders: AsterOrder[] = []; + private sessionQuoteVolume = 0; + private prevPositionAmt = 0; + private initializedPosition = false; private readonly listeners = new Map>(); constructor(private readonly config: TradingConfig, private readonly exchange: ExchangeAdapter) { this.tradeLog = createTradeLog(this.config.maxLogEntries); - this.stateFile = `trend-${this.config.symbol}.json`; this.bootstrap(); - void this.restoreState(); } start(): void { @@ -124,6 +122,8 @@ export class TrendEngine { private bootstrap(): void { this.exchange.watchAccount((snapshot) => { this.accountSnapshot = snapshot; + const position = getPosition(snapshot, this.config.symbol); + this.updateSessionVolume(position); this.emitUpdate(); }); this.exchange.watchOrders((orders) => { @@ -131,7 +131,6 @@ export class TrendEngine { this.openOrders = Array.isArray(orders) ? orders.filter((order) => order.type !== "MARKET" && order.symbol === this.config.symbol) : []; - this.reconcileSavedOpenOrders(); this.emitUpdate(); }); this.exchange.watchDepth(this.config.symbol, (depth) => { @@ -194,6 +193,7 @@ export class TrendEngine { } } + this.updateSessionVolume(position); this.lastSma30 = sma30; this.lastPrice = price; this.emitUpdate(); @@ -309,7 +309,15 @@ export class TrendEngine { try { if (this.openOrders.length > 0) { const orderIdList = this.openOrders.map((order) => order.orderId); - await this.exchange.cancelOrders({ symbol: this.config.symbol, orderIdList }); + try { + await this.exchange.cancelOrders({ symbol: this.config.symbol, orderIdList }); + } catch (err) { + if (isUnknownOrderError(err)) { + this.tradeLog.push("order", "止损前撤单发现订单已不存在"); + } else { + throw err; + } + } } await marketClose( this.exchange, @@ -322,9 +330,13 @@ export class TrendEngine { this.config.tradeAmount, (type, detail) => this.tradeLog.push(type, detail) ); - this.tradeLog.push("close", `止损平仓: ${direction === "long" ? "SELL" : "BUY"}`); + this.tradeLog.push("close", `止损平仓: ${direction === "long" ? "SELL" : "BUY"}`); } catch (err) { - this.tradeLog.push("error", `止损平仓失败: ${String(err)}`); + if (isUnknownOrderError(err)) { + this.tradeLog.push("order", "止损平仓时目标订单已不存在"); + } else { + this.tradeLog.push("error", `止损平仓失败: ${String(err)}`); + } } return { closed: true, pnl }; } @@ -365,7 +377,11 @@ export class TrendEngine { try { await this.exchange.cancelOrder({ symbol: this.config.symbol, orderId: currentOrder.orderId }); } catch (err) { - this.tradeLog.push("error", `取消原止损单失败: ${String(err)}`); + if (isUnknownOrderError(err)) { + this.tradeLog.push("order", "原止损单已不存在,跳过撤销"); + } else { + this.tradeLog.push("error", `取消原止损单失败: ${String(err)}`); + } } await this.tryPlaceStopLoss(side, nextStopPrice, lastPrice); this.tradeLog.push("stop", `移动止损到 ${nextStopPrice}`); @@ -401,7 +417,6 @@ export class TrendEngine { if (handlers) { handlers.forEach((handler) => handler(snapshot)); } - void this.persistSnapshot(snapshot); } private buildSnapshot(): TrendEngineSnapshot { @@ -431,6 +446,7 @@ export class TrendEngine { unrealized: position.unrealizedProfit, totalProfit: this.totalProfit, totalTrades: this.totalTrades, + sessionVolume: this.sessionQuoteVolume, tradeLog: this.tradeLog.all(), openOrders: this.openOrders, depth: this.depthSnapshot, @@ -440,49 +456,36 @@ export class TrendEngine { }; } - private async restoreState(): Promise { - if (this.savedStateApplied) return; - this.savedStateApplied = true; - const state = await loadState<{ - totalProfit?: number; - totalTrades?: number; - lastOpenPlan?: OpenOrderPlan; - tradeLog?: TradeLogEntry[]; - openOrders?: AsterOrder[]; - }>(this.stateFile); - if (!state) return; - if (typeof state.totalProfit === "number") this.totalProfit = state.totalProfit; - if (typeof state.totalTrades === "number") this.totalTrades = state.totalTrades; - if (state.lastOpenPlan) this.lastOpenPlan = state.lastOpenPlan; - if (Array.isArray(state.tradeLog)) this.tradeLog.replace(state.tradeLog); - if (Array.isArray(state.openOrders)) this.savedOpenOrders = state.openOrders; - } - - private reconcileSavedOpenOrders(): void { - if (!this.savedOpenOrders.length) return; - const currentIds = new Set(this.openOrders.map((order) => order.orderId)); - const missing = this.savedOpenOrders.filter((order) => !currentIds.has(order.orderId)); - if (missing.length) { - this.tradeLog.push( - "order", - `检测到 ${missing.length} 个历史挂单与当前状态不符,将按策略逻辑重新挂单` - ); + private updateSessionVolume(position: PositionSnapshot): void { + const price = this.getReferencePrice(); + if (!this.initializedPosition) { + this.prevPositionAmt = position.positionAmt; + this.initializedPosition = true; + return; } - this.savedOpenOrders = []; + if (price == null) { + this.prevPositionAmt = position.positionAmt; + return; + } + const delta = Math.abs(position.positionAmt - this.prevPositionAmt); + if (delta > 0) { + this.sessionQuoteVolume += delta * price; + } + this.prevPositionAmt = position.positionAmt; } - private async persistSnapshot(snapshot: TrendEngineSnapshot): Promise { - const now = Date.now(); - if (now - this.lastPersistedAt < 1000) return; - this.lastPersistedAt = now; - await saveState(this.stateFile, { - totalProfit: this.totalProfit, - totalTrades: this.totalTrades, - lastOpenPlan: this.lastOpenPlan, - tradeLog: snapshot.tradeLog, - openOrders: snapshot.openOrders.filter((order) => order.symbol === this.config.symbol), - position: snapshot.position, - timestamp: snapshot.lastUpdated, - }); + private getReferencePrice(): number | null { + if (this.tickerSnapshot) { + const last = Number(this.tickerSnapshot.lastPrice); + if (Number.isFinite(last)) return last; + } + if (this.depthSnapshot) { + const bid = Number(this.depthSnapshot.bids?.[0]?.[0]); + const ask = Number(this.depthSnapshot.asks?.[0]?.[0]); + if (Number.isFinite(bid) && Number.isFinite(ask)) return (bid + ask) / 2; + } + if (this.lastPrice != null && Number.isFinite(this.lastPrice)) return this.lastPrice; + return null; } + } diff --git a/src/ui/MakerApp.tsx b/src/ui/MakerApp.tsx index 09eae7c..8732574 100644 --- a/src/ui/MakerApp.tsx +++ b/src/ui/MakerApp.tsx @@ -79,7 +79,8 @@ export function MakerApp({ onExit }: MakerAppProps) { const topAsk = snapshot.topAsk; const spreadDisplay = snapshot.spread != null ? `${snapshot.spread.toFixed(4)} USDT` : "-"; const hasPosition = Math.abs(snapshot.position.positionAmt) > 1e-5; - const openOrderRows = snapshot.openOrders.map((order) => ({ + const sortedOrders = [...snapshot.openOrders].sort((a, b) => (Number(b.updateTime ?? 0) - Number(a.updateTime ?? 0)) || Number(b.orderId) - Number(a.orderId)); + const openOrderRows = sortedOrders.slice(0, 8).map((order) => ({ id: order.orderId, side: order.side, price: order.price, @@ -113,7 +114,7 @@ export function MakerApp({ onExit }: MakerAppProps) { { key: "reduceOnly", header: "RO", minWidth: 4 }, ]; - const lastLogs = snapshot.tradeLog.slice(-10); + const lastLogs = snapshot.tradeLog.slice(-5); return ( @@ -136,6 +137,9 @@ export function MakerApp({ onExit }: MakerAppProps) { 浮动盈亏: {formatNumber(snapshot.pnl, 4)} USDT | 账户未实现盈亏: {formatNumber(snapshot.accountUnrealized, 4)} USDT + + 累计成交量: {formatNumber(snapshot.sessionVolume, 2)} USDT + ) : ( 当前无持仓 diff --git a/src/ui/TrendApp.tsx b/src/ui/TrendApp.tsx index 83f1267..4a4faa4 100644 --- a/src/ui/TrendApp.tsx +++ b/src/ui/TrendApp.tsx @@ -79,8 +79,9 @@ export function TrendApp({ onExit }: TrendAppProps) { const { position, tradeLog, openOrders, trend, ready, lastPrice, sma30 } = snapshot; const hasPosition = Math.abs(position.positionAmt) > 1e-5; - const lastLogs = tradeLog.slice(-10); - const orderRows = openOrders.slice(0, 8).map((order) => ({ + const lastLogs = tradeLog.slice(-5); + const sortedOrders = [...openOrders].sort((a, b) => (Number(b.updateTime ?? 0) - Number(a.updateTime ?? 0)) || Number(b.orderId) - Number(a.orderId)); + const orderRows = sortedOrders.slice(0, 8).map((order) => ({ id: order.orderId, side: order.side, type: order.type, @@ -130,6 +131,9 @@ export function TrendApp({ onExit }: TrendAppProps) { 累计交易次数: {snapshot.totalTrades} | 累计收益: {formatNumber(snapshot.totalProfit, 4)} USDT + + 累计成交量: {formatNumber(snapshot.sessionVolume, 2)} USDT + {snapshot.lastOpenSignal.side ? ( 最近开仓信号: {snapshot.lastOpenSignal.side} @ {formatNumber(snapshot.lastOpenSignal.price, 2)} diff --git a/src/utils/errors.ts b/src/utils/errors.ts new file mode 100644 index 0000000..dda74cc --- /dev/null +++ b/src/utils/errors.ts @@ -0,0 +1,15 @@ +export function isUnknownOrderError(error: unknown): boolean { + const message = extractMessage(error); + if (!message) return false; + return message.includes("Unknown order") || message.includes("code\":-2011"); +} + +export function extractMessage(error: unknown): string { + if (typeof error === "string") return error; + if (error instanceof Error) return error.message; + try { + return JSON.stringify(error); + } catch { + return String(error); + } +} diff --git a/src/utils/persistence.ts b/src/utils/persistence.ts deleted file mode 100644 index 41f1138..0000000 --- a/src/utils/persistence.ts +++ /dev/null @@ -1,37 +0,0 @@ -import fs from "fs/promises"; -import path from "path"; - -const DATA_DIR = path.resolve(process.cwd(), "data"); -let dataDirReady = false; - -async function ensureDataDir(): Promise { - if (dataDirReady) return; - await fs.mkdir(DATA_DIR, { recursive: true }).catch(() => undefined); - dataDirReady = true; -} - -export async function loadState(fileName: string): Promise { - try { - await ensureDataDir(); - const filePath = path.join(DATA_DIR, fileName); - const data = await fs.readFile(filePath, "utf8"); - return JSON.parse(data) as T; - } catch (error: any) { - if (error?.code === "ENOENT") return null; - console.error(`[state] load ${fileName} failed`, error); - return null; - } -} - -export async function saveState(fileName: string, state: unknown): Promise { - try { - await ensureDataDir(); - const filePath = path.join(DATA_DIR, fileName); - const tempPath = `${filePath}.tmp`; - const payload = JSON.stringify(state, null, 2); - await fs.writeFile(tempPath, payload, "utf8"); - await fs.rename(tempPath, filePath); - } catch (error) { - console.error(`[state] save ${fileName} failed`, error); - } -} diff --git a/tests/order-coordinator.test.ts b/tests/order-coordinator.test.ts index 680f876..c17e794 100644 --- a/tests/order-coordinator.test.ts +++ b/tests/order-coordinator.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from "vitest"; import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { AsterOrder } from "../src/exchanges/types"; +import type { OrderLockMap, OrderTimerMap, OrderPendingMap } from "../src/core/order-coordinator"; import { deduplicateOrders, placeOrder, @@ -9,9 +10,6 @@ import { placeTrailingStopOrder, marketClose, unlockOperating, - OrderLockMap, - OrderTimerMap, - OrderPendingMap, } from "../src/core/order-coordinator"; const baseOrder: AsterOrder = { @@ -169,7 +167,7 @@ describe("order-coordinator", () => { timers, pending, "SELL", - "BUY", + 1, log ); expect(adapter.createOrder).toHaveBeenCalled(); @@ -178,7 +176,8 @@ describe("order-coordinator", () => { it("unlockOperating clears timers and pending", () => { const locks: OrderLockMap = { LIMIT: true }; - const timers: OrderTimerMap = { LIMIT: setTimeout(() => undefined, 0) }; + const fakeTimer = {} as ReturnType; + const timers: OrderTimerMap = { LIMIT: fakeTimer }; const pending: OrderPendingMap = { LIMIT: "123" }; unlockOperating(locks, timers, pending, "LIMIT"); expect(locks.LIMIT).toBe(false);