diff --git a/.gitignore b/.gitignore index 151e3b49..629f8a8e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,3 @@ config.json # 决策日志 decision_logs/ coin_pool_cache/ -nofx-auto diff --git a/web/src/App.tsx b/web/src/App.tsx index f31dd891..0ee53253 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -4,6 +4,8 @@ import { api } from './lib/api'; import { EquityChart } from './components/EquityChart'; import { CompetitionPage } from './components/CompetitionPage'; import AILearning from './components/AILearning'; +import { LanguageProvider, useLanguage } from './contexts/LanguageContext'; +import { t, type Language } from './i18n/translations'; import type { SystemStatus, AccountInfo, @@ -16,6 +18,7 @@ import type { type Page = 'competition' | 'trader'; function App() { + const { language, setLanguage } = useLanguage(); const [currentPage, setCurrentPage] = useState('competition'); const [selectedTraderId, setSelectedTraderId] = useState(); const [lastUpdate, setLastUpdate] = useState('--:--:--'); @@ -106,14 +109,62 @@ function App() {

- AI Trading Competition + {t('appTitle', language)}

- Qwen vs DeepSeek · Real-time + {t('subtitle', language)}

+ {/* GitHub Link */} + { + e.currentTarget.style.background = '#2B3139'; + e.currentTarget.style.color = '#EAECEF'; + e.currentTarget.style.borderColor = '#F0B90B'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.background = '#1E2329'; + e.currentTarget.style.color = '#848E9C'; + e.currentTarget.style.borderColor = '#2B3139'; + }} + > + + + + GitHub + + + {/* Language Toggle */} +
+ + +
+ {/* Page Toggle */}
@@ -170,7 +221,7 @@ function App() { style={{ background: status.is_running ? '#0ECB81' : '#F6465D' }} /> - {status.is_running ? 'RUNNING' : 'STOPPED'} + {t(status.is_running ? 'running' : 'stopped', language)}
)} @@ -192,6 +243,7 @@ function App() { decisions={decisions} stats={stats} lastUpdate={lastUpdate} + language={language} /> )} @@ -199,8 +251,32 @@ function App() { {/* Footer */} @@ -216,6 +292,7 @@ function TraderDetailsPage({ decisions, stats, lastUpdate, + language, }: { selectedTrader?: TraderInfo; status?: SystemStatus; @@ -224,6 +301,7 @@ function TraderDetailsPage({ decisions?: DecisionRecord[]; stats?: Statistics; lastUpdate: string; + language: Language; }) { if (!selectedTrader) { return ( @@ -290,26 +368,26 @@ function TraderDetailsPage({ {/* Account Overview */}
0 : false} /> = 0 ? '+' : ''}${account?.total_pnl.toFixed(2) || '0.00'} USDT`} change={account?.total_pnl_pct || 0} positive={account ? account.total_pnl >= 0 : false} />
@@ -326,11 +404,11 @@ function TraderDetailsPage({

- 📈 Current Positions + 📈 {t('currentPositions', language)}

{positions && positions.length > 0 && (
- {positions.length} Active + {positions.length} {t('active', language)}
)}
@@ -339,15 +417,15 @@ function TraderDetailsPage({ - - - - - - - - - + + + + + + + + + @@ -362,7 +440,7 @@ function TraderDetailsPage({ : { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' } } > - {pos.side.toUpperCase()} + {t(pos.side === 'long' ? 'long' : 'short', language)} @@ -391,8 +469,8 @@ function TraderDetailsPage({ ) : (
📊
-
无持仓
-
当前没有活跃的交易持仓
+
{t('noPositions', language)}
+
{t('noActivePositions', language)}
)} @@ -410,10 +488,10 @@ function TraderDetailsPage({ 🧠
-

Recent Decisions

+

{t('recentDecisions', language)}

{decisions && decisions.length > 0 && (
- Last {decisions.length} trading cycles + {t('lastCycles', language, { count: decisions.length })}
)}
@@ -423,13 +501,13 @@ function TraderDetailsPage({
{decisions && decisions.length > 0 ? ( decisions.map((decision, i) => ( - + )) ) : (
🧠
-
No Decisions Yet
-
AI trading decisions will appear here
+
{t('noDecisionsYet', language)}
+
{t('aiDecisionsWillAppear', language)}
)}
@@ -480,7 +558,7 @@ function StatCard({ } // Decision Card Component with CoT Trace - Binance Style -function DecisionCard({ decision }: { decision: DecisionRecord }) { +function DecisionCard({ decision, language }: { decision: DecisionRecord; language: Language }) { const [showCoT, setShowCoT] = useState(false); return ( @@ -488,7 +566,7 @@ function DecisionCard({ decision }: { decision: DecisionRecord }) { {/* Header */}
-
Cycle #{decision.cycle_number}
+
{t('cycle', language)} #{decision.cycle_number}
{new Date(decision.timestamp).toLocaleString()}
@@ -500,7 +578,7 @@ function DecisionCard({ decision }: { decision: DecisionRecord }) { : { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' } } > - {decision.success ? 'Success' : 'Failed'} + {t(decision.success ? 'success' : 'failed', language)}
@@ -512,8 +590,8 @@ function DecisionCard({ decision }: { decision: DecisionRecord }) { className="flex items-center gap-2 text-sm transition-colors" style={{ color: '#F0B90B' }} > - 💭 AI思维链分析 - {showCoT ? '▼ 收起' : '▶ 展开'} + {t('aiThinking', language)} + {showCoT ? t('collapse', language) : t('expand', language)} {showCoT && (
@@ -586,4 +664,11 @@ function DecisionCard({ decision }: { decision: DecisionRecord }) { ); } -export default App; +// Wrap App with LanguageProvider +export default function AppWithLanguage() { + return ( + + + + ); +} diff --git a/web/src/components/AILearning.tsx b/web/src/components/AILearning.tsx index de21e07f..fd9d614f 100644 --- a/web/src/components/AILearning.tsx +++ b/web/src/components/AILearning.tsx @@ -1,5 +1,7 @@ import { useEffect, useState } from 'react'; import useSWR from 'swr'; +import { useLanguage } from '../contexts/LanguageContext'; +import { t } from '../i18n/translations'; interface TradeOutcome { symbol: string; @@ -52,6 +54,7 @@ interface DecisionRecord { const fetcher = (url: string) => fetch(url).then(res => res.json()); export default function AILearning({ traderId }: AILearningProps) { + const { language } = useLanguage(); const { data: performance, error } = useSWR( `http://localhost:8080/api/performance?trader_id=${traderId}`, fetcher, @@ -68,7 +71,7 @@ export default function AILearning({ traderId }: AILearningProps) { if (error) { return (
-
⚠️ 加载AI学习数据失败
+
{t('loadingError', language)}
); } @@ -76,7 +79,7 @@ export default function AILearning({ traderId }: AILearningProps) { if (!performance) { return (
-
📊 加载中...
+
📊 {t('loading', language)}
); } @@ -86,10 +89,10 @@ export default function AILearning({ traderId }: AILearningProps) {
🧠 -

AI 学习分析

+

{t('aiLearning', language)}

- 暂无完整交易数据(需要完成开仓→平仓的完整周期) + {t('noCompleteData', language)}
); @@ -115,9 +118,9 @@ export default function AILearning({ traderId }: AILearningProps) { 🧠
-

AI Learning & Reflection

+

{t('aiLearning', language)}

- {performance.total_trades} trades analyzed · Real-time evolution + {t('tradesAnalyzed', language, { count: performance.total_trades })}

@@ -150,10 +153,10 @@ export default function AILearning({ traderId }: AILearningProps) {

- Latest Reflection + {t('latestReflection', language)}

- Cycle #{latestDecisions[0].cycle_number} · {new Date(latestDecisions[0].timestamp).toLocaleTimeString()} + {t('cycle', language)} #{latestDecisions[0].cycle_number} · {new Date(latestDecisions[0].timestamp).toLocaleTimeString()}

@@ -171,7 +174,7 @@ export default function AILearning({ traderId }: AILearningProps) { {latestDecisions[0].cot_trace && (
- 📋 Full Chain of Thought + {t('fullCoT', language)}
-
Total Trades
+
{t('totalTrades', language)}
{performance.total_trades}
@@ -209,7 +212,7 @@ export default function AILearning({ traderId }: AILearningProps) { border: `1px solid ${(performance.win_rate || 0) >= 50 ? 'rgba(14, 203, 129, 0.3)' : 'rgba(246, 70, 93, 0.3)'}`, boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)' }}> -
Win Rate
+
{t('winRate', language)}
= 50 ? '#10B981' : '#F87171' }}> @@ -226,7 +229,7 @@ export default function AILearning({ traderId }: AILearningProps) { border: '1px solid rgba(14, 203, 129, 0.2)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)' }}> -
Avg Win
+
{t('avgWin', language)}
+{(performance.avg_win || 0).toFixed(2)}%
@@ -238,7 +241,7 @@ export default function AILearning({ traderId }: AILearningProps) { border: '1px solid rgba(246, 70, 93, 0.2)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)' }}> -
Avg Loss
+
{t('avgLoss', language)}
{(performance.avg_loss || 0).toFixed(2)}%
@@ -258,9 +261,9 @@ export default function AILearning({ traderId }: AILearningProps) {
-
Profit Factor
+
{t('profitFactor', language)}
- Avg Win ÷ Avg Loss + {t('avgWinDivLoss', language)}
= 2.0 ? '#10B981' : (performance.profit_factor || 0) >= 1.5 ? '#F0B90B' : '#94A3B8' }}> - {(performance.profit_factor || 0) >= 2.0 && '🔥 Excellent - Strong profitability'} - {(performance.profit_factor || 0) >= 1.5 && (performance.profit_factor || 0) < 2.0 && '✓ Good - Stable profits'} - {(performance.profit_factor || 0) >= 1.0 && (performance.profit_factor || 0) < 1.5 && '⚠️ Fair - Needs optimization'} - {(performance.profit_factor || 0) > 0 && (performance.profit_factor || 0) < 1.0 && '❌ Poor - Losses exceed gains'} + {(performance.profit_factor || 0) >= 2.0 && t('excellent', language)} + {(performance.profit_factor || 0) >= 1.5 && (performance.profit_factor || 0) < 2.0 && t('good', language)} + {(performance.profit_factor || 0) >= 1.0 && (performance.profit_factor || 0) < 1.5 && t('fair', language)} + {(performance.profit_factor || 0) > 0 && (performance.profit_factor || 0) < 1.0 && t('poor', language)}
@@ -298,7 +301,7 @@ export default function AILearning({ traderId }: AILearningProps) { }}>
🏆 - Best Performer + {t('bestPerformer', language)}
{performance.best_symbol} @@ -306,7 +309,7 @@ export default function AILearning({ traderId }: AILearningProps) { {symbolStats[performance.best_symbol] && (
{symbolStats[performance.best_symbol].total_pn_l > 0 ? '+' : ''} - {symbolStats[performance.best_symbol].total_pn_l.toFixed(2)}% P&L + {symbolStats[performance.best_symbol].total_pn_l.toFixed(2)}% {t('pnl', language)}
)}
@@ -320,7 +323,7 @@ export default function AILearning({ traderId }: AILearningProps) { }}>
📉 - Worst Performer + {t('worstPerformer', language)}
{performance.worst_symbol} @@ -328,7 +331,7 @@ export default function AILearning({ traderId }: AILearningProps) { {symbolStats[performance.worst_symbol] && (
{symbolStats[performance.worst_symbol].total_pn_l > 0 ? '+' : ''} - {symbolStats[performance.worst_symbol].total_pn_l.toFixed(2)}% P&L + {symbolStats[performance.worst_symbol].total_pn_l.toFixed(2)}% {t('pnl', language)}
)}
@@ -345,7 +348,7 @@ export default function AILearning({ traderId }: AILearningProps) { }}>

- 📊 Symbol Performance + {t('symbolPerformance', language)}

@@ -411,11 +414,11 @@ export default function AILearning({ traderId }: AILearningProps) {
📜
-

Trade History

+

{t('tradeHistory', language)}

{performance?.recent_trades && performance.recent_trades.length > 0 - ? `Recent ${performance.recent_trades.length} completed trades` - : 'Completed trades will appear here'} + ? t('completedTrades', language, { count: performance.recent_trades.length }) + : t('completedTradesWillAppear', language)}

@@ -459,7 +462,7 @@ export default function AILearning({ traderId }: AILearningProps) { background: 'rgba(240, 185, 11, 0.2)', color: '#FCD34D' }}> - Latest + {t('latest', language)} )}
@@ -473,13 +476,13 @@ export default function AILearning({ traderId }: AILearningProps) { {/* 价格信息 */}
-
Entry
+
{t('entry', language)}
{trade.open_price.toFixed(4)}
-
Exit
+
{t('exit', language)}
{trade.close_price.toFixed(4)}
@@ -508,7 +511,7 @@ export default function AILearning({ traderId }: AILearningProps) { background: 'rgba(248, 113, 113, 0.2)', color: '#FCA5A5' }}> - Stop Loss + {t('stopLoss', language)} )}
@@ -531,7 +534,7 @@ export default function AILearning({ traderId }: AILearningProps) { ) : (
📜
-
No completed trades yet
+
{t('noCompletedTrades', language)}
)}
@@ -556,23 +559,23 @@ export default function AILearning({ traderId }: AILearningProps) { 💡
-

How AI Learns & Evolves

+

{t('howAILearns', language)}

- Analyzes last 20 trading cycles before each decision + {t('aiLearningPoint1', language)}
- Identifies best & worst performing symbols + {t('aiLearningPoint2', language)}
- Optimizes position sizing based on win rate + {t('aiLearningPoint3', language)}
- Avoids repeating past mistakes + {t('aiLearningPoint4', language)}
diff --git a/web/src/components/CompetitionPage.tsx b/web/src/components/CompetitionPage.tsx index 87db5bd0..a72f6dfb 100644 --- a/web/src/components/CompetitionPage.tsx +++ b/web/src/components/CompetitionPage.tsx @@ -2,8 +2,11 @@ import useSWR from 'swr'; import { api } from '../lib/api'; import type { CompetitionData } from '../types'; import { ComparisonChart } from './ComparisonChart'; +import { useLanguage } from '../contexts/LanguageContext'; +import { t } from '../i18n/translations'; export function CompetitionPage() { + const { language } = useLanguage(); const { data: competition } = useSWR( 'competition', api.getCompetition, @@ -57,18 +60,18 @@ export function CompetitionPage() {

- AI Competition + {t('aiCompetition', language)} - {competition.count} traders + {competition.count} {t('traders', language)}

- Qwen vs DeepSeek · Live Battle + {t('liveBattle', language)}

-
🥇 Leader
+
{t('leader', language)}
{leader?.trader_name}
= 0 ? '#0ECB81' : '#F6465D' }}> {leader.total_pnl >= 0 ? '+' : ''}{leader.total_pnl_pct.toFixed(2)}% @@ -82,10 +85,10 @@ export function CompetitionPage() {

- 📈 Performance Comparison + {t('performanceComparison', language)}

- Real-time PnL % + {t('realTimePnL', language)}
@@ -95,10 +98,10 @@ export function CompetitionPage() {

- 🥇 Leaderboard + {t('leaderboard', language)}

- LIVE + {t('live', language)}
@@ -134,7 +137,7 @@ export function CompetitionPage() {
{/* Total Equity */}
-
Equity
+
{t('equity', language)}
{trader.total_equity.toFixed(2)}
@@ -142,7 +145,7 @@ export function CompetitionPage() { {/* P&L */}
-
P&L
+
{t('pnl', language)}
= 0 ? '#0ECB81' : '#F6465D' }} @@ -157,7 +160,7 @@ export function CompetitionPage() { {/* Positions */}
-
Pos
+
{t('pos', language)}
{trader.position_count}
@@ -191,7 +194,7 @@ export function CompetitionPage() { {competition.traders.length === 2 && (

- ⚔️ Head-to-Head Battle + {t('headToHead', language)}

{sortedTraders.map((trader, index) => { @@ -228,12 +231,12 @@ export function CompetitionPage() {
{isWinning && gap > 0 && (
- Leading by {gap.toFixed(2)}% + {t('leadingBy', language, { gap: gap.toFixed(2) })}
)} {!isWinning && gap < 0 && (
- Behind by {Math.abs(gap).toFixed(2)}% + {t('behindBy', language, { gap: Math.abs(gap).toFixed(2) })}
)}
diff --git a/web/src/components/EquityChart.tsx b/web/src/components/EquityChart.tsx index acdcbdab..505069b9 100644 --- a/web/src/components/EquityChart.tsx +++ b/web/src/components/EquityChart.tsx @@ -11,6 +11,8 @@ import { } from 'recharts'; import useSWR from 'swr'; import { api } from '../lib/api'; +import { useLanguage } from '../contexts/LanguageContext'; +import { t } from '../i18n/translations'; interface EquityPoint { timestamp: string; @@ -25,6 +27,7 @@ interface EquityChartProps { } export function EquityChart({ traderId }: EquityChartProps) { + const { language } = useLanguage(); const [displayMode, setDisplayMode] = useState<'dollar' | 'percent'>('dollar'); const { data: history, error } = useSWR( @@ -49,7 +52,7 @@ export function EquityChart({ traderId }: EquityChartProps) {
⚠️
-
加载失败
+
{t('loadingError', language)}
{error.message}
@@ -60,11 +63,11 @@ export function EquityChart({ traderId }: EquityChartProps) { if (!history || history.length === 0) { return (
-

账户净值曲线

+

{t('accountEquityCurve', language)}

📊
-
暂无历史数据
-
运行几个周期后将显示收益率曲线
+
{t('noHistoricalData', language)}
+
{t('dataWillAppear', language)}
); @@ -153,7 +156,7 @@ export function EquityChart({ traderId }: EquityChartProps) { {/* Header */}
-

账户净值曲线

+

{t('accountEquityCurve', language)}

{account?.total_equity.toFixed(2) || '0.00'} @@ -239,7 +242,7 @@ export function EquityChart({ traderId }: EquityChartProps) { stroke="#474D57" strokeDasharray="3 3" label={{ - value: displayMode === 'dollar' ? '初始' : '0%', + value: displayMode === 'dollar' ? t('initialBalance', language).split(' ')[0] : '0%', fill: '#848E9C', fontSize: 12, }} @@ -259,27 +262,27 @@ export function EquityChart({ traderId }: EquityChartProps) { {/* Footer Stats */}
-
初始余额
+
{t('initialBalance', language)}
{initialBalance.toFixed(2)} USDT
-
当前净值
+
{t('currentEquity', language)}
{currentValue.raw_equity.toFixed(2)} USDT
-
历史周期
-
{history.length} 个
+
{t('historicalCycles', language)}
+
{history.length} {t('cycles', language)}
-
显示范围
+
{t('displayRange', language)}
{history.length > MAX_DISPLAY_POINTS - ? `最近 ${MAX_DISPLAY_POINTS}` - : '全部数据' + ? `${t('recent', language)} ${MAX_DISPLAY_POINTS}` + : t('allData', language) }
diff --git a/web/src/contexts/LanguageContext.tsx b/web/src/contexts/LanguageContext.tsx new file mode 100644 index 00000000..77b8a98a --- /dev/null +++ b/web/src/contexts/LanguageContext.tsx @@ -0,0 +1,37 @@ +import { createContext, useContext, useState, ReactNode } from 'react'; +import type { Language } from '../i18n/translations'; + +interface LanguageContextType { + language: Language; + setLanguage: (lang: Language) => void; +} + +const LanguageContext = createContext(undefined); + +export function LanguageProvider({ children }: { children: ReactNode }) { + // Initialize language from localStorage or default to English + const [language, setLanguage] = useState(() => { + const saved = localStorage.getItem('language'); + return (saved === 'en' || saved === 'zh') ? saved : 'en'; + }); + + // Save language to localStorage whenever it changes + const handleSetLanguage = (lang: Language) => { + setLanguage(lang); + localStorage.setItem('language', lang); + }; + + return ( + + {children} + + ); +} + +export function useLanguage() { + const context = useContext(LanguageContext); + if (!context) { + throw new Error('useLanguage must be used within LanguageProvider'); + } + return context; +} diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts new file mode 100644 index 00000000..397bd08e --- /dev/null +++ b/web/src/i18n/translations.ts @@ -0,0 +1,251 @@ +export type Language = 'en' | 'zh'; + +export const translations = { + en: { + // Header + appTitle: 'AI Trading Competition', + subtitle: 'Qwen vs DeepSeek · Real-time', + competition: 'Competition', + details: 'Details', + running: 'RUNNING', + stopped: 'STOPPED', + + // Footer + footerTitle: 'NOFX - AI Trading Competition System', + footerWarning: '⚠️ Trading involves risk. Use at your own discretion.', + + // Stats Cards + totalEquity: 'Total Equity', + availableBalance: 'Available Balance', + totalPnL: 'Total P&L', + positions: 'Positions', + margin: 'Margin', + free: 'Free', + + // Positions Table + currentPositions: 'Current Positions', + active: 'Active', + symbol: 'Symbol', + side: 'Side', + entryPrice: 'Entry Price', + markPrice: 'Mark Price', + quantity: 'Quantity', + positionValue: 'Position Value', + leverage: 'Leverage', + unrealizedPnL: 'Unrealized P&L', + liqPrice: 'Liq. Price', + long: 'LONG', + short: 'SHORT', + noPositions: 'No Positions', + noActivePositions: 'No active trading positions', + + // Recent Decisions + recentDecisions: 'Recent Decisions', + lastCycles: 'Last {count} trading cycles', + noDecisionsYet: 'No Decisions Yet', + aiDecisionsWillAppear: 'AI trading decisions will appear here', + cycle: 'Cycle', + success: 'Success', + failed: 'Failed', + aiThinking: 'AI Chain of Thought', + collapse: 'Collapse', + expand: 'Expand', + + // Equity Chart + accountEquityCurve: 'Account Equity Curve', + noHistoricalData: 'No Historical Data', + dataWillAppear: 'Equity curve will appear after running a few cycles', + initialBalance: 'Initial Balance', + currentEquity: 'Current Equity', + historicalCycles: 'Historical Cycles', + displayRange: 'Display Range', + recent: 'Recent', + allData: 'All Data', + cycles: 'Cycles', + + // Competition Page + aiCompetition: 'AI Competition', + traders: 'traders', + liveBattle: 'Qwen vs DeepSeek · Live Battle', + leader: 'Leader', + leaderboard: 'Leaderboard', + live: 'LIVE', + performanceComparison: 'Performance Comparison', + realTimePnL: 'Real-time PnL %', + headToHead: 'Head-to-Head Battle', + leadingBy: 'Leading by {gap}%', + behindBy: 'Behind by {gap}%', + equity: 'Equity', + pnl: 'P&L', + pos: 'Pos', + + // AI Learning + aiLearning: 'AI Learning & Reflection', + tradesAnalyzed: '{count} trades analyzed · Real-time evolution', + latestReflection: 'Latest Reflection', + fullCoT: 'Full Chain of Thought', + totalTrades: 'Total Trades', + winRate: 'Win Rate', + avgWin: 'Avg Win', + avgLoss: 'Avg Loss', + profitFactor: 'Profit Factor', + avgWinDivLoss: 'Avg Win ÷ Avg Loss', + excellent: '🔥 Excellent - Strong profitability', + good: '✓ Good - Stable profits', + fair: '⚠️ Fair - Needs optimization', + poor: '❌ Poor - Losses exceed gains', + bestPerformer: 'Best Performer', + worstPerformer: 'Worst Performer', + symbolPerformance: 'Symbol Performance', + tradeHistory: 'Trade History', + completedTrades: 'Recent {count} completed trades', + noCompletedTrades: 'No completed trades yet', + completedTradesWillAppear: 'Completed trades will appear here', + entry: 'Entry', + exit: 'Exit', + stopLoss: 'Stop Loss', + latest: 'Latest', + + // AI Learning Description + howAILearns: 'How AI Learns & Evolves', + aiLearningPoint1: 'Analyzes last 20 trading cycles before each decision', + aiLearningPoint2: 'Identifies best & worst performing symbols', + aiLearningPoint3: 'Optimizes position sizing based on win rate', + aiLearningPoint4: 'Avoids repeating past mistakes', + + // Loading & Error + loading: 'Loading...', + loadingError: '⚠️ Failed to load AI learning data', + noCompleteData: 'No complete trading data (needs to complete open → close cycle)', + }, + zh: { + // Header + appTitle: 'AI交易竞赛', + subtitle: 'Qwen vs DeepSeek · 实时', + competition: '竞赛', + details: '详情', + running: '运行中', + stopped: '已停止', + + // Footer + footerTitle: 'NOFX - AI交易竞赛系统', + footerWarning: '⚠️ 交易有风险,请谨慎使用。', + + // Stats Cards + totalEquity: '总净值', + availableBalance: '可用余额', + totalPnL: '总盈亏', + positions: '持仓', + margin: '保证金', + free: '空闲', + + // Positions Table + currentPositions: '当前持仓', + active: '活跃', + symbol: '币种', + side: '方向', + entryPrice: '入场价', + markPrice: '标记价', + quantity: '数量', + positionValue: '仓位价值', + leverage: '杠杆', + unrealizedPnL: '未实现盈亏', + liqPrice: '强平价', + long: '多头', + short: '空头', + noPositions: '无持仓', + noActivePositions: '当前没有活跃的交易持仓', + + // Recent Decisions + recentDecisions: '最近决策', + lastCycles: '最近 {count} 个交易周期', + noDecisionsYet: '暂无决策', + aiDecisionsWillAppear: 'AI交易决策将显示在这里', + cycle: '周期', + success: '成功', + failed: '失败', + aiThinking: '💭 AI思维链分析', + collapse: '▼ 收起', + expand: '▶ 展开', + + // Equity Chart + accountEquityCurve: '账户净值曲线', + noHistoricalData: '暂无历史数据', + dataWillAppear: '运行几个周期后将显示收益率曲线', + initialBalance: '初始余额', + currentEquity: '当前净值', + historicalCycles: '历史周期', + displayRange: '显示范围', + recent: '最近', + allData: '全部数据', + cycles: '个', + + // Competition Page + aiCompetition: 'AI竞赛', + traders: '位交易者', + liveBattle: 'Qwen vs DeepSeek · 实时对战', + leader: '🥇 领先者', + leaderboard: '🥇 排行榜', + live: '直播', + performanceComparison: '📈 表现对比', + realTimePnL: '实时盈亏百分比', + headToHead: '⚔️ 正面对决', + leadingBy: '领先 {gap}%', + behindBy: '落后 {gap}%', + equity: '净值', + pnl: '盈亏', + pos: '仓位', + + // AI Learning + aiLearning: 'AI学习与反思', + tradesAnalyzed: '已分析 {count} 笔交易 · 实时演化', + latestReflection: '最新反思', + fullCoT: '📋 完整思维链', + totalTrades: '总交易数', + winRate: '胜率', + avgWin: '平均盈利', + avgLoss: '平均亏损', + profitFactor: '盈亏比', + avgWinDivLoss: '平均盈利 ÷ 平均亏损', + excellent: '🔥 优秀 - 盈利能力强', + good: '✓ 良好 - 稳定盈利', + fair: '⚠️ 一般 - 需要优化', + poor: '❌ 较差 - 亏损超过盈利', + bestPerformer: '最佳表现', + worstPerformer: '最差表现', + symbolPerformance: '📊 币种表现', + tradeHistory: '历史成交', + completedTrades: '最近 {count} 笔已完成交易', + noCompletedTrades: '暂无完成的交易', + completedTradesWillAppear: '已完成的交易将显示在这里', + entry: '入场', + exit: '出场', + stopLoss: '止损', + latest: '最新', + + // AI Learning Description + howAILearns: '💡 AI如何学习和进化', + aiLearningPoint1: '每次决策前分析最近20个交易周期', + aiLearningPoint2: '识别表现最好和最差的币种', + aiLearningPoint3: '根据胜率优化仓位大小', + aiLearningPoint4: '避免重复过去的错误', + + // Loading & Error + loading: '加载中...', + loadingError: '⚠️ 加载AI学习数据失败', + noCompleteData: '暂无完整交易数据(需要完成开仓→平仓的完整周期)', + } +}; + +export function t(key: string, lang: Language, params?: Record): string { + let text = translations[lang][key as keyof typeof translations['en']] || key; + + // Replace parameters like {count}, {gap}, etc. + if (params) { + Object.entries(params).forEach(([param, value]) => { + text = text.replace(`{${param}}`, String(value)); + }); + } + + return text; +}
SymbolSideEntry PriceMark PriceQuantityPosition ValueLeverageUnrealized P&LLiq. Price{t('symbol', language)}{t('side', language)}{t('entryPrice', language)}{t('markPrice', language)}{t('quantity', language)}{t('positionValue', language)}{t('leverage', language)}{t('unrealizedPnL', language)}{t('liqPrice', language)}
{pos.entry_price.toFixed(4)}