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, { refreshInterval: 5000, revalidateOnFocus: true, } ); if (!competition || !competition.traders) { return (
); } // 按收益率排序 const sortedTraders = [...competition.traders].sort( (a, b) => b.total_pnl_pct - a.total_pnl_pct ); // 找出领先者 const leader = sortedTraders[0]; return (
{/* Competition Header - 精简版 */}
🏆

{t('aiCompetition', language)} {competition.count} {t('traders', language)}

{t('liveBattle', language)}

{t('leader', language)}
{leader?.trader_name}
= 0 ? '#0ECB81' : '#F6465D' }}> {leader.total_pnl >= 0 ? '+' : ''}{leader.total_pnl_pct.toFixed(2)}%
{/* Left/Right Split: Performance Chart + Leaderboard */}
{/* Left: Performance Comparison Chart */}

{t('performanceComparison', language)}

{t('realTimePnL', language)}
{/* Right: Leaderboard */}

{t('leaderboard', language)}

{t('live', language)}
{sortedTraders.map((trader, index) => { const isLeader = index === 0; const aiModelColor = trader.ai_model === 'qwen' ? '#c084fc' : '#60a5fa'; return (
{/* Rank & Name */}
{index === 0 ? '🥇' : index === 1 ? '🥈' : '🥉'}
{trader.trader_name}
{trader.ai_model.toUpperCase()}
{/* Stats */}
{/* Total Equity */}
{t('equity', language)}
{trader.total_equity.toFixed(2)}
{/* P&L */}
{t('pnl', language)}
= 0 ? '#0ECB81' : '#F6465D' }} > {trader.total_pnl >= 0 ? '+' : ''} {trader.total_pnl_pct.toFixed(2)}%
{trader.total_pnl >= 0 ? '+' : ''}{trader.total_pnl.toFixed(2)}
{/* Positions */}
{t('pos', language)}
{trader.position_count}
{trader.margin_used_pct.toFixed(1)}%
{/* Status */}
{trader.is_running ? '●' : '○'}
); })}
{/* Head-to-Head Stats */} {competition.traders.length === 2 && (

{t('headToHead', language)}

{sortedTraders.map((trader, index) => { const isWinning = index === 0; const opponent = sortedTraders[1 - index]; const gap = trader.total_pnl_pct - opponent.total_pnl_pct; return (
{trader.trader_name}
= 0 ? '#0ECB81' : '#F6465D' }}> {trader.total_pnl >= 0 ? '+' : ''}{trader.total_pnl_pct.toFixed(2)}%
{isWinning && gap > 0 && (
{t('leadingBy', language, { gap: gap.toFixed(2) })}
)} {!isWinning && gap < 0 && (
{t('behindBy', language, { gap: Math.abs(gap).toFixed(2) })}
)}
); })}
)}
); }