Update: Merge nofx improvements

- Frontend trading records and UI enhancements
- Optimized AI prompts and decision engine
- Performance analysis and comparison features
- Binance-style UI improvements
This commit is contained in:
tinkle
2025-10-28 21:45:28 +08:00
parent afe513b7aa
commit 14ffb0593a
13 changed files with 1440 additions and 545 deletions

View File

@@ -0,0 +1,688 @@
import { useEffect, useState } from 'react';
import useSWR from 'swr';
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;
recent_trades: TradeOutcome[];
symbol_stats: { [key: string]: SymbolPerformance };
best_symbol: string;
worst_symbol: string;
}
interface AILearningProps {
traderId: string;
}
interface DecisionRecord {
timestamp: string;
cycle_number: number;
cot_trace: string;
success: boolean;
}
const fetcher = (url: string) => fetch(url).then(res => res.json());
export default function AILearning({ traderId }: AILearningProps) {
const { data: performance, error } = useSWR<PerformanceAnalysis>(
`http://localhost:8080/api/performance?trader_id=${traderId}`,
fetcher,
{ refreshInterval: 10000 }
);
// 获取最新的决策记录查看AI的思考过程
const { data: latestDecisions } = useSWR<DecisionRecord[]>(
`http://localhost:8080/api/decisions/latest?trader_id=${traderId}`,
fetcher,
{ refreshInterval: 10000 }
);
if (error) {
return (
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
<div style={{ color: '#F6465D' }}> AI学习数据失败</div>
</div>
);
}
if (!performance) {
return (
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
<div style={{ color: '#848E9C' }}>📊 ...</div>
</div>
);
}
if (!performance || performance.total_trades === 0) {
return (
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
<div className="flex items-center gap-2 mb-2">
<span className="text-xl">🧠</span>
<h2 className="text-lg font-bold" style={{ color: '#EAECEF' }}>AI </h2>
</div>
<div style={{ color: '#848E9C' }}>
</div>
</div>
);
}
// 安全地获取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)
);
// 提取AI的最新反思从CoT trace中
const latestReflection = extractReflectionFromCoT(latestDecisions?.[0]?.cot_trace);
return (
<div className="space-y-6">
{/* 标题区 - 更简洁 */}
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl" style={{
background: 'linear-gradient(135deg, #8B5CF6 0%, #6366F1 100%)',
boxShadow: '0 4px 14px rgba(139, 92, 246, 0.4)'
}}>
🧠
</div>
<div>
<h2 className="text-2xl font-bold" style={{ color: '#EAECEF' }}>AI Learning & Reflection</h2>
<p className="text-sm" style={{ color: '#848E9C' }}>
{performance.total_trades} trades analyzed · Real-time evolution
</p>
</div>
</div>
{/* 主要内容:现代化网格布局 */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
{/* 左侧大区域AI反思 + 核心指标 (5列) */}
<div className="lg:col-span-5 space-y-6">
{/* AI最新反思 - 渐变卡片 */}
{latestReflection && latestDecisions && latestDecisions.length > 0 && (
<div className="rounded-2xl p-6 relative overflow-hidden" style={{
background: 'linear-gradient(135deg, #1E1B4B 0%, #312E81 50%, #1E293B 100%)',
border: '1px solid rgba(139, 92, 246, 0.2)',
boxShadow: '0 10px 40px rgba(139, 92, 246, 0.15)'
}}>
{/* 背景装饰 */}
<div className="absolute top-0 right-0 w-64 h-64 rounded-full opacity-10" style={{
background: 'radial-gradient(circle, #8B5CF6 0%, transparent 70%)',
filter: 'blur(40px)'
}} />
<div className="relative">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-lg flex items-center justify-center text-xl" style={{
background: 'rgba(139, 92, 246, 0.2)',
border: '1px solid rgba(139, 92, 246, 0.3)'
}}>
🎯
</div>
<div>
<h3 className="text-base font-bold" style={{ color: '#C4B5FD' }}>
Latest Reflection
</h3>
<p className="text-xs" style={{ color: '#94A3B8' }}>
Cycle #{latestDecisions[0].cycle_number} · {new Date(latestDecisions[0].timestamp).toLocaleTimeString()}
</p>
</div>
</div>
<div className="rounded-xl p-4 text-sm leading-relaxed whitespace-pre-wrap" style={{
background: 'rgba(0, 0, 0, 0.4)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(139, 92, 246, 0.1)',
color: '#DDD6FE',
fontFamily: 'ui-sans-serif, system-ui'
}}>
{latestReflection}
</div>
{latestDecisions[0].cot_trace && (
<details className="mt-4">
<summary className="cursor-pointer text-sm font-semibold flex items-center gap-2 hover:opacity-80 transition-opacity" style={{ color: '#A78BFA' }}>
<span>📋 Full Chain of Thought</span>
</summary>
<div className="mt-3 rounded-xl p-4 text-xs leading-relaxed whitespace-pre-wrap max-h-80 overflow-y-auto" style={{
background: 'rgba(0, 0, 0, 0.5)',
border: '1px solid rgba(139, 92, 246, 0.15)',
color: '#A5B4FC',
fontFamily: 'ui-monospace, monospace'
}}>
{latestDecisions[0].cot_trace}
</div>
</details>
)}
</div>
</div>
)}
{/* 核心指标网格 - 玻璃态设计 */}
<div className="grid grid-cols-2 gap-4">
{/* 总交易数 */}
<div className="rounded-xl p-4 backdrop-blur-sm" style={{
background: 'rgba(30, 35, 41, 0.6)',
border: '1px solid rgba(99, 102, 241, 0.2)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
}}>
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>Total Trades</div>
<div className="text-3xl font-bold mono" style={{ color: '#E0E7FF' }}>
{performance.total_trades}
</div>
</div>
{/* 胜率 */}
<div className="rounded-xl p-4 backdrop-blur-sm" style={{
background: (performance.win_rate || 0) >= 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)'
}}>
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>Win Rate</div>
<div className="text-3xl font-bold mono" style={{
color: (performance.win_rate || 0) >= 50 ? '#10B981' : '#F87171'
}}>
{(performance.win_rate || 0).toFixed(1)}%
</div>
<div className="text-xs mt-1" style={{ color: '#94A3B8' }}>
{performance.winning_trades || 0}W / {performance.losing_trades || 0}L
</div>
</div>
{/* 平均盈利 */}
<div className="rounded-xl p-4 backdrop-blur-sm" style={{
background: 'rgba(14, 203, 129, 0.08)',
border: '1px solid rgba(14, 203, 129, 0.2)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
}}>
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>Avg Win</div>
<div className="text-3xl font-bold mono" style={{ color: '#10B981' }}>
+{(performance.avg_win || 0).toFixed(2)}%
</div>
</div>
{/* 平均亏损 */}
<div className="rounded-xl p-4 backdrop-blur-sm" style={{
background: 'rgba(246, 70, 93, 0.08)',
border: '1px solid rgba(246, 70, 93, 0.2)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
}}>
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>Avg Loss</div>
<div className="text-3xl font-bold mono" style={{ color: '#F87171' }}>
{(performance.avg_loss || 0).toFixed(2)}%
</div>
</div>
</div>
{/* 盈亏比 - 突出显示 */}
<div className="rounded-2xl p-6 relative overflow-hidden" style={{
background: 'linear-gradient(135deg, rgba(240, 185, 11, 0.15) 0%, rgba(252, 213, 53, 0.05) 100%)',
border: '1px solid rgba(240, 185, 11, 0.3)',
boxShadow: '0 8px 24px rgba(240, 185, 11, 0.2)'
}}>
<div className="absolute top-0 right-0 w-40 h-40 rounded-full opacity-20" style={{
background: 'radial-gradient(circle, #F0B90B 0%, transparent 70%)',
filter: 'blur(30px)'
}} />
<div className="relative flex items-center justify-between">
<div>
<div className="text-sm font-semibold mb-1" style={{ color: '#FCD34D' }}>Profit Factor</div>
<div className="text-xs" style={{ color: '#94A3B8' }}>
Avg Win ÷ Avg Loss
</div>
</div>
<div className="text-5xl font-bold mono" style={{
color: (performance.profit_factor || 0) >= 2.0 ? '#10B981' :
(performance.profit_factor || 0) >= 1.5 ? '#F0B90B' :
(performance.profit_factor || 0) >= 1.0 ? '#FB923C' : '#F87171'
}}>
{(performance.profit_factor || 0) > 0 ? (performance.profit_factor || 0).toFixed(2) : 'N/A'}
</div>
</div>
<div className="mt-3 text-xs font-semibold" style={{
color: (performance.profit_factor || 0) >= 2.0 ? '#10B981' :
(performance.profit_factor || 0) >= 1.5 ? '#F0B90B' : '#94A3B8'
}}>
{(performance.profit_factor || 0) >= 2.0 && '🔥 Excellent - Strong profitability'}
{(performance.profit_factor || 0) >= 1.5 && (performance.profit_factor || 0) < 2.0 && '✓ Good - Stable profits'}
{(performance.profit_factor || 0) >= 1.0 && (performance.profit_factor || 0) < 1.5 && '⚠️ Fair - Needs optimization'}
{(performance.profit_factor || 0) > 0 && (performance.profit_factor || 0) < 1.0 && '❌ Poor - Losses exceed gains'}
</div>
</div>
</div>
{/* 左侧结束 */}
{/* 中间列:数据表格 (4列) */}
<div className="lg:col-span-4 space-y-6">
{/* 最佳/最差币种 */}
{(performance.best_symbol || performance.worst_symbol) && (
<div className="grid grid-cols-2 gap-4">
{performance.best_symbol && (
<div className="rounded-xl p-5 backdrop-blur-sm" style={{
background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.15) 0%, rgba(14, 203, 129, 0.05) 100%)',
border: '1px solid rgba(16, 185, 129, 0.3)',
boxShadow: '0 4px 16px rgba(16, 185, 129, 0.1)'
}}>
<div className="flex items-center gap-2 mb-3">
<span className="text-xl">🏆</span>
<span className="text-xs font-semibold" style={{ color: '#6EE7B7' }}>Best Performer</span>
</div>
<div className="text-2xl font-bold mono mb-1" style={{ color: '#10B981' }}>
{performance.best_symbol}
</div>
{symbolStats[performance.best_symbol] && (
<div className="text-sm font-semibold" style={{ color: '#6EE7B7' }}>
{symbolStats[performance.best_symbol].total_pn_l > 0 ? '+' : ''}
{symbolStats[performance.best_symbol].total_pn_l.toFixed(2)}% P&L
</div>
)}
</div>
)}
{performance.worst_symbol && (
<div className="rounded-xl p-5 backdrop-blur-sm" style={{
background: 'linear-gradient(135deg, rgba(248, 113, 113, 0.15) 0%, rgba(246, 70, 93, 0.05) 100%)',
border: '1px solid rgba(248, 113, 113, 0.3)',
boxShadow: '0 4px 16px rgba(248, 113, 113, 0.1)'
}}>
<div className="flex items-center gap-2 mb-3">
<span className="text-xl">📉</span>
<span className="text-xs font-semibold" style={{ color: '#FCA5A5' }}>Worst Performer</span>
</div>
<div className="text-2xl font-bold mono mb-1" style={{ color: '#F87171' }}>
{performance.worst_symbol}
</div>
{symbolStats[performance.worst_symbol] && (
<div className="text-sm font-semibold" style={{ color: '#FCA5A5' }}>
{symbolStats[performance.worst_symbol].total_pn_l > 0 ? '+' : ''}
{symbolStats[performance.worst_symbol].total_pn_l.toFixed(2)}% P&L
</div>
)}
</div>
)}
</div>
)}
{/* 币种表现统计 - 现代化表格 */}
{symbolStatsList.length > 0 && (
<div className="rounded-2xl overflow-hidden" style={{
background: 'rgba(30, 35, 41, 0.4)',
border: '1px solid rgba(99, 102, 241, 0.2)',
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.2)'
}}>
<div className="p-5 border-b" style={{ borderColor: 'rgba(99, 102, 241, 0.2)', background: 'rgba(30, 35, 41, 0.6)' }}>
<h3 className="font-bold flex items-center gap-2" style={{ color: '#E0E7FF' }}>
📊 Symbol Performance
</h3>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr style={{ background: 'rgba(15, 23, 42, 0.6)' }}>
<th className="text-left px-4 py-3 text-xs font-semibold" style={{ color: '#94A3B8' }}>Symbol</th>
<th className="text-right px-4 py-3 text-xs font-semibold" style={{ color: '#94A3B8' }}>Trades</th>
<th className="text-right px-4 py-3 text-xs font-semibold" style={{ color: '#94A3B8' }}>Win Rate</th>
<th className="text-right px-4 py-3 text-xs font-semibold" style={{ color: '#94A3B8' }}>Total P&L</th>
<th className="text-right px-4 py-3 text-xs font-semibold" style={{ color: '#94A3B8' }}>Avg P&L</th>
</tr>
</thead>
<tbody>
{symbolStatsList.map((stat, idx) => (
<tr key={stat.symbol} className="transition-colors hover:bg-white/5" style={{
borderTop: idx > 0 ? '1px solid rgba(99, 102, 241, 0.1)' : 'none'
}}>
<td className="px-4 py-3">
<span className="font-bold mono text-sm" style={{ color: '#E0E7FF' }}>{stat.symbol}</span>
</td>
<td className="px-4 py-3 text-right mono text-sm" style={{ color: '#CBD5E1' }}>
{stat.total_trades}
</td>
<td className="px-4 py-3 text-right mono text-sm font-semibold" style={{
color: (stat.win_rate || 0) >= 50 ? '#10B981' : '#F87171'
}}>
{(stat.win_rate || 0).toFixed(1)}%
</td>
<td className="px-4 py-3 text-right mono text-sm font-bold" style={{
color: (stat.total_pn_l || 0) > 0 ? '#10B981' : '#F87171'
}}>
{(stat.total_pn_l || 0) > 0 ? '+' : ''}{(stat.total_pn_l || 0).toFixed(2)}%
</td>
<td className="px-4 py-3 text-right mono text-sm" style={{
color: (stat.avg_pn_l || 0) > 0 ? '#10B981' : '#F87171'
}}>
{(stat.avg_pn_l || 0) > 0 ? '+' : ''}{(stat.avg_pn_l || 0).toFixed(2)}%
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
{/* 中间列结束 */}
{/* 右侧列:历史成交记录 (3列) */}
<div className="lg:col-span-3">
<div className="rounded-2xl overflow-hidden sticky top-24" style={{
background: 'rgba(30, 35, 41, 0.4)',
border: '1px solid rgba(240, 185, 11, 0.2)',
maxHeight: 'calc(100vh - 200px)'
}}>
{/* 标题 - 固定在顶部 */}
<div className="p-4 border-b backdrop-blur-sm" style={{
background: 'rgba(240, 185, 11, 0.1)',
borderColor: 'rgba(240, 185, 11, 0.3)'
}}>
<div className="flex items-center gap-2">
<span className="text-xl">📜</span>
<div>
<h3 className="font-bold text-sm" style={{ color: '#FCD34D' }}>Trade History</h3>
<p className="text-xs" style={{ color: '#94A3B8' }}>
{performance?.recent_trades && performance.recent_trades.length > 0
? `Recent ${performance.recent_trades.length} completed trades`
: 'Completed trades will appear here'}
</p>
</div>
</div>
</div>
{/* 滚动内容区域 */}
<div className="overflow-y-auto p-4 space-y-3" style={{ maxHeight: 'calc(100vh - 300px)' }}>
{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 (
<div key={idx} className="rounded-xl p-4 backdrop-blur-sm transition-all hover:scale-[1.02]" style={{
background: isRecent
? isProfitable
? 'linear-gradient(135deg, rgba(16, 185, 129, 0.15) 0%, rgba(14, 203, 129, 0.05) 100%)'
: 'linear-gradient(135deg, rgba(248, 113, 113, 0.15) 0%, rgba(246, 70, 93, 0.05) 100%)'
: 'rgba(30, 35, 41, 0.4)',
border: isRecent
? isProfitable ? '1px solid rgba(16, 185, 129, 0.4)' : '1px solid rgba(248, 113, 113, 0.4)'
: '1px solid rgba(71, 85, 105, 0.3)',
boxShadow: isRecent
? '0 4px 16px rgba(139, 92, 246, 0.2)'
: '0 2px 8px rgba(0, 0, 0, 0.1)'
}}>
{/* 头部:币种和方向 */}
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<span className="text-base font-bold mono" style={{ color: '#E0E7FF' }}>
{trade.symbol}
</span>
<span className="text-xs px-2 py-1 rounded font-bold" style={{
background: trade.side === 'long' ? 'rgba(14, 203, 129, 0.2)' : 'rgba(246, 70, 93, 0.2)',
color: trade.side === 'long' ? '#10B981' : '#F87171'
}}>
{trade.side.toUpperCase()}
</span>
{isRecent && (
<span className="text-xs px-2 py-0.5 rounded font-semibold" style={{
background: 'rgba(240, 185, 11, 0.2)',
color: '#FCD34D'
}}>
Latest
</span>
)}
</div>
<div className="text-lg font-bold mono" style={{
color: isProfitable ? '#10B981' : '#F87171'
}}>
{isProfitable ? '+' : ''}{trade.pn_l_pct.toFixed(2)}%
</div>
</div>
{/* 价格信息 */}
<div className="grid grid-cols-2 gap-2 mb-3 text-xs">
<div>
<div style={{ color: '#94A3B8' }}>Entry</div>
<div className="font-mono font-semibold" style={{ color: '#CBD5E1' }}>
{trade.open_price.toFixed(4)}
</div>
</div>
<div className="text-right">
<div style={{ color: '#94A3B8' }}>Exit</div>
<div className="font-mono font-semibold" style={{ color: '#CBD5E1' }}>
{trade.close_price.toFixed(4)}
</div>
</div>
</div>
{/* 盈亏详情 */}
<div className="rounded-lg p-2 mb-2" style={{
background: isProfitable ? 'rgba(16, 185, 129, 0.1)' : 'rgba(248, 113, 113, 0.1)'
}}>
<div className="flex items-center justify-between text-xs">
<span style={{ color: '#94A3B8' }}>P&L</span>
<span className="font-bold mono" style={{
color: isProfitable ? '#10B981' : '#F87171'
}}>
{isProfitable ? '+' : ''}{trade.pn_l.toFixed(2)} USDT
</span>
</div>
</div>
{/* 时间和持仓时长 */}
<div className="flex items-center justify-between text-xs" style={{ color: '#94A3B8' }}>
<span> {formatDuration(trade.duration)}</span>
{trade.was_stop_loss && (
<span className="px-2 py-0.5 rounded font-semibold" style={{
background: 'rgba(248, 113, 113, 0.2)',
color: '#FCA5A5'
}}>
Stop Loss
</span>
)}
</div>
{/* 交易时间 */}
<div className="text-xs mt-2 pt-2 border-t" style={{
color: '#64748B',
borderColor: 'rgba(71, 85, 105, 0.3)'
}}>
{new Date(trade.close_time).toLocaleString('en-US', {
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})}
</div>
</div>
);
})
) : (
<div className="p-6 text-center">
<div className="text-4xl mb-2 opacity-50">📜</div>
<div style={{ color: '#94A3B8' }}>No completed trades yet</div>
</div>
)}
</div>
</div>
</div>
{/* 右侧列结束 */}
</div>
{/* 3列布局结束 */}
{/* AI学习说明 - 现代化设计 */}
<div className="rounded-2xl p-6 backdrop-blur-sm" style={{
background: 'linear-gradient(135deg, rgba(240, 185, 11, 0.1) 0%, rgba(252, 213, 53, 0.05) 100%)',
border: '1px solid rgba(240, 185, 11, 0.2)',
boxShadow: '0 4px 16px rgba(240, 185, 11, 0.1)'
}}>
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-lg flex items-center justify-center text-xl flex-shrink-0" style={{
background: 'rgba(240, 185, 11, 0.2)',
border: '1px solid rgba(240, 185, 11, 0.3)'
}}>
💡
</div>
<div>
<h3 className="font-bold mb-3 text-base" style={{ color: '#FCD34D' }}>How AI Learns & Evolves</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 text-sm">
<div className="flex items-start gap-2">
<span style={{ color: '#F0B90B' }}></span>
<span style={{ color: '#CBD5E1' }}>Analyzes last 20 trading cycles before each decision</span>
</div>
<div className="flex items-start gap-2">
<span style={{ color: '#F0B90B' }}></span>
<span style={{ color: '#CBD5E1' }}>Identifies best & worst performing symbols</span>
</div>
<div className="flex items-start gap-2">
<span style={{ color: '#F0B90B' }}></span>
<span style={{ color: '#CBD5E1' }}>Optimizes position sizing based on win rate</span>
</div>
<div className="flex items-start gap-2">
<span style={{ color: '#F0B90B' }}></span>
<span style={{ color: '#CBD5E1' }}>Avoids repeating past mistakes</span>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
// 格式化持仓时长
function formatDuration(duration: string | undefined): string {
if (!duration) return '-';
// duration格式: "1h23m45s" or "23m45.123s"
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;
}
// 从CoT trace中提取AI的历史表现分析和反思
function extractReflectionFromCoT(cotTrace: string | undefined): string | null {
if (!cotTrace) return null;
// 优先提取【历史经验反思】部分(新格式)
const reflectionMatch = cotTrace.match(/【历史经验反思】\s*([\s\S]*?)(?=【|$)/);
if (reflectionMatch) {
const reflection = reflectionMatch[1].trim();
if (reflection.length > 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;
}

View File

@@ -45,151 +45,155 @@ export function CompetitionPage() {
const leader = sortedTraders[0];
return (
<div className="space-y-6 animate-fade-in">
{/* Competition Header - Binance Style */}
<div className="rounded p-6 animate-scale-in" style={{ background: 'linear-gradient(135deg, rgba(240, 185, 11, 0.15) 0%, rgba(252, 213, 53, 0.05) 100%)', border: '1px solid rgba(240, 185, 11, 0.2)', boxShadow: '0 0 30px rgba(240, 185, 11, 0.15)' }}>
<div className="flex items-center justify-between">
<div className="space-y-5 animate-fade-in">
{/* Competition Header - 精简版 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl" style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)'
}}>
🏆
</div>
<div>
<h1 className="text-2xl font-bold mb-1 flex items-center gap-3" style={{ color: '#EAECEF' }}>
<span>🏆</span>
AI Trading Competition
<span className="text-sm font-normal px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.15)', color: '#F0B90B' }}>
{competition.count} Traders
<h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
AI Competition
<span className="text-xs font-normal px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.15)', color: '#F0B90B' }}>
{competition.count} traders
</span>
</h1>
<p className="text-sm" style={{ color: '#848E9C' }}>
Qwen vs DeepSeek · Real-time Performance Battle
<p className="text-xs" style={{ color: '#848E9C' }}>
Qwen vs DeepSeek · Live Battle
</p>
</div>
<div className="text-right">
<div className="text-xs" style={{ color: '#848E9C' }}>Current Leader</div>
<div className="text-lg font-bold" style={{ color: '#F0B90B' }}>{leader?.trader_name}</div>
</div>
<div className="text-right">
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>🥇 Leader</div>
<div className="text-lg font-bold" style={{ color: '#F0B90B' }}>{leader?.trader_name}</div>
<div className="text-sm font-semibold" style={{ color: leader.total_pnl >= 0 ? '#0ECB81' : '#F6465D' }}>
{leader.total_pnl >= 0 ? '+' : ''}{leader.total_pnl_pct.toFixed(2)}%
</div>
</div>
</div>
{/* Leader Board - Binance Style */}
<div className="binance-card p-6 animate-slide-in" style={{ animationDelay: '0.1s' }}>
<div className="flex items-center justify-between mb-5">
<h2 className="text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
🥇 Leaderboard
</h2>
<div className="text-xs px-3 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', color: '#F0B90B', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
LIVE
{/* Left/Right Split: Performance Chart + Leaderboard */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
{/* Left: Performance Comparison Chart */}
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.1s' }}>
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
📈 Performance Comparison
</h2>
<div className="text-xs" style={{ color: '#848E9C' }}>
Real-time PnL %
</div>
</div>
<ComparisonChart traders={sortedTraders} />
</div>
<div className="space-y-3">
{sortedTraders.map((trader, index) => {
const isLeader = index === 0;
const aiModelColor = trader.ai_model === 'qwen' ? '#c084fc' : '#60a5fa';
return (
<div
key={trader.trader_id}
className="rounded p-4 transition-all duration-300 hover:translate-y-[-2px]"
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 4px 20px rgba(240, 185, 11, 0.15), 0 0 0 1px rgba(240, 185, 11, 0.2)' : '0 2px 8px rgba(0, 0, 0, 0.3)'
}}
>
<div className="flex items-center justify-between">
{/* Rank & Name */}
<div className="flex items-center gap-4">
<div className="text-3xl w-8">
{index === 0 ? '🥇' : index === 1 ? '🥈' : '🥉'}
</div>
<div>
<div className="font-bold text-base" style={{ color: '#EAECEF' }}>{trader.trader_name}</div>
<div className="text-xs mono font-semibold" style={{ color: aiModelColor }}>
{trader.ai_model.toUpperCase()} Model
{/* Right: Leaderboard */}
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.1s' }}>
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
🥇 Leaderboard
</h2>
<div className="text-xs px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', color: '#F0B90B', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
LIVE
</div>
</div>
<div className="space-y-2">
{sortedTraders.map((trader, index) => {
const isLeader = index === 0;
const aiModelColor = trader.ai_model === 'qwen' ? '#c084fc' : '#60a5fa';
return (
<div
key={trader.trader_id}
className="rounded p-3 transition-all duration-300 hover:translate-y-[-1px]"
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)'
}}
>
<div className="flex items-center justify-between">
{/* Rank & Name */}
<div className="flex items-center gap-3">
<div className="text-2xl w-6">
{index === 0 ? '🥇' : index === 1 ? '🥈' : '🥉'}
</div>
</div>
</div>
{/* Stats */}
<div className="flex items-center gap-6">
{/* Total Equity */}
<div className="text-right">
<div className="text-xs" style={{ color: '#848E9C' }}>Total Equity</div>
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>
{trader.total_equity.toFixed(2)} USDT
<div>
<div className="font-bold text-sm" style={{ color: '#EAECEF' }}>{trader.trader_name}</div>
<div className="text-xs mono font-semibold" style={{ color: aiModelColor }}>
{trader.ai_model.toUpperCase()}
</div>
</div>
</div>
{/* P&L */}
<div className="text-right min-w-[120px]">
<div className="text-xs" style={{ color: '#848E9C' }}>Total P&L</div>
<div
className="text-2xl font-bold mono"
style={{ color: trader.total_pnl >= 0 ? '#0ECB81' : '#F6465D' }}
>
{trader.total_pnl >= 0 ? '+' : ''}
{trader.total_pnl_pct.toFixed(2)}%
{/* Stats */}
<div className="flex items-center gap-3">
{/* Total Equity */}
<div className="text-right">
<div className="text-xs" style={{ color: '#848E9C' }}>Equity</div>
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
{trader.total_equity.toFixed(2)}
</div>
</div>
<div className="text-xs mono" style={{ color: '#848E9C' }}>
{trader.total_pnl >= 0 ? '+' : ''}
{trader.total_pnl.toFixed(2)} USDT
</div>
</div>
{/* Positions */}
<div className="text-right">
<div className="text-xs" style={{ color: '#848E9C' }}>Positions</div>
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>
{trader.position_count}
{/* P&L */}
<div className="text-right min-w-[90px]">
<div className="text-xs" style={{ color: '#848E9C' }}>P&L</div>
<div
className="text-lg font-bold mono"
style={{ color: trader.total_pnl >= 0 ? '#0ECB81' : '#F6465D' }}
>
{trader.total_pnl >= 0 ? '+' : ''}
{trader.total_pnl_pct.toFixed(2)}%
</div>
<div className="text-xs mono" style={{ color: '#848E9C' }}>
{trader.total_pnl >= 0 ? '+' : ''}{trader.total_pnl.toFixed(2)}
</div>
</div>
<div className="text-xs" style={{ color: '#848E9C' }}>
Margin: {trader.margin_used_pct.toFixed(1)}%
{/* Positions */}
<div className="text-right">
<div className="text-xs" style={{ color: '#848E9C' }}>Pos</div>
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
{trader.position_count}
</div>
<div className="text-xs" style={{ color: '#848E9C' }}>
{trader.margin_used_pct.toFixed(1)}%
</div>
</div>
</div>
{/* Cycles */}
<div className="text-right">
<div className="text-xs" style={{ color: '#848E9C' }}>Cycles</div>
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>{trader.call_count}</div>
</div>
{/* Status */}
<div>
<div
className="px-3 py-1 rounded text-xs font-bold"
style={trader.is_running
? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
}
>
{trader.is_running ? 'RUNNING' : 'STOPPED'}
{/* Status */}
<div>
<div
className="px-2 py-1 rounded text-xs font-bold"
style={trader.is_running
? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
}
>
{trader.is_running ? '●' : '○'}
</div>
</div>
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
{/* Performance Comparison Chart */}
<div className="binance-card p-6 animate-slide-in" style={{ animationDelay: '0.2s' }}>
<div className="flex items-center justify-between mb-5">
<h2 className="text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
📈 Performance Comparison
</h2>
<div className="text-xs" style={{ color: '#848E9C' }}>
Real-time PnL % Chart
);
})}
</div>
</div>
<ComparisonChart traders={sortedTraders} />
</div>
{/* Head-to-Head Stats */}
{competition.traders.length === 2 && (
<div className="binance-card p-6 animate-slide-in" style={{ animationDelay: '0.3s' }}>
<h2 className="text-xl font-bold mb-5 flex items-center gap-2" style={{ color: '#EAECEF' }}>
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.3s' }}>
<h2 className="text-lg font-bold mb-4 flex items-center gap-2" style={{ color: '#EAECEF' }}>
Head-to-Head Battle
</h2>
<div className="grid grid-cols-2 gap-6">
<div className="grid grid-cols-2 gap-4">
{sortedTraders.map((trader, index) => {
const isWinning = index === 0;
const opponent = sortedTraders[1 - index];
@@ -198,37 +202,37 @@ export function CompetitionPage() {
return (
<div
key={trader.trader_id}
className="p-6 rounded transition-all duration-300 hover:scale-105"
className="p-4 rounded transition-all duration-300 hover:scale-[1.02]"
style={isWinning
? {
background: 'linear-gradient(135deg, rgba(14, 203, 129, 0.08) 0%, rgba(14, 203, 129, 0.02) 100%)',
border: '2px solid rgba(14, 203, 129, 0.3)',
boxShadow: '0 4px 20px rgba(14, 203, 129, 0.15)'
boxShadow: '0 3px 15px rgba(14, 203, 129, 0.12)'
}
: {
background: '#0B0E11',
border: '1px solid #2B3139',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)'
boxShadow: '0 1px 4px rgba(0, 0, 0, 0.3)'
}
}
>
<div className="text-center">
<div
className="text-lg font-bold mb-2"
className="text-base font-bold mb-2"
style={{ color: trader.ai_model === 'qwen' ? '#c084fc' : '#60a5fa' }}
>
{trader.trader_name}
</div>
<div className="text-3xl font-bold mono mb-2" style={{ color: trader.total_pnl >= 0 ? '#0ECB81' : '#F6465D' }}>
<div className="text-2xl font-bold mono mb-1" style={{ color: trader.total_pnl >= 0 ? '#0ECB81' : '#F6465D' }}>
{trader.total_pnl >= 0 ? '+' : ''}{trader.total_pnl_pct.toFixed(2)}%
</div>
{isWinning && gap > 0 && (
<div className="text-sm font-semibold" style={{ color: '#0ECB81' }}>
<div className="text-xs font-semibold" style={{ color: '#0ECB81' }}>
Leading by {gap.toFixed(2)}%
</div>
)}
{!isWinning && gap < 0 && (
<div className="text-sm font-semibold" style={{ color: '#F6465D' }}>
<div className="text-xs font-semibold" style={{ color: '#F6465D' }}>
Behind by {Math.abs(gap).toFixed(2)}%
</div>
)}

View File

@@ -149,9 +149,9 @@ export function EquityChart({ traderId }: EquityChartProps) {
};
return (
<div className="binance-card p-6 animate-fade-in">
<div className="binance-card p-5 animate-fade-in">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-bold mb-2" style={{ color: '#EAECEF' }}>线</h3>
<div className="flex items-baseline gap-4">
@@ -205,8 +205,8 @@ export function EquityChart({ traderId }: EquityChartProps) {
{/* Chart */}
<div className="my-2" style={{ borderRadius: '8px', overflow: 'hidden' }}>
<ResponsiveContainer width="100%" height={420}>
<LineChart data={chartData} margin={{ top: 20, right: 30, left: 10, bottom: 40 }}>
<ResponsiveContainer width="100%" height={280}>
<LineChart data={chartData} margin={{ top: 10, right: 20, left: 5, bottom: 30 }}>
<defs>
<linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#F0B90B" stopOpacity={0.8} />
@@ -257,26 +257,26 @@ export function EquityChart({ traderId }: EquityChartProps) {
</div>
{/* Footer Stats */}
<div className="mt-5 grid grid-cols-4 gap-4 pt-5" style={{ borderTop: '1px solid #2B3139' }}>
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
<div className="mt-3 grid grid-cols-4 gap-3 pt-3" style={{ borderTop: '1px solid #2B3139' }}>
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}></div>
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
{initialBalance.toFixed(2)} USDT
</div>
</div>
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}></div>
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
{currentValue.raw_equity.toFixed(2)} USDT
</div>
</div>
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}></div>
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>{history.length} </div>
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>{history.length} </div>
</div>
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}></div>
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
{history.length > MAX_DISPLAY_POINTS
? `最近 ${MAX_DISPLAY_POINTS}`
: '全部数据'