diff --git a/.env.example b/.env.example index 494bc63..10a4f81 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ +# UI language (zh | en) +LANG=zh + # Exchange selection EXCHANGE=aster # Pick aster (default) or grvt/lighter/backpack/paradex diff --git a/src/config.ts b/src/config.ts index 08ea03d..655cfa8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,6 +4,7 @@ */ import { resolveExchangeId, type SupportedExchangeId } from "./exchanges/create-adapter"; +import { language, type Language } from "./i18n"; export interface TradingConfig { symbol: string; @@ -211,3 +212,5 @@ export function isBasisStrategyEnabled(): boolean { const normalized = raw.trim().toLowerCase(); return normalized === "1" || normalized === "true" || normalized === "yes"; } + +export const uiLanguage: Language = language; diff --git a/src/core/lib/rate-limit.ts b/src/core/lib/rate-limit.ts index 815c613..ecd79cf 100644 --- a/src/core/lib/rate-limit.ts +++ b/src/core/lib/rate-limit.ts @@ -1,4 +1,5 @@ import type { LogHandler } from "../order-coordinator"; +import { t } from "../../i18n"; type RateLimitState = "normal" | "degraded" | "paused"; @@ -32,13 +33,13 @@ export class RateLimitController { private suppressEntries(source?: string): void { if (this.entriesSuppressed) return; this.entriesSuppressed = true; - this.log("info", `${source ? `${source} ` : ""}限频期间暂停新开仓`); + this.log("info", t("rate.limit.suppress", { source: source ? `${source} ` : "" })); } private allowEntries(): void { if (!this.entriesSuppressed) return; this.entriesSuppressed = false; - this.log("info", "限频恢复,允许重新开仓"); + this.log("info", t("rate.limit.resumeEntries")); } beforeCycle(): RateLimitDecision { @@ -47,7 +48,7 @@ export class RateLimitController { if (this.pausedUntil != null && now >= this.pausedUntil) { this.state = "degraded"; this.pausedUntil = null; - this.log("info", "限频暂停结束,继续以降频模式运行"); + this.log("info", t("rate.limit.pausedEnd")); } else { this.lastCycleAt = now; return "paused"; @@ -69,7 +70,7 @@ export class RateLimitController { this.state = "degraded"; this.log( "warn", - `${source ? `${source} ` : ""}触发 429,降频至 ${(this.currentInterval() / 1000).toFixed(2)}s` + t("rate.limit.hit", { source: source ? `${source} ` : "", interval: (this.currentInterval() / 1000).toFixed(2) }) ); this.lastCycleAt = now; this.suppressEntries(source); @@ -80,7 +81,7 @@ export class RateLimitController { this.pausedUntil = now + this.pauseMs; this.log( "warn", - `${source ? `${source} ` : ""}连续 429,暂停请求 ${(this.pauseMs / 1000).toFixed(0)}s` + t("rate.limit.consecutive", { source: source ? `${source} ` : "", seconds: (this.pauseMs / 1000).toFixed(0) }) ); this.suppressEntries(source); return; @@ -88,7 +89,7 @@ export class RateLimitController { this.pausedUntil = now + this.pauseMs; this.log( "warn", - `${source ? `${source} ` : ""}限频仍在持续,延长暂停 ${(this.pauseMs / 1000).toFixed(0)}s` + t("rate.limit.still", { source: source ? `${source} ` : "", seconds: (this.pauseMs / 1000).toFixed(0) }) ); this.suppressEntries(source); } @@ -99,7 +100,7 @@ export class RateLimitController { const now = Date.now(); if (now - this.lastRateLimitAt >= this.recoveryMs) { this.state = "normal"; - this.log("info", "限频恢复,重置为正常请求频率"); + this.log("info", t("rate.limit.reset")); this.allowEntries(); this.lastRateLimitAt = 0; } @@ -119,4 +120,3 @@ export class RateLimitController { return this.baseInterval; } } - diff --git a/src/exchanges/resolve-from-env.ts b/src/exchanges/resolve-from-env.ts index 1768d57..13a9a44 100644 --- a/src/exchanges/resolve-from-env.ts +++ b/src/exchanges/resolve-from-env.ts @@ -4,6 +4,7 @@ import type { AsterCredentials } from "./aster-adapter"; import type { LighterCredentials } from "./lighter/adapter"; import type { BackpackCredentials } from "./backpack/adapter"; import type { ParadexCredentials } from "./paradex/adapter"; +import { t } from "../i18n"; interface BuildAdapterOptions { symbol: string; @@ -41,7 +42,7 @@ function resolveAsterCredentials(): AsterCredentials { const apiKey = process.env.ASTER_API_KEY; const apiSecret = process.env.ASTER_API_SECRET; if (!apiKey || !apiSecret) { - throw new Error("缺少 ASTER_API_KEY 或 ASTER_API_SECRET 环境变量"); + throw new Error(t("env.missingAster")); } return { apiKey, apiSecret }; } @@ -50,11 +51,11 @@ function resolveLighterCredentials(symbol: string): LighterCredentials { const accountIndexRaw = process.env.LIGHTER_ACCOUNT_INDEX; const apiPrivateKey = process.env.LIGHTER_API_PRIVATE_KEY; if (!accountIndexRaw || !apiPrivateKey) { - throw new Error("缺少 LIGHTER_ACCOUNT_INDEX 或 LIGHTER_API_PRIVATE_KEY 环境变量"); + throw new Error(t("env.missingLighter")); } const accountIndex = Number(accountIndexRaw); if (!Number.isInteger(accountIndex)) { - throw new Error("LIGHTER_ACCOUNT_INDEX 必须是整数"); + throw new Error(t("env.lighterIndexInteger")); } const credentials: LighterCredentials = { displaySymbol: symbol, @@ -76,7 +77,7 @@ function resolveBackpackCredentials(symbol: string): BackpackCredentials { const apiKey = process.env.BACKPACK_API_KEY; const apiSecret = process.env.BACKPACK_API_SECRET; if (!apiKey || !apiSecret) { - throw new Error("缺少 BACKPACK_API_KEY 或 BACKPACK_API_SECRET 环境变量"); + throw new Error(t("env.missingBackpack")); } const credentials: BackpackCredentials = { apiKey, @@ -94,13 +95,13 @@ function resolveParadexCredentials(): ParadexCredentials { const walletAddress = process.env.PARADEX_WALLET_ADDRESS; if (!privateKey || !walletAddress) { - throw new Error("Paradex 需要配置 PARADEX_PRIVATE_KEY 与 PARADEX_WALLET_ADDRESS"); + throw new Error(t("env.missingParadex")); } if (!isHex32(privateKey)) { - throw new Error("PARADEX_PRIVATE_KEY 必须是 0x 开头的 32 字节十六进制字符串"); + throw new Error(t("env.invalidParadexPrivateKey")); } if (!isHexAddress(walletAddress)) { - throw new Error("PARADEX_WALLET_ADDRESS 必须是有效的 0x 开头 40 字节十六进制地址"); + throw new Error(t("env.invalidParadexAddress")); } const credentials: ParadexCredentials = { diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..e54442f --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,629 @@ +export type Language = "zh" | "en"; + +const normalizeLanguage = (value: string | undefined): Language => { + if (!value) return "zh"; + const normalized = value.trim().toLowerCase(); + if (normalized === "en" || normalized.startsWith("en-") || normalized.startsWith("en_")) return "en"; + if (normalized === "zh" || normalized.startsWith("zh")) return "zh"; + if (normalized === "english") return "en"; + if (normalized === "chinese") return "zh"; + return "zh"; +}; + +export const language: Language = normalizeLanguage(process.env.LANG); + +type TranslationValue = string | ((params: Record, lang: Language) => string); + +type TranslationEntry = { + zh: TranslationValue; + en: TranslationValue; +}; + +const translations: Record = { + "app.strategy.trend.label": { zh: "趋势跟随策略 (SMA30)", en: "Trend Following (SMA30)" }, + "app.strategy.trend.desc": { + zh: "监控均线信号,自动进出场并维护止损/止盈", + en: "Monitors SMA signals, automates entries/exits, maintains stops.", + }, + "app.strategy.guardian.label": { zh: "Guardian 防守策略", en: "Guardian Protection" }, + "app.strategy.guardian.desc": { + zh: "不主动开仓,只为现有仓位补挂/移动止损,防止裸奔", + en: "Does not open positions; only manages stops for existing positions.", + }, + "app.strategy.maker.label": { zh: "做市刷单策略", en: "Maker Market Making" }, + "app.strategy.maker.desc": { + zh: "双边挂单提供流动性,自动追价与风控止损", + en: "Places two-sided quotes, auto-chases and risk-manages stops.", + }, + "app.strategy.grid.label": { zh: "基础网格策略", en: "Grid Strategy" }, + "app.strategy.grid.desc": { + zh: "在上下边界之间布设等比网格,自动加仓与减仓", + en: "Places geometric grids between bounds, auto scale-in/out.", + }, + "app.strategy.offset.label": { zh: "偏移做市策略", en: "Offset Maker Strategy" }, + "app.strategy.offset.desc": { + zh: "根据盘口深度自动偏移挂单并在极端不平衡时撤退", + en: "Offsets quotes by depth, retreats on extreme imbalance.", + }, + "app.strategy.basis.label": { zh: "期现套利策略", en: "Basis Arbitrage" }, + "app.strategy.basis.desc": { + zh: "监控期货与现货盘口差价,辅助发现套利机会", + en: "Monitors futures/spot spread to surface arbitrage windows.", + }, + "app.integrity.warning": { + zh: "警告: 版权校验失败,当前版本可能被篡改。", + en: "Warning: Copyright integrity check failed; build may be tampered.", + }, + "app.pickStrategy": { zh: "请选择要运行的策略", en: "Select a strategy to run" }, + "app.pickHint": { + zh: "使用 ↑/↓ 选择,回车开始,Ctrl+C 退出。", + en: "Use ↑/↓ to choose, Enter to start, Ctrl+C to exit.", + }, + "common.waiting": { zh: "等待", en: "Waiting" }, + "common.startFailed": { zh: "启动失败: {message}", en: "Failed to start: {message}" }, + "common.checkEnv": { + zh: "请检查环境变量和网络连通性。", + en: "Please check environment variables and network connectivity.", + }, + "common.initializing": { zh: "正在初始化{target}…", en: "Initializing {target}..." }, + "common.statusWithBack": { + zh: "状态: {status} | 按 Esc 返回策略选择", + en: "Status: {status} | Press Esc to return to menu.", + }, + "common.backHint": { zh: "按 Esc 返回策略选择", en: "Press Esc to return to menu." }, + "common.section.position": { zh: "持仓", en: "Position" }, + "common.section.performance": { zh: "绩效", en: "Performance" }, + "common.section.orders": { zh: "当前挂单", en: "Open Orders" }, + "common.section.recent": { zh: "最近事件", en: "Recent Events" }, + "common.section.recentTrades": { zh: "最近交易与事件", en: "Recent Trades & Events" }, + "common.noPosition": { zh: "当前无持仓", en: "No open position" }, + "common.noOrders": { zh: "暂无挂单", en: "No open orders" }, + "common.noLogs": { zh: "暂无日志", en: "No logs yet" }, + "common.direction.long": { zh: "多", en: "Long" }, + "common.direction.short": { zh: "空", en: "Short" }, + "common.enabled": { zh: "启用", en: "Enabled" }, + "common.disabled": { zh: "关闭", en: "Disabled" }, + "status.live": { zh: "实时运行", en: "Live" }, + "status.running": { zh: "运行中", en: "Running" }, + "status.paused": { zh: "暂停", en: "Paused" }, + "status.waitingData": { zh: "等待市场数据", en: "Waiting for market data" }, + "trend.name": { zh: "趋势策略", en: "trend strategy" }, + "trend.title": { zh: "趋势策略仪表盘", en: "Trend Strategy Dashboard" }, + "trend.headerLine": { + zh: "交易所: {exchange} | 交易对: {symbol} | 最近价格: {lastPrice} | SMA30: {sma} | 趋势: {trend}", + en: "Exchange: {exchange} | Symbol: {symbol} | Last: {lastPrice} | SMA30: {sma} | Trend: {trend}", + }, + "trend.statusLine": { + zh: "状态: {status} | 按 Esc 返回策略选择", + en: "Status: {status} | Press Esc to return to menu.", + }, + "trend.positionLine": { + zh: "方向: {direction} | 数量: {qty} | 开仓价: {entry}", + en: "Direction: {direction} | Size: {qty} | Entry: {entry}", + }, + "trend.pnlLine": { + zh: "浮动盈亏: {pnl} USDT | 账户未实现盈亏: {unrealized} USDT", + en: "Floating PnL: {pnl} USDT | Account Unrealized: {unrealized} USDT", + }, + "trend.performanceLine": { + zh: "累计交易次数: {trades} | 累计收益: {profit} USDT", + en: "Total trades: {trades} | Total profit: {profit} USDT", + }, + "trend.volumeLine": { zh: "累计成交量: {volume} USDT", en: "Total volume: {volume} USDT" }, + "trend.lastSignal": { + zh: "最近开仓信号: {side} @ {price}", + en: "Last entry signal: {side} @ {price}", + }, + "trend.readyMessage": { zh: "正在等待交易所推送数据…", en: "Waiting for exchange feeds..." }, + "trend.label.long": { zh: "做多", en: "Long" }, + "trend.label.short": { zh: "做空", en: "Short" }, + "trend.label.none": { zh: "无信号", en: "No signal" }, + "guardian.name": { zh: "Guardian 策略", en: "Guardian strategy" }, + "guardian.title": { zh: "Guardian 策略仪表盘", en: "Guardian Strategy Dashboard" }, + "guardian.readyMessage": { zh: "正在等待行情/账户推送…", en: "Waiting for market/account feeds..." }, + "guardian.startFailed": { + zh: "Guardian 策略启动失败: {message}", + en: "Guardian strategy failed to start: {message}", + }, + "guardian.initializing": { zh: "正在初始化 Guardian 策略…", en: "Initializing Guardian strategy..." }, + "guardian.headerLine": { + zh: "交易所: {exchange} | 交易对: {symbol} | 最近价格: {lastPrice} | 状态: {status}", + en: "Exchange: {exchange} | Symbol: {symbol} | Last: {lastPrice} | Status: {status}", + }, + "guardian.hint": { + zh: "策略只会维护止损/止盈,不会主动开仓。按 Esc 返回菜单。", + en: "Maintains stops/take-profit only; does not open positions. Press Esc to return.", + }, + "guardian.positionTitle": { zh: "当前仓位与风控", en: "Position & Protection" }, + "guardian.positionLine": { + zh: "方向: {direction} | 数量: {qty} | 开仓价: {entry} | 浮动盈亏: {pnl} USDT", + en: "Direction: {direction} | Size: {qty} | Entry: {entry} | Floating PnL: {pnl} USDT", + }, + "guardian.stopLine": { + zh: "目标止损价: {targetStop} | 当前止损单: {stopOrder} | 动态止盈触发: {trailingTrigger} | 动态止盈单: {trailingOrder}", + en: "Target stop: {targetStop} | Active stop: {stopOrder} | Trailing trigger: {trailingTrigger} | Trailing order: {trailingOrder}", + }, + "guardian.status.protecting": { zh: "已挂止损", en: "Stop placed" }, + "guardian.status.pending": { zh: "缺少止损,正在同步", en: "Missing stop, syncing" }, + "guardian.status.listening": { zh: "监听中", en: "Listening" }, + "guardian.stateLabel": { zh: "Guardian 状态: {state}", en: "Guardian status: {state}" }, + "guardian.noPosition": { + zh: "当前无持仓,Guardian 正在监听新的仓位变化。", + en: "No open position; Guardian is listening for new positions.", + }, + "guardian.noProtectiveOrders": { zh: "暂无保护类挂单", en: "No protective orders" }, + "maker.name": { zh: "做市策略", en: "market-making strategy" }, + "maker.title": { zh: "做市策略仪表盘", en: "Maker Strategy Dashboard" }, + "maker.initializing": { zh: "正在初始化做市策略…", en: "Initializing maker strategy..." }, + "maker.headerLine": { + zh: "交易所: {exchange} | 交易对: {symbol} | 买一价: {bid} | 卖一价: {ask} | 点差: {spread}", + en: "Exchange: {exchange} | Symbol: {symbol} | Best Bid: {bid} | Best Ask: {ask} | Spread: {spread}", + }, + "maker.dataStatus": { zh: "数据状态:", en: "Data status:" }, + "maker.feed.account": { zh: "账户", en: "Account" }, + "maker.feed.orders": { zh: "订单", en: "Orders" }, + "maker.feed.depth": { zh: "深度", en: "Depth" }, + "maker.feed.ticker": { zh: "Ticker", en: "Ticker" }, + "maker.positionLine": { + zh: "方向: {direction} | 数量: {qty} | 开仓价: {entry}", + en: "Direction: {direction} | Size: {qty} | Entry: {entry}", + }, + "maker.pnlLine": { + zh: "浮动盈亏: {pnl} USDT | 账户未实现盈亏: {accountPnl} USDT", + en: "Floating PnL: {pnl} USDT | Account Unrealized: {accountPnl} USDT", + }, + "maker.targetOrders": { zh: "目标挂单", en: "Target Orders" }, + "maker.noTargetOrders": { zh: "暂无目标挂单", en: "No target orders" }, + "offset.name": { zh: "偏移做市策略", en: "offset maker strategy" }, + "offset.title": { zh: "偏移做市策略仪表盘", en: "Offset Maker Strategy Dashboard" }, + "offset.initializing": { zh: "正在初始化偏移做市策略…", en: "Initializing offset maker strategy..." }, + "offset.headerLine": { + zh: "交易所: {exchange} | 交易对: {symbol} | 买一价: {bid} | 卖一价: {ask} | 点差: {spread}", + en: "Exchange: {exchange} | Symbol: {symbol} | Best Bid: {bid} | Best Ask: {ask} | Spread: {spread}", + }, + "offset.depthLine": { + zh: "买10档累计: {buy} | 卖10档累计: {sell} | 状态: {status}", + en: "Top 10 bid sum: {buy} | Top 10 ask sum: {sell} | Status: {status}", + }, + "offset.strategyStatus": { + zh: "当前挂单策略: BUY {buyStatus} | SELL {sellStatus} | 按 Esc 返回策略选择", + en: "Quote status: BUY {buyStatus} | SELL {sellStatus} | Press Esc to return to menu", + }, + "offset.imbalance.balanced": { zh: "均衡", en: "Balanced" }, + "offset.imbalance.buy": { zh: "买盘占优", en: "Bid dominant" }, + "offset.imbalance.sell": { zh: "卖盘占优", en: "Ask dominant" }, + "grid.name": { zh: "网格策略", en: "grid strategy" }, + "grid.title": { zh: "网格策略仪表盘", en: "Grid Strategy Dashboard" }, + "grid.initializing": { zh: "正在初始化网格策略…", en: "Initializing grid strategy..." }, + "grid.headerLine": { + zh: "交易所: {exchange} | 交易对: {symbol} | 状态: {status} | 方向: {direction}", + en: "Exchange: {exchange} | Symbol: {symbol} | Status: {status} | Direction: {direction}", + }, + "grid.priceLine": { + zh: "实时价格: {lastPrice} | 下界: {lower} | 上界: {upper} | 网格数量: {count}", + en: "Last price: {lastPrice} | Lower: {lower} | Upper: {upper} | Grid count: {count}", + }, + "grid.dataStatus": { zh: "数据状态:", en: "Data status:" }, + "grid.stopReason": { zh: "暂停原因: {reason}", en: "Pause reason: {reason}" }, + "grid.configTitle": { zh: "网格配置", en: "Grid Config" }, + "grid.configSize": { + zh: "单笔数量: {orderSize} | 最大仓位: {maxPosition}", + en: "Order size: {orderSize} | Max position: {maxPosition}", + }, + "grid.configRisk": { + zh: "止损阈值: {stopLoss}% | 重启阈值: {restart}% | 自动重启: {autoRestart}", + en: "Stop loss: {stopLoss}% | Restart trigger: {restart}% | Auto restart: {autoRestart}", + }, + "grid.refreshInterval": { zh: "刷新间隔: {interval} ms", en: "Refresh interval: {interval} ms" }, + "grid.positionLine": { + zh: "当前持仓: {direction} | 数量: {qty} | 均价: {avgPrice}", + en: "Position: {direction} | Size: {qty} | Avg price: {avgPrice}", + }, + "grid.unrealizedLine": { + zh: "未实现盈亏: {pnl} | 标记价: {mark}", + en: "Unrealized PnL: {pnl} | Mark: {mark}", + }, + "grid.linesTitle": { zh: "网格线", en: "Grid Lines" }, + "grid.noLines": { zh: "暂无网格线", en: "No grid lines" }, + "grid.direction.both": { zh: "双向", en: "Both" }, + "grid.direction.long": { zh: "多", en: "Long" }, + "grid.direction.short": { zh: "空", en: "Short" }, + "basis.onlyAster": { + zh: "期现套利策略目前仅支持 Aster 交易所。请设置 EXCHANGE=aster 后重试。", + en: "Basis arbitrage currently supports only Aster. Set EXCHANGE=aster and retry.", + }, + "basis.startFailed": { + zh: "无法启动期现套利策略: {message}", + en: "Unable to start basis arbitrage: {message}", + }, + "basis.initializing": { zh: "正在初始化期现套利监控…", en: "Initializing basis arbitrage monitor..." }, + "basis.title": { zh: "期现套利仪表盘", en: "Basis Arbitrage Dashboard" }, + "basis.headerLine": { + zh: "交易所: {exchange} | 期货合约: {futures} | 现货交易对: {spot}", + en: "Exchange: {exchange} | Futures: {futures} | Spot: {spot}", + }, + "basis.statusLine": { + zh: "按 Esc 返回策略选择 | 数据状态: 期货({futuresStatus}) 现货({spotStatus}) 资金费率({fundingStatus})", + en: "Press Esc to return | Feeds: Futures({futuresStatus}) Spot({spotStatus}) Funding({fundingStatus})", + }, + "basis.lastUpdated": { zh: "最近更新时间: {time}", en: "Last updated: {time}" }, + "basis.section.futures": { zh: "期货盘口", en: "Futures Book" }, + "basis.section.spot": { zh: "现货盘口", en: "Spot Book" }, + "basis.bookLine": { zh: "买一: {bid} | 卖一: {ask}", en: "Best bid: {bid} | Best ask: {ask}" }, + "basis.updatedAt": { zh: "更新时间: {time}", en: "Updated: {time}" }, + "basis.section.funding": { zh: "资金费率", en: "Funding" }, + "basis.fundingRate": { zh: "当前资金费率: {rate}", en: "Current funding rate: {rate}" }, + "basis.fundingTimes": { + zh: "资金费率更新时间: {updated} | 下次结算时间: {next}", + en: "Funding updated: {updated} | Next settlement: {next}", + }, + "basis.fundingIncome": { + zh: "单次资金费率收益(估): {per} | 日收益(估): {perDay}", + en: "Est. income per funding: {per} | Est. daily income: {perDay}", + }, + "basis.takerFees": { + zh: "双边吃单手续费(估): {fees} | 回本所需资金费率次数: {count}", + en: "Est. taker fees (round trip): {fees} | Funding counts to breakeven: {count}", + }, + "basis.spotBalanceTitle": { zh: "现货账户余额(非0)", en: "Spot balances (non-zero)" }, + "basis.futuresBalanceTitle": { zh: "合约账户余额(非0)", en: "Futures balances (non-zero)" }, + "basis.balanceLine": { zh: "{asset}: 可用 {free} | 冻结 {locked}", en: "{asset}: Free {free} | Locked {locked}" }, + "basis.futuresBalanceLine": { + zh: "{asset}: 钱包 {wallet} | 可用 {available}", + en: "{asset}: Wallet {wallet} | Available {available}", + }, + "basis.none": { zh: "无", en: "None" }, + "basis.spreadTitle": { zh: "套利差价(卖期货 / 买现货)", en: "Arb spread (sell futures / buy spot)" }, + "basis.spreadLine": { zh: "毛价差: {spread} USDT | {bps} bp", en: "Gross spread: {spread} USDT | {bps} bp" }, + "basis.netSpreadLine": { + zh: "扣除 taker 手续费 ({feePct}% × 双边): {net} USDT | {netBps} bp", + en: "Net after taker fee ({feePct}% x round trip): {net} USDT | {netBps} bp", + }, + "rate.limit.suppress": { + zh: "{source}限频期间暂停新开仓", + en: "{source}Rate limit active, pausing new entries", + }, + "rate.limit.resumeEntries": { zh: "限频恢复,允许重新开仓", en: "Rate limit cleared, resuming entries" }, + "rate.limit.pausedEnd": { zh: "限频暂停结束,继续以降频模式运行", en: "Pause ended; running in degraded mode" }, + "rate.limit.hit": { + zh: "{source}触发 429,降频至 {interval}s", + en: "{source}429 detected, slowing to {interval}s", + }, + "rate.limit.consecutive": { + zh: "{source}连续 429,暂停请求 {seconds}s", + en: "{source}Consecutive 429s, pausing requests for {seconds}s", + }, + "rate.limit.still": { + zh: "{source}限频仍在持续,延长暂停 {seconds}s", + en: "{source}Rate limit persists, extending pause {seconds}s", + }, + "rate.limit.reset": { zh: "限频恢复,重置为正常请求频率", en: "Rate limit cleared, reset to normal cadence" }, + "env.missingAster": { + zh: "缺少 ASTER_API_KEY 或 ASTER_API_SECRET 环境变量", + en: "Missing ASTER_API_KEY or ASTER_API_SECRET", + }, + "env.missingLighter": { + zh: "缺少 LIGHTER_ACCOUNT_INDEX 或 LIGHTER_API_PRIVATE_KEY 环境变量", + en: "Missing LIGHTER_ACCOUNT_INDEX or LIGHTER_API_PRIVATE_KEY", + }, + "env.lighterIndexInteger": { + zh: "LIGHTER_ACCOUNT_INDEX 必须是整数", + en: "LIGHTER_ACCOUNT_INDEX must be an integer", + }, + "env.missingBackpack": { + zh: "缺少 BACKPACK_API_KEY 或 BACKPACK_API_SECRET 环境变量", + en: "Missing BACKPACK_API_KEY or BACKPACK_API_SECRET", + }, + "env.missingParadex": { + zh: "Paradex 需要配置 PARADEX_PRIVATE_KEY 与 PARADEX_WALLET_ADDRESS", + en: "Paradex requires PARADEX_PRIVATE_KEY and PARADEX_WALLET_ADDRESS", + }, + "env.invalidParadexPrivateKey": { + zh: "PARADEX_PRIVATE_KEY 必须是 0x 开头的 32 字节十六进制字符串", + en: "PARADEX_PRIVATE_KEY must be a 0x-prefixed 32-byte hex string", + }, + "env.invalidParadexAddress": { + zh: "PARADEX_WALLET_ADDRESS 必须是有效的 0x 开头 40 字节十六进制地址", + en: "PARADEX_WALLET_ADDRESS must be a valid 0x-prefixed 40-byte hex address", + }, + "log.subscribe.accountFail": { + zh: "订阅账户失败: {error}", + en: "Failed to subscribe account: {error}", + }, + "log.process.accountError": { + zh: "账户推送处理异常: {error}", + en: "Account stream processing error: {error}", + }, + "log.subscribe.orderFail": { + zh: "订阅订单失败: {error}", + en: "Failed to subscribe orders: {error}", + }, + "log.process.orderError": { + zh: "订单推送处理异常: {error}", + en: "Order stream processing error: {error}", + }, + "log.subscribe.tickerFail": { + zh: "订阅Ticker失败: {error}", + en: "Failed to subscribe ticker: {error}", + }, + "log.process.tickerError": { + zh: "价格推送处理异常: {error}", + en: "Price stream processing error: {error}", + }, + "log.guardian.executeError": { + zh: "Guardian 执行异常: {error}", + en: "Guardian runtime error: {error}", + }, + "log.guardian.entryPricePending": { + zh: "持仓均价尚未同步,等待交易所账户快照更新后再补挂止损", + en: "Entry price not synced yet; waiting for account snapshot before placing stop.", + }, + "log.guardian.pricePending": { + zh: "行情尚未就绪,等待最新价格以同步止损", + en: "Market data not ready; waiting for latest price to sync stop.", + }, + "log.guardian.placeStopFail": { + zh: "挂止损单失败: {error}", + en: "Failed to place stop order: {error}", + }, + "log.guardian.stopMissingSkip": { + zh: "原止损单已不存在,跳过撤销", + en: "Existing stop missing, skipping cancel.", + }, + "log.guardian.cancelStopFail": { + zh: "取消原止损单失败: {error}", + en: "Failed to cancel existing stop: {error}", + }, + "log.guardian.moveStop": { + zh: "移动止损到 {price}", + en: "Moved stop to {price}", + }, + "log.guardian.moveStopFail": { + zh: "移动止损失败: {error}", + en: "Failed to move stop: {error}", + }, + "log.guardian.restoreStop": { + zh: "恢复原止损 @ {price}", + en: "Restored original stop @ {price}", + }, + "log.guardian.restoreStopFail": { + zh: "恢复原止损失败: {error}", + en: "Failed to restore original stop: {error}", + }, + "log.guardian.trailingFail": { + zh: "挂动态止盈失败: {error}", + en: "Failed to place trailing stop: {error}", + }, + "log.guardian.cleanupOrders": { + zh: "清理遗留保护单: {ids}", + en: "Cleaning leftover protective orders: {ids}", + }, + "log.guardian.protectiveMissing": { + zh: "保护单已不存在,跳过清理", + en: "Protective orders already gone; skipping cleanup.", + }, + "log.guardian.cleanupFail": { + zh: "清理保护单失败: {error}", + en: "Failed to clean protective orders: {error}", + }, + "log.guardian.dispatchError": { + zh: "更新分发异常: {error}", + en: "Update dispatch error: {error}", + }, + "log.guardian.snapshotFail": { + zh: "构建快照失败: {error}", + en: "Failed to build snapshot: {error}", + }, + "log.guardian.precisionSynced": { + zh: "已同步交易精度: priceTick={priceTick} qtyStep={qtyStep}", + en: "Synced precision: priceTick={priceTick} qtyStep={qtyStep}", + }, + "log.guardian.precisionFailed": { + zh: "同步精度失败: {error}", + en: "Failed to sync precision: {error}", + }, + "log.basis.subscribeFuturesDepthFail": { + zh: "订阅期货深度失败: {error}", + en: "Failed to subscribe futures depth: {error}", + }, + "log.basis.processFuturesDepthError": { + zh: "处理期货深度异常: {error}", + en: "Error processing futures depth: {error}", + }, + "log.basis.futuresReady": { + zh: "期货深度已就绪 ({symbol})", + en: "Futures depth ready ({symbol})", + }, + "log.basis.spotDepthError": { + zh: "获取现货盘口失败: {error}", + en: "Failed to fetch spot orderbook: {error}", + }, + "log.basis.fundingReady": { + zh: "资金费率已就绪 ({symbol})", + en: "Funding rate ready ({symbol})", + }, + "log.basis.fundingError": { + zh: "获取资金费率失败: {error}", + en: "Failed to fetch funding rate: {error}", + }, + "log.basis.spotBalanceError": { + zh: "获取现货余额失败: {error}", + en: "Failed to fetch spot balance: {error}", + }, + "log.basis.futuresBalanceError": { + zh: "获取合约余额失败: {error}", + en: "Failed to fetch futures balance: {error}", + }, + "log.basis.spotReady": { zh: "现货盘口已就绪 ({symbol})", en: "Spot orderbook ready ({symbol})" }, + "log.basis.pushError": { zh: "推送订阅失败: {error}", en: "Subscription push failed: {error}" }, + "log.basis.entryOpportunity": { + zh: "入场机会: 扣费后价差 {bp} bp | 距下次资金费约 {minutes} 分钟", + en: "Entry opportunity: net spread {bp} bp | ~{minutes} mins to next funding", + }, + "log.basis.exitOpportunity": { + zh: "出场机会: 资金费率为负 | 距收取约 {minutes} 分钟", + en: "Exit opportunity: funding negative | ~{minutes} mins to settlement", + }, + "log.account.snapshotSynced": { zh: "账户快照已同步", en: "Account snapshot synced" }, + "log.order.snapshotReturned": { zh: "订单快照已返回", en: "Order snapshot received" }, + "log.depth.ready": { zh: "获得最新深度行情", en: "Latest depth ready" }, + "log.ticker.ready": { zh: "Ticker 已就绪", en: "Ticker ready" }, + "log.subscribe.depthFail": { zh: "订阅深度失败: {error}", en: "Failed to subscribe depth: {error}" }, + "log.process.depthError": { + zh: "深度推送处理异常: {error}", + en: "Depth stream processing error: {error}", + }, + "log.maker.loopError": { zh: "做市循环异常: {error}", en: "Maker loop error: {error}" }, + "log.maker.cleanOrdersStart": { zh: "启动时清理历史挂单", en: "Cleaning legacy orders at startup" }, + "log.maker.cleanOrdersMissing": { + zh: "历史挂单已消失,跳过启动清理", + en: "Legacy orders already gone, skipping startup cleanup", + }, + "log.maker.cleanOrdersFail": { zh: "启动撤单失败: {error}", en: "Failed to cancel at startup: {error}" }, + "log.maker.cancelMismatched": { + zh: "撤销不匹配订单 {side} @ {price} reduceOnly={reduceOnly}", + en: "Cancel unmatched order {side} @ {price} reduceOnly={reduceOnly}", + }, + "log.maker.cancelMissing": { + zh: "撤销时发现订单已被成交/取消,忽略", + en: "Order already filled/canceled, ignoring cancel", + }, + "log.maker.cancelFail": { zh: "撤销订单失败: {error}", en: "Failed to cancel order: {error}" }, + "log.maker.placeFail": { + zh: "挂单失败({side} {price}): {error}", + en: "Failed to place order ({side} {price}): {error}", + }, + "log.maker.avgPending": { + zh: "做市持仓均价未同步,等待账户快照刷新后再执行止损判断", + en: "Maker entry price not synced; waiting for account snapshot before stop check", + }, + "log.maker.stopTriggered": { + zh: "触发止损,方向={direction} 当前亏损={pnl} USDT", + en: "Stop triggered direction={direction} current loss={pnl} USDT", + }, + "log.maker.stopOrderMissing": { zh: "止损平仓时订单已不存在", en: "Stop close order missing" }, + "log.maker.stopCloseFail": { zh: "止损平仓失败: {error}", en: "Failed to close on stop: {error}" }, + "log.maker.orderMissing": { zh: "订单已不存在,撤销跳过", en: "Order already gone, skipping cancel" }, + "log.common.precisionSynced": { + zh: "已同步交易精度: priceTick={priceTick} qtyStep={qtyStep}", + en: "Synced precision: priceTick={priceTick} qtyStep={qtyStep}", + }, + "log.common.precisionFailed": { zh: "同步精度失败: {error}", en: "Failed to sync precision: {error}" }, + "log.maker.updateHandlerError": { zh: "更新回调处理异常: {error}", en: "Update handler error: {error}" }, + "log.maker.snapshotDispatchError": { + zh: "快照或更新分发异常: {error}", + en: "Snapshot/update dispatch error: {error}", + }, + "log.maker.waitAccount": { zh: "等待账户快照同步,尚未开始做市", en: "Waiting for account snapshot before quoting" }, + "log.maker.waitDepth": { zh: "等待深度行情推送,尚未开始做市", en: "Waiting for depth stream before quoting" }, + "log.maker.waitTicker": { zh: "等待Ticker推送,尚未开始做市", en: "Waiting for ticker stream before quoting" }, + "log.maker.waitOrders": { + zh: "等待订单快照返回,尚未执行初始化撤单", + en: "Waiting for order snapshot before startup cancels", + }, + "log.maker.noTargets": { zh: "当前无目标挂单,等待下一次刷新", en: "No target orders; waiting for next refresh" }, + "log.maker.targetsSummary": { zh: "目标挂单: {summary}", en: "Target orders: {summary}" }, + "log.maker.balanceThrottle": { + zh: "余额不足,暂停新挂单 {seconds}s: {detail}", + en: "Insufficient balance, pausing new orders for {seconds}s: {detail}", + }, + "log.maker.balanceResumed": { + zh: "余额检测恢复,重新尝试挂单", + en: "Balance check recovered, retrying orders", + }, + "log.maker.rateLimit429": { zh: "MakerEngine 429: {error}", en: "MakerEngine 429: {error}" }, + "log.kline.subscribeFail": { zh: "订阅K线失败: {error}", en: "Failed to subscribe klines: {error}" }, + "log.kline.processError": { zh: "K线推送处理异常: {error}", en: "Kline stream processing error: {error}" }, + "log.trend.klineInsufficient": { + zh: "K线不足 {count}/{min},最近收盘({recentCount}): {recent}", + en: "Insufficient klines {count}/{min}, recent closes ({recentCount}): {recent}", + }, + "log.trend.klineReady": { + zh: "K线就绪 {count} 根,可计算 SMA30。最近收盘: {recent}", + en: "Klines ready {count} bars; SMA30 available. Recent closes: {recent}", + }, + "log.trend.rateLimit429": { zh: "TrendEngine 429: {error}", en: "TrendEngine 429: {error}" }, + "log.trend.loopError": { zh: "策略循环异常: {error}", en: "Strategy loop error: {error}" }, + "log.trend.rateLimitUpdateError": { + zh: "限频控制器状态更新失败: {error}", + en: "Rate limit controller update failed: {error}", + }, + "log.trend.detectPosition": { + zh: "检测到已有持仓: {direction} {amount} @ {price}", + en: "Detected existing position: {direction} {amount} @ {price}", + }, + "log.trend.detectOrders": { + zh: "检测到已有挂单 {count} 笔,将按策略规则接管", + en: "Detected {count} existing orders; taking over per strategy rules", + }, + "log.trend.stopCooldown": { + zh: "止损后冷却中 {seconds}s,忽略入场信号", + en: "Post-stop cooldown {seconds}s; ignoring entry signals", + }, + "log.trend.alreadyEntered": { + zh: "本分钟已入场,忽略新的 SMA 入场信号", + en: "Entry already executed this minute; ignoring new SMA signal", + }, + "log.trend.bandwidthBlocked": { + zh: "布林带宽度不足:{bandwidth} < {minBandwidth},忽略入场信号", + en: "Bollinger bandwidth too low: {bandwidth} < {minBandwidth}, ignoring entry", + }, + "log.trend.cancelMissing": { zh: "撤单时部分订单已不存在,忽略", en: "Some orders missing during cancel; ignore" }, + "log.trend.cancelFail": { zh: "撤销挂单失败: {error}", en: "Failed to cancel orders: {error}" }, + "log.trend.crossDown": { zh: "下穿SMA30,市价开空", en: "Crossed below SMA30, market sell" }, + "log.trend.crossUp": { zh: "上穿SMA30,市价开多", en: "Crossed above SMA30, market buy" }, + "log.trend.marketOrderFail": { zh: "市价下单失败: {error}", en: "Market order failed: {error}" }, + "log.trend.entryPricePending": { + zh: "持仓均价尚未同步,等待交易所账户快照更新后再执行风控", + en: "Entry price not synced; waiting for account snapshot before risk checks", + }, + "log.trend.stopPreCancelMissing": { zh: "止损前撤单发现订单已不存在", en: "Stop pre-close cancel found missing order" }, + "log.trend.marketCloseGuard": { + zh: "市价平仓保护触发:closePx={closePx} mark={mark} 偏离 {pctDiff}% > {limitPct}%", + en: "Market close guard triggered: closePx={closePx} mark={mark} deviation {pctDiff}% > {limitPct}%", + }, + "log.trend.stopClose": { zh: "止损平仓: {side}", en: "Stop close: {side}" }, + "log.trend.targetStopMissing": { zh: "止损平仓时目标订单已不存在", en: "Target order missing during stop close" }, + "log.trend.stopCloseFail": { zh: "止损平仓失败: {error}", en: "Failed to close position on stop: {error}" }, + "log.trend.placeStopFail": { zh: "挂止损单失败: {error}", en: "Failed to place stop order: {error}" }, + "log.trend.stopMissingSkip": { zh: "原止损单已不存在,跳过撤销", en: "Existing stop missing, skipping cancel" }, + "log.trend.cancelStopFail": { zh: "取消原止损单失败: {error}", en: "Failed to cancel existing stop: {error}" }, + "log.trend.moveStop": { zh: "移动止损到 {price}", en: "Moved stop to {price}" }, + "log.trend.moveStopFail": { zh: "移动止损失败: {error}", en: "Failed to move stop: {error}" }, + "log.trend.restoreStop": { zh: "恢复原止损 @ {price}", en: "Restored original stop @ {price}" }, + "log.trend.restoreStopFail": { zh: "恢复原止损失败: {error}", en: "Failed to restore original stop: {error}" }, + "log.trend.trailingFail": { zh: "挂动态止盈失败: {error}", en: "Failed to place trailing stop: {error}" }, + "log.trend.precisionSynced": { + zh: "已同步交易精度: priceTick={priceTick} qtyStep={qtyStep}", + en: "Synced precision: priceTick={priceTick} qtyStep={qtyStep}", + }, + "log.trend.precisionFailed": { zh: "同步精度失败: {error}", en: "Failed to sync precision: {error}" }, + "log.trend.updateHandlerError": { zh: "更新回调处理异常: {error}", en: "Update handler error: {error}" }, + "log.trend.snapshotDispatchError": { zh: "快照或更新分发异常: {error}", en: "Snapshot/update dispatch error: {error}" }, +}; + +const formatTemplate = (template: string, params: Record): string => { + return template.replace(/\{(\w+)\}/g, (_match, key) => { + const value = params[key]; + return value === undefined || value === null ? `{${key}}` : String(value); + }); +}; + +export type TranslationKey = keyof typeof translations | string; + +export function t(key: TranslationKey, params: Record = {}, lang: Language = language): string { + const entry = translations[key as keyof typeof translations]; + const value = entry ? entry[lang] ?? entry.zh : null; + if (typeof value === "function") { + return value(params, lang); + } + if (typeof value === "string") { + return Object.keys(params).length ? formatTemplate(value, params) : value; + } + // Fallback: return key to surface missing translations + return String(key); +} + +export function isEnglish(lang: Language = language): boolean { + return lang === "en"; +} diff --git a/src/strategy/basis-arb-engine.ts b/src/strategy/basis-arb-engine.ts index f17ccdd..ae6072f 100644 --- a/src/strategy/basis-arb-engine.ts +++ b/src/strategy/basis-arb-engine.ts @@ -5,6 +5,7 @@ import { AsterSpotRestClient, AsterRestClient } from "../exchanges/aster/client" import { createTradeLog, type TradeLogEntry } from "../logging/trade-log"; import { StrategyEventEmitter } from "./common/event-emitter"; import { safeSubscribe, type LogHandler } from "./common/subscriptions"; +import { t } from "../i18n"; export interface BasisArbSnapshot { ready: boolean; @@ -159,8 +160,8 @@ export class BasisArbEngine { }, log, { - subscribeFail: (error) => `订阅期货深度失败: ${String(error)}`, - processFail: (error) => `处理期货深度异常: ${String(error)}`, + subscribeFail: (error) => t("log.basis.subscribeFuturesDepthFail", { error: String(error) }), + processFail: (error) => t("log.basis.processFuturesDepthError", { error: String(error) }), } ); } @@ -179,7 +180,7 @@ export class BasisArbEngine { this.futures.updatedAt = depth.eventTime ?? depth.tradeTime ?? this.now(); if (!this.feedReady.futures) { this.feedReady.futures = true; - this.tradeLog.push("info", `期货深度已就绪 (${this.config.futuresSymbol})`); + this.tradeLog.push("info", t("log.basis.futuresReady", { symbol: this.config.futuresSymbol })); } if (this.feedReady.futures && this.feedReady.spot && this.marketReadyAt == null) { this.marketReadyAt = this.now(); @@ -197,7 +198,10 @@ export class BasisArbEngine { this.applySpotTicker(ticker); } catch (error) { this.feedReady.spot = false; - this.tradeLog.push("error", `获取现货盘口失败: ${String(error instanceof Error ? error.message : error)}`); + this.tradeLog.push( + "error", + t("log.basis.spotDepthError", { error: String(error instanceof Error ? error.message : error) }) + ); } finally { this.spotInFlight = false; } @@ -217,13 +221,16 @@ export class BasisArbEngine { this.funding.updatedAt = typeof ts === "number" ? ts : this.now(); if (!this.feedReady.funding) { this.feedReady.funding = true; - this.tradeLog.push("info", `资金费率已就绪 (${this.config.futuresSymbol})`); + this.tradeLog.push("info", t("log.basis.fundingReady", { symbol: this.config.futuresSymbol })); } this.emitUpdate(); } } catch (error) { this.feedReady.funding = false; - this.tradeLog.push("error", `获取资金费率失败: ${String(error instanceof Error ? error.message : error)}`); + this.tradeLog.push( + "error", + t("log.basis.fundingError", { error: String(error instanceof Error ? error.message : error) }) + ); } finally { this.fundingInFlight = false; } @@ -250,7 +257,10 @@ export class BasisArbEngine { this.spotBalances = next; this.emitUpdate(); } catch (error) { - this.tradeLog.push("error", `获取现货余额失败: ${String(error instanceof Error ? error.message : error)}`); + this.tradeLog.push( + "error", + t("log.basis.spotBalanceError", { error: String(error instanceof Error ? error.message : error) }) + ); } finally { this.spotAccountInFlight = false; } @@ -278,7 +288,10 @@ export class BasisArbEngine { this.futuresBalances = next; this.emitUpdate(); } catch (error) { - this.tradeLog.push("error", `获取合约余额失败: ${String(error instanceof Error ? error.message : error)}`); + this.tradeLog.push( + "error", + t("log.basis.futuresBalanceError", { error: String(error instanceof Error ? error.message : error) }) + ); } finally { this.futuresAccountInFlight = false; } @@ -295,7 +308,7 @@ export class BasisArbEngine { this.spot.updatedAt = ticker.time ?? this.now(); if (!this.feedReady.spot) { this.feedReady.spot = true; - this.tradeLog.push("info", `现货盘口已就绪 (${this.config.spotSymbol})`); + this.tradeLog.push("info", t("log.basis.spotReady", { symbol: this.config.spotSymbol })); } if (this.feedReady.futures && this.feedReady.spot && this.marketReadyAt == null) { this.marketReadyAt = this.now(); @@ -308,7 +321,7 @@ export class BasisArbEngine { const snapshot = this.buildSnapshot(); this.evaluateSignals(snapshot); this.events.emit("update", snapshot, (error) => { - this.tradeLog.push("error", `推送订阅失败: ${String(error)}`); + this.tradeLog.push("error", t("log.basis.pushError", { error: String(error) })); }); } @@ -410,7 +423,7 @@ export class BasisArbEngine { this.lastEntrySignalAt = now; const bpTxt = (spreadBps as number).toFixed(2); const minutes = Math.floor(((msUntilFunding as number) / 60000)); - this.tradeLog.push("entry", `入场机会: 扣费后价差 ${bpTxt} bp | 距下次资金费约 ${minutes} 分钟`); + this.tradeLog.push("entry", t("log.basis.entryOpportunity", { bp: bpTxt, minutes })); } } @@ -419,7 +432,7 @@ export class BasisArbEngine { if (now - this.lastExitSignalAt >= 60 * 1000) { // debounce 60s this.lastExitSignalAt = now; const minutes = Math.max(0, Math.floor(((msUntilFunding as number) / 60000))); - this.tradeLog.push("exit", `出场机会: 资金费率为负 | 距收取约 ${minutes} 分钟`); + this.tradeLog.push("exit", t("log.basis.exitOpportunity", { minutes })); } } } diff --git a/src/strategy/guardian-engine.ts b/src/strategy/guardian-engine.ts index 31421b6..501cf24 100644 --- a/src/strategy/guardian-engine.ts +++ b/src/strategy/guardian-engine.ts @@ -19,6 +19,7 @@ import { createTradeLog, type TradeLogEntry } from "../logging/trade-log"; import { extractMessage, isUnknownOrderError } from "../utils/errors"; import { formatPriceToString } from "../utils/math"; import { computePositionPnl } from "../utils/pnl"; +import { t } from "../i18n"; export interface GuardianEngineSnapshot { ready: boolean; @@ -108,8 +109,8 @@ export class GuardianEngine { }, log, { - subscribeFail: (error) => `订阅账户失败: ${String(error)}`, - processFail: (error) => `账户推送处理异常: ${extractMessage(error)}`, + subscribeFail: (error) => t("log.subscribe.accountFail", { error: String(error) }), + processFail: (error) => t("log.process.accountError", { error: extractMessage(error) }), } ); @@ -135,8 +136,8 @@ export class GuardianEngine { }, log, { - subscribeFail: (error) => `订阅订单失败: ${String(error)}`, - processFail: (error) => `订单推送处理异常: ${extractMessage(error)}`, + subscribeFail: (error) => t("log.subscribe.orderFail", { error: String(error) }), + processFail: (error) => t("log.process.orderError", { error: extractMessage(error) }), } ); @@ -148,8 +149,8 @@ export class GuardianEngine { }, log, { - subscribeFail: (error) => `订阅Ticker失败: ${String(error)}`, - processFail: (error) => `价格推送处理异常: ${extractMessage(error)}`, + subscribeFail: (error) => t("log.subscribe.tickerFail", { error: String(error) }), + processFail: (error) => t("log.process.tickerError", { error: extractMessage(error) }), } ); } @@ -179,7 +180,7 @@ export class GuardianEngine { } await this.ensureProtection(); } catch (error) { - this.tradeLog.push("error", `Guardian 执行异常: ${extractMessage(error)}`); + this.tradeLog.push("error", t("log.guardian.executeError", { error: extractMessage(error) })); } finally { this.processing = false; this.emitUpdate(); @@ -200,7 +201,7 @@ export class GuardianEngine { const hasEntryPrice = Number.isFinite(position.entryPrice) && Math.abs(position.entryPrice) > 1e-8; if (!hasEntryPrice) { if (!this.entryPricePendingLogged) { - this.tradeLog.push("info", "持仓均价尚未同步,等待交易所账户快照更新后再补挂止损"); + this.tradeLog.push("info", t("log.guardian.entryPricePending")); this.entryPricePendingLogged = true; } return; @@ -210,7 +211,7 @@ export class GuardianEngine { const price = this.getLastPrice(); if (!Number.isFinite(price)) { if (!this.priceUnavailableLogged) { - this.tradeLog.push("info", "行情尚未就绪,等待最新价格以同步止损"); + this.tradeLog.push("info", t("log.guardian.pricePending")); this.priceUnavailableLogged = true; } return; @@ -406,7 +407,7 @@ export class GuardianEngine { this.lastStopAttempt.price = stopPrice; this.lastStopAttempt.at = now; } catch (err) { - this.tradeLog.push("error", `挂止损单失败: ${String(err)}`); + this.tradeLog.push("error", t("log.guardian.placeStopFail", { error: String(err) })); this.lastStopAttempt.side = side; this.lastStopAttempt.price = stopPrice; this.lastStopAttempt.at = now; @@ -430,10 +431,10 @@ export class GuardianEngine { await this.exchange.cancelOrder({ symbol: this.config.symbol, orderId: currentOrder.orderId }); } catch (err) { if (isUnknownOrderError(err)) { - this.tradeLog.push("order", "原止损单已不存在,跳过撤销"); + this.tradeLog.push("order", t("log.guardian.stopMissingSkip")); this.openOrders = this.openOrders.filter((o) => o.orderId !== currentOrder.orderId); } else { - this.tradeLog.push("error", `取消原止损单失败: ${String(err)}`); + this.tradeLog.push("error", t("log.guardian.cancelStopFail", { error: String(err) })); } } try { @@ -462,10 +463,15 @@ export class GuardianEngine { { priceTick: this.config.priceTick, qtyStep: this.config.qtyStep } ); if (order) { - this.tradeLog.push("stop", `移动止损到 ${formatPriceToString(nextStopPrice, this.resolvePriceDecimals())}`); + this.tradeLog.push( + "stop", + t("log.guardian.moveStop", { + price: formatPriceToString(nextStopPrice, this.resolvePriceDecimals()), + }) + ); } } catch (err) { - this.tradeLog.push("error", `移动止损失败: ${String(err)}`); + this.tradeLog.push("error", t("log.guardian.moveStopFail", { error: String(err) })); try { const position = getPosition(this.accountSnapshot, this.config.symbol); const quantity = Math.abs(position.positionAmt); @@ -494,11 +500,13 @@ export class GuardianEngine { if (restored && Number.isFinite(existingStopPrice)) { this.tradeLog.push( "order", - `恢复原止损 @ ${formatPriceToString(existingStopPrice, this.resolvePriceDecimals())}` + t("log.guardian.restoreStop", { + price: formatPriceToString(existingStopPrice, this.resolvePriceDecimals()), + }) ); } } catch (recoverErr) { - this.tradeLog.push("error", `恢复原止损失败: ${String(recoverErr)}`); + this.tradeLog.push("error", t("log.guardian.restoreStopFail", { error: String(recoverErr) })); } } } @@ -531,7 +539,7 @@ export class GuardianEngine { { priceTick: this.config.priceTick, qtyStep: this.config.qtyStep } ); } catch (err) { - this.tradeLog.push("error", `挂动态止盈失败: ${String(err)}`); + this.tradeLog.push("error", t("log.guardian.trailingFail", { error: String(err) })); } } @@ -541,12 +549,12 @@ export class GuardianEngine { const orderIdList = protectiveOrders.map((order) => order.orderId); try { await this.exchange.cancelOrders({ symbol: this.config.symbol, orderIdList }); - this.tradeLog.push("order", `清理遗留保护单: ${orderIdList.join(",")}`); + this.tradeLog.push("order", t("log.guardian.cleanupOrders", { ids: orderIdList.join(",") })); } catch (err) { if (isUnknownOrderError(err)) { - this.tradeLog.push("order", "保护单已不存在,跳过清理"); + this.tradeLog.push("order", t("log.guardian.protectiveMissing")); } else { - this.tradeLog.push("error", `清理保护单失败: ${String(err)}`); + this.tradeLog.push("error", t("log.guardian.cleanupFail", { error: String(err) })); } } } @@ -625,10 +633,10 @@ export class GuardianEngine { try { const snapshot = this.buildSnapshot(); this.events.emit("update", snapshot, (error) => { - this.tradeLog.push("error", `更新分发异常: ${String(error)}`); + this.tradeLog.push("error", t("log.guardian.dispatchError", { error: String(error) })); }); } catch (err) { - this.tradeLog.push("error", `构建快照失败: ${String(err)}`); + this.tradeLog.push("error", t("log.guardian.snapshotFail", { error: String(err) })); } } @@ -668,12 +676,15 @@ export class GuardianEngine { if (updated) { this.tradeLog.push( "info", - `已同步交易精度: priceTick=${precision.priceTick} qtyStep=${precision.qtyStep}` + t("log.guardian.precisionSynced", { + priceTick: precision.priceTick, + qtyStep: precision.qtyStep, + }) ); } }) .catch((error) => { - this.tradeLog.push("error", `同步精度失败: ${extractMessage(error)}`); + this.tradeLog.push("error", t("log.guardian.precisionFailed", { error: extractMessage(error) })); this.precisionSync = null; setTimeout(() => this.syncPrecision(), 2000); }); diff --git a/src/strategy/maker-engine.ts b/src/strategy/maker-engine.ts index 3c59fbc..bf9a6a2 100644 --- a/src/strategy/maker-engine.ts +++ b/src/strategy/maker-engine.ts @@ -27,6 +27,7 @@ import { RateLimitController } from "../core/lib/rate-limit"; import { StrategyEventEmitter } from "./common/event-emitter"; import { safeSubscribe, type LogHandler } from "./common/subscriptions"; import { SessionVolumeTracker } from "./common/session-volume"; +import { t } from "../i18n"; interface DesiredOrder { side: "BUY" | "SELL"; @@ -164,7 +165,7 @@ export class MakerEngine { const position = getPosition(snapshot, this.config.symbol); this.sessionVolume.update(position, this.getReferencePrice()); if (!this.feedArrived.account) { - this.tradeLog.push("info", "账户快照已同步"); + this.tradeLog.push("info", t("log.account.snapshotSynced")); this.feedArrived.account = true; } this.feedStatus.account = true; @@ -172,8 +173,8 @@ export class MakerEngine { }, log, { - subscribeFail: (error) => `订阅账户失败: ${String(error)}`, - processFail: (error) => `账户推送处理异常: ${String(error)}`, + subscribeFail: (error) => t("log.subscribe.accountFail", { error: String(error) }), + processFail: (error) => t("log.process.accountError", { error: String(error) }), } ); @@ -192,7 +193,7 @@ export class MakerEngine { } this.initialOrderSnapshotReady = true; if (!this.feedArrived.orders) { - this.tradeLog.push("info", "订单快照已返回"); + this.tradeLog.push("info", t("log.order.snapshotReturned")); this.feedArrived.orders = true; } this.feedStatus.orders = true; @@ -200,8 +201,8 @@ export class MakerEngine { }, log, { - subscribeFail: (error) => `订阅订单失败: ${String(error)}`, - processFail: (error) => `订单推送处理异常: ${String(error)}`, + subscribeFail: (error) => t("log.subscribe.orderFail", { error: String(error) }), + processFail: (error) => t("log.process.orderError", { error: String(error) }), } ); @@ -210,7 +211,7 @@ export class MakerEngine { (depth) => { this.depthSnapshot = depth; if (!this.feedArrived.depth) { - this.tradeLog.push("info", "获得最新深度行情"); + this.tradeLog.push("info", t("log.depth.ready")); this.feedArrived.depth = true; } this.feedStatus.depth = true; @@ -218,8 +219,8 @@ export class MakerEngine { }, log, { - subscribeFail: (error) => `订阅深度失败: ${String(error)}`, - processFail: (error) => `深度推送处理异常: ${String(error)}`, + subscribeFail: (error) => t("log.subscribe.depthFail", { error: String(error) }), + processFail: (error) => t("log.process.depthError", { error: String(error) }), } ); @@ -228,7 +229,7 @@ export class MakerEngine { (ticker) => { this.tickerSnapshot = ticker; if (!this.feedArrived.ticker) { - this.tradeLog.push("info", "Ticker 已就绪"); + this.tradeLog.push("info", t("log.ticker.ready")); this.feedArrived.ticker = true; } this.feedStatus.ticker = true; @@ -236,8 +237,8 @@ export class MakerEngine { }, log, { - subscribeFail: (error) => `订阅Ticker失败: ${String(error)}`, - processFail: (error) => `价格推送处理异常: ${String(error)}`, + subscribeFail: (error) => t("log.subscribe.tickerFail", { error: String(error) }), + processFail: (error) => t("log.process.tickerError", { error: String(error) }), } ); @@ -331,9 +332,9 @@ export class MakerEngine { hadRateLimit = true; this.rateLimit.registerRateLimit("maker"); await this.enforceRateLimitStop(); - this.tradeLog.push("warn", `MakerEngine 429: ${String(error)}`); + this.tradeLog.push("warn", t("log.maker.rateLimit429", { error: String(error) })); } else { - this.tradeLog.push("error", `做市循环异常: ${String(error)}`); + this.tradeLog.push("error", t("log.maker.loopError", { error: String(error) })); } this.emitUpdate(); } finally { @@ -367,18 +368,18 @@ export class MakerEngine { unlockOperating(this.locks, this.timers, this.pending, "LIMIT"); this.openOrders = []; this.emitUpdate(); - this.tradeLog.push("order", "启动时清理历史挂单"); + this.tradeLog.push("order", t("log.maker.cleanOrdersStart")); this.initialOrderResetDone = true; return true; } catch (error) { if (isUnknownOrderError(error)) { - this.tradeLog.push("order", "历史挂单已消失,跳过启动清理"); + this.tradeLog.push("order", t("log.maker.cleanOrdersMissing")); this.initialOrderResetDone = true; this.openOrders = []; this.emitUpdate(); return true; } - this.tradeLog.push("error", `启动撤单失败: ${String(error)}`); + this.tradeLog.push("error", t("log.maker.cleanOrdersFail", { error: String(error) })); return false; } } @@ -401,16 +402,20 @@ export class MakerEngine { () => { this.tradeLog.push( "order", - `撤销不匹配订单 ${order.side} @ ${order.price} reduceOnly=${order.reduceOnly}` + t("log.maker.cancelMismatched", { + side: order.side, + price: order.price, + reduceOnly: order.reduceOnly, + }) ); }, () => { - this.tradeLog.push("order", "撤销时发现订单已被成交/取消,忽略"); + this.tradeLog.push("order", t("log.maker.cancelMissing")); this.pendingCancelOrders.delete(String(order.orderId)); this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId); }, (error) => { - this.tradeLog.push("error", `撤销订单失败: ${String(error)}`); + this.tradeLog.push("error", t("log.maker.cancelFail", { error: String(error) })); this.pendingCancelOrders.delete(String(order.orderId)); this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId); } @@ -449,7 +454,11 @@ export class MakerEngine { } this.tradeLog.push( "error", - `挂单失败(${target.side} ${target.price}): ${extractMessage(error)}` + t("log.maker.placeFail", { + side: target.side, + price: target.price, + error: extractMessage(error), + }) ); } } @@ -462,7 +471,7 @@ export class MakerEngine { const hasEntryPrice = Number.isFinite(position.entryPrice) && Math.abs(position.entryPrice) > 1e-8; if (!hasEntryPrice) { if (!this.entryPricePendingLogged) { - this.tradeLog.push("info", "做市持仓均价未同步,等待账户快照刷新后再执行止损判断"); + this.tradeLog.push("info", t("log.maker.avgPending")); this.entryPricePendingLogged = true; } return; @@ -478,7 +487,10 @@ export class MakerEngine { const closeSidePrice = closeSideIsSell ? bidPrice : askPrice; this.tradeLog.push( "stop", - `触发止损,方向=${position.positionAmt > 0 ? "多" : "空"} 当前亏损=${pnl.toFixed(4)} USDT` + t("log.maker.stopTriggered", { + direction: position.positionAmt > 0 ? t("common.direction.long") : t("common.direction.short"), + pnl: pnl.toFixed(4), + }) ); try { await this.flushOrders(); @@ -501,9 +513,9 @@ export class MakerEngine { ); } catch (error) { if (isUnknownOrderError(error)) { - this.tradeLog.push("order", "止损平仓时订单已不存在"); + this.tradeLog.push("order", t("log.maker.stopOrderMissing")); } else { - this.tradeLog.push("error", `止损平仓失败: ${String(error)}`); + this.tradeLog.push("error", t("log.maker.stopCloseFail", { error: String(error) })); } } } @@ -522,12 +534,12 @@ export class MakerEngine { // 成功撤销不记录日志,保持现有行为 }, () => { - this.tradeLog.push("order", "订单已不存在,撤销跳过"); + this.tradeLog.push("order", t("log.maker.orderMissing")); this.pendingCancelOrders.delete(String(order.orderId)); this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId); }, (error) => { - this.tradeLog.push("error", `撤销订单失败: ${String(error)}`); + this.tradeLog.push("error", t("log.maker.cancelFail", { error: String(error) })); this.pendingCancelOrders.delete(String(order.orderId)); this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId); } @@ -559,12 +571,15 @@ export class MakerEngine { if (updated) { this.tradeLog.push( "info", - `已同步交易精度: priceTick=${precision.priceTick} qtyStep=${precision.qtyStep}` + t("log.common.precisionSynced", { + priceTick: precision.priceTick, + qtyStep: precision.qtyStep, + }) ); } }) .catch((error) => { - this.tradeLog.push("error", `同步精度失败: ${extractMessage(error)}`); + this.tradeLog.push("error", t("log.common.precisionFailed", { error: extractMessage(error) })); this.precisionSync = null; setTimeout(() => this.syncPrecision(), 2000); }); @@ -581,10 +596,10 @@ export class MakerEngine { try { const snapshot = this.buildSnapshot(); this.events.emit("update", snapshot, (error) => { - this.tradeLog.push("error", `更新回调处理异常: ${String(error)}`); + this.tradeLog.push("error", t("log.maker.updateHandlerError", { error: String(error) })); }); } catch (err) { - this.tradeLog.push("error", `快照或更新分发异常: ${String(err)}`); + this.tradeLog.push("error", t("log.maker.snapshotDispatchError", { error: String(err) })); } } @@ -619,19 +634,19 @@ export class MakerEngine { private logReadinessBlockers(): void { if (!this.feedStatus.account && !this.readinessLogged.account) { - this.tradeLog.push("info", "等待账户快照同步,尚未开始做市"); + this.tradeLog.push("info", t("log.maker.waitAccount")); this.readinessLogged.account = true; } if (!this.feedStatus.depth && !this.readinessLogged.depth) { - this.tradeLog.push("info", "等待深度行情推送,尚未开始做市"); + this.tradeLog.push("info", t("log.maker.waitDepth")); this.readinessLogged.depth = true; } if (!this.feedStatus.ticker && !this.readinessLogged.ticker) { - this.tradeLog.push("info", "等待Ticker推送,尚未开始做市"); + this.tradeLog.push("info", t("log.maker.waitTicker")); this.readinessLogged.ticker = true; } if (!this.feedStatus.orders && !this.readinessLogged.orders) { - this.tradeLog.push("info", "等待订单快照返回,尚未执行初始化撤单"); + this.tradeLog.push("info", t("log.maker.waitOrders")); this.readinessLogged.orders = true; } } @@ -648,7 +663,7 @@ export class MakerEngine { private logDesiredOrders(desired: DesiredOrder[]): void { if (!desired.length) { if (this.lastDesiredSummary !== "none") { - this.tradeLog.push("info", "当前无目标挂单,等待下一次刷新"); + this.tradeLog.push("info", t("log.maker.noTargets")); this.lastDesiredSummary = "none"; } return; @@ -657,7 +672,7 @@ export class MakerEngine { .map((order) => `${order.side}@${order.price}${order.reduceOnly ? "(RO)" : ""}`) .join(" | "); if (summary !== this.lastDesiredSummary) { - this.tradeLog.push("info", `目标挂单: ${summary}`); + this.tradeLog.push("info", t("log.maker.targetsSummary", { summary })); this.lastDesiredSummary = summary; } } @@ -673,14 +688,14 @@ export class MakerEngine { this.insufficientBalanceCooldownUntil = now + INSUFFICIENT_BALANCE_COOLDOWN_MS; this.lastInsufficientMessage = detail; const seconds = Math.ceil(INSUFFICIENT_BALANCE_COOLDOWN_MS / 1000); - this.tradeLog.push("warn", `余额不足,暂停新挂单 ${seconds}s: ${detail}`); + this.tradeLog.push("warn", t("log.maker.balanceThrottle", { seconds, detail })); this.insufficientBalanceNotified = true; } private applyInsufficientBalanceState(now: number): boolean { const active = now < this.insufficientBalanceCooldownUntil; if (!active && this.insufficientBalanceNotified) { - this.tradeLog.push("info", "余额检测恢复,重新尝试挂单"); + this.tradeLog.push("info", t("log.maker.balanceResumed")); this.insufficientBalanceNotified = false; this.lastInsufficientMessage = null; } diff --git a/src/strategy/trend-engine.ts b/src/strategy/trend-engine.ts index 26f2574..3976435 100644 --- a/src/strategy/trend-engine.ts +++ b/src/strategy/trend-engine.ts @@ -35,6 +35,7 @@ import { RateLimitController } from "../core/lib/rate-limit"; import { StrategyEventEmitter } from "./common/event-emitter"; import { safeSubscribe, type LogHandler } from "./common/subscriptions"; import { SessionVolumeTracker } from "./common/session-volume"; +import { t } from "../i18n"; export interface TrendEngineSnapshot { ready: boolean; @@ -175,8 +176,8 @@ export class TrendEngine { }, log, { - subscribeFail: (error) => `订阅账户失败: ${String(error)}`, - processFail: (error) => `账户推送处理异常: ${extractMessage(error)}`, + subscribeFail: (error) => t("log.subscribe.accountFail", { error: String(error) }), + processFail: (error) => t("log.process.accountError", { error: extractMessage(error) }), } ); @@ -209,8 +210,8 @@ export class TrendEngine { }, log, { - subscribeFail: (error) => `订阅订单失败: ${String(error)}`, - processFail: (error) => `订单推送处理异常: ${extractMessage(error)}`, + subscribeFail: (error) => t("log.subscribe.orderFail", { error: String(error) }), + processFail: (error) => t("log.process.orderError", { error: extractMessage(error) }), } ); @@ -222,8 +223,8 @@ export class TrendEngine { }, log, { - subscribeFail: (error) => `订阅深度失败: ${String(error)}`, - processFail: (error) => `深度推送处理异常: ${extractMessage(error)}`, + subscribeFail: (error) => t("log.subscribe.depthFail", { error: String(error) }), + processFail: (error) => t("log.process.depthError", { error: extractMessage(error) }), } ); @@ -235,8 +236,8 @@ export class TrendEngine { }, log, { - subscribeFail: (error) => `订阅Ticker失败: ${String(error)}`, - processFail: (error) => `价格推送处理异常: ${extractMessage(error)}`, + subscribeFail: (error) => t("log.subscribe.tickerFail", { error: String(error) }), + processFail: (error) => t("log.process.tickerError", { error: extractMessage(error) }), } ); @@ -251,8 +252,8 @@ export class TrendEngine { }, log, { - subscribeFail: (error) => `订阅K线失败: ${String(error)}`, - processFail: (error) => `K线推送处理异常: ${extractMessage(error)}`, + subscribeFail: (error) => t("log.kline.subscribeFail", { error: String(error) }), + processFail: (error) => t("log.kline.processError", { error: extractMessage(error) }), } ); } @@ -287,7 +288,12 @@ export class TrendEngine { const closes = this.klineSnapshot.slice(-5).map((k) => Number(k.close).toFixed(2)); this.tradeLog.push( "info", - `K线不足 ${count}/${minKlines},最近收盘(${closes.length}): ${closes.join(", ")}` + t("log.trend.klineInsufficient", { + count, + min: minKlines, + recentCount: closes.length, + recent: closes.join(", "), + }) ); this.klineInsufficientLogged = true; } @@ -297,7 +303,7 @@ export class TrendEngine { const closes = this.klineSnapshot.slice(-5).map((k) => Number(k.close).toFixed(2)); this.tradeLog.push( "info", - `K线就绪 ${count} 根,可计算 SMA30。最近收盘: ${closes.join(", ")}` + t("log.trend.klineReady", { count, recent: closes.join(", ") }) ); this.klineReadyLogged = true; } @@ -361,16 +367,16 @@ export class TrendEngine { hadRateLimit = true; this.rateLimit.registerRateLimit("trend"); await this.enforceRateLimitStop(); - this.tradeLog.push("warn", `TrendEngine 429: ${String(error)}`); + this.tradeLog.push("warn", t("log.trend.rateLimit429", { error: String(error) })); } else { - this.tradeLog.push("error", `策略循环异常: ${String(error)}`); + this.tradeLog.push("error", t("log.trend.loopError", { error: String(error) })); } this.emitUpdate(); } finally { try { this.rateLimit.onCycleComplete(hadRateLimit); } catch (rateLimitError) { - this.tradeLog.push("error", `限频控制器状态更新失败: ${String(rateLimitError)}`); + this.tradeLog.push("error", t("log.trend.rateLimitUpdateError", { error: String(rateLimitError) })); } finally { this.processing = false; } @@ -395,11 +401,15 @@ export class TrendEngine { if (hasPosition) { this.tradeLog.push( "info", - `检测到已有持仓: ${position.positionAmt > 0 ? "多" : "空"} ${Math.abs(position.positionAmt).toFixed(4)} @ ${position.entryPrice.toFixed(2)}` + t("log.trend.detectPosition", { + direction: position.positionAmt > 0 ? t("common.direction.long") : t("common.direction.short"), + amount: Math.abs(position.positionAmt).toFixed(4), + price: position.entryPrice.toFixed(2), + }) ); } if (this.openOrders.length > 0) { - this.tradeLog.push("info", `检测到已有挂单 ${this.openOrders.length} 笔,将按策略规则接管`); + this.tradeLog.push("info", t("log.trend.detectOrders", { count: this.openOrders.length })); } this.startupLogged = true; } @@ -415,12 +425,12 @@ export class TrendEngine { // 止损后的冷却期:60s 内不允许基于 SMA 穿越再次入场 if (this.lastStopLossAt != null && now - this.lastStopLossAt < 60_000) { const remaining = Math.max(0, 60_000 - (now - this.lastStopLossAt)); - this.tradeLog.push("info", `止损后冷却中 ${(remaining / 1000).toFixed(0)}s,忽略入场信号`); + this.tradeLog.push("info", t("log.trend.stopCooldown", { seconds: (remaining / 1000).toFixed(0) })); return; } // 同一分钟只允许一次入场 if (this.lastEntryMinute != null && this.lastEntryMinute === currentMinute) { - this.tradeLog.push("info", "本分钟已入场,忽略新的 SMA 入场信号"); + this.tradeLog.push("info", t("log.trend.alreadyEntered")); return; } if ( @@ -431,7 +441,10 @@ export class TrendEngine { if (now - this.lastBollingerBlockLogged > 15_000) { this.tradeLog.push( "info", - `布林带宽度不足:${Number(currentBandwidth).toFixed(4)} < ${this.config.minBollingerBandwidth},忽略入场信号` + t("log.trend.bandwidthBlocked", { + bandwidth: Number(currentBandwidth).toFixed(4), + minBandwidth: this.config.minBollingerBandwidth, + }) ); this.lastBollingerBlockLogged = now; } @@ -450,22 +463,22 @@ export class TrendEngine { this.openOrders = []; } catch (err) { if (isUnknownOrderError(err)) { - this.tradeLog.push("order", "撤单时部分订单已不存在,忽略"); + this.tradeLog.push("order", t("log.trend.cancelMissing")); this.cancelAllRequested = true; // 与成功撤单路径保持一致,立即清空本地缓存,等待订单流推送重建 this.pendingCancelOrders.clear(); this.openOrders = []; } else { - this.tradeLog.push("error", `撤销挂单失败: ${String(err)}`); + this.tradeLog.push("error", t("log.trend.cancelFail", { error: String(err) })); this.cancelAllRequested = false; } } } if (this.lastPrice > currentSma && currentPrice < currentSma) { - await this.submitMarketOrder("SELL", currentPrice, "下穿SMA30,市价开空"); + await this.submitMarketOrder("SELL", currentPrice, t("log.trend.crossDown")); this.lastEntryMinute = currentMinute; } else if (this.lastPrice < currentSma && currentPrice > currentSma) { - await this.submitMarketOrder("BUY", currentPrice, "上穿SMA30,市价开多"); + await this.submitMarketOrder("BUY", currentPrice, t("log.trend.crossUp")); this.lastEntryMinute = currentMinute; } } @@ -493,7 +506,7 @@ export class TrendEngine { this.tradeLog.push("open", `${reason}: ${side} @ ${price}`); this.lastOpenPlan = { side, price }; } catch (err) { - this.tradeLog.push("error", `市价下单失败: ${String(err)}`); + this.tradeLog.push("error", t("log.trend.marketOrderFail", { error: String(err) })); } } @@ -504,7 +517,7 @@ export class TrendEngine { const hasEntryPrice = Number.isFinite(position.entryPrice) && Math.abs(position.entryPrice) > 1e-8; if (!hasEntryPrice) { if (!this.entryPricePendingLogged) { - this.tradeLog.push("info", "持仓均价尚未同步,等待交易所账户快照更新后再执行风控"); + this.tradeLog.push("info", t("log.trend.entryPricePending")); this.entryPricePendingLogged = true; } return { closed: false, pnl: position.unrealizedProfit }; @@ -697,7 +710,7 @@ export class TrendEngine { orderIdSet.forEach((id) => this.pendingCancelOrders.add(id)); } catch (err) { if (isUnknownOrderError(err)) { - this.tradeLog.push("order", "止损前撤单发现订单已不存在"); + this.tradeLog.push("order", t("log.trend.stopPreCancelMissing")); // 清理本地缓存,避免重复对同一订单执行撤单 for (const id of orderIdSet) { this.pendingCancelOrders.delete(id); @@ -720,7 +733,12 @@ export class TrendEngine { if (pctDiff > limitPct) { this.tradeLog.push( "info", - `市价平仓保护触发:closePx=${Number(closeSidePrice).toFixed(2)} mark=${mark.toFixed(2)} 偏离 ${(pctDiff * 100).toFixed(2)}% > ${(limitPct * 100).toFixed(2)}%` + t("log.trend.marketCloseGuard", { + closePx: Number(closeSidePrice).toFixed(2), + mark: mark.toFixed(2), + pctDiff: (pctDiff * 100).toFixed(2), + limitPct: (limitPct * 100).toFixed(2), + }) ); return { closed: false, pnl }; } @@ -747,14 +765,14 @@ export class TrendEngine { { qtyStep: this.config.qtyStep } ); result.closed = true; - this.tradeLog.push("close", `止损平仓: ${direction === "long" ? "SELL" : "BUY"}`); + this.tradeLog.push("close", t("log.trend.stopClose", { side: direction === "long" ? "SELL" : "BUY" })); // 记录止损时间以便短期内抑制再次入场 this.lastStopLossAt = Date.now(); } catch (err) { if (isUnknownOrderError(err)) { - this.tradeLog.push("order", "止损平仓时目标订单已不存在"); + this.tradeLog.push("order", t("log.trend.targetStopMissing")); } else { - this.tradeLog.push("error", `止损平仓失败: ${String(err)}`); + this.tradeLog.push("error", t("log.trend.stopCloseFail", { error: String(err) })); } return result; } @@ -808,7 +826,7 @@ export class TrendEngine { ); this.lastStopAttempt = { side, price: stopPrice, at: Date.now() }; } catch (err) { - this.tradeLog.push("error", `挂止损单失败: ${String(err)}`); + this.tradeLog.push("error", t("log.trend.placeStopFail", { error: String(err) })); // 记录尝试以避免在错误被抛回时立即再次重复尝试 this.lastStopAttempt = { side, price: stopPrice, at: Date.now() }; } @@ -833,11 +851,11 @@ export class TrendEngine { await this.exchange.cancelOrder({ symbol: this.config.symbol, orderId: currentOrder.orderId }); } catch (err) { if (isUnknownOrderError(err)) { - this.tradeLog.push("order", "原止损单已不存在,跳过撤销"); + this.tradeLog.push("order", t("log.trend.stopMissingSkip")); // 订单已不存在,移除本地记录,防止后续重复匹配 this.openOrders = this.openOrders.filter((o) => o.orderId !== currentOrder.orderId); } else { - this.tradeLog.push("error", `取消原止损单失败: ${String(err)}`); + this.tradeLog.push("error", t("log.trend.cancelStopFail", { error: String(err) })); } } // 仅在成功创建新止损单后记录“移动止损”日志 @@ -867,10 +885,18 @@ export class TrendEngine { { priceTick: this.config.priceTick, qtyStep: this.config.qtyStep } ); if (order) { - this.tradeLog.push("stop", `移动止损到 ${formatPriceToString(nextStopPrice, Math.max(0, Math.floor(Math.log10(1 / this.config.priceTick))))}`); + this.tradeLog.push( + "stop", + t("log.trend.moveStop", { + price: formatPriceToString( + nextStopPrice, + Math.max(0, Math.floor(Math.log10(1 / this.config.priceTick))) + ), + }) + ); } } catch (err) { - this.tradeLog.push("error", `移动止损失败: ${String(err)}`); + this.tradeLog.push("error", t("log.trend.moveStopFail", { error: String(err) })); // 回滚策略:尝试用原价恢复止损,以避免出现短时间内无止损保护 try { const position = getPosition(this.accountSnapshot, this.config.symbol); @@ -902,11 +928,19 @@ export class TrendEngine { { priceTick: this.config.priceTick, qtyStep: this.config.qtyStep } ); if (restored) { - this.tradeLog.push("order", `恢复原止损 @ ${formatPriceToString(existingStopPrice, Math.max(0, Math.floor(Math.log10(1 / this.config.priceTick))))}`); + this.tradeLog.push( + "order", + t("log.trend.restoreStop", { + price: formatPriceToString( + existingStopPrice, + Math.max(0, Math.floor(Math.log10(1 / this.config.priceTick))) + ), + }) + ); } } } catch (recoverErr) { - this.tradeLog.push("error", `恢复原止损失败: ${String(recoverErr)}`); + this.tradeLog.push("error", t("log.trend.restoreStopFail", { error: String(recoverErr) })); } } } @@ -939,7 +973,7 @@ export class TrendEngine { { priceTick: this.config.priceTick, qtyStep: this.config.qtyStep } ); } catch (err) { - this.tradeLog.push("error", `挂动态止盈失败: ${String(err)}`); + this.tradeLog.push("error", t("log.trend.trailingFail", { error: String(err) })); } } @@ -968,12 +1002,12 @@ export class TrendEngine { if (updated) { this.tradeLog.push( "info", - `已同步交易精度: priceTick=${precision.priceTick} qtyStep=${precision.qtyStep}` + t("log.trend.precisionSynced", { priceTick: precision.priceTick, qtyStep: precision.qtyStep }) ); } }) .catch((error) => { - this.tradeLog.push("error", `同步精度失败: ${extractMessage(error)}`); + this.tradeLog.push("error", t("log.trend.precisionFailed", { error: extractMessage(error) })); this.precisionSync = null; setTimeout(() => this.syncPrecision(), 2000); }); @@ -983,10 +1017,10 @@ export class TrendEngine { try { const snapshot = this.buildSnapshot(); this.events.emit("update", snapshot, (error) => { - this.tradeLog.push("error", `更新回调处理异常: ${String(error)}`); + this.tradeLog.push("error", t("log.trend.updateHandlerError", { error: String(error) })); }); } catch (err) { - this.tradeLog.push("error", `快照或更新分发异常: ${String(err)}`); + this.tradeLog.push("error", t("log.trend.snapshotDispatchError", { error: String(err) })); } } diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 7bd751a..489c660 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -9,6 +9,7 @@ import { BasisApp } from "./BasisApp"; import { isBasisStrategyEnabled } from "../config"; import { loadCopyrightFragments, verifyCopyrightIntegrity } from "../utils/copyright"; import { resolveExchangeId } from "../exchanges/create-adapter"; +import { t } from "../i18n"; interface StrategyOption { id: "trend" | "guardian" | "maker" | "offset-maker" | "basis" | "grid"; @@ -20,32 +21,32 @@ interface StrategyOption { const BASE_STRATEGIES: StrategyOption[] = [ { id: "trend", - label: "趋势跟随策略 (SMA30)", - description: "监控均线信号,自动进出场并维护止损/止盈", + label: t("app.strategy.trend.label"), + description: t("app.strategy.trend.desc"), component: TrendApp, }, { id: "guardian", - label: "Guardian 防守策略", - description: "不主动开仓,只为现有仓位补挂/移动止损,防止裸奔", + label: t("app.strategy.guardian.label"), + description: t("app.strategy.guardian.desc"), component: GuardianApp, }, { id: "maker", - label: "做市刷单策略", - description: "双边挂单提供流动性,自动追价与风控止损", + label: t("app.strategy.maker.label"), + description: t("app.strategy.maker.desc"), component: MakerApp, }, { id: "grid", - label: "基础网格策略", - description: "在上下边界之间布设等比网格,自动加仓与减仓", + label: t("app.strategy.grid.label"), + description: t("app.strategy.grid.desc"), component: GridApp, }, { id: "offset-maker", - label: "偏移做市策略", - description: "根据盘口深度自动偏移挂单并在极端不平衡时撤退", + label: t("app.strategy.offset.label"), + description: t("app.strategy.offset.desc"), component: OffsetMakerApp, }, ]; @@ -66,8 +67,8 @@ export function App() { ...BASE_STRATEGIES, { id: "basis" as const, - label: "期现套利策略", - description: "监控期货与现货盘口差价,辅助发现套利机会", + label: t("app.strategy.basis.label"), + description: t("app.strategy.basis.desc"), component: BasisApp, }, ]; @@ -99,13 +100,13 @@ export function App() { {copyright.bannerText} {integrityOk ? null : ( - 警告: 版权校验失败,当前版本可能被篡改。 + {t("app.integrity.warning")} )} ──────────────────────────────────────────────────── - 请选择要运行的策略 - 使用 ↑/↓ 选择,回车开始,Ctrl+C 退出。 + {t("app.pickStrategy")} + {t("app.pickHint")} {strategies.map((strategy, index) => { const active = index === cursor; diff --git a/src/ui/BasisApp.tsx b/src/ui/BasisApp.tsx index d1aad6b..16b6308 100644 --- a/src/ui/BasisApp.tsx +++ b/src/ui/BasisApp.tsx @@ -5,6 +5,7 @@ import { getExchangeDisplayName, resolveExchangeId } from "../exchanges/create-a import { buildAdapterFromEnv } from "../exchanges/resolve-from-env"; import { BasisArbEngine, type BasisArbSnapshot } from "../strategy/basis-arb-engine"; import { formatNumber } from "../utils/format"; +import { t } from "../i18n"; interface BasisAppProps { onExit: () => void; @@ -31,7 +32,7 @@ export function BasisApp({ onExit }: BasisAppProps) { useEffect(() => { if (exchangeId !== "aster") { - setError(new Error("期现套利策略目前仅支持 Aster 交易所。请设置 EXCHANGE=aster 后重试。")); + setError(new Error(t("basis.onlyAster"))); return; } try { @@ -57,8 +58,8 @@ export function BasisApp({ onExit }: BasisAppProps) { if (error) { return ( - 无法启动期现套利策略: {error.message} - 按 Esc 返回菜单。 + {t("basis.startFailed", { message: error.message })} + {t("common.backHint")} ); } @@ -66,7 +67,7 @@ export function BasisApp({ onExit }: BasisAppProps) { if (!snapshot) { return ( - 正在初始化期现套利监控… + {t("basis.initializing")} ); } @@ -85,10 +86,15 @@ export function BasisApp({ onExit }: BasisAppProps) { const fundingRatePct = snapshot.fundingRate != null ? `${(snapshot.fundingRate * 100).toFixed(4)}%` : "-"; const fundingUpdated = snapshot.fundingLastUpdate ? new Date(snapshot.fundingLastUpdate).toLocaleTimeString() : "-"; const nextFundingTime = snapshot.nextFundingTime ? new Date(snapshot.nextFundingTime).toLocaleTimeString() : "-"; - const fundingIncomePerFunding = snapshot.fundingIncomePerFunding != null ? `${formatNumber(snapshot.fundingIncomePerFunding, 4)} USDT` : "-"; - const fundingIncomePerDay = snapshot.fundingIncomePerDay != null ? `${formatNumber(snapshot.fundingIncomePerDay, 4)} USDT` : "-"; - const takerFeesPerRoundTrip = snapshot.takerFeesPerRoundTrip != null ? `${formatNumber(snapshot.takerFeesPerRoundTrip, 4)} USDT` : "-"; - const fundingCountToBreakeven = snapshot.fundingCountToBreakeven != null ? `${formatNumber(snapshot.fundingCountToBreakeven, 2)} 次` : "-"; + const fundingIncomePerFunding = + snapshot.fundingIncomePerFunding != null ? `${formatNumber(snapshot.fundingIncomePerFunding, 4)} USDT` : "-"; + const fundingIncomePerDay = + snapshot.fundingIncomePerDay != null ? `${formatNumber(snapshot.fundingIncomePerDay, 4)} USDT` : "-"; + const takerFeesPerRoundTrip = + snapshot.takerFeesPerRoundTrip != null ? `${formatNumber(snapshot.takerFeesPerRoundTrip, 4)} USDT` : "-"; + const fundingCountToBreakeven = + snapshot.fundingCountToBreakeven != null ? formatNumber(snapshot.fundingCountToBreakeven, 2) : "-"; + const feePct = (basisConfig.takerFeeRate * 100).toFixed(4); const feedStatus = snapshot.feedStatus; const lastLogs = snapshot.tradeLog.slice(-5); const spotBalances = (snapshot.spotBalances ?? []).filter((b) => Math.abs(b.free) > 0 || Math.abs(b.locked) > 0); @@ -97,72 +103,94 @@ export function BasisApp({ onExit }: BasisAppProps) { return ( - Basis Arbitrage Dashboard + {t("basis.title")} - 交易所: {exchangeName} | 期货合约: {snapshot.futuresSymbol} | 现货交易对: {snapshot.spotSymbol} + {t("basis.headerLine", { + exchange: exchangeName, + futures: snapshot.futuresSymbol, + spot: snapshot.spotSymbol, + })} - 按 Esc 返回策略选择 | 数据状态: 期货({feedStatus.futures ? "OK" : "--"}) 现货({feedStatus.spot ? "OK" : "--"}) 资金费率({feedStatus.funding ? "OK" : "--"}) - 最近更新时间: {lastUpdated} + + {t("basis.statusLine", { + futuresStatus: feedStatus.futures ? "OK" : "--", + spotStatus: feedStatus.spot ? "OK" : "--", + fundingStatus: feedStatus.funding ? "OK" : "--", + })} + + {t("basis.lastUpdated", { time: lastUpdated })} - 期货盘口 - 买一: {futuresBid} | 卖一: {futuresAsk} - 更新时间: {futuresUpdated} + {t("basis.section.futures")} + {t("basis.bookLine", { bid: futuresBid, ask: futuresAsk })} + {t("basis.updatedAt", { time: futuresUpdated })} - 现货盘口 - 买一: {spotBid} | 卖一: {spotAsk} - 更新时间: {spotUpdated} + {t("basis.section.spot")} + {t("basis.bookLine", { bid: spotBid, ask: spotAsk })} + {t("basis.updatedAt", { time: spotUpdated })} - 资金费率 - 当前资金费率: {fundingRatePct} - 资金费率更新时间: {fundingUpdated} | 下次结算时间: {nextFundingTime} - 单次资金费率收益(估): {fundingIncomePerFunding} | 日收益(估): {fundingIncomePerDay} - 双边吃单手续费(估): {takerFeesPerRoundTrip} | 回本所需资金费率次数: {fundingCountToBreakeven} + {t("basis.section.funding")} + {t("basis.fundingRate", { rate: fundingRatePct })} + + {t("basis.fundingTimes", { updated: fundingUpdated, next: nextFundingTime })} + + {t("basis.fundingIncome", { per: fundingIncomePerFunding, perDay: fundingIncomePerDay })} + {t("basis.takerFees", { fees: takerFeesPerRoundTrip, count: fundingCountToBreakeven })} - 现货账户余额(非0) + {t("basis.spotBalanceTitle")} {spotBalances.length ? ( spotBalances.map((b) => ( - {b.asset}: 可用 {formatNumber(b.free, 8)} | 冻结 {formatNumber(b.locked, 8)} + {t("basis.balanceLine", { + asset: b.asset, + free: formatNumber(b.free, 8), + locked: formatNumber(b.locked, 8), + })} )) ) : ( - + {t("basis.none")} )} - 合约账户余额(非0) + {t("basis.futuresBalanceTitle")} {futuresBalances.length ? ( futuresBalances.map((b) => ( - {b.asset}: 钱包 {formatNumber(b.wallet, 8)} | 可用 {formatNumber(b.available, 8)} + {t("basis.futuresBalanceLine", { + asset: b.asset, + wallet: formatNumber(b.wallet, 8), + available: formatNumber(b.available, 8), + })} )) ) : ( - + {t("basis.none")} )} - 套利差价(卖期货 / 买现货) - 毛价差: {spread} USDT | {spreadBps} bp + {t("basis.spreadTitle")} + + {t("basis.spreadLine", { spread, bps: spreadBps })} + - 扣除 taker 手续费 ({(basisConfig.takerFeeRate * 100).toFixed(4)}% × 双边): {netSpread} USDT | {netSpreadBps} bp + {t("basis.netSpreadLine", { feePct, net: netSpread, netBps: netSpreadBps })} - 最近事件 + {t("common.section.recent")} {lastLogs.length ? ( lastLogs.map((entry, index) => { const color = entry.type === "entry" ? "green" : entry.type === "exit" ? "red" : undefined; @@ -173,7 +201,7 @@ export function BasisApp({ onExit }: BasisAppProps) { ); }) ) : ( - 暂无日志 + {t("common.noLogs")} )} diff --git a/src/ui/GridApp.tsx b/src/ui/GridApp.tsx index 6d85f7f..6ebfdc7 100644 --- a/src/ui/GridApp.tsx +++ b/src/ui/GridApp.tsx @@ -6,6 +6,7 @@ import { buildAdapterFromEnv } from "../exchanges/resolve-from-env"; import { GridEngine, type GridEngineSnapshot } from "../strategy/grid-engine"; import { DataTable, type TableColumn } from "./components/DataTable"; import { formatNumber } from "../utils/format"; +import { t } from "../i18n"; interface GridAppProps { onExit: () => void; @@ -59,8 +60,8 @@ export function GridApp({ onExit }: GridAppProps) { if (error) { return ( - 启动失败: {error.message} - 请检查环境变量和网络连通性。 + {t("common.startFailed", { message: error.message })} + {t("common.checkEnv")} ); } @@ -68,17 +69,17 @@ export function GridApp({ onExit }: GridAppProps) { if (!snapshot) { return ( - 正在初始化网格策略… + {t("grid.initializing")} ); } const feedStatus = snapshot.feedStatus; const feedEntries: Array<{ key: keyof typeof feedStatus; label: string }> = [ - { key: "account", label: "账户" }, - { key: "orders", label: "订单" }, - { key: "depth", label: "深度" }, - { key: "ticker", label: "行情" }, + { key: "account", label: t("maker.feed.account") }, + { key: "orders", label: t("maker.feed.orders") }, + { key: "depth", label: t("maker.feed.depth") }, + { key: "ticker", label: t("maker.feed.ticker") }, ]; const stopReason = snapshot.running ? null : snapshot.stopReason; const lastLogs = snapshot.tradeLog.slice(-5); @@ -112,71 +113,112 @@ export function GridApp({ onExit }: GridAppProps) { price: order.price, amount: formatNumber(order.amount, 4), })); + const statusLabel = snapshot.running ? t("status.running") : t("status.paused"); + const directionLabel = + snapshot.direction === "both" + ? t("grid.direction.both") + : snapshot.direction === "long" + ? t("grid.direction.long") + : t("grid.direction.short"); return ( - Grid Strategy Dashboard + {t("grid.title")} - 交易所: {exchangeName} | 交易对: {snapshot.symbol} | 状态: {snapshot.running ? "运行中" : "暂停"} | 方向: {snapshot.direction} + {t("grid.headerLine", { + exchange: exchangeName, + symbol: snapshot.symbol, + status: statusLabel, + direction: directionLabel, + })} - 实时价格: {formatNumber(snapshot.lastPrice, 4)} | 下界: {formatNumber(snapshot.lowerPrice, 4)} | 上界: {formatNumber(snapshot.upperPrice, 4)} | 网格数量: {snapshot.gridLines.length} + {t("grid.priceLine", { + lastPrice: formatNumber(snapshot.lastPrice, 4), + lower: formatNumber(snapshot.lowerPrice, 4), + upper: formatNumber(snapshot.upperPrice, 4), + count: snapshot.gridLines.length, + })} - 数据状态: + + {t("grid.dataStatus")} {feedEntries.map((entry, index) => ( {index === 0 ? " " : " "} {entry.label} ))} - | 按 Esc 返回策略选择 + {" | "} + {t("common.backHint")} - {stopReason ? 暂停原因: {stopReason} : null} + {stopReason ? {t("grid.stopReason", { reason: stopReason })} : null} - 网格配置 + {t("grid.configTitle")} - 单笔数量: {formatNumber(gridConfig.orderSize, 6)} | 最大仓位: {formatNumber(gridConfig.maxPositionSize, 6)} + {t("grid.configSize", { + orderSize: formatNumber(gridConfig.orderSize, 6), + maxPosition: formatNumber(gridConfig.maxPositionSize, 6), + })} - 止损阈值: {(gridConfig.stopLossPct * 100).toFixed(2)}% | 重启阈值: {(gridConfig.restartTriggerPct * 100).toFixed(2)}% | 自动重启: {gridConfig.autoRestart ? "启用" : "关闭"} + {t("grid.configRisk", { + stopLoss: (gridConfig.stopLossPct * 100).toFixed(2), + restart: (gridConfig.restartTriggerPct * 100).toFixed(2), + autoRestart: gridConfig.autoRestart ? t("common.enabled") : t("common.disabled"), + })} - 刷新间隔: {gridConfig.refreshIntervalMs} ms + {t("grid.refreshInterval", { interval: gridConfig.refreshIntervalMs })} - 持仓 + {t("common.section.position")} {hasPosition ? ( <> - 当前持仓: {position.positionAmt > 0 ? "多" : "空"} | 数量: {formatNumber(Math.abs(position.positionAmt), 6)} | 均价: {formatNumber(position.entryPrice, 4)} + {t("grid.positionLine", { + direction: position.positionAmt > 0 ? t("common.direction.long") : t("common.direction.short"), + qty: formatNumber(Math.abs(position.positionAmt), 6), + avgPrice: formatNumber(position.entryPrice, 4), + })} - 未实现盈亏: {formatNumber(position.unrealizedProfit, 4)} | 标记价: {formatNumber(position.markPrice, 4)} + {t("grid.unrealizedLine", { + pnl: formatNumber(position.unrealizedProfit, 4), + mark: formatNumber(position.markPrice, 4), + })} ) : ( - 当前无持仓 + {t("common.noPosition")} )} - 网格线 - {gridRows.length > 0 ? : 暂无网格线} + {t("grid.linesTitle")} + {gridRows.length > 0 ? ( + + ) : ( + {t("grid.noLines")} + )} - 目标挂单 - {desiredRows.length > 0 ? : 暂无目标挂单} + {t("maker.targetOrders")} + {desiredRows.length > 0 ? ( + + ) : ( + {t("maker.noTargetOrders")} + )} - 最近事件 + {t("common.section.recent")} {lastLogs.length > 0 ? ( lastLogs.map((item, index) => ( @@ -184,7 +226,7 @@ export function GridApp({ onExit }: GridAppProps) { )) ) : ( - 暂无日志 + {t("common.noLogs")} )} diff --git a/src/ui/GuardianApp.tsx b/src/ui/GuardianApp.tsx index 1b18dff..a420f11 100644 --- a/src/ui/GuardianApp.tsx +++ b/src/ui/GuardianApp.tsx @@ -6,12 +6,13 @@ import { buildAdapterFromEnv } from "../exchanges/resolve-from-env"; import { GuardianEngine, type GuardianEngineSnapshot } from "../strategy/guardian-engine"; import { formatNumber } from "../utils/format"; import { DataTable, type TableColumn } from "./components/DataTable"; +import { t } from "../i18n"; interface GuardianAppProps { onExit: () => void; } -const READY_MESSAGE = "正在等待行情/账户推送…"; +const READY_MESSAGE = t("guardian.readyMessage"); const inputSupported = Boolean(process.stdin && (process.stdin as any).isTTY); export function GuardianApp({ onExit }: GuardianAppProps) { @@ -55,8 +56,8 @@ export function GuardianApp({ onExit }: GuardianAppProps) { if (error) { return ( - Guardian 策略启动失败: {error.message} - 请检查环境变量和网络连通性。 + {t("guardian.startFailed", { message: error.message })} + {t("common.checkEnv")} ); } @@ -64,7 +65,7 @@ export function GuardianApp({ onExit }: GuardianAppProps) { if (!snapshot) { return ( - 正在初始化 Guardian 策略… + {t("guardian.initializing")} ); } @@ -97,43 +98,65 @@ export function GuardianApp({ onExit }: GuardianAppProps) { return ( - Guardian Strategy Dashboard + {t("guardian.title")} - 交易所: {exchangeName} | 交易对: {snapshot.symbol} | 最近价格: {formatNumber(snapshot.lastPrice, 2)} | 状态: {ready ? "实时运行" : READY_MESSAGE} + {t("guardian.headerLine", { + exchange: exchangeName, + symbol: snapshot.symbol, + lastPrice: formatNumber(snapshot.lastPrice, 2), + status: ready ? t("status.live") : READY_MESSAGE, + })} - 策略只会维护止损/止盈,不会主动开仓。按 Esc 返回菜单。 + {t("guardian.hint")} - 当前仓位与风控 + {t("guardian.positionTitle")} {hasPosition ? ( <> - 方向: {position.positionAmt > 0 ? "多" : "空"} | 数量: {formatNumber(Math.abs(position.positionAmt), 4)} | 开仓价: {formatNumber(position.entryPrice, 2)} | 浮动盈亏: {formatNumber(snapshot.pnl, 4)} USDT + {t("guardian.positionLine", { + direction: position.positionAmt > 0 ? t("common.direction.long") : t("common.direction.short"), + qty: formatNumber(Math.abs(position.positionAmt), 4), + entry: formatNumber(position.entryPrice, 2), + pnl: formatNumber(snapshot.pnl, 4), + })} - 目标止损价: {formatNumber(snapshot.targetStopPrice, 2)} | 当前止损单: {formatNumber(stopOrderPrice, 2)} | 动态止盈触发: {formatNumber(snapshot.trailingActivationPrice, 2)} | 动态止盈单: {formatNumber(trailingActivate, 2)} + {t("guardian.stopLine", { + targetStop: formatNumber(snapshot.targetStopPrice, 2), + stopOrder: formatNumber(stopOrderPrice, 2), + trailingTrigger: formatNumber(snapshot.trailingActivationPrice, 2), + trailingOrder: formatNumber(trailingActivate, 2), + })} - Guardian 状态: {guardStatus === "protecting" ? "已挂止损" : guardStatus === "pending" ? "缺少止损,正在同步" : "监听中"} + {t("guardian.stateLabel", { + state: + guardStatus === "protecting" + ? t("guardian.status.protecting") + : guardStatus === "pending" + ? t("guardian.status.pending") + : t("guardian.status.listening"), + })} ) : ( - 当前无持仓,Guardian 正在监听新的仓位变化。 + {t("guardian.noPosition")} )} - 当前挂单 + {t("common.section.orders")} {orderRows.length > 0 ? ( ) : ( - 暂无保护类挂单 + {t("guardian.noProtectiveOrders")} )} - 最近事件 + {t("common.section.recent")} {lastLogs.length > 0 ? ( lastLogs.map((item, index) => ( @@ -141,7 +164,7 @@ export function GuardianApp({ onExit }: GuardianAppProps) { )) ) : ( - 暂无日志 + {t("common.noLogs")} )} diff --git a/src/ui/MakerApp.tsx b/src/ui/MakerApp.tsx index b346d67..dd89c74 100644 --- a/src/ui/MakerApp.tsx +++ b/src/ui/MakerApp.tsx @@ -6,6 +6,7 @@ import { buildAdapterFromEnv } from "../exchanges/resolve-from-env"; import { MakerEngine, type MakerEngineSnapshot } from "../strategy/maker-engine"; import { DataTable, type TableColumn } from "./components/DataTable"; import { formatNumber } from "../utils/format"; +import { t } from "../i18n"; interface MakerAppProps { onExit: () => void; @@ -54,8 +55,8 @@ export function MakerApp({ onExit }: MakerAppProps) { if (error) { return ( - 启动失败: {error.message} - 请检查环境变量和网络连通性。 + {t("common.startFailed", { message: error.message })} + {t("common.checkEnv")} ); } @@ -63,7 +64,7 @@ export function MakerApp({ onExit }: MakerAppProps) { if (!snapshot) { return ( - 正在初始化做市策略… + {t("maker.initializing")} ); } @@ -113,22 +114,29 @@ export function MakerApp({ onExit }: MakerAppProps) { const lastLogs = snapshot.tradeLog.slice(-5); const feedStatus = snapshot.feedStatus; const feedEntries: Array<{ key: keyof typeof feedStatus; label: string }> = [ - { key: "account", label: "账户" }, - { key: "orders", label: "订单" }, - { key: "depth", label: "深度" }, - { key: "ticker", label: "Ticker" }, + { key: "account", label: t("maker.feed.account") }, + { key: "orders", label: t("maker.feed.orders") }, + { key: "depth", label: t("maker.feed.depth") }, + { key: "ticker", label: t("maker.feed.ticker") }, ]; + const readyStatus = snapshot.ready ? t("status.live") : t("status.waitingData"); return ( - Maker Strategy Dashboard + {t("maker.title")} - 交易所: {exchangeName} | 交易对: {snapshot.symbol} | 买一价: {formatNumber(topBid, priceDigits)} | 卖一价: {formatNumber(topAsk, priceDigits)} | 点差: {spreadDisplay} + {t("maker.headerLine", { + exchange: exchangeName, + symbol: snapshot.symbol, + bid: formatNumber(topBid, priceDigits), + ask: formatNumber(topAsk, priceDigits), + spread: spreadDisplay, + })} - 状态: {snapshot.ready ? "实时运行" : "等待市场数据"} | 按 Esc 返回策略选择 + {t("trend.statusLine", { status: readyStatus })} - 数据状态: + {t("maker.dataStatus")} {feedEntries.map((entry, index) => ( {index === 0 ? " " : " "} @@ -140,44 +148,52 @@ export function MakerApp({ onExit }: MakerAppProps) { - 持仓 + {t("common.section.position")} {hasPosition ? ( <> - 方向: {snapshot.position.positionAmt > 0 ? "多" : "空"} | 数量: {formatNumber(Math.abs(snapshot.position.positionAmt), 4)} | 开仓价: {formatNumber(snapshot.position.entryPrice, priceDigits)} + {t("maker.positionLine", { + direction: + snapshot.position.positionAmt > 0 ? t("common.direction.long") : t("common.direction.short"), + qty: formatNumber(Math.abs(snapshot.position.positionAmt), 4), + entry: formatNumber(snapshot.position.entryPrice, priceDigits), + })} - 浮动盈亏: {formatNumber(snapshot.pnl, 4)} USDT | 账户未实现盈亏: {formatNumber(snapshot.accountUnrealized, 4)} USDT + {t("maker.pnlLine", { + pnl: formatNumber(snapshot.pnl, 4), + accountPnl: formatNumber(snapshot.accountUnrealized, 4), + })} ) : ( - 当前无持仓 + {t("common.noPosition")} )} - 目标挂单 + {t("maker.targetOrders")} {desiredRows.length > 0 ? ( ) : ( - 暂无目标挂单 + {t("maker.noTargetOrders")} )} - 累计成交量: {formatNumber(snapshot.sessionVolume, 2)} USDT + {t("trend.volumeLine", { volume: formatNumber(snapshot.sessionVolume, 2) })} - 当前挂单 + {t("common.section.orders")} {openOrderRows.length > 0 ? ( ) : ( - 暂无挂单 + {t("common.noOrders")} )} - 最近事件 + {t("common.section.recent")} {lastLogs.length > 0 ? ( lastLogs.map((item, index) => ( @@ -185,7 +201,7 @@ export function MakerApp({ onExit }: MakerAppProps) { )) ) : ( - 暂无日志 + {t("common.noLogs")} )} diff --git a/src/ui/OffsetMakerApp.tsx b/src/ui/OffsetMakerApp.tsx index 0a67f69..22c3e85 100644 --- a/src/ui/OffsetMakerApp.tsx +++ b/src/ui/OffsetMakerApp.tsx @@ -6,6 +6,7 @@ import { buildAdapterFromEnv } from "../exchanges/resolve-from-env"; import { OffsetMakerEngine, type OffsetMakerEngineSnapshot } from "../strategy/offset-maker-engine"; import { DataTable, type TableColumn } from "./components/DataTable"; import { formatNumber } from "../utils/format"; +import { t } from "../i18n"; interface OffsetMakerAppProps { onExit: () => void; @@ -54,8 +55,8 @@ export function OffsetMakerApp({ onExit }: OffsetMakerAppProps) { if (error) { return ( - 启动失败: {error.message} - 请检查环境变量和网络连通性。 + {t("common.startFailed", { message: error.message })} + {t("common.checkEnv")} ); } @@ -63,7 +64,7 @@ export function OffsetMakerApp({ onExit }: OffsetMakerAppProps) { if (!snapshot) { return ( - 正在初始化偏移做市策略… + {t("offset.initializing")} ); } @@ -113,68 +114,91 @@ export function OffsetMakerApp({ onExit }: OffsetMakerAppProps) { ]; const lastLogs = snapshot.tradeLog.slice(-5); - const imbalanceLabel = snapshot.depthImbalance === "balanced" - ? "均衡" - : snapshot.depthImbalance === "buy_dominant" - ? "买盘占优" - : "卖盘占优"; + const imbalanceLabel = + snapshot.depthImbalance === "balanced" + ? t("offset.imbalance.balanced") + : snapshot.depthImbalance === "buy_dominant" + ? t("offset.imbalance.buy") + : t("offset.imbalance.sell"); + const readyStatus = snapshot.ready ? t("status.live") : t("status.waitingData"); return ( - Offset Maker Strategy Dashboard + {t("offset.title")} - 交易所: {exchangeName} | 交易对: {snapshot.symbol} | 买一价: {formatNumber(topBid, priceDigits)} | 卖一价: {formatNumber(topAsk, priceDigits)} | 点差: {spreadDisplay} + {t("offset.headerLine", { + exchange: exchangeName, + symbol: snapshot.symbol, + bid: formatNumber(topBid, priceDigits), + ask: formatNumber(topAsk, priceDigits), + spread: spreadDisplay, + })} - 买10档累计: {formatNumber(snapshot.buyDepthSum10, 4)} | 卖10档累计: {formatNumber(snapshot.sellDepthSum10, 4)} | 状态: {imbalanceLabel} + {t("offset.depthLine", { + buy: formatNumber(snapshot.buyDepthSum10, 4), + sell: formatNumber(snapshot.sellDepthSum10, 4), + status: imbalanceLabel, + })} - 当前挂单策略: BUY {snapshot.skipBuySide ? "暂停" : "启用"} | SELL {snapshot.skipSellSide ? "暂停" : "启用"} | 按 Esc 返回策略选择 + {t("offset.strategyStatus", { + buyStatus: snapshot.skipBuySide ? t("common.disabled") : t("common.enabled"), + sellStatus: snapshot.skipSellSide ? t("common.disabled") : t("common.enabled"), + })} - 状态: {snapshot.ready ? "实时运行" : "等待市场数据"} + {t("trend.statusLine", { status: readyStatus })} - 持仓 + {t("common.section.position")} {hasPosition ? ( <> - 方向: {snapshot.position.positionAmt > 0 ? "多" : "空"} | 数量: {formatNumber(Math.abs(snapshot.position.positionAmt), 4)} | 开仓价: {formatNumber(snapshot.position.entryPrice, priceDigits)} + {t("maker.positionLine", { + direction: + snapshot.position.positionAmt > 0 ? t("common.direction.long") : t("common.direction.short"), + qty: formatNumber(Math.abs(snapshot.position.positionAmt), 4), + entry: formatNumber(snapshot.position.entryPrice, priceDigits), + })} - 浮动盈亏: {formatNumber(snapshot.pnl, 4)} USDT | 账户未实现盈亏: {formatNumber(snapshot.accountUnrealized, 4)} USDT + {t("maker.pnlLine", { + pnl: formatNumber(snapshot.pnl, 4), + accountPnl: formatNumber(snapshot.accountUnrealized, 4), + })} ) : ( - 当前无持仓 + {t("common.noPosition")} )} - 目标挂单 + {t("maker.targetOrders")} {desiredRows.length > 0 ? ( ) : ( - 暂无目标挂单 + {t("maker.noTargetOrders")} )} - 累计成交量: {formatNumber(snapshot.sessionVolume, 2)} USDT + {t("trend.volumeLine", { volume: formatNumber(snapshot.sessionVolume, 2) })} - 当前挂单 + {t("common.section.orders")} {openOrderRows.length > 0 ? ( ) : ( - 暂无挂单 + {t("common.noOrders")} )} - 最近事件 + {t("common.section.recent")} {lastLogs.length > 0 ? ( lastLogs.map((item, index) => ( @@ -182,7 +206,7 @@ export function OffsetMakerApp({ onExit }: OffsetMakerAppProps) { )) ) : ( - 暂无日志 + {t("common.noLogs")} )} diff --git a/src/ui/TrendApp.tsx b/src/ui/TrendApp.tsx index af52d35..e2b6271 100644 --- a/src/ui/TrendApp.tsx +++ b/src/ui/TrendApp.tsx @@ -4,10 +4,11 @@ import { tradingConfig } from "../config"; import { getExchangeDisplayName, resolveExchangeId } from "../exchanges/create-adapter"; import { buildAdapterFromEnv } from "../exchanges/resolve-from-env"; import { TrendEngine, type TrendEngineSnapshot } from "../strategy/trend-engine"; -import { formatNumber } from "../utils/format"; +import { formatNumber, formatTrendLabel } from "../utils/format"; import { DataTable, type TableColumn } from "./components/DataTable"; +import { t } from "../i18n"; -const READY_MESSAGE = "正在等待交易所推送数据…"; +const READY_MESSAGE = t("trend.readyMessage"); interface TrendAppProps { onExit: () => void; @@ -56,8 +57,8 @@ export function TrendApp({ onExit }: TrendAppProps) { if (error) { return ( - 启动失败: {error.message} - 请检查环境变量和网络连通性。 + {t("common.startFailed", { message: error.message })} + {t("common.checkEnv")} ); } @@ -65,7 +66,7 @@ export function TrendApp({ onExit }: TrendAppProps) { if (!snapshot) { return ( - 正在初始化趋势策略… + {t("common.initializing", { target: t("trend.name") })} ); } @@ -96,56 +97,77 @@ export function TrendApp({ onExit }: TrendAppProps) { return ( - Trend Strategy Dashboard + {t("trend.title")} - 交易所: {exchangeName} | 交易对: {snapshot.symbol} | 最近价格: {formatNumber(lastPrice, 2)} | SMA30: {formatNumber(sma30, 2)} | 趋势: {trend} + {t("trend.headerLine", { + exchange: exchangeName, + symbol: snapshot.symbol, + lastPrice: formatNumber(lastPrice, 2), + sma: formatNumber(sma30, 2), + trend: formatTrendLabel(trend), + })} + + + {t("trend.statusLine", { status: ready ? t("status.live") : READY_MESSAGE })} - 状态: {ready ? "实时运行" : READY_MESSAGE} | 按 Esc 返回策略选择 - 持仓 + {t("common.section.position")} {hasPosition ? ( <> - 方向: {position.positionAmt > 0 ? "多" : "空"} | 数量: {formatNumber(Math.abs(position.positionAmt), 4)} | 开仓价: {formatNumber(position.entryPrice, 2)} + {t("trend.positionLine", { + direction: position.positionAmt > 0 ? t("common.direction.long") : t("common.direction.short"), + qty: formatNumber(Math.abs(position.positionAmt), 4), + entry: formatNumber(position.entryPrice, 2), + })} - 浮动盈亏: {formatNumber(snapshot.pnl, 4)} USDT | 账户未实现盈亏: {formatNumber(snapshot.unrealized, 4)} USDT + {t("trend.pnlLine", { + pnl: formatNumber(snapshot.pnl, 4), + unrealized: formatNumber(snapshot.unrealized, 4), + })} ) : ( - 当前无持仓 + {t("common.noPosition")} )} - 绩效 + {t("common.section.performance")} - 累计交易次数: {snapshot.totalTrades} | 累计收益: {formatNumber(snapshot.totalProfit, 4)} USDT + {t("trend.performanceLine", { + trades: snapshot.totalTrades, + profit: formatNumber(snapshot.totalProfit, 4), + })} - 累计成交量: {formatNumber(sessionVolume, 2)} USDT + {t("trend.volumeLine", { volume: formatNumber(sessionVolume, 2) })} {snapshot.lastOpenSignal.side ? ( - 最近开仓信号: {snapshot.lastOpenSignal.side} @ {formatNumber(snapshot.lastOpenSignal.price, 2)} + {t("trend.lastSignal", { + side: snapshot.lastOpenSignal.side, + price: formatNumber(snapshot.lastOpenSignal.price, 2), + })} ) : null} - 当前挂单 + {t("common.section.orders")} {orderRows.length > 0 ? ( ) : ( - 暂无挂单 + {t("common.noOrders")} )} - 最近交易与事件 + {t("common.section.recentTrades")} {lastLogs.length > 0 ? ( lastLogs.map((item, index) => ( @@ -153,7 +175,7 @@ export function TrendApp({ onExit }: TrendAppProps) { )) ) : ( - 暂无日志 + {t("common.noLogs")} )} diff --git a/src/utils/format.ts b/src/utils/format.ts index d246225..0e9aead 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -1,8 +1,14 @@ +import { t } from "../i18n"; + +export type TrendLabel = "做多" | "做空" | "无信号"; + +export function formatTrendLabel(trend: TrendLabel): string { + if (trend === "做多") return t("trend.label.long"); + if (trend === "做空") return t("trend.label.short"); + return t("trend.label.none"); +} + export function formatNumber(value: number | null | undefined, digits = 4, fallback = "-"): string { if (value == null || Number.isNaN(value)) return fallback; return Number(value).toFixed(digits); } - -export function formatTrendLabel(trend: "做多" | "做空" | "无信号"): string { - return trend; -}