diff --git a/src/core/order-coordinator.ts b/src/core/order-coordinator.ts index 62685c8..8f684d2 100644 --- a/src/core/order-coordinator.ts +++ b/src/core/order-coordinator.ts @@ -10,6 +10,7 @@ import { import { roundDownToTick, roundQtyDownToStep } from "../utils/math"; import { isUnknownOrderError } from "../utils/errors"; import { isOrderPriceAllowedByMark } from "../utils/strategy"; +import { t } from "../i18n"; export type OrderLockMap = Record; export type OrderTimerMap = Record | null>; @@ -90,7 +91,7 @@ function enforceMarkPriceGuard( toCheckPrice: number | null | undefined, guard: OrderGuardOptions | undefined, log: LogHandler, - context: string + kind: string ): boolean { if (!guard || guard.maxPct == null) return true; const allowed = isOrderPriceAllowedByMark({ @@ -104,7 +105,13 @@ function enforceMarkPriceGuard( const markStr = Number.isFinite(Number(guard.markPrice)) ? Number(guard.markPrice).toFixed(2) : String(guard.markPrice); log( "info", - `${context} 保护触发:side=${side} price=${priceStr} mark=${markStr} 超过 ${(guard.maxPct! * 100).toFixed(2)}%` + t("log.order.markGuardBlocked", { + kind, + side, + price: priceStr, + mark: markStr, + pct: (guard.maxPct! * 100).toFixed(2), + }) ); return false; } @@ -137,7 +144,7 @@ export function lockOperating( timers[type] = setTimeout(() => { locks[type] = false; pendings[type] = null; - log("info", `${type} 操作超时自动解锁`); + log("info", t("log.order.lockTimeout", { type })); }, timeout); } @@ -182,12 +189,12 @@ export async function deduplicateOrders( try { lockOperating(locks, timers, pendings, type, log); await adapter.cancelOrders({ symbol, orderIdList }); - log("order", `去重撤销重复 ${type} 单: ${orderIdList.join(",")}`); + log("order", t("log.order.dedupeCancelled", { type, ids: orderIdList.join(",") })); } catch (err) { if (isUnknownOrderError(err)) { - log("order", "去重时发现订单已不存在,跳过删除"); + log("order", t("log.order.dedupeGone")); } else { - log("error", `去重撤单失败: ${String(err)}`); + log("error", t("log.order.dedupeFailed", { error: String(err) })); } } finally { unlockOperating(locks, timers, pendings, type); @@ -203,10 +210,10 @@ export async function placeOrder( const type = "LIMIT"; if (isOperating(locks, type)) return; const priceNum = Number(request.price); - if (!enforceMarkPriceGuard(side, priceNum, guard, log, "限价单")) return; + if (!enforceMarkPriceGuard(side, priceNum, guard, log, t("order.kind.limit"))) return; const quantity = normalizeQuantity(request.amount, request.qtyStep ?? DEFAULT_QTY_STEP); if (quantity <= 0) { - log("error", "限价单数量无效,跳过下单"); + log("error", t("log.order.invalidQuantity", { kind: t("order.kind.limit") })); return; } if (!request.skipDedupe) { @@ -229,12 +236,21 @@ export async function placeOrder( clientOrderId: request.clientOrderId, }); pendings[type] = String(order.orderId); - log("order", `挂限价单: ${side} @ ${priceNum} 数量 ${quantity} reduceOnly=${reduceOnly}${request.slPrice ? ` sl=${request.slPrice}` : ""}`); + log( + "order", + t("log.order.limitPlaced", { + side, + price: priceNum, + quantity, + reduceOnly, + sl: request.slPrice ? ` sl=${request.slPrice}` : "", + }) + ); return order; } catch (err) { unlockOperating(locks, timers, pendings, type); if (isUnknownOrderError(err)) { - log("order", "订单已成交或被撤销,跳过新单"); + log("order", t("log.order.limitGone")); return undefined; } throw err; @@ -249,10 +265,10 @@ export async function placeMarketOrder( const { side, openOrders, guard, reduceOnly = false } = request; const type = "MARKET"; if (isOperating(locks, type)) return; - if (!enforceMarkPriceGuard(side, guard?.expectedPrice ?? null, guard, log, "市价单")) return; + if (!enforceMarkPriceGuard(side, guard?.expectedPrice ?? null, guard, log, t("order.kind.market"))) return; const quantity = normalizeQuantity(request.amount, request.qtyStep ?? DEFAULT_QTY_STEP); if (quantity <= 0) { - log("error", "市价单数量无效,跳过下单"); + log("error", t("log.order.invalidQuantity", { kind: t("order.kind.market") })); return; } await deduplicateOrders(ctx, openOrders, type, side); @@ -268,12 +284,12 @@ export async function placeMarketOrder( closePosition, }); pendings[type] = String(order.orderId); - log("order", `市价单: ${side} 数量 ${quantity} reduceOnly=${reduceOnly}`); + log("order", t("log.order.marketPlaced", { side, quantity, reduceOnly })); return order; } catch (err) { unlockOperating(locks, timers, pendings, type); if (isUnknownOrderError(err)) { - log("order", "市价单失败但订单已不存在,忽略"); + log("order", t("log.order.marketGone")); return undefined; } throw err; @@ -288,21 +304,21 @@ export async function placeStopLossOrder( const { side, openOrders, guard, stopPrice, lastPrice } = request; const type = "STOP_MARKET"; if (isOperating(locks, type)) return; - if (!enforceMarkPriceGuard(side, stopPrice, guard, log, "止损单")) return; + if (!enforceMarkPriceGuard(side, stopPrice, guard, log, t("order.kind.stop"))) return; if (lastPrice != null) { if (side === "SELL" && stopPrice >= lastPrice) { - log("error", `止损价 ${stopPrice} 高于或等于当前价 ${lastPrice},取消挂单`); + log("error", t("log.order.stopAboveLast", { stopPrice, lastPrice })); return; } if (side === "BUY" && stopPrice <= lastPrice) { - log("error", `止损价 ${stopPrice} 低于或等于当前价 ${lastPrice},取消挂单`); + log("error", t("log.order.stopBelowLast", { stopPrice, lastPrice })); return; } } const normalizedStop = roundDownToTick(stopPrice, request.priceTick ?? DEFAULT_PRICE_TICK); const normalizedQty = normalizeQuantity(request.quantity, request.qtyStep ?? DEFAULT_QTY_STEP); if (normalizedQty <= 0) { - log("error", "止损单数量无效,跳过下单"); + log("error", t("log.order.invalidQuantity", { kind: t("order.kind.stop") })); return; } @@ -322,12 +338,12 @@ export async function placeStopLossOrder( triggerType: "STOP_LOSS", }); pendings[type] = String(order.orderId); - log("stop", `挂止损单: ${side} STOP_MARKET @ ${normalizedStop}`); + log("stop", t("log.order.stopPlaced", { side, stopPrice: normalizedStop })); return order; } catch (err) { unlockOperating(locks, timers, pendings, type); if (isUnknownOrderError(err)) { - log("order", "止损单已失效,跳过"); + log("order", t("log.order.stopGone")); return undefined; } throw err; @@ -343,14 +359,14 @@ export async function placeTrailingStopOrder( const type = "TRAILING_STOP_MARKET"; if (isOperating(locks, type)) return; if (!adapter.supportsTrailingStops()) { - log("error", "当前交易所不支持动态止盈单"); + log("error", t("log.order.trailingUnsupported")); return; } - if (!enforceMarkPriceGuard(side, activationPrice, guard, log, "动态止盈单")) return; + if (!enforceMarkPriceGuard(side, activationPrice, guard, log, t("order.kind.trailing"))) return; const normalizedActivation = roundDownToTick(activationPrice, request.priceTick ?? DEFAULT_PRICE_TICK); const normalizedQty = normalizeQuantity(request.quantity, request.qtyStep ?? DEFAULT_QTY_STEP); if (normalizedQty <= 0) { - log("error", "动态止盈单数量无效,跳过下单"); + log("error", t("log.order.invalidQuantity", { kind: t("order.kind.trailing") })); return; } await deduplicateOrders(ctx, openOrders, type, side); @@ -369,13 +385,17 @@ export async function placeTrailingStopOrder( pendings[type] = String(order.orderId); log( "order", - `挂动态止盈单: ${side} activation=${normalizedActivation} callbackRate=${callbackRate}` + t("log.order.trailingPlaced", { + side, + activation: normalizedActivation, + callbackRate, + }) ); return order; } catch (err) { unlockOperating(locks, timers, pendings, type); if (isUnknownOrderError(err)) { - log("order", "动态止盈单已失效,跳过"); + log("order", t("log.order.trailingGone")); return undefined; } throw err; @@ -387,7 +407,7 @@ export async function marketClose(ctx: OrderContext, request: MarketCloseRequest const { side, openOrders, guard, qtyStep } = request; const type = "MARKET"; if (isOperating(locks, type)) return; - if (!enforceMarkPriceGuard(side, guard?.expectedPrice ?? null, guard, log, "市价平仓")) return; + if (!enforceMarkPriceGuard(side, guard?.expectedPrice ?? null, guard, log, t("order.kind.close"))) return; const rawQuantity = Math.abs(request.quantity); let normalizedQty = qtyStep != null ? normalizeQuantity(rawQuantity, qtyStep) : rawQuantity; @@ -400,7 +420,7 @@ export async function marketClose(ctx: OrderContext, request: MarketCloseRequest } } if (normalizedQty <= 0) { - log("error", "市价平仓数量无效,跳过下单"); + log("error", t("log.order.invalidQuantity", { kind: t("order.kind.close") })); return; } @@ -416,11 +436,11 @@ export async function marketClose(ctx: OrderContext, request: MarketCloseRequest closePosition: true, }); pendings[type] = String(order.orderId); - log("close", `市价平仓: ${side}`); + log("close", t("log.order.closePlaced", { side })); } catch (err) { unlockOperating(locks, timers, pendings, type); if (isUnknownOrderError(err)) { - log("order", "市场平仓时订单已不存在"); + log("order", t("log.order.closeGone")); return; } throw err; diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 7003b75..db45890 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -725,6 +725,141 @@ const translations: Record = { "log.trend.trailingFail": { zh: "挂动态止盈失败: {error}", en: "Failed to place trailing stop: {error}" }, "log.trend.updateHandlerError": { zh: "更新回调处理异常: {error}", en: "Update handler error: {error}" }, "log.trend.snapshotDispatchError": { zh: "快照或更新分发异常: {error}", en: "Snapshot/update dispatch error: {error}" }, + // --- core/order-coordinator --- + "order.kind.limit": { zh: "限价单", en: "Limit order" }, + "order.kind.market": { zh: "市价单", en: "Market order" }, + "order.kind.stop": { zh: "止损单", en: "Stop order" }, + "order.kind.trailing": { zh: "动态止盈单", en: "Trailing stop order" }, + "order.kind.close": { zh: "市价平仓", en: "Market close" }, + "log.order.markGuardBlocked": { + zh: "{kind} 保护触发:side={side} price={price} mark={mark} 超过 {pct}%", + en: "{kind} blocked by mark-price guard: side={side} price={price} mark={mark} exceeds {pct}%", + }, + "log.order.lockTimeout": { + zh: "{type} 操作超时自动解锁", + en: "{type} operation timed out; lock released", + }, + "log.order.dedupeCancelled": { + zh: "去重撤销重复 {type} 单: {ids}", + en: "Cancelled duplicate {type} orders: {ids}", + }, + "log.order.dedupeGone": { + zh: "去重时发现订单已不存在,跳过删除", + en: "Order already gone while deduplicating; skipping cancel", + }, + "log.order.dedupeFailed": { + zh: "去重撤单失败: {error}", + en: "Failed to cancel duplicates: {error}", + }, + "log.order.invalidQuantity": { + zh: "{kind}数量无效,跳过下单", + en: "{kind} quantity is invalid; skipping", + }, + "log.order.limitPlaced": { + zh: "挂限价单: {side} @ {price} 数量 {quantity} reduceOnly={reduceOnly}{sl}", + en: "Placed limit order: {side} @ {price} qty {quantity} reduceOnly={reduceOnly}{sl}", + }, + "log.order.limitGone": { + zh: "订单已成交或被撤销,跳过新单", + en: "Order already filled or cancelled; skipping new order", + }, + "log.order.marketPlaced": { + zh: "市价单: {side} 数量 {quantity} reduceOnly={reduceOnly}", + en: "Market order: {side} qty {quantity} reduceOnly={reduceOnly}", + }, + "log.order.marketGone": { + zh: "市价单失败但订单已不存在,忽略", + en: "Market order failed but the order is already gone; ignoring", + }, + "log.order.stopAboveLast": { + zh: "止损价 {stopPrice} 高于或等于当前价 {lastPrice},取消挂单", + en: "Stop price {stopPrice} is at or above the last price {lastPrice}; not placing", + }, + "log.order.stopBelowLast": { + zh: "止损价 {stopPrice} 低于或等于当前价 {lastPrice},取消挂单", + en: "Stop price {stopPrice} is at or below the last price {lastPrice}; not placing", + }, + "log.order.stopPlaced": { + zh: "挂止损单: {side} STOP_MARKET @ {stopPrice}", + en: "Placed stop order: {side} STOP_MARKET @ {stopPrice}", + }, + "log.order.stopGone": { zh: "止损单已失效,跳过", en: "Stop order no longer valid; skipping" }, + "log.order.trailingUnsupported": { + zh: "当前交易所不支持动态止盈单", + en: "This exchange does not support trailing stop orders", + }, + "log.order.trailingPlaced": { + zh: "挂动态止盈单: {side} activation={activation} callbackRate={callbackRate}", + en: "Placed trailing stop: {side} activation={activation} callbackRate={callbackRate}", + }, + "log.order.trailingGone": { + zh: "动态止盈单已失效,跳过", + en: "Trailing stop no longer valid; skipping", + }, + "log.order.closePlaced": { zh: "市价平仓: {side}", en: "Market close: {side}" }, + "log.order.closeGone": { + zh: "市场平仓时订单已不存在", + en: "Order already gone while closing at market", + }, + // --- utils/standx-token-expiry --- + "token.expiringSoon": { + zh: "StandX Token 将在 {minutes} 分钟后过期", + en: "StandX token expires in {minutes} minutes", + }, + "token.expiredCancelling": { + zh: "StandX Token 已过期,正在取消所有挂单", + en: "StandX token expired; cancelling all open orders", + }, + "token.expiredWithPosition": { + zh: "StandX Token 已过期,仅保留平仓/止损逻辑", + en: "StandX token expired; only close/stop logic remains active", + }, + "token.expiredSilent": { + zh: "StandX Token 已过期,进入静默数据接收模式", + en: "StandX token expired; entering silent data-only mode", + }, + "log.token.closeOnlyForced": { + zh: "Token 过期,强制进入平仓模式,仅允许 reduce-only 订单", + en: "Token expired; forcing close-only mode, reduce-only orders only", + }, + "log.token.silentEntered": { + zh: "进入静默数据接收模式,不再进行任何交易操作", + en: "Entered silent data-only mode; no further trading actions", + }, + "log.token.ordersCancelled": { + zh: "Token 过期,已撤销所有挂单", + en: "Token expired; cancelled all open orders", + }, + "log.token.cancelOrderMissing": { + zh: "Token 过期撤单时订单已不存在", + en: "Order already gone while cancelling after token expiry", + }, + "log.token.cancelFailed": { + zh: "Token 过期撤单失败: {error}", + en: "Failed to cancel orders after token expiry: {error}", + }, + "notify.token.title": { zh: "Token 已过期", en: "Token expired" }, + "notify.token.closeOnly": { + zh: "Token 已过期,进入平仓模式,不再开新仓", + en: "Token expired; entering close-only mode, no new positions", + }, + "notify.token.silent": { + zh: "Token 已过期,策略进入静默模式", + en: "Token expired; strategy entering silent mode", + }, + // --- strategy/common/isolated-margin-guard --- + "log.margin.switched": { + zh: "已切换为逐仓模式 (isolated),恢复策略运行", + en: "Switched to isolated margin; resuming strategy", + }, + "log.margin.switchUnconfirmed": { + zh: "逐仓模式切换未确认,当前模式: {mode}", + en: "Isolated margin switch unconfirmed; current mode: {mode}", + }, + "log.margin.switchFailed": { + zh: "切换逐仓模式失败: {error}", + en: "Failed to switch to isolated margin: {error}", + }, }; const formatTemplate = (template: string, params: Record): string => { diff --git a/src/strategy/common/isolated-margin-guard.ts b/src/strategy/common/isolated-margin-guard.ts index 81cda74..5b05fc9 100644 --- a/src/strategy/common/isolated-margin-guard.ts +++ b/src/strategy/common/isolated-margin-guard.ts @@ -1,5 +1,6 @@ import type { AccountSnapshot } from "../../exchanges/types"; import { extractMessage } from "../../utils/errors"; +import { t } from "../../i18n"; import type { LogHandler } from "./subscriptions"; /** Snapshot refreshes to wait through before giving up on the switch (~5s total). */ @@ -74,15 +75,15 @@ export class IsolatedMarginGuard { this.deps.applySnapshot(next); } if (this.currentMode() === "isolated") { - this.deps.log("info", "已切换为逐仓模式 (isolated),恢复策略运行"); + this.deps.log("info", t("log.margin.switched")); return true; } await sleep(CONFIRM_INTERVAL_MS); } - this.deps.log("warn", `逐仓模式切换未确认,当前模式: ${this.currentMode() ?? "unknown"}`); + this.deps.log("warn", t("log.margin.switchUnconfirmed", { mode: this.currentMode() ?? "unknown" })); return false; } catch (error) { - this.deps.log("error", `切换逐仓模式失败: ${extractMessage(error)}`); + this.deps.log("error", t("log.margin.switchFailed", { error: extractMessage(error) })); return false; } finally { this.ensuring = null; diff --git a/src/strategy/common/token-expiry-guard.ts b/src/strategy/common/token-expiry-guard.ts index 7e062cb..44e7da5 100644 --- a/src/strategy/common/token-expiry-guard.ts +++ b/src/strategy/common/token-expiry-guard.ts @@ -6,6 +6,7 @@ import { type TokenExpiryState, type TokenExpiryStatus, } from "../../utils/standx-token-expiry"; +import { t } from "../../i18n"; import type { LogHandler } from "./subscriptions"; /** What the engine should do for the rest of this tick. */ @@ -76,13 +77,13 @@ export class TokenExpiryGuard { if (status.state === "expired_with_position") { if (!this.closeOnly) { this.closeOnly = true; - this.deps.log("info", "Token 过期,强制进入平仓模式,仅允许 reduce-only 订单"); + this.deps.log("info", t("log.token.closeOnlyForced")); } return { halt: false, closeOnly: true }; } if (status.state === "silent" && previousState !== "silent") { - this.deps.log("info", "进入静默数据接收模式,不再进行任何交易操作"); + this.deps.log("info", t("log.token.silentEntered")); } return { halt: true, closeOnly: this.closeOnly }; } @@ -120,18 +121,18 @@ export class TokenExpiryGuard { if (this.cancelDone || openOrderCount === 0) return; try { await this.deps.cancelAllOrders(); - this.deps.log("order", "Token 过期,已撤销所有挂单"); + this.deps.log("order", t("log.token.ordersCancelled")); this.deps.onOrdersCancelled(); this.cancelDone = true; } catch (error) { if (isUnknownOrderError(error)) { // Nothing left to cancel is the outcome we wanted. - this.deps.log("order", "Token 过期撤单时订单已不存在"); + this.deps.log("order", t("log.token.cancelOrderMissing")); this.cancelDone = true; return; } // Leave cancelDone false so the next tick retries. - this.deps.log("error", `Token 过期撤单失败: ${extractMessage(error)}`); + this.deps.log("error", t("log.token.cancelFailed", { error: extractMessage(error) })); } } } diff --git a/src/strategy/maker-points-engine.ts b/src/strategy/maker-points-engine.ts index 50c5919..83c875c 100644 --- a/src/strategy/maker-points-engine.ts +++ b/src/strategy/maker-points-engine.ts @@ -213,10 +213,8 @@ export class MakerPointsEngine { type: "token_expired", level: "warn", symbol: this.config.symbol, - title: "Token 已过期", - message: hasPosition - ? "Token 已过期,进入平仓模式,不再开新仓" - : "Token 已过期,策略进入静默模式", + title: t("notify.token.title"), + message: hasPosition ? t("notify.token.closeOnly") : t("notify.token.silent"), details: { hasPosition, hasOpenOrders, state }, }), cancelAllOrders: () => this.exchange.cancelAllOrders({ symbol: this.config.symbol }), diff --git a/src/strategy/trend-engine.ts b/src/strategy/trend-engine.ts index 50323e2..68d1678 100644 --- a/src/strategy/trend-engine.ts +++ b/src/strategy/trend-engine.ts @@ -32,6 +32,7 @@ import { createTradeLog, type TradeLogEntry } from "../logging/trade-log"; import { decryptCopyright } from "../utils/copyright"; import { isRateLimitError } from "../utils/errors"; import { RateLimitController } from "../core/lib/rate-limit"; +import type { TrendLabel } from "../utils/format"; import { StrategyEventEmitter } from "./common/event-emitter"; import { createPrecisionSyncer, type PrecisionSyncer } from "./common/precision-syncer"; import { safeSubscribe, type LogHandler } from "./common/subscriptions"; @@ -44,7 +45,7 @@ export interface TrendEngineSnapshot { lastPrice: number | null; sma30: number | null; bollingerBandwidth: number | null; - trend: "做多" | "做空" | "无信号"; + trend: TrendLabel; position: PositionSnapshot; pnl: number; unrealized: number; @@ -978,13 +979,14 @@ export class TrendEngine { const position = getPosition(this.accountSnapshot, this.config.symbol); const price = this.tickerSnapshot ? Number(this.tickerSnapshot.lastPrice) : null; const sma30 = this.lastSma30; - const trend = price == null || sma30 == null - ? "无信号" - : price > sma30 - ? "做多" - : price < sma30 - ? "做空" - : "无信号"; + const trend: TrendLabel = + price == null || sma30 == null + ? "none" + : price > sma30 + ? "long" + : price < sma30 + ? "short" + : "none"; const pnl = price != null ? computePositionPnl(position, price, price) : 0; return { ready: this.isReady(), diff --git a/src/utils/format.ts b/src/utils/format.ts index 0e9aead..5829ee0 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -1,10 +1,11 @@ import { t } from "../i18n"; -export type TrendLabel = "做多" | "做空" | "无信号"; +/** Direction the trend engine reports; a domain value, not display text. */ +export type TrendLabel = "long" | "short" | "none"; export function formatTrendLabel(trend: TrendLabel): string { - if (trend === "做多") return t("trend.label.long"); - if (trend === "做空") return t("trend.label.short"); + if (trend === "long") return t("trend.label.long"); + if (trend === "short") return t("trend.label.short"); return t("trend.label.none"); } diff --git a/src/utils/standx-token-expiry.ts b/src/utils/standx-token-expiry.ts index ba0301c..d0a5cc9 100644 --- a/src/utils/standx-token-expiry.ts +++ b/src/utils/standx-token-expiry.ts @@ -1,4 +1,5 @@ import { isStandxTokenExpired, getStandxTokenExpiryInfo, standxTokenConfig } from "../config"; +import { t } from "../i18n"; export type TokenExpiryState = "active" | "expired" | "expired_with_position" | "silent"; @@ -68,18 +69,18 @@ export function formatTokenExpiryMessage(status: TokenExpiryStatus): string | nu if (!status.expired) { if (status.remainingMs != null && status.remainingMs < 3600_000) { const mins = Math.ceil(status.remainingMs / 60_000); - return `StandX Token 将在 ${mins} 分钟后过期`; + return t("token.expiringSoon", { minutes: mins }); } return null; } switch (status.state) { case "expired": - return "StandX Token 已过期,正在取消所有挂单"; + return t("token.expiredCancelling"); case "expired_with_position": - return "StandX Token 已过期,仅保留平仓/止损逻辑"; + return t("token.expiredWithPosition"); case "silent": - return "StandX Token 已过期,进入静默数据接收模式"; + return t("token.expiredSilent"); default: return null; } diff --git a/tests/isolated-margin-guard.test.ts b/tests/isolated-margin-guard.test.ts index b1e5d63..adcf1f4 100644 --- a/tests/isolated-margin-guard.test.ts +++ b/tests/isolated-margin-guard.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from "vitest"; import { IsolatedMarginGuard } from "../src/strategy/common/isolated-margin-guard"; import type { AccountSnapshot } from "../src/exchanges/types"; +import { t } from "../src/i18n"; const SYMBOL = "BTC-USD"; @@ -71,7 +72,7 @@ describe("IsolatedMarginGuard", () => { }); expect(await guard.ensureIsolated()).toBe(true); expect(changeMarginMode).toHaveBeenCalledWith({ symbol: SYMBOL, marginMode: "isolated" }); - expect(logs.some(([, detail]) => detail.includes("已切换为逐仓模式"))).toBe(true); + expect(logs.some(([, detail]) => detail === t("log.margin.switched"))).toBe(true); }); it("gives up after the confirm attempts run out", async () => { diff --git a/tests/order-coordinator.test.ts b/tests/order-coordinator.test.ts index 539fba4..68eafda 100644 --- a/tests/order-coordinator.test.ts +++ b/tests/order-coordinator.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi, beforeEach, afterAll } from "vitest"; import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { Order } from "../src/exchanges/types"; +import { t } from "../src/i18n"; import type { OrderContext, OrderLockMap, @@ -84,7 +85,7 @@ describe("order-coordinator", () => { ]; await deduplicateOrders(ctx, openOrders, "LIMIT", "BUY"); expect(adapter.cancelOrders).toHaveBeenCalledWith({ symbol: "BTCUSDT", orderIdList: [2] }); - expect(log).toHaveBeenCalledWith("order", expect.stringContaining("去重撤销重复")); + expect(log).toHaveBeenCalledWith("order", t("log.order.dedupeCancelled", { type: "LIMIT", ids: "2" })); }); it("places limit orders and records pending id", async () => { @@ -112,7 +113,7 @@ describe("order-coordinator", () => { lastPrice: 100, }); expect(adapter.createOrder).toHaveBeenCalled(); - expect(log).toHaveBeenCalledWith("stop", expect.stringContaining("STOP_MARKET")); + expect(log).toHaveBeenCalledWith("stop", t("log.order.stopPlaced", { side: "SELL", stopPrice: 99 })); }); it("places trailing stop order", async () => { @@ -125,7 +126,10 @@ describe("order-coordinator", () => { callbackRate: 0.2, }); expect(adapter.createOrder).toHaveBeenCalled(); - expect(log).toHaveBeenCalledWith("order", expect.stringContaining("挂动态止盈单")); + expect(log).toHaveBeenCalledWith( + "order", + t("log.order.trailingPlaced", { side: "SELL", activation: 101, callbackRate: 0.2 }) + ); }); it("market close cancels open orders before placing close order", async () => { @@ -136,7 +140,7 @@ describe("order-coordinator", () => { quantity: 1, }); expect(adapter.createOrder).toHaveBeenCalled(); - expect(log).toHaveBeenCalledWith("close", expect.stringContaining("市价平仓")); + expect(log).toHaveBeenCalledWith("close", t("log.order.closePlaced", { side: "SELL" })); }); it("unlockOperating clears timers and pending", () => { diff --git a/tests/token-expiry-guard.test.ts b/tests/token-expiry-guard.test.ts index bcc2b3c..4a37f7a 100644 --- a/tests/token-expiry-guard.test.ts +++ b/tests/token-expiry-guard.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi, afterEach } from "vitest"; import { standxTokenConfig } from "../src/config"; +import { t } from "../src/i18n"; import { TokenExpiryGuard } from "../src/strategy/common/token-expiry-guard"; const HOUR_MS = 3_600_000; @@ -83,7 +84,7 @@ describe("TokenExpiryGuard", () => { await guard.evaluate({ positionAmt: 0, openOrderCount: 0 }); await guard.evaluate({ positionAmt: 0, openOrderCount: 0 }); const entryLogs = logs.filter( - ([type, detail]) => type === "info" && detail.includes("静默数据接收模式") + ([type, detail]) => type === "info" && detail === t("log.token.silentEntered") ); expect(entryLogs).toHaveLength(1); });