import { useState } from 'react'; import { Trophy, Medal } from 'lucide-react'; import useSWR from 'swr'; import { api } from '../lib/api'; import type { CompetitionData } from '../types'; import { ComparisonChart } from './ComparisonChart'; import { TraderConfigViewModal } from './TraderConfigViewModal'; import { getTraderColor } from '../utils/traderColors'; import { useLanguage } from '../contexts/LanguageContext'; import { t } from '../i18n/translations'; export function CompetitionPage() { const { language } = useLanguage(); const [selectedTrader, setSelectedTrader] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const { data: competition } = useSWR( 'competition', api.getCompetition, { refreshInterval: 15000, // 15秒刷新(竞赛数据不需要太频繁更新) revalidateOnFocus: false, dedupingInterval: 10000, } ); const handleTraderClick = async (traderId: string) => { try { const traderConfig = await api.getTraderConfig(traderId); setSelectedTrader(traderConfig); setIsModalOpen(true); } catch (error) { console.error('Failed to fetch trader config:', error); // 对于未登录用户,不显示详细配置,这是正常行为 // 竞赛页面主要用于查看排行榜和基本信息 } }; const closeModal = () => { setIsModalOpen(false); setSelectedTrader(null); }; 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) >= 0 ? '+' : ''}{leader?.total_pnl_pct?.toFixed(2) || '0.00'}%
{/* 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 traderColor = getTraderColor(sortedTraders, trader.trader_id); return (
handleTraderClick(trader.trader_id)} className="rounded p-3 transition-all duration-300 hover:translate-y-[-1px] cursor-pointer hover:shadow-lg" style={{ background: isLeader ? 'linear-gradient(135deg, rgba(240, 185, 11, 0.08) 0%, #0B0E11 100%)' : '#0B0E11', border: `1px solid ${isLeader ? 'rgba(240, 185, 11, 0.4)' : '#2B3139'}`, boxShadow: isLeader ? '0 3px 15px rgba(240, 185, 11, 0.12), 0 0 0 1px rgba(240, 185, 11, 0.15)' : '0 1px 4px rgba(0, 0, 0, 0.3)' }} >
{/* Rank & Name */}
{trader.trader_name}
{trader.ai_model.toUpperCase()} + {trader.exchange.toUpperCase()}
{/* Stats */}
{/* Total Equity */}
{t('equity', language)}
{trader.total_equity?.toFixed(2) || '0.00'}
{/* P&L */}
{t('pnl', language)}
= 0 ? '#0ECB81' : '#F6465D' }} > {(trader.total_pnl ?? 0) >= 0 ? '+' : ''} {trader.total_pnl_pct?.toFixed(2) || '0.00'}%
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}{trader.total_pnl?.toFixed(2) || '0.00'}
{/* 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}
{trader.ai_model.toUpperCase()} + {trader.exchange.toUpperCase()}
= 0 ? '#0ECB81' : '#F6465D' }}> {(trader.total_pnl ?? 0) >= 0 ? '+' : ''}{trader.total_pnl_pct?.toFixed(2) || '0.00'}%
{isWinning && gap > 0 && (
{t('leadingBy', language, { gap: gap.toFixed(2) })}
)} {!isWinning && gap < 0 && (
{t('behindBy', language, { gap: Math.abs(gap).toFixed(2) })}
)}
); })}
)} {/* Trader Config View Modal */}
); }