import { useEffect, useState } from 'react'; import useSWR from 'swr'; 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, Position, DecisionRecord, Statistics, TraderInfo, } from './types'; type Page = 'competition' | 'trader'; function App() { const { language, setLanguage } = useLanguage(); // 从URL hash读取初始页面状态(支持刷新保持页面) const getInitialPage = (): Page => { const hash = window.location.hash.slice(1); // 去掉 # return hash === 'trader' || hash === 'details' ? 'trader' : 'competition'; }; const [currentPage, setCurrentPage] = useState(getInitialPage()); const [selectedTraderId, setSelectedTraderId] = useState(); const [lastUpdate, setLastUpdate] = useState('--:--:--'); // 监听URL hash变化,同步页面状态 useEffect(() => { const handleHashChange = () => { const hash = window.location.hash.slice(1); if (hash === 'trader' || hash === 'details') { setCurrentPage('trader'); } else if (hash === 'competition' || hash === '') { setCurrentPage('competition'); } }; window.addEventListener('hashchange', handleHashChange); return () => window.removeEventListener('hashchange', handleHashChange); }, []); // 切换页面时更新URL hash const navigateToPage = (page: Page) => { setCurrentPage(page); window.location.hash = page === 'competition' ? '' : 'trader'; }; // 获取trader列表 const { data: traders } = useSWR('traders', api.getTraders, { refreshInterval: 10000, }); // 当获取到traders后,设置默认选中第一个 useEffect(() => { if (traders && traders.length > 0 && !selectedTraderId) { setSelectedTraderId(traders[0].trader_id); } }, [traders, selectedTraderId]); // 如果在trader页面,获取该trader的数据 const { data: status } = useSWR( currentPage === 'trader' && selectedTraderId ? `status-${selectedTraderId}` : null, () => api.getStatus(selectedTraderId), { refreshInterval: 15000, // 15秒刷新(配合后端15秒缓存) revalidateOnFocus: false, // 禁用聚焦时重新验证,减少请求 dedupingInterval: 10000, // 10秒去重,防止短时间内重复请求 } ); const { data: account } = useSWR( currentPage === 'trader' && selectedTraderId ? `account-${selectedTraderId}` : null, () => api.getAccount(selectedTraderId), { refreshInterval: 15000, // 15秒刷新(配合后端15秒缓存) revalidateOnFocus: false, // 禁用聚焦时重新验证,减少请求 dedupingInterval: 10000, // 10秒去重,防止短时间内重复请求 } ); const { data: positions } = useSWR( currentPage === 'trader' && selectedTraderId ? `positions-${selectedTraderId}` : null, () => api.getPositions(selectedTraderId), { refreshInterval: 15000, // 15秒刷新(配合后端15秒缓存) revalidateOnFocus: false, // 禁用聚焦时重新验证,减少请求 dedupingInterval: 10000, // 10秒去重,防止短时间内重复请求 } ); const { data: decisions } = useSWR( currentPage === 'trader' && selectedTraderId ? `decisions/latest-${selectedTraderId}` : null, () => api.getLatestDecisions(selectedTraderId), { refreshInterval: 30000, // 30秒刷新(决策更新频率较低) revalidateOnFocus: false, dedupingInterval: 20000, } ); const { data: stats } = useSWR( currentPage === 'trader' && selectedTraderId ? `statistics-${selectedTraderId}` : null, () => api.getStatistics(selectedTraderId), { refreshInterval: 30000, // 30秒刷新(统计数据更新频率较低) revalidateOnFocus: false, dedupingInterval: 20000, } ); useEffect(() => { if (account) { const now = new Date().toLocaleTimeString(); setLastUpdate(now); } }, [account]); const selectedTrader = traders?.find((t) => t.trader_id === selectedTraderId); return (
{/* Header - Binance Style */}
{/* Mobile: Two rows, Desktop: Single row */}
{/* Left: Logo and Title */}

{t('appTitle', language)}

{t('subtitle', language)}

{/* Right: Controls - Wrap on mobile */}
{/* GitHub Link - Hidden on mobile, icon only on tablet */} { 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 */}
{/* Trader Selector (only show on trader page) */} {currentPage === 'trader' && traders && traders.length > 0 && ( )} {/* Status Indicator (only show on trader page) */} {currentPage === 'trader' && status && (
{t(status.is_running ? 'running' : 'stopped', language)}
)}
{/* Main Content */}
{currentPage === 'competition' ? ( ) : ( )}
{/* Footer */}
); } // Trader Details Page Component function TraderDetailsPage({ selectedTrader, status, account, positions, decisions, lastUpdate, language, }: { selectedTrader?: TraderInfo; status?: SystemStatus; account?: AccountInfo; positions?: Position[]; decisions?: DecisionRecord[]; stats?: Statistics; lastUpdate: string; language: Language; }) { if (!selectedTrader) { return (
{/* Loading Skeleton - Binance Style */}
{[1, 2, 3, 4].map((i) => (
))}
); } return (
{/* Trader Header */}

🤖 {selectedTrader.trader_name}

AI Model: {selectedTrader.ai_model.toUpperCase()} {status && ( <> Cycles: {status.call_count} Runtime: {status.runtime_minutes} min )}
{/* Debug Info */} {account && (
🔄 Last Update: {lastUpdate} | Total Equity: {account.total_equity?.toFixed(2) || '0.00'} | Available: {account.available_balance?.toFixed(2) || '0.00'} | P&L: {account.total_pnl?.toFixed(2) || '0.00'}{' '} ({account.total_pnl_pct?.toFixed(2) || '0.00'}%)
)} {/* Account Overview */}
0} /> = 0 ? '+' : ''}${account?.total_pnl?.toFixed(2) || '0.00'} USDT`} change={account?.total_pnl_pct || 0} positive={(account?.total_pnl ?? 0) >= 0} />
{/* 主要内容区:左右分屏 */}
{/* 左侧:图表 + 持仓 */}
{/* Equity Chart */}
{/* Current Positions */}

📈 {t('currentPositions', language)}

{positions && positions.length > 0 && (
{positions.length} {t('active', language)}
)}
{positions && positions.length > 0 ? (
{positions.map((pos, i) => ( ))}
{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.symbol} {t(pos.side === 'long' ? 'long' : 'short', language)} {pos.entry_price.toFixed(4)} {pos.mark_price.toFixed(4)} {pos.quantity.toFixed(4)} {(pos.quantity * pos.mark_price).toFixed(2)} USDT {pos.leverage}x = 0 ? '#0ECB81' : '#F6465D', fontWeight: 'bold' }} > {pos.unrealized_pnl >= 0 ? '+' : ''} {pos.unrealized_pnl.toFixed(2)} ({pos.unrealized_pnl_pct.toFixed(2)}%) {pos.liquidation_price.toFixed(4)}
) : (
📊
{t('noPositions', language)}
{t('noActivePositions', language)}
)}
{/* 左侧结束 */} {/* 右侧:Recent Decisions - 卡片容器 */}
{/* 标题 */}
🧠

{t('recentDecisions', language)}

{decisions && decisions.length > 0 && (
{t('lastCycles', language, { count: decisions.length })}
)}
{/* 决策列表 - 可滚动 */}
{decisions && decisions.length > 0 ? ( decisions.map((decision, i) => ( )) ) : (
🧠
{t('noDecisionsYet', language)}
{t('aiDecisionsWillAppear', language)}
)}
{/* 右侧结束 */}
{/* AI Learning & Performance Analysis */}
); } // Stat Card Component - Binance Style Enhanced function StatCard({ title, value, change, positive, subtitle, }: { title: string; value: string; change?: number; positive?: boolean; subtitle?: string; }) { return (
{title}
{value}
{change !== undefined && (
{positive ? '▲' : '▼'} {positive ? '+' : ''} {change.toFixed(2)}%
)} {subtitle &&
{subtitle}
}
); } // Decision Card Component with CoT Trace - Binance Style function DecisionCard({ decision, language }: { decision: DecisionRecord; language: Language }) { const [showInputPrompt, setShowInputPrompt] = useState(false); const [showCoT, setShowCoT] = useState(false); return (
{/* Header */}
{t('cycle', language)} #{decision.cycle_number}
{new Date(decision.timestamp).toLocaleString()}
{t(decision.success ? 'success' : 'failed', language)}
{/* Input Prompt - Collapsible */} {decision.input_prompt && (
{showInputPrompt && (
{decision.input_prompt}
)}
)} {/* AI Chain of Thought - Collapsible */} {decision.cot_trace && (
{showCoT && (
{decision.cot_trace}
)}
)} {/* Decisions Actions */} {decision.decisions && decision.decisions.length > 0 && (
{decision.decisions.map((action, j) => (
{action.symbol} {action.action} {action.leverage > 0 && {action.leverage}x} {action.price > 0 && ( @{action.price.toFixed(4)} )} {action.success ? '✓' : '✗'} {action.error && {action.error}}
))}
)} {/* Account State Summary */} {decision.account_state && (
净值: {decision.account_state.total_balance.toFixed(2)} USDT 可用: {decision.account_state.available_balance.toFixed(2)} USDT 保证金率: {decision.account_state.margin_used_pct.toFixed(1)}% 持仓: {decision.account_state.position_count}
)} {/* Execution Logs */} {decision.execution_log && decision.execution_log.length > 0 && (
{decision.execution_log.map((log, k) => (
{log}
))}
)} {/* Error Message */} {decision.error_message && (
❌ {decision.error_message}
)}
); } // Wrap App with LanguageProvider export default function AppWithLanguage() { return ( ); }