mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-10 08:48:07 +00:00
Implement internationalization support by adding translation functionality and updating UI components to use translated strings. Add language configuration in .env.example and integrate translations across various strategy and UI components.
This commit is contained in:
+16
-15
@@ -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() {
|
||||
<Box flexDirection="column" paddingX={1} paddingY={1}>
|
||||
<Text color="gray">{copyright.bannerText}</Text>
|
||||
{integrityOk ? null : (
|
||||
<Text color="red">警告: 版权校验失败,当前版本可能被篡改。</Text>
|
||||
<Text color="red">{t("app.integrity.warning")}</Text>
|
||||
)}
|
||||
<Box height={1}>
|
||||
<Text color="gray">────────────────────────────────────────────────────</Text>
|
||||
</Box>
|
||||
<Text color="cyanBright">请选择要运行的策略</Text>
|
||||
<Text color="gray">使用 ↑/↓ 选择,回车开始,Ctrl+C 退出。</Text>
|
||||
<Text color="cyanBright">{t("app.pickStrategy")}</Text>
|
||||
<Text color="gray">{t("app.pickHint")}</Text>
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
{strategies.map((strategy, index) => {
|
||||
const active = index === cursor;
|
||||
|
||||
+62
-34
@@ -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 (
|
||||
<Box flexDirection="column" padding={1}>
|
||||
<Text color="red">无法启动期现套利策略: {error.message}</Text>
|
||||
<Text color="gray">按 Esc 返回菜单。</Text>
|
||||
<Text color="red">{t("basis.startFailed", { message: error.message })}</Text>
|
||||
<Text color="gray">{t("common.backHint")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -66,7 +67,7 @@ export function BasisApp({ onExit }: BasisAppProps) {
|
||||
if (!snapshot) {
|
||||
return (
|
||||
<Box padding={1}>
|
||||
<Text>正在初始化期现套利监控…</Text>
|
||||
<Text>{t("basis.initializing")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="cyanBright">Basis Arbitrage Dashboard</Text>
|
||||
<Text color="cyanBright">{t("basis.title")}</Text>
|
||||
<Text>
|
||||
交易所: {exchangeName} | 期货合约: {snapshot.futuresSymbol} | 现货交易对: {snapshot.spotSymbol}
|
||||
{t("basis.headerLine", {
|
||||
exchange: exchangeName,
|
||||
futures: snapshot.futuresSymbol,
|
||||
spot: snapshot.spotSymbol,
|
||||
})}
|
||||
</Text>
|
||||
<Text color="gray">按 Esc 返回策略选择 | 数据状态: 期货({feedStatus.futures ? "OK" : "--"}) 现货({feedStatus.spot ? "OK" : "--"}) 资金费率({feedStatus.funding ? "OK" : "--"})</Text>
|
||||
<Text color="gray">最近更新时间: {lastUpdated}</Text>
|
||||
<Text color="gray">
|
||||
{t("basis.statusLine", {
|
||||
futuresStatus: feedStatus.futures ? "OK" : "--",
|
||||
spotStatus: feedStatus.spot ? "OK" : "--",
|
||||
fundingStatus: feedStatus.funding ? "OK" : "--",
|
||||
})}
|
||||
</Text>
|
||||
<Text color="gray">{t("basis.lastUpdated", { time: lastUpdated })}</Text>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="row" marginBottom={1}>
|
||||
<Box flexDirection="column" marginRight={4}>
|
||||
<Text color="greenBright">期货盘口</Text>
|
||||
<Text>买一: {futuresBid} | 卖一: {futuresAsk}</Text>
|
||||
<Text color="gray">更新时间: {futuresUpdated}</Text>
|
||||
<Text color="greenBright">{t("basis.section.futures")}</Text>
|
||||
<Text>{t("basis.bookLine", { bid: futuresBid, ask: futuresAsk })}</Text>
|
||||
<Text color="gray">{t("basis.updatedAt", { time: futuresUpdated })}</Text>
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text color="greenBright">现货盘口</Text>
|
||||
<Text>买一: {spotBid} | 卖一: {spotAsk}</Text>
|
||||
<Text color="gray">更新时间: {spotUpdated}</Text>
|
||||
<Text color="greenBright">{t("basis.section.spot")}</Text>
|
||||
<Text>{t("basis.bookLine", { bid: spotBid, ask: spotAsk })}</Text>
|
||||
<Text color="gray">{t("basis.updatedAt", { time: spotUpdated })}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="yellow">资金费率</Text>
|
||||
<Text>当前资金费率: {fundingRatePct}</Text>
|
||||
<Text color="gray">资金费率更新时间: {fundingUpdated} | 下次结算时间: {nextFundingTime}</Text>
|
||||
<Text>单次资金费率收益(估): {fundingIncomePerFunding} | 日收益(估): {fundingIncomePerDay}</Text>
|
||||
<Text>双边吃单手续费(估): {takerFeesPerRoundTrip} | 回本所需资金费率次数: {fundingCountToBreakeven}</Text>
|
||||
<Text color="yellow">{t("basis.section.funding")}</Text>
|
||||
<Text>{t("basis.fundingRate", { rate: fundingRatePct })}</Text>
|
||||
<Text color="gray">
|
||||
{t("basis.fundingTimes", { updated: fundingUpdated, next: nextFundingTime })}
|
||||
</Text>
|
||||
<Text>{t("basis.fundingIncome", { per: fundingIncomePerFunding, perDay: fundingIncomePerDay })}</Text>
|
||||
<Text>{t("basis.takerFees", { fees: takerFeesPerRoundTrip, count: fundingCountToBreakeven })}</Text>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="row" marginBottom={1}>
|
||||
<Box flexDirection="column" marginRight={4}>
|
||||
<Text color="cyan">现货账户余额(非0)</Text>
|
||||
<Text color="cyan">{t("basis.spotBalanceTitle")}</Text>
|
||||
{spotBalances.length ? (
|
||||
spotBalances.map((b) => (
|
||||
<Text key={`spot-${b.asset}`}>
|
||||
{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),
|
||||
})}
|
||||
</Text>
|
||||
))
|
||||
) : (
|
||||
<Text color="gray">无</Text>
|
||||
<Text color="gray">{t("basis.none")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text color="cyan">合约账户余额(非0)</Text>
|
||||
<Text color="cyan">{t("basis.futuresBalanceTitle")}</Text>
|
||||
{futuresBalances.length ? (
|
||||
futuresBalances.map((b) => (
|
||||
<Text key={`fut-${b.asset}`}>
|
||||
{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),
|
||||
})}
|
||||
</Text>
|
||||
))
|
||||
) : (
|
||||
<Text color="gray">无</Text>
|
||||
<Text color="gray">{t("basis.none")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color={snapshot.opportunity ? "greenBright" : "redBright"}>套利差价(卖期货 / 买现货)</Text>
|
||||
<Text color={snapshot.opportunity ? "green" : undefined}>毛价差: {spread} USDT | {spreadBps} bp</Text>
|
||||
<Text color={snapshot.opportunity ? "greenBright" : "redBright"}>{t("basis.spreadTitle")}</Text>
|
||||
<Text color={snapshot.opportunity ? "green" : undefined}>
|
||||
{t("basis.spreadLine", { spread, bps: spreadBps })}
|
||||
</Text>
|
||||
<Text color={snapshot.opportunity ? "green" : "red"}>
|
||||
扣除 taker 手续费 ({(basisConfig.takerFeeRate * 100).toFixed(4)}% × 双边): {netSpread} USDT | {netSpreadBps} bp
|
||||
{t("basis.netSpreadLine", { feePct, net: netSpread, netBps: netSpreadBps })}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column">
|
||||
<Text color="yellow">最近事件</Text>
|
||||
<Text color="yellow">{t("common.section.recent")}</Text>
|
||||
{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) {
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Text color="gray">暂无日志</Text>
|
||||
<Text color="gray">{t("common.noLogs")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
+69
-27
@@ -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 (
|
||||
<Box flexDirection="column" padding={1}>
|
||||
<Text color="red">启动失败: {error.message}</Text>
|
||||
<Text color="gray">请检查环境变量和网络连通性。</Text>
|
||||
<Text color="red">{t("common.startFailed", { message: error.message })}</Text>
|
||||
<Text color="gray">{t("common.checkEnv")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -68,17 +69,17 @@ export function GridApp({ onExit }: GridAppProps) {
|
||||
if (!snapshot) {
|
||||
return (
|
||||
<Box padding={1}>
|
||||
<Text>正在初始化网格策略…</Text>
|
||||
<Text>{t("grid.initializing")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="cyanBright">Grid Strategy Dashboard</Text>
|
||||
<Text color="cyanBright">{t("grid.title")}</Text>
|
||||
<Text>
|
||||
交易所: {exchangeName} | 交易对: {snapshot.symbol} | 状态: {snapshot.running ? "运行中" : "暂停"} | 方向: {snapshot.direction}
|
||||
{t("grid.headerLine", {
|
||||
exchange: exchangeName,
|
||||
symbol: snapshot.symbol,
|
||||
status: statusLabel,
|
||||
direction: directionLabel,
|
||||
})}
|
||||
</Text>
|
||||
<Text>
|
||||
实时价格: {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,
|
||||
})}
|
||||
</Text>
|
||||
<Text color="gray">数据状态:
|
||||
<Text color="gray">
|
||||
{t("grid.dataStatus")}
|
||||
{feedEntries.map((entry, index) => (
|
||||
<Text key={entry.key} color={feedStatus[entry.key] ? "green" : "red"}>
|
||||
{index === 0 ? " " : " "}
|
||||
{entry.label}
|
||||
</Text>
|
||||
))}
|
||||
| 按 Esc 返回策略选择
|
||||
{" | "}
|
||||
{t("common.backHint")}
|
||||
</Text>
|
||||
{stopReason ? <Text color="yellow">暂停原因: {stopReason}</Text> : null}
|
||||
{stopReason ? <Text color="yellow">{t("grid.stopReason", { reason: stopReason })}</Text> : null}
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="row" marginBottom={1}>
|
||||
<Box flexDirection="column" marginRight={4}>
|
||||
<Text color="greenBright">网格配置</Text>
|
||||
<Text color="greenBright">{t("grid.configTitle")}</Text>
|
||||
<Text>
|
||||
单笔数量: {formatNumber(gridConfig.orderSize, 6)} | 最大仓位: {formatNumber(gridConfig.maxPositionSize, 6)}
|
||||
{t("grid.configSize", {
|
||||
orderSize: formatNumber(gridConfig.orderSize, 6),
|
||||
maxPosition: formatNumber(gridConfig.maxPositionSize, 6),
|
||||
})}
|
||||
</Text>
|
||||
<Text>
|
||||
止损阈值: {(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"),
|
||||
})}
|
||||
</Text>
|
||||
<Text>
|
||||
刷新间隔: {gridConfig.refreshIntervalMs} ms
|
||||
{t("grid.refreshInterval", { interval: gridConfig.refreshIntervalMs })}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text color="greenBright">持仓</Text>
|
||||
<Text color="greenBright">{t("common.section.position")}</Text>
|
||||
{hasPosition ? (
|
||||
<>
|
||||
<Text>
|
||||
当前持仓: {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),
|
||||
})}
|
||||
</Text>
|
||||
<Text>
|
||||
未实现盈亏: {formatNumber(position.unrealizedProfit, 4)} | 标记价: {formatNumber(position.markPrice, 4)}
|
||||
{t("grid.unrealizedLine", {
|
||||
pnl: formatNumber(position.unrealizedProfit, 4),
|
||||
mark: formatNumber(position.markPrice, 4),
|
||||
})}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<Text color="gray">当前无持仓</Text>
|
||||
<Text color="gray">{t("common.noPosition")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="yellow">网格线</Text>
|
||||
{gridRows.length > 0 ? <DataTable columns={gridColumns} rows={gridRows} /> : <Text color="gray">暂无网格线</Text>}
|
||||
<Text color="yellow">{t("grid.linesTitle")}</Text>
|
||||
{gridRows.length > 0 ? (
|
||||
<DataTable columns={gridColumns} rows={gridRows} />
|
||||
) : (
|
||||
<Text color="gray">{t("grid.noLines")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="yellow">目标挂单</Text>
|
||||
{desiredRows.length > 0 ? <DataTable columns={desiredColumns} rows={desiredRows} /> : <Text color="gray">暂无目标挂单</Text>}
|
||||
<Text color="yellow">{t("maker.targetOrders")}</Text>
|
||||
{desiredRows.length > 0 ? (
|
||||
<DataTable columns={desiredColumns} rows={desiredRows} />
|
||||
) : (
|
||||
<Text color="gray">{t("maker.noTargetOrders")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column">
|
||||
<Text color="yellow">最近事件</Text>
|
||||
<Text color="yellow">{t("common.section.recent")}</Text>
|
||||
{lastLogs.length > 0 ? (
|
||||
lastLogs.map((item, index) => (
|
||||
<Text key={`${item.time}-${index}`}>
|
||||
@@ -184,7 +226,7 @@ export function GridApp({ onExit }: GridAppProps) {
|
||||
</Text>
|
||||
))
|
||||
) : (
|
||||
<Text color="gray">暂无日志</Text>
|
||||
<Text color="gray">{t("common.noLogs")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
+39
-16
@@ -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 (
|
||||
<Box flexDirection="column" padding={1}>
|
||||
<Text color="red">Guardian 策略启动失败: {error.message}</Text>
|
||||
<Text color="gray">请检查环境变量和网络连通性。</Text>
|
||||
<Text color="red">{t("guardian.startFailed", { message: error.message })}</Text>
|
||||
<Text color="gray">{t("common.checkEnv")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -64,7 +65,7 @@ export function GuardianApp({ onExit }: GuardianAppProps) {
|
||||
if (!snapshot) {
|
||||
return (
|
||||
<Box padding={1}>
|
||||
<Text>正在初始化 Guardian 策略…</Text>
|
||||
<Text>{t("guardian.initializing")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -97,43 +98,65 @@ export function GuardianApp({ onExit }: GuardianAppProps) {
|
||||
return (
|
||||
<Box flexDirection="column" paddingX={1} paddingY={0}>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="cyanBright">Guardian Strategy Dashboard</Text>
|
||||
<Text color="cyanBright">{t("guardian.title")}</Text>
|
||||
<Text>
|
||||
交易所: {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,
|
||||
})}
|
||||
</Text>
|
||||
<Text color="gray">策略只会维护止损/止盈,不会主动开仓。按 Esc 返回菜单。</Text>
|
||||
<Text color="gray">{t("guardian.hint")}</Text>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="greenBright">当前仓位与风控</Text>
|
||||
<Text color="greenBright">{t("guardian.positionTitle")}</Text>
|
||||
{hasPosition ? (
|
||||
<>
|
||||
<Text>
|
||||
方向: {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),
|
||||
})}
|
||||
</Text>
|
||||
<Text>
|
||||
目标止损价: {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),
|
||||
})}
|
||||
</Text>
|
||||
<Text color={snapshot.requiresStop ? "yellow" : "gray"}>
|
||||
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"),
|
||||
})}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<Text color="gray">当前无持仓,Guardian 正在监听新的仓位变化。</Text>
|
||||
<Text color="gray">{t("guardian.noPosition")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="yellow">当前挂单</Text>
|
||||
<Text color="yellow">{t("common.section.orders")}</Text>
|
||||
{orderRows.length > 0 ? (
|
||||
<DataTable columns={orderColumns} rows={orderRows} />
|
||||
) : (
|
||||
<Text color="gray">暂无保护类挂单</Text>
|
||||
<Text color="gray">{t("guardian.noProtectiveOrders")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column">
|
||||
<Text color="yellow">最近事件</Text>
|
||||
<Text color="yellow">{t("common.section.recent")}</Text>
|
||||
{lastLogs.length > 0 ? (
|
||||
lastLogs.map((item, index) => (
|
||||
<Text key={`${item.time}-${index}`}>
|
||||
@@ -141,7 +164,7 @@ export function GuardianApp({ onExit }: GuardianAppProps) {
|
||||
</Text>
|
||||
))
|
||||
) : (
|
||||
<Text color="gray">暂无日志</Text>
|
||||
<Text color="gray">{t("common.noLogs")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
+38
-22
@@ -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 (
|
||||
<Box flexDirection="column" padding={1}>
|
||||
<Text color="red">启动失败: {error.message}</Text>
|
||||
<Text color="gray">请检查环境变量和网络连通性。</Text>
|
||||
<Text color="red">{t("common.startFailed", { message: error.message })}</Text>
|
||||
<Text color="gray">{t("common.checkEnv")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -63,7 +64,7 @@ export function MakerApp({ onExit }: MakerAppProps) {
|
||||
if (!snapshot) {
|
||||
return (
|
||||
<Box padding={1}>
|
||||
<Text>正在初始化做市策略…</Text>
|
||||
<Text>{t("maker.initializing")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="cyanBright">Maker Strategy Dashboard</Text>
|
||||
<Text color="cyanBright">{t("maker.title")}</Text>
|
||||
<Text>
|
||||
交易所: {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,
|
||||
})}
|
||||
</Text>
|
||||
<Text color="gray">状态: {snapshot.ready ? "实时运行" : "等待市场数据"} | 按 Esc 返回策略选择</Text>
|
||||
<Text color="gray">{t("trend.statusLine", { status: readyStatus })}</Text>
|
||||
<Text>
|
||||
数据状态:
|
||||
{t("maker.dataStatus")}
|
||||
{feedEntries.map((entry, index) => (
|
||||
<Text key={entry.key} color={feedStatus[entry.key] ? "green" : "red"}>
|
||||
{index === 0 ? " " : " "}
|
||||
@@ -140,44 +148,52 @@ export function MakerApp({ onExit }: MakerAppProps) {
|
||||
|
||||
<Box flexDirection="row" marginBottom={1}>
|
||||
<Box flexDirection="column" marginRight={4}>
|
||||
<Text color="greenBright">持仓</Text>
|
||||
<Text color="greenBright">{t("common.section.position")}</Text>
|
||||
{hasPosition ? (
|
||||
<>
|
||||
<Text>
|
||||
方向: {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),
|
||||
})}
|
||||
</Text>
|
||||
<Text>
|
||||
浮动盈亏: {formatNumber(snapshot.pnl, 4)} USDT | 账户未实现盈亏: {formatNumber(snapshot.accountUnrealized, 4)} USDT
|
||||
{t("maker.pnlLine", {
|
||||
pnl: formatNumber(snapshot.pnl, 4),
|
||||
accountPnl: formatNumber(snapshot.accountUnrealized, 4),
|
||||
})}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<Text color="gray">当前无持仓</Text>
|
||||
<Text color="gray">{t("common.noPosition")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text color="greenBright">目标挂单</Text>
|
||||
<Text color="greenBright">{t("maker.targetOrders")}</Text>
|
||||
{desiredRows.length > 0 ? (
|
||||
<DataTable columns={desiredColumns} rows={desiredRows} />
|
||||
) : (
|
||||
<Text color="gray">暂无目标挂单</Text>
|
||||
<Text color="gray">{t("maker.noTargetOrders")}</Text>
|
||||
)}
|
||||
<Text>
|
||||
累计成交量: {formatNumber(snapshot.sessionVolume, 2)} USDT
|
||||
{t("trend.volumeLine", { volume: formatNumber(snapshot.sessionVolume, 2) })}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="yellow">当前挂单</Text>
|
||||
<Text color="yellow">{t("common.section.orders")}</Text>
|
||||
{openOrderRows.length > 0 ? (
|
||||
<DataTable columns={openOrderColumns} rows={openOrderRows} />
|
||||
) : (
|
||||
<Text color="gray">暂无挂单</Text>
|
||||
<Text color="gray">{t("common.noOrders")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column">
|
||||
<Text color="yellow">最近事件</Text>
|
||||
<Text color="yellow">{t("common.section.recent")}</Text>
|
||||
{lastLogs.length > 0 ? (
|
||||
lastLogs.map((item, index) => (
|
||||
<Text key={`${item.time}-${index}`}>
|
||||
@@ -185,7 +201,7 @@ export function MakerApp({ onExit }: MakerAppProps) {
|
||||
</Text>
|
||||
))
|
||||
) : (
|
||||
<Text color="gray">暂无日志</Text>
|
||||
<Text color="gray">{t("common.noLogs")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
+48
-24
@@ -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 (
|
||||
<Box flexDirection="column" padding={1}>
|
||||
<Text color="red">启动失败: {error.message}</Text>
|
||||
<Text color="gray">请检查环境变量和网络连通性。</Text>
|
||||
<Text color="red">{t("common.startFailed", { message: error.message })}</Text>
|
||||
<Text color="gray">{t("common.checkEnv")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -63,7 +64,7 @@ export function OffsetMakerApp({ onExit }: OffsetMakerAppProps) {
|
||||
if (!snapshot) {
|
||||
return (
|
||||
<Box padding={1}>
|
||||
<Text>正在初始化偏移做市策略…</Text>
|
||||
<Text>{t("offset.initializing")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="cyanBright">Offset Maker Strategy Dashboard</Text>
|
||||
<Text color="cyanBright">{t("offset.title")}</Text>
|
||||
<Text>
|
||||
交易所: {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,
|
||||
})}
|
||||
</Text>
|
||||
<Text>
|
||||
买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,
|
||||
})}
|
||||
</Text>
|
||||
<Text color="gray">
|
||||
当前挂单策略: 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"),
|
||||
})}
|
||||
</Text>
|
||||
<Text color="gray">状态: {snapshot.ready ? "实时运行" : "等待市场数据"}</Text>
|
||||
<Text color="gray">{t("trend.statusLine", { status: readyStatus })}</Text>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="row" marginBottom={1}>
|
||||
<Box flexDirection="column" marginRight={4}>
|
||||
<Text color="greenBright">持仓</Text>
|
||||
<Text color="greenBright">{t("common.section.position")}</Text>
|
||||
{hasPosition ? (
|
||||
<>
|
||||
<Text>
|
||||
方向: {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),
|
||||
})}
|
||||
</Text>
|
||||
<Text>
|
||||
浮动盈亏: {formatNumber(snapshot.pnl, 4)} USDT | 账户未实现盈亏: {formatNumber(snapshot.accountUnrealized, 4)} USDT
|
||||
{t("maker.pnlLine", {
|
||||
pnl: formatNumber(snapshot.pnl, 4),
|
||||
accountPnl: formatNumber(snapshot.accountUnrealized, 4),
|
||||
})}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<Text color="gray">当前无持仓</Text>
|
||||
<Text color="gray">{t("common.noPosition")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text color="greenBright">目标挂单</Text>
|
||||
<Text color="greenBright">{t("maker.targetOrders")}</Text>
|
||||
{desiredRows.length > 0 ? (
|
||||
<DataTable columns={desiredColumns} rows={desiredRows} />
|
||||
) : (
|
||||
<Text color="gray">暂无目标挂单</Text>
|
||||
<Text color="gray">{t("maker.noTargetOrders")}</Text>
|
||||
)}
|
||||
<Text>
|
||||
累计成交量: {formatNumber(snapshot.sessionVolume, 2)} USDT
|
||||
{t("trend.volumeLine", { volume: formatNumber(snapshot.sessionVolume, 2) })}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="yellow">当前挂单</Text>
|
||||
<Text color="yellow">{t("common.section.orders")}</Text>
|
||||
{openOrderRows.length > 0 ? (
|
||||
<DataTable columns={openOrderColumns} rows={openOrderRows} />
|
||||
) : (
|
||||
<Text color="gray">暂无挂单</Text>
|
||||
<Text color="gray">{t("common.noOrders")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column">
|
||||
<Text color="yellow">最近事件</Text>
|
||||
<Text color="yellow">{t("common.section.recent")}</Text>
|
||||
{lastLogs.length > 0 ? (
|
||||
lastLogs.map((item, index) => (
|
||||
<Text key={`${item.time}-${index}`}>
|
||||
@@ -182,7 +206,7 @@ export function OffsetMakerApp({ onExit }: OffsetMakerAppProps) {
|
||||
</Text>
|
||||
))
|
||||
) : (
|
||||
<Text color="gray">暂无日志</Text>
|
||||
<Text color="gray">{t("common.noLogs")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
+42
-20
@@ -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 (
|
||||
<Box flexDirection="column" padding={1}>
|
||||
<Text color="red">启动失败: {error.message}</Text>
|
||||
<Text color="gray">请检查环境变量和网络连通性。</Text>
|
||||
<Text color="red">{t("common.startFailed", { message: error.message })}</Text>
|
||||
<Text color="gray">{t("common.checkEnv")}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -65,7 +66,7 @@ export function TrendApp({ onExit }: TrendAppProps) {
|
||||
if (!snapshot) {
|
||||
return (
|
||||
<Box padding={1}>
|
||||
<Text>正在初始化趋势策略…</Text>
|
||||
<Text>{t("common.initializing", { target: t("trend.name") })}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -96,56 +97,77 @@ export function TrendApp({ onExit }: TrendAppProps) {
|
||||
return (
|
||||
<Box flexDirection="column" paddingX={1} paddingY={0}>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="cyanBright">Trend Strategy Dashboard</Text>
|
||||
<Text color="cyanBright">{t("trend.title")}</Text>
|
||||
<Text>
|
||||
交易所: {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),
|
||||
})}
|
||||
</Text>
|
||||
<Text color="gray">
|
||||
{t("trend.statusLine", { status: ready ? t("status.live") : READY_MESSAGE })}
|
||||
</Text>
|
||||
<Text color="gray">状态: {ready ? "实时运行" : READY_MESSAGE} | 按 Esc 返回策略选择</Text>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="row" marginBottom={1}>
|
||||
<Box flexDirection="column" marginRight={4}>
|
||||
<Text color="greenBright">持仓</Text>
|
||||
<Text color="greenBright">{t("common.section.position")}</Text>
|
||||
{hasPosition ? (
|
||||
<>
|
||||
<Text>
|
||||
方向: {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),
|
||||
})}
|
||||
</Text>
|
||||
<Text>
|
||||
浮动盈亏: {formatNumber(snapshot.pnl, 4)} USDT | 账户未实现盈亏: {formatNumber(snapshot.unrealized, 4)} USDT
|
||||
{t("trend.pnlLine", {
|
||||
pnl: formatNumber(snapshot.pnl, 4),
|
||||
unrealized: formatNumber(snapshot.unrealized, 4),
|
||||
})}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<Text color="gray">当前无持仓</Text>
|
||||
<Text color="gray">{t("common.noPosition")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text color="greenBright">绩效</Text>
|
||||
<Text color="greenBright">{t("common.section.performance")}</Text>
|
||||
<Text>
|
||||
累计交易次数: {snapshot.totalTrades} | 累计收益: {formatNumber(snapshot.totalProfit, 4)} USDT
|
||||
{t("trend.performanceLine", {
|
||||
trades: snapshot.totalTrades,
|
||||
profit: formatNumber(snapshot.totalProfit, 4),
|
||||
})}
|
||||
</Text>
|
||||
<Text>
|
||||
累计成交量: {formatNumber(sessionVolume, 2)} USDT
|
||||
{t("trend.volumeLine", { volume: formatNumber(sessionVolume, 2) })}
|
||||
</Text>
|
||||
{snapshot.lastOpenSignal.side ? (
|
||||
<Text color="gray">
|
||||
最近开仓信号: {snapshot.lastOpenSignal.side} @ {formatNumber(snapshot.lastOpenSignal.price, 2)}
|
||||
{t("trend.lastSignal", {
|
||||
side: snapshot.lastOpenSignal.side,
|
||||
price: formatNumber(snapshot.lastOpenSignal.price, 2),
|
||||
})}
|
||||
</Text>
|
||||
) : null}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="yellow">当前挂单</Text>
|
||||
<Text color="yellow">{t("common.section.orders")}</Text>
|
||||
{orderRows.length > 0 ? (
|
||||
<DataTable columns={orderColumns} rows={orderRows} />
|
||||
) : (
|
||||
<Text color="gray">暂无挂单</Text>
|
||||
<Text color="gray">{t("common.noOrders")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box flexDirection="column">
|
||||
<Text color="yellow">最近交易与事件</Text>
|
||||
<Text color="yellow">{t("common.section.recentTrades")}</Text>
|
||||
{lastLogs.length > 0 ? (
|
||||
lastLogs.map((item, index) => (
|
||||
<Text key={`${item.time}-${index}`}>
|
||||
@@ -153,7 +175,7 @@ export function TrendApp({ onExit }: TrendAppProps) {
|
||||
</Text>
|
||||
))
|
||||
) : (
|
||||
<Text color="gray">暂无日志</Text>
|
||||
<Text color="gray">{t("common.noLogs")}</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user