From 6c87a513033a673b86153a139bb955afc5fc0917 Mon Sep 17 00:00:00 2001 From: tinkle-community Date: Wed, 29 Oct 2025 04:54:53 +0800 Subject: [PATCH] UI: Enhance AI learning dashboard visual design Improvements: - Add gradient background with glowing effects - Redesign header section with larger icon and enhanced typography - Improve card layouts with modern shadows and hover effects - Optimize Sharpe ratio and profit factor display sections - Add better visual hierarchy and spacing - Enhance color schemes for better readability - Remove unused decision record fetching code - Clean up comments for better code clarity Visual enhancements: - Glassmorphism effects with backdrop blur - Animated gradient backgrounds - Enhanced metric cards with better contrast - Improved symbol performance table design - Modernized trade history cards with gradients Co-Authored-By: tinkle-community --- web/src/components/AILearning.tsx | 917 +++++++++++++++--------------- 1 file changed, 446 insertions(+), 471 deletions(-) diff --git a/web/src/components/AILearning.tsx b/web/src/components/AILearning.tsx index 0155bdd1..47901fbb 100644 --- a/web/src/components/AILearning.tsx +++ b/web/src/components/AILearning.tsx @@ -33,7 +33,7 @@ interface PerformanceAnalysis { avg_win: number; avg_loss: number; profit_factor: number; - sharpe_ratio: number; // 夏普比率(风险调整后收益) + sharpe_ratio: number; recent_trades: TradeOutcome[]; symbol_stats: { [key: string]: SymbolPerformance }; best_symbol: string; @@ -44,14 +44,6 @@ interface AILearningProps { traderId: string; } -interface DecisionRecord { - timestamp: string; - cycle_number: number; - input_prompt: string; - cot_trace: string; - success: boolean; -} - const fetcher = (url: string) => fetch(url).then(res => res.json()); export default function AILearning({ traderId }: AILearningProps) { @@ -62,13 +54,6 @@ export default function AILearning({ traderId }: AILearningProps) { { refreshInterval: 10000 } ); - // 获取最新的决策记录,查看AI的思考过程 - const { data: latestDecisions } = useSWR( - `http://localhost:8080/api/decisions/latest?trader_id=${traderId}`, - fetcher, - { refreshInterval: 10000 } - ); - if (error) { return (
@@ -99,449 +84,525 @@ export default function AILearning({ traderId }: AILearningProps) { ); } - // 安全地获取symbol_stats 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 })} -

+
+ {/* 标题区 - 优化设计 */} +
+
+
+
+ 🧠 +
+
+

+ {t('aiLearning', language)} +

+

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

+
- {/* 主要内容:现代化网格布局 */} -
- - {/* 左侧大区域:核心指标 (5列) */} -
- {/* 核心指标网格 - 玻璃态设计 */} -
- {/* 总交易数 */} -
-
{t('totalTrades', language)}
-
- {performance.total_trades} -
+ {/* 核心指标卡片 - 4列网格 */} +
+ {/* 总交易数 */} +
+
+
+
+ {t('totalTrades', language)}
- - {/* 胜率 */} -
= 50 - ? 'rgba(14, 203, 129, 0.1)' - : 'rgba(246, 70, 93, 0.1)', - border: `1px solid ${(performance.win_rate || 0) >= 50 ? 'rgba(14, 203, 129, 0.3)' : 'rgba(246, 70, 93, 0.3)'}`, - boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)' - }}> -
{t('winRate', language)}
-
= 50 ? '#10B981' : '#F87171' - }}> - {(performance.win_rate || 0).toFixed(1)}% -
-
- {performance.winning_trades || 0}W / {performance.losing_trades || 0}L -
+
+ {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('avgWin', language)}
-
- +{(performance.avg_win || 0).toFixed(2)}% -
+ {t('winRate', language)}
- - {/* 平均亏损 */} -
= 50 ? '#10B981' : '#F87171' }}> -
{t('avgLoss', language)}
-
- {(performance.avg_loss || 0).toFixed(2)}% -
+ {(performance.win_rate || 0).toFixed(1)}% +
+
+ {performance.winning_trades || 0}W / {performance.losing_trades || 0}L
+
- {/* 夏普比率 - AI自我进化核心指标 */} -
-
+ {/* 平均盈利 */} +
+
+
+
+ {t('avgWin', language)} +
+
+ +{(performance.avg_win || 0).toFixed(2)}% +
+
📈 Average
+
+
-
-
- 🧬 -
-
夏普比率
-
风险调整后收益 · AI自我进化指标
-
+ {/* 平均亏损 */} +
+
+
+
+ {t('avgLoss', language)} +
+
+ {(performance.avg_loss || 0).toFixed(2)}% +
+
📉 Average
+
+
+
+ + {/* 关键指标:夏普比率 & 盈亏比 - 2列网格 */} +
+ {/* 夏普比率 */} +
+
+
+
+
+ 🧬
+
+
夏普比率
+
风险调整后收益 · AI自我进化指标
+
+
-
-
= 2 ? '#10B981' : - (performance.sharpe_ratio || 0) >= 1 ? '#22D3EE' : - (performance.sharpe_ratio || 0) >= 0 ? '#F0B90B' : '#F87171' - }}> - {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' - }}> - {performance.sharpe_ratio >= 2 ? '🟢 卓越表现' : - performance.sharpe_ratio >= 1 ? '🟢 良好表现' : - performance.sharpe_ratio >= 0 ? '🟡 波动较大' : '🔴 需要调整'} -
-
- )} +
+
= 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 && ( -
-
- {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已自动进入保守模式,减少仓位和交易频率。'} +
+
= 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('profitFactor', language)} +
{t('avgWinDivLoss', language)}
-
+ +
+
= 2.0 ? '#10B981' : (performance.profit_factor || 0) >= 1.5 ? '#F0B90B' : - (performance.profit_factor || 0) >= 1.0 ? '#FB923C' : '#F87171' + (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)} +
+
-
= 2.0 ? '#10B981' : - (performance.profit_factor || 0) >= 1.5 ? '#F0B90B' : '#94A3B8' +
- {(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 && '❌ 平均亏损大于盈利,需要调整策略或降低交易频率。'} +
- {/* 左侧结束 */} +
- {/* 中间列:数据表格 (4列) */} -
- {/* 最佳/最差币种 */} - {(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)}% {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)}% {t('pnl', language)} -
- )} -
- )} -
- )} - - {/* 币种表现统计 - 现代化表格 */} - {symbolStatsList.length > 0 && ( -
+ {performance.best_symbol && ( +
-
-

- {t('symbolPerformance', language)} -

+
+ 🏆 + {t('bestPerformer', language)}
-
- - - - - - - - - - - - {symbolStatsList.map((stat, idx) => ( - 0 ? '1px solid rgba(99, 102, 241, 0.1)' : 'none' - }}> - - - - - - - ))} - -
SymbolTradesWin RateTotal P&LAvg P&L
- {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)}% -
+
+ {performance.best_symbol}
+ {symbolStats[performance.best_symbol] && ( +
+ {symbolStats[performance.best_symbol].total_pn_l > 0 ? '+' : ''} + {symbolStats[performance.best_symbol].total_pn_l.toFixed(2)}% {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)}% {t('pnl', language)} +
+ )} +
+ )}
- {/* 中间列结束 */} + )} - {/* 右侧列:历史成交记录 (3列) */} -
-
+ {/* 左侧:币种表现统计表格 */} + {symbolStatsList.length > 0 && ( +
- {/* 标题 - 固定在顶部 */} -
-
- 📜 -
-

{t('tradeHistory', language)}

-

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

-
+

+ 📊 {t('symbolPerformance', language)} +

+
+
+ + + + + + + + + + + + {symbolStatsList.map((stat, idx) => ( + 0 ? '1px solid rgba(99, 102, 241, 0.1)' : 'none' + }}> + + + + + + + ))} + +
SymbolTradesWin RateTotal P&LAvg P&L
+ {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; +
+ {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)} - - )} -
-
+
+
+ + {trade.symbol} + + - {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 - + {isRecent && ( + - {isProfitable ? '+' : ''}{trade.pn_l.toFixed(2)} USDT - -
-
- - {/* 时间和持仓时长 */} -
- ⏱️ {formatDuration(trade.duration)} - {trade.was_stop_loss && ( - - {t('stopLoss', language)} + {t('latest', language)} )}
- - {/* 交易时间 */} -
- {new Date(trade.close_time).toLocaleString('en-US', { - month: 'short', - day: '2-digit', - hour: '2-digit', - minute: '2-digit' - })} + {isProfitable ? '+' : ''}{trade.pn_l_pct.toFixed(2)}%
- ); - }) - ) : ( -
-
📜
-
{t('noCompletedTrades', language)}
-
- )} -
+ +
+
+
{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)}
+
+ )}
- {/* 右侧列结束 */} -
- {/* 3列布局结束 */} {/* AI学习说明 - 现代化设计 */}
50) { - return `🎯 AI历史经验总结\n\n${reflection}`; - } - } - - // 尝试提取"历史表现反馈"部分(兼容旧格式) - const performanceSectionMatch = cotTrace.match(/## 📊 历史表现反馈[\s\S]*?(?=##|$)/); - if (performanceSectionMatch) { - const performanceSection = performanceSectionMatch[0]; - - // 提取关键学习点 - const lines: string[] = []; - - // 提取总体统计 - const statsMatch = performanceSection.match(/总交易数:(\d+).*?胜率:([\d.]+)%.*?盈亏比:([\d.]+)/s); - if (statsMatch) { - const [, totalTrades, winRate, profitFactor] = statsMatch; - lines.push(`📈 历史表现回顾:`); - lines.push(` • 完成了 ${totalTrades} 笔交易,胜率 ${winRate}%`); - lines.push(` • 盈亏比 ${profitFactor}(平均盈利/平均亏损)`); - lines.push(''); - } - - // 提取最近交易 - const recentTradesMatch = performanceSection.match(/最近5笔交易[\s\S]*?(?=##|表现最好|$)/); - if (recentTradesMatch) { - const tradesText = recentTradesMatch[0]; - const tradeLines = tradesText.split('\n').filter(line => line.trim().startsWith('-')); - - if (tradeLines.length > 0) { - lines.push(`🔍 最近交易分析:`); - tradeLines.slice(0, 3).forEach(line => { - lines.push(` ${line.trim()}`); - }); - lines.push(''); - } - } - - // 提取最佳/最差币种 - const bestWorstMatch = performanceSection.match(/表现最好:([A-Z]+).*?\((.*?)\).*?表现最差:([A-Z]+).*?\((.*?)\)/s); - if (bestWorstMatch) { - const [, bestSymbol, bestPnl, worstSymbol, worstPnl] = bestWorstMatch; - lines.push(`💡 币种表现洞察:`); - lines.push(` 🏆 ${bestSymbol} 表现最佳 ${bestPnl}`); - lines.push(` 💔 ${worstSymbol} 表现较差 ${worstPnl}`); - lines.push(''); - } - - // 尝试提取AI的分析或决策理由 - const analysisMatch = cotTrace.match(/(?:分析|策略|决策)[::]([\s\S]*?)(?:\n\n|##|$)/); - if (analysisMatch) { - const analysis = analysisMatch[1].trim(); - if (analysis.length > 20 && analysis.length < 500) { - lines.push(`🎯 AI策略调整:`); - lines.push(` ${analysis.substring(0, 300)}${analysis.length > 300 ? '...' : ''}`); - } - } - - if (lines.length > 0) { - return lines.join('\n'); - } - } - - // 如果没有找到历史表现反馈,尝试提取整体思路 - const thinkingMatch = cotTrace.match(/(?:思考|分析|策略)[::]([\s\S]{50,500}?)(?:\n\n|##|决策|$)/); - if (thinkingMatch) { - return `🤔 AI思考过程:\n\n${thinkingMatch[1].trim().substring(0, 400)}...`; - } - - // 如果都没有,返回CoT的前面部分 - if (cotTrace.length > 100) { - return `💭 AI分析摘要:\n\n${cotTrace.substring(0, 400).trim()}...`; - } - - return null; -}