import useSWR from 'swr'; import { useLanguage } from '../contexts/LanguageContext'; import { t } from '../i18n/translations'; import { api } from '../lib/api'; interface TradeOutcome { symbol: string; side: string; open_price: number; close_price: number; pn_l: number; pn_l_pct: number; duration: string; open_time: string; close_time: string; was_stop_loss: boolean; } interface SymbolPerformance { symbol: string; total_trades: number; winning_trades: number; losing_trades: number; win_rate: number; total_pn_l: number; avg_pn_l: number; } interface PerformanceAnalysis { total_trades: number; winning_trades: number; losing_trades: number; win_rate: number; avg_win: number; avg_loss: number; profit_factor: number; sharpe_ratio: number; recent_trades: TradeOutcome[]; symbol_stats: { [key: string]: SymbolPerformance }; best_symbol: string; worst_symbol: string; } interface AILearningProps { traderId: string; } export default function AILearning({ traderId }: AILearningProps) { const { language } = useLanguage(); const { data: performance, error } = useSWR( traderId ? `performance-${traderId}` : 'performance', () => api.getPerformance(traderId), { refreshInterval: 30000, // 30秒刷新(AI学习分析数据更新频率较低) revalidateOnFocus: false, dedupingInterval: 20000, } ); if (error) { return (
{t('loadingError', language)}
); } if (!performance) { return (
📊 {t('loading', language)}
); } if (!performance || performance.total_trades === 0) { return (
🧠

{t('aiLearning', language)}

{t('noCompleteData', language)}
); } const symbolStats = performance.symbol_stats || {}; const symbolStatsList = Object.values(symbolStats).filter(stat => stat != null).sort( (a, b) => (b.total_pn_l || 0) - (a.total_pn_l || 0) ); return (
{/* 标题区 - 优化设计 */}
🧠

{t('aiLearning', language)}

{t('tradesAnalyzed', language, { count: performance.total_trades })}

{/* 核心指标卡片 - 4列网格 */}
{/* 总交易数 */}
{t('totalTrades', language)}
{performance.total_trades}
📊 Trades
{/* 胜率 */}
= 50 ? 'linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(30, 35, 41, 0.8) 100%)' : 'linear-gradient(135deg, rgba(248, 113, 113, 0.2) 0%, rgba(30, 35, 41, 0.8) 100%)', border: `1px solid ${(performance.win_rate || 0) >= 50 ? 'rgba(16, 185, 129, 0.4)' : 'rgba(248, 113, 113, 0.4)'}`, boxShadow: `0 4px 16px ${(performance.win_rate || 0) >= 50 ? 'rgba(16, 185, 129, 0.2)' : 'rgba(248, 113, 113, 0.2)'}` }}>
= 50 ? '#10B981' : '#F87171'} 0%, transparent 70%)`, filter: 'blur(20px)' }} />
= 50 ? '#6EE7B7' : '#FCA5A5' }}> {t('winRate', language)}
= 50 ? '#10B981' : '#F87171' }}> {(performance.win_rate || 0).toFixed(1)}%
{performance.winning_trades || 0}W / {performance.losing_trades || 0}L
{/* 平均盈利 */}
{t('avgWin', language)}
+{(performance.avg_win || 0).toFixed(2)}
📈 USDT Average
{/* 平均亏损 */}
{t('avgLoss', language)}
{(performance.avg_loss || 0).toFixed(2)}
📉 USDT Average
{/* 关键指标:夏普比率 & 盈亏比 - 2列网格 */}
{/* 夏普比率 */}
🧬
夏普比率
风险调整后收益 · AI自我进化指标
= 2 ? '#10B981' : (performance.sharpe_ratio || 0) >= 1 ? '#22D3EE' : (performance.sharpe_ratio || 0) >= 0 ? '#F0B90B' : '#F87171', textShadow: '0 4px 12px rgba(0, 0, 0, 0.3)' }}> {performance.sharpe_ratio ? performance.sharpe_ratio.toFixed(2) : 'N/A'}
{performance.sharpe_ratio !== undefined && (
= 2 ? '#10B981' : (performance.sharpe_ratio || 0) >= 1 ? '#22D3EE' : (performance.sharpe_ratio || 0) >= 0 ? '#F0B90B' : '#F87171', background: (performance.sharpe_ratio || 0) >= 2 ? 'rgba(16, 185, 129, 0.2)' : (performance.sharpe_ratio || 0) >= 1 ? 'rgba(34, 211, 238, 0.2)' : (performance.sharpe_ratio || 0) >= 0 ? 'rgba(240, 185, 11, 0.2)' : 'rgba(248, 113, 113, 0.2)' }}> {performance.sharpe_ratio >= 2 ? '🟢 卓越表现' : performance.sharpe_ratio >= 1 ? '🟢 良好表现' : performance.sharpe_ratio >= 0 ? '🟡 波动较大' : '🔴 需要调整'}
)}
{performance.sharpe_ratio !== undefined && (
{performance.sharpe_ratio >= 2 && '✨ AI策略非常有效!风险调整后收益优异,可适度扩大仓位但保持纪律。'} {performance.sharpe_ratio >= 1 && performance.sharpe_ratio < 2 && '✅ 策略表现稳健,风险收益平衡良好,继续保持当前策略。'} {performance.sharpe_ratio >= 0 && performance.sharpe_ratio < 1 && '⚠️ 收益为正但波动较大,AI正在优化策略,降低风险。'} {performance.sharpe_ratio < 0 && '🚨 当前策略需要调整!AI已自动进入保守模式,减少仓位和交易频率。'}
)}
{/* 盈亏比 */}
💰
{t('profitFactor', language)}
{t('avgWinDivLoss', language)}
= 2.0 ? '#10B981' : (performance.profit_factor || 0) >= 1.5 ? '#F0B90B' : (performance.profit_factor || 0) >= 1.0 ? '#FB923C' : '#F87171', textShadow: '0 4px 12px rgba(0, 0, 0, 0.3)' }}> {(performance.profit_factor || 0) > 0 ? (performance.profit_factor || 0).toFixed(2) : 'N/A'}
= 2.0 ? '#10B981' : (performance.profit_factor || 0) >= 1.5 ? '#F0B90B' : '#94A3B8', background: (performance.profit_factor || 0) >= 2.0 ? 'rgba(16, 185, 129, 0.2)' : (performance.profit_factor || 0) >= 1.5 ? 'rgba(240, 185, 11, 0.2)' : 'rgba(148, 163, 184, 0.2)' }}> {(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)}
{(performance.profit_factor || 0) >= 2.0 && '🔥 盈利能力出色!每亏1元能赚' + (performance.profit_factor || 0).toFixed(1) + '元,AI策略表现优异。'} {(performance.profit_factor || 0) >= 1.5 && (performance.profit_factor || 0) < 2.0 && '✓ 策略稳定盈利,盈亏比健康,继续保持纪律性交易。'} {(performance.profit_factor || 0) >= 1.0 && (performance.profit_factor || 0) < 1.5 && '⚠️ 策略略有盈利但需优化,AI正在调整仓位和止损策略。'} {(performance.profit_factor || 0) > 0 && (performance.profit_factor || 0) < 1.0 && '❌ 平均亏损大于盈利,需要调整策略或降低交易频率。'}
{/* 最佳/最差币种 - 独立行 */} {(performance.best_symbol || performance.worst_symbol) && (
{performance.best_symbol && (
🏆 {t('bestPerformer', language)}
{performance.best_symbol}
{symbolStats[performance.best_symbol] && (
{symbolStats[performance.best_symbol].total_pn_l > 0 ? '+' : ''} {symbolStats[performance.best_symbol].total_pn_l.toFixed(2)} USDT {t('pnl', language)}
)}
)} {performance.worst_symbol && (
📉 {t('worstPerformer', language)}
{performance.worst_symbol}
{symbolStats[performance.worst_symbol] && (
{symbolStats[performance.worst_symbol].total_pn_l > 0 ? '+' : ''} {symbolStats[performance.worst_symbol].total_pn_l.toFixed(2)} USDT {t('pnl', language)}
)}
)}
)} {/* 币种表现 & 历史成交 - 左右分屏 2列布局 */}
{/* 左侧:币种表现统计表格 */} {symbolStatsList.length > 0 && (

📊 {t('symbolPerformance', language)}

{symbolStatsList.map((stat, idx) => ( 0 ? '1px solid rgba(99, 102, 241, 0.1)' : 'none' }}> ))}
Symbol Trades Win Rate Total P&L (USDT) Avg P&L (USDT)
{stat.symbol} {stat.total_trades} = 50 ? '#10B981' : '#F87171' }}> {(stat.win_rate || 0).toFixed(1)}% 0 ? '#10B981' : '#F87171' }}> {(stat.total_pn_l || 0) > 0 ? '+' : ''}{(stat.total_pn_l || 0).toFixed(2)} 0 ? '#10B981' : '#F87171' }}> {(stat.avg_pn_l || 0) > 0 ? '+' : ''}{(stat.avg_pn_l || 0).toFixed(2)}
)} {/* 右侧:历史成交记录 */}
📜

{t('tradeHistory', language)}

{performance?.recent_trades && performance.recent_trades.length > 0 ? t('completedTrades', language, { count: performance.recent_trades.length }) : t('completedTradesWillAppear', language)}

{performance?.recent_trades && performance.recent_trades.length > 0 ? ( performance.recent_trades.map((trade: TradeOutcome, idx: number) => { const isProfitable = trade.pn_l >= 0; const isRecent = idx === 0; return (
{trade.symbol} {trade.side.toUpperCase()} {isRecent && ( {t('latest', language)} )}
{isProfitable ? '+' : ''}{trade.pn_l_pct.toFixed(2)}%
{t('entry', language)}
{trade.open_price.toFixed(4)}
{t('exit', language)}
{trade.close_price.toFixed(4)}
P&L {isProfitable ? '+' : ''}{trade.pn_l.toFixed(2)} USDT
⏱️ {formatDuration(trade.duration)} {trade.was_stop_loss && ( {t('stopLoss', language)} )}
{new Date(trade.close_time).toLocaleString('en-US', { month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
); }) ) : (
📜
{t('noCompletedTrades', language)}
)}
{/* AI学习说明 - 现代化设计 */}
💡

{t('howAILearns', language)}

{t('aiLearningPoint1', language)}
{t('aiLearningPoint2', language)}
{t('aiLearningPoint3', language)}
{t('aiLearningPoint4', language)}
); } // 格式化持仓时长 function formatDuration(duration: string | undefined): string { if (!duration) return '-'; const match = duration.match(/(\d+h)?(\d+m)?(\d+\.?\d*s)?/); if (!match) return duration; const hours = match[1] || ''; const minutes = match[2] || ''; const seconds = match[3] || ''; let result = ''; if (hours) result += hours.replace('h', '小时'); if (minutes) result += minutes.replace('m', '分'); if (!hours && seconds) result += seconds.replace(/(\d+)\.?\d*s/, '$1秒'); return result || duration; }