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) { return (
) } // 如果有数据返回但没有交易员,显示空状态 if (!competition.traders || competition.traders.length === 0) { return (
{/* Competition Header - 精简版 */}

{t('aiCompetition', language)} 0 {t('traders', language)}

{t('liveBattle', language)}

{/* Empty State */}

{t('noTraders', language)}

{t('createFirstTrader', language)}

) } // 按收益率排序 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}
= 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 */}
) }