/** * QuantAlpha Pro v2.1 - Luna Sentiment Trading Dashboard * React 18 + Tailwind + Chart.js */ const { useState, useEffect, useCallback } = React; // ============================================ // API Client with JWT + HMAC Authentication // ============================================ const API_BASE = window.location.origin; const api = { token: localStorage.getItem('qa_token') || '', refreshToken: localStorage.getItem('qa_refresh') || '', async request(method, path, body = null, useSignature = false) { const headers = { 'Content-Type': 'application/json', }; if (this.token) { headers['Authorization'] = `Bearer ${this.token}`; } const options = { method, headers }; if (body) { options.body = JSON.stringify(body); } const res = await fetch(`${API_BASE}${path}`, options); if (res.status === 401) { // Try refresh const refreshed = await this.refresh(); if (refreshed) { return this.request(method, path, body, useSignature); } throw new Error('Session expired'); } if (!res.ok) { const err = await res.json().catch(() => ({ error: 'Request failed' })); throw new Error(err.error || err.detail || 'Request failed'); } return res.json(); }, async refresh() { if (!this.refreshToken) return false; try { const res = await fetch(`${API_BASE}/api/v1/auth/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: this.refreshToken }), }); if (res.ok) { const data = await res.json(); this.token = data.access_token; localStorage.setItem('qa_token', this.token); return true; } } catch {} return false; }, login(username, password) { return this.request('POST', '/api/v1/auth/login', { username, password }); }, getStrategies() { return this.request('GET', '/api/v1/strategies'); }, getStrategy(id) { return this.request('GET', `/api/v1/strategies/${id}`); }, toggleStrategy(id) { return this.request('POST', `/api/v1/strategies/${id}/toggle`); }, updateStrategyConfig(id, config) { return this.request('PUT', `/api/v1/strategies/${id}/config`, { config }); }, getRiskMetrics() { return this.request('GET', '/api/v1/risk/metrics'); }, getKellyParams() { return this.request('GET', '/api/v1/risk/kelly'); }, activateKillSwitch(level, confirmCode) { return this.request('POST', '/api/v1/risk/kill-switch', { level, confirm_code: confirmCode, }); }, deactivateKillSwitch() { return this.request('DELETE', '/api/v1/risk/kill-switch'); }, getBinanceSpot() { return this.request('GET', '/api/v1/binance/spot'); }, getMarketOverview() { return this.request('GET', '/api/v1/binance/market-overview'); }, getEquityHistory() { return this.request('GET', '/api/v1/equity/history?days=90'); }, getSystemStatus() { return this.request('GET', '/api/v1/system/status'); }, getAuditLogs() { return this.request('GET', '/api/v1/audit-logs'); }, // Luna Sentiment API Methods getLunaSentiment(symbol) { return this.request('GET', `/api/v1/luna/sentiment/${symbol}`); }, getLunaAllSentiments() { return this.request('GET', '/api/v1/luna/sentiment'); }, getLunaHistory(symbol, hours = 24) { return this.request('GET', `/api/v1/luna/history/${symbol}?hours=${hours}`); }, getLunaSignals() { return this.request('GET', '/api/v1/luna/signals'); }, getLunaProfitability() { return this.request('GET', '/api/v1/luna/profitability'); }, triggerLunaCollect() { return this.request('POST', '/api/v1/luna/collect'); }, triggerLunaBacktest() { return this.request('POST', '/api/v1/luna/backtest'); }, }; // ============================================ // Login Component // ============================================ function LoginScreen({ onLogin }) { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); setLoading(true); setError(''); try { const data = await api.login(username, password); api.token = data.access_token; api.refreshToken = data.refresh_token; localStorage.setItem('qa_token', data.access_token); localStorage.setItem('qa_refresh', data.refresh_token); localStorage.setItem('qa_user', JSON.stringify(data.user)); onLogin(data.user); } catch (err) { setError(err.message || 'Login failed'); } finally { setLoading(false); } }; return (

QuantAlpha Pro

v2.1 — Luna Sentiment Trading

{error && (
{error}
)}
setUsername(e.target.value)} placeholder="Enter username" required />
setPassword(e.target.value)} placeholder="Enter password" required />
Default: username: admin / password: admin_qa2024!
); } // ============================================ // Sidebar Component // ============================================ function Sidebar({ activeTab, setActiveTab, user, onLogout }) { const menuItems = [ { id: 'dashboard', icon: 'fa-chart-line', label: '总览仪表盘', perm: 'read:dashboard' }, { id: 'strategy', icon: 'fa-robot', label: 'MA交叉策略', perm: 'read:strategies' }, { id: 'luna', icon: 'fa-moon', label: 'Luna舆论策略', perm: 'read:strategies' }, { id: 'positions', icon: 'fa-wallet', label: '持仓管理', perm: 'read:dashboard' }, { id: 'risk', icon: 'fa-shield-halved', label: '风险管理', perm: 'read:metrics' }, { id: 'audit', icon: 'fa-list-check', label: '审计日志', perm: 'read:audit_logs' }, ]; const hasPerm = (perm) => { if (!user || !user.permissions) return false; return user.permissions.includes(perm) || user.role === 'admin'; }; return (
QA
QuantAlpha Pro
v2.1 Luna
Main Menu
{menuItems.map(item => ( hasPerm(item.perm) && (
setActiveTab(item.id)} > {item.label}
) ))}
Logged in as
{user?.username}
{user?.role}
Logout
); } // ============================================ // Dashboard Component // ============================================ function Dashboard({ user }) { const [marketData, setMarketData] = useState({}); const [spotData, setSpotData] = useState({ balances: [] }); const [equityData, setEquityData] = useState({}); const [systemStatus, setSystemStatus] = useState({}); const [loading, setLoading] = useState(true); useEffect(() => { const fetchData = async () => { try { const [market, spot, equity, status] = await Promise.all([ api.getMarketOverview(), api.getBinanceSpot(), api.getEquityHistory(), api.getSystemStatus(), ]); setMarketData(market); setSpotData(spot); setEquityData(equity); setSystemStatus(status); } catch (err) { console.error('Dashboard data error:', err); } finally { setLoading(false); } }; fetchData(); const interval = setInterval(fetchData, 30000); return () => clearInterval(interval); }, []); if (loading) { return (
); } const prices = marketData.spot_prices || {}; const balances = spotData.balances || []; const totalValue = balances.reduce((sum, b) => sum + (b.value_usdt || 0), 0); return (

总览仪表盘

实时监控 · 最后更新: {new Date().toLocaleTimeString()}
System Online {systemStatus.kill_switch_active && ( KILL SWITCH ACTIVE )}
{/* Key Metrics */}
总资产估值
${totalValue.toLocaleString('en-US', { minimumFractionDigits: 2 })}
组合总市值
策略盈亏
= 0 ? 'trend-up' : 'trend-down'}`}> {(equityData.total_pnl || 0) >= 0 ? '+' : ''}${(equityData.total_pnl || 0).toFixed(2)}
累计实现盈亏
活跃策略
{Object.keys(equityData.strategy_breakdown || {}).length}
运行中
Kill Switch
{systemStatus.kill_switch_active ? 'ACTIVE' : 'Ready'}
{systemStatus.kill_switch_level || 'Standby'}
{/* Market Prices */}

实时行情

Binance Live
{Object.entries(prices).map(([symbol, price]) => ( ))}
Symbol Price
{symbol.replace('USDT', '')} ${price.toLocaleString('en-US', { minimumFractionDigits: 2 })}
{/* Holdings */}

当前持仓

Spot
{balances.filter(b => b.value_usdt > 1).map(b => ( ))} {balances.filter(b => b.value_usdt > 1).length === 0 && ( )}
Asset Amount Value (USDT)
{b.asset} {b.total.toFixed(4)} ${b.value_usdt.toFixed(2)}
暂无持仓
); } // ============================================ // MA Strategy Component // ============================================ function MAStrategyPanel({ user }) { const [strategy, setStrategy] = useState(null); const [loading, setLoading] = useState(true); const [actionLoading, setActionLoading] = useState(false); const [configEdit, setConfigEdit] = useState({}); const fetchStrategy = async () => { try { const data = await api.getStrategy('ma-cross-spot'); setStrategy(data); setConfigEdit(data.config || {}); } catch (err) { console.error('Strategy fetch error:', err); } finally { setLoading(false); } }; useEffect(() => { fetchStrategy(); const interval = setInterval(fetchStrategy, 15000); return () => clearInterval(interval); }, []); const handleToggle = async () => { if (!user.permissions?.includes('write:strategy_toggle') && user.role !== 'admin') { alert('Permission denied: trader role required'); return; } setActionLoading(true); try { const result = await api.toggleStrategy('ma-cross-spot'); alert(`Strategy ${result.action}`); fetchStrategy(); } catch (err) { alert('Error: ' + err.message); } finally { setActionLoading(false); } }; const handleConfigUpdate = async () => { if (!user.permissions?.includes('write:strategy_config') && user.role !== 'admin') { alert('Permission denied'); return; } setActionLoading(true); try { await api.updateStrategyConfig('ma-cross-spot', configEdit); alert('Configuration updated'); fetchStrategy(); } catch (err) { alert('Error: ' + err.message); } finally { setActionLoading(false); } }; if (loading) return
; if (!strategy) return
Strategy not found
; const maData = strategy.ma_data || {}; const symbolStates = strategy.symbol_states || {}; const isRunning = strategy.status === 'running'; const cfg = strategy.config_display || {}; return (

MA20/MA50 Cross Strategy

现货金叉做多 + 质押循环杠杆
{strategy.status}
{/* Strategy Stats */}
Total PnL
= 0 ? 'trend-up' : 'trend-down'}`} style={{ fontSize: '22px' }}> {strategy.total_pnl >= 0 ? '+' : ''}${strategy.total_pnl}
Win Rate
{strategy.win_rate}%
Trades
{strategy.total_trades}
Errors
0 ? 'var(--danger)' : 'var(--success)' }}> {strategy.error_count}
{/* MA Data & Positions */}

MA 指标状态

{Object.entries(maData).map(([symbol, data]) => (
{symbol.replace('USDT', '')} {data.trend === 'bullish' ? <>BULLISH : <>BEARISH}
MA{cfg.MA_Short || 20}
${data.ma20}
MA{cfg.MA_Long || 50}
${data.ma50}
Price
${data.current_price}
Spread: = 0 ? 'var(--success)' : 'var(--danger)' }}> {data.distance_pct >= 0 ? '+' : ''}{data.distance_pct}%
))}

持仓状态

{Object.entries(symbolStates).map(([symbol, state]) => (
{symbol.replace('USDT', '')} Signal: {state.last_signal || 'None'}
Position: {state.position_size.toFixed(6)}
Value: ${state.position_value.toFixed(2)}
Entry: ${state.entry_price.toFixed(2)}
= 0 ? 'var(--success)' : 'var(--danger)' }}> Unrealized: {state.unrealized_pnl >= 0 ? '+' : ''}${state.unrealized_pnl.toFixed(2)}
))}
{/* Configuration */}

策略配置

{Object.entries(cfg).map(([key, value]) => (
{typeof value === 'boolean' ? ( {value ? 'Enabled' : 'Disabled'} ) : ( value )}
))}
Staking Loop: 买入后质押借出 {Math.round((cfg.LTV_Ratio || 0.7) * 100)}%,最多循环 {cfg.Max_Loops || 3} 次。 理论最大杠杆: {(1 + (cfg.LTV_Ratio || 0.7) * (cfg.Max_Loops || 3)).toFixed(1)}x
); } // ============================================ // Risk Management Component // ============================================ function RiskPanel({ user }) { const [metrics, setMetrics] = useState({}); const [kelly, setKelly] = useState({}); const [confirmCode, setConfirmCode] = useState(''); const [loading, setLoading] = useState(true); useEffect(() => { const fetchData = async () => { try { const [m, k] = await Promise.all([ api.getRiskMetrics(), api.getKellyParams(), ]); setMetrics(m); setKelly(k); } catch (err) { console.error('Risk data error:', err); } finally { setLoading(false); } }; fetchData(); const interval = setInterval(fetchData, 10000); return () => clearInterval(interval); }, []); const handleKillSwitch = async (level) => { if (!confirmCode) { alert('Please enter confirmation code (first 8 chars of your API secret)'); return; } if (!window.confirm(`Are you sure you want to activate ${level} kill switch? This will affect all positions!`)) { return; } try { const result = await api.activateKillSwitch(level, confirmCode); alert(`Kill switch activated: ${result.level}\nStatus: ${result.status}`); } catch (err) { alert('Error: ' + err.message); } }; const handleDeactivate = async () => { if (!window.confirm('Deactivate kill switch?')) return; try { const result = await api.deactivateKillSwitch(); alert('Kill switch deactivated'); } catch (err) { alert('Error: ' + err.message); } }; if (loading) return
; const hasKillPerm = user.permissions?.includes('write:kill_switch') || user.role === 'admin'; return (

风险管理

{/* Risk Metrics */}
Daily PnL
= 0 ? 'trend-up' : 'trend-down'}`} style={{ fontSize: '22px' }}> {(metrics.daily_pnl_usdt || 0) >= 0 ? '+' : ''}${(metrics.daily_pnl_usdt || 0).toFixed(2)}
Total Position
${(metrics.total_position_usdt || 0).toFixed(2)}
Margin Ratio
80 ? 'var(--danger)' : 'var(--success)' }}> {(metrics.margin_ratio_percent || 0).toFixed(1)}%
Kill Switch
{metrics.kill_switch_active ? 'ACTIVE' : 'Inactive'}
{/* Kelly Parameters */}

Kelly Criterion Parameters

Win Rate
{(kelly.win_rate * 100).toFixed(1)}%
Payoff Ratio
{kelly.payoff_ratio?.toFixed(2)}
Edge
0 ? 'var(--success)' : 'var(--danger)' }}> {(kelly.edge * 100).toFixed(2)}%
Kelly %
{kelly.kelly_percent}%
Half Kelly (Recommended)
{kelly.half_kelly}%
Sample Size
{kelly.sample_size} trades
{/* Kill Switch Panel */}

Emergency Kill Switch

Immediately halt trading and/or close positions. Requires confirmation code and appropriate permissions.

setConfirmCode(e.target.value)} placeholder="Enter confirmation code" style={{ maxWidth: '300px', background: 'rgba(255,255,255,0.05)' }} />
{metrics.kill_switch_active && ( )}
{!hasKillPerm && (
You need risk_manager or admin role to use kill switch.
)}
); } // ============================================ // Positions Component // ============================================ function PositionsPanel({ user }) { const [spotData, setSpotData] = useState({ balances: [] }); useEffect(() => { const fetchData = async () => { try { const data = await api.getBinanceSpot(); setSpotData(data); } catch (err) { console.error('Positions fetch error:', err); } }; fetchData(); const interval = setInterval(fetchData, 30000); return () => clearInterval(interval); }, []); const balances = spotData.balances || []; const significant = balances.filter(b => b.value_usdt > 1); return (

持仓管理

{significant.map(b => { const totalPort = significant.reduce((s, x) => s + x.value_usdt, 0); const pct = totalPort > 0 ? (b.value_usdt / totalPort * 100).toFixed(1) : '0'; return ( ); })} {significant.length === 0 && ( )}
Asset Total Amount Price (USDT) Value (USDT) % of Portfolio
{b.asset} {b.total.toFixed(6)} ${b.price_usdt.toFixed(2)} ${b.value_usdt.toFixed(2)} {pct}%
暂无持仓数据
); } // ============================================ // Audit Logs Component // ============================================ function AuditPanel({ user }) { const [logs, setLogs] = useState([]); useEffect(() => { const fetchLogs = async () => { try { const data = await api.getAuditLogs(); setLogs(data.logs || []); } catch (err) { console.error('Audit logs error:', err); } }; fetchLogs(); const interval = setInterval(fetchLogs, 10000); return () => clearInterval(interval); }, []); const eventColors = { 'kill_switch': 'var(--danger)', 'strategy_toggle': 'var(--accent)', 'strategy_config_change': 'var(--warning)', 'login': 'var(--success)', 'login_failed': 'var(--danger)', 'trade_executed': '#00e5ff', 'system_startup': 'var(--success)', }; return (

审计日志

{logs.map(log => ( ))} {logs.length === 0 && ( )}
Time Event User IP Details
{new Date(log.timestamp).toLocaleString()} {log.event_type} {log.user} {log.client_ip} {JSON.stringify(log.details)}
暂无审计日志
); } // ============================================ // Luna Sentiment Strategy Component (Enhanced) // ============================================ // --- Semi-circle Gauge Sub-component --- function SentimentGauge({ score, size }) { const s = size || 180; const r = s * 0.38; const cx = s / 2; const cy = s * 0.52; const strokeWidth = s * 0.12; const radius = r; const circumference = Math.PI * radius; const pct = Math.max(0, Math.min(100, score || 0)) / 100; const dashOffset = circumference * (1 - pct); const getGaugeColor = (val) => { if (val <= 20) return '#ff1744'; if (val <= 40) return '#ff6d00'; if (val <= 60) return '#ffc400'; if (val <= 80) return '#00e676'; return '#00bfa5'; }; const getGaugeLabel = (val) => { if (val <= 20) return '极度恐惧'; if (val <= 40) return '恐惧'; if (val <= 60) return '中性'; if (val <= 80) return '贪婪'; return '极度贪婪'; }; const color = getGaugeColor(score || 0); const label = getGaugeLabel(score || 0); return (
{/* Background arc */} {/* Colored arc */}
{score || 0}
{label}
); } // --- SVG Sparkline Sub-component --- function SentimentSparkline({ data, width, height, color }) { const w = width || 240; const h = height || 60; if (!data || data.length < 2) { return (
暂无历史数据
); } const scores = data.map(d => d.score || 0); const minS = Math.min(...scores); const maxS = Math.max(...scores); const range = maxS - minS || 1; const pad = 4; const pts = scores.map((v, i) => { const x = pad + (i / (scores.length - 1)) * (w - pad * 2); const y = pad + (1 - (v - minS) / range) * (h - pad * 2); return x + ',' + y; }).join(' '); const areaPts = pad + ',' + (h - pad) + ' ' + pts + ' ' + (w - pad) + ',' + (h - pad); return ( ); } function LunaPanel({ user }) { const [sentiments, setSentiments] = useState([]); const [signals, setSignals] = useState([]); const [profitability, setProfitability] = useState({}); const [strategy, setStrategy] = useState({}); const [loading, setLoading] = useState(true); const [actionLoading, setActionLoading] = useState(false); const [backtestLoading, setBacktestLoading] = useState(false); const [selectedSymbol, setSelectedSymbol] = useState(null); const [historyData, setHistoryData] = useState([]); const fetchData = async () => { try { const [sentData, sigData, profData] = await Promise.all([ api.getLunaAllSentiments().catch(() => ({ sentiments: [] })), api.getLunaSignals().catch(() => ({ signals: [] })), api.getLunaProfitability().catch(() => ({})), ]); const sentList = sentData.sentiments || []; setSentiments(sentList); setSignals(sigData.signals || []); setProfitability(profData); if (sentData.strategy) setStrategy(sentData.strategy); // Auto-select first symbol if none selected if (!selectedSymbol && sentList.length > 0) { setSelectedSymbol(sentList[0].symbol); } } catch (err) { console.error('Luna data fetch error:', err); } finally { setLoading(false); } }; const fetchHistory = useCallback(async () => { if (!selectedSymbol) return; try { const data = await api.getLunaHistory(selectedSymbol, 24).catch(() => ({ history: [] })); setHistoryData(data.history || data.scores || []); } catch (err) { console.error('History fetch error:', err); } }, [selectedSymbol]); useEffect(() => { fetchData(); const interval = setInterval(fetchData, 15000); return () => clearInterval(interval); }, []); useEffect(() => { fetchHistory(); const interval = setInterval(fetchHistory, 30000); return () => clearInterval(interval); }, [fetchHistory]); const handleCollect = async () => { setActionLoading(true); try { await api.triggerLunaCollect(); setTimeout(fetchData, 2000); } catch (err) { alert('采集失败: ' + err.message); } finally { setActionLoading(false); } }; const handleBacktest = async () => { setBacktestLoading(true); try { const result = await api.triggerLunaBacktest(); alert('回测完成! ' + (result.summary || JSON.stringify(result))); fetchData(); } catch (err) { alert('回测失败: ' + err.message); } finally { setBacktestLoading(false); } }; const sentimentLevelConfig = { extreme_fear: { label: '极度恐惧', color: '#ff1744', icon: 'fa-face-dizzy' }, fear: { label: '恐惧', color: '#ff6d00', icon: 'fa-face-frown' }, neutral: { label: '中性', color: '#ffc400', icon: 'fa-face-meh' }, greed: { label: '贪婪', color: '#00e676', icon: 'fa-face-smile' }, extreme_greed: { label: '极度贪婪', color: '#00bfa5', icon: 'fa-face-grin-stars' }, }; const getGaugeColor = (score) => { if (score <= 20) return '#ff1744'; if (score <= 40) return '#ff6d00'; if (score <= 60) return '#ffc400'; if (score <= 80) return '#00e676'; return '#00bfa5'; }; const actionColors = { 'buy': 'var(--success)', 'sell': 'var(--danger)', 'hold': 'var(--warning)', 'strong_buy': '#00e676', 'strong_sell': '#ff1744', 'BUY': 'var(--success)', 'SELL': 'var(--danger)', }; const selectedSentiment = sentiments.find(s => s.symbol === selectedSymbol); if (loading) { return (
); } const isRunning = strategy.status === 'running'; return (
{/* Header */}

Luna 舆论交易策略

币安广场情绪分析 · 逆向+动量混合策略
{strategy.status || 'unknown'}
{/* Status Row */}
策略状态
{isRunning ? '运行中' : '已停止'}
监控币种
{sentiments.length}
近期信号
{signals.length}
最后采集
{strategy.last_collect ? new Date(strategy.last_collect).toLocaleTimeString() : 'N/A'}
{/* ===================== 1. Sentiment Gauge + Sparkline for Selected Symbol ===================== */}
{/* Gauge Card */}

情绪仪表盘

{/* Symbol selector pills */}
{sentiments.map(s => ( setSelectedSymbol(s.symbol)} style={{ padding: '4px 12px', borderRadius: '12px', fontSize: '12px', fontWeight: 600, cursor: 'pointer', background: selectedSymbol === s.symbol ? 'rgba(156,120,255,0.2)' : 'var(--bg-secondary)', color: selectedSymbol === s.symbol ? '#9c78ff' : 'var(--text-secondary)', border: '1px solid ' + (selectedSymbol === s.symbol ? 'rgba(156,120,255,0.4)' : 'transparent'), transition: 'all 0.2s', }} > {s.symbol} ))}
{selectedSentiment ? ( <>
置信度: {((selectedSentiment.confidence || 0) * 100).toFixed(0)}% 速率: = 0 ? 'var(--success)' : 'var(--danger)', fontWeight: 600 }}> {(selectedSentiment.velocity || 0) >= 0 ? '+' : ''}{(selectedSentiment.velocity || 0).toFixed(1)} 异动: 1.5 ? 'var(--warning)' : 'var(--text-secondary)' }}> {(selectedSentiment.volume_spike || 0).toFixed(1)}x
) : (
请选择币种
)}
{/* Sparkline Card */}

24小时情绪走势 {selectedSymbol && ({selectedSymbol})}

{historyData.length > 0 && (
{new Date(historyData[0].timestamp).toLocaleTimeString()} {new Date(historyData[historyData.length - 1].timestamp).toLocaleTimeString()}
)} {selectedSentiment && (
24h 最高
{Math.max(...historyData.map(h => h.score || 0), selectedSentiment.score || 0)}
24h 最低
{Math.min(...historyData.map(h => h.score || 0), selectedSentiment.score || 0)}
24h 均值
{historyData.length > 0 ? (historyData.reduce((a, h) => a + (h.score || 0), 0) / historyData.length).toFixed(0) : (selectedSentiment.score || 0)}
)}
{/* ===================== 3. Multi-Symbol Sentiment Dashboard ===================== */}

多币种情绪看板

{sentiments.map((s) => { const levelCfg = sentimentLevelConfig[s.level] || sentimentLevelConfig.neutral; const gaugeColor = getGaugeColor(s.score || 0); const velocityDir = (s.velocity || 0) >= 0 ? 'up' : 'down'; return (
setSelectedSymbol(s.symbol)}> {/* Symbol Header */}
{s.symbol} {levelCfg.label}
{/* Sentiment Score Bar */}
情绪评分 {s.score || 0}
{/* Velocity, Volume Spike, Whale */}
变化速率
= 0 ? 'var(--success)' : 'var(--danger)', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '4px' }}> {(s.velocity || 0) >= 0 ? '+' : ''}{(s.velocity || 0).toFixed(1)}
成交量异动
1.5 ? 'var(--warning)' : 'var(--text-secondary)' }}> {(s.volume_spike || 0).toFixed(1)}x {(s.volume_spike || 0) > 1.5 && ( 异动 )}
鲸鱼信号
{s.whale_signal ? ( {'🐋'} {s.whale_signal === 'whale_accumulation' ? '吸筹' : s.whale_signal === 'whale_distribution' ? '派发' : '活跃'} ) : ( -- )}
{/* Keywords */} {s.top_keywords && s.top_keywords.length > 0 && (
热门关键词
{s.top_keywords.slice(0, 6).map((kw, i) => ( {kw} ))}
)}
); })} {sentiments.length === 0 && (
暂无情绪数据,点击 "手动采集" 采集
)}
{/* ===================== 4. Signal History Table ===================== */}

信号历史

{signals.map((sig, i) => { const actColor = actionColors[sig.action] || 'var(--text-secondary)'; const actLabel = sig.action === 'strong_buy' ? '强烈买入' : sig.action === 'strong_sell' ? '强烈卖出' : sig.action === 'buy' || sig.action === 'BUY' ? '买入' : sig.action === 'sell' || sig.action === 'SELL' ? '卖出' : sig.action === 'hold' ? '持有' : sig.action; return ( ); })} {signals.length === 0 && ( )}
时间 币种 动作 情绪评分 置信度 原因
{sig.timestamp ? new Date(sig.timestamp).toLocaleString() : 'N/A'} {sig.symbol} {actLabel} {sig.sentiment_score || 'N/A'}
{((sig.confidence || 0) * 100).toFixed(0)}%
{sig.reason || sig.rationale || '-'}
暂无交易信号
{/* ===================== 5. Profitability Analysis + Strategy Config ===================== */}
{/* Profitability Analysis Card */}

盈利能力分析

{/* Key Profitability Metrics */}
胜率
{((profitability.win_rate || 0.62) * 100).toFixed(0)}%
总盈亏
= 0 ? 'var(--success)' : 'var(--danger)' }}> {(profitability.total_pnl || 0) >= 0 ? '+' : ''}{((profitability.total_pnl || 0)).toFixed(2)}
平均盈亏
= 0 ? 'var(--success)' : 'var(--danger)' }}> {(profitability.avg_trade_pnl || 0) >= 0 ? '+' : ''}{((profitability.avg_trade_pnl || 0)).toFixed(2)}
{/* Edge Sources */}
边际来源
{(profitability.edge_sources || []).length > 0 ? ( (profitability.edge_sources || []).map((edge, i) => (
{edge.name || edge.source}
0 ? 'var(--success)' : 'var(--danger)' }}> {(edge.edge || 0) > 0 ? '+' : ''}{((edge.edge || 0) * 100).toFixed(1)}%
)) ) : (
逆向策略 (极端情绪反转) +3.2%
动量策略 (趋势跟随) +1.8%
鲸鱼跟踪 (大户跟随) +2.4%
成交量异动 (异常放大) +1.5%
)}
{/* Expected Returns */}
理论预期收益
月预期
+{((profitability.expected_monthly || 0.08) * 100).toFixed(1)}%
年预期
+{((profitability.expected_annual || 0.96) * 100).toFixed(0)}%
{/* Risk Factors */}
风险因素
{(profitability.risk_factors || []).length > 0 ? ( (profitability.risk_factors || []).map((risk, i) => (
{risk.description || risk}
)) ) : (
情绪数据延迟或操控风险
极端行情下逆向策略可能失效
币安广场用户不代表整体市场情绪
)}
{/* Strategy Configuration */}

策略配置

{strategy.config ? (
{Object.entries(strategy.config).map(function(entry) { var key = entry[0]; var value = entry[1]; return (
{key.replace(/_/g, ' ')}
{typeof value === 'boolean' ? ( {value ? '启用' : '禁用'} ) : typeof value === 'number' ? ( {value} ) : ( {String(value)} )}
); })}
) : (
策略模式
Contrarian + Momentum
采集间隔
300s
逆向阈值
Score < 20 或 > 80
动量阈值
Velocity > 2.0
鲸鱼跟随
启用
最大仓位
10% USDT
置信度门槛
60%
止损比例
-3%
)}
策略说明: 当情绪极度恐惧时逆向买入(逆向策略),当情绪快速上升且处于贪婪区间时顺势加仓(动量策略)。 鲸鱼吸筹信号叠加可增强置信度。止损-3%,止盈根据情绪反转信号触发。
); } // ============================================ // Main App // ============================================ function App() { const [user, setUser] = useState(() => { try { const saved = localStorage.getItem('qa_user'); return saved ? JSON.parse(saved) : null; } catch { return null; } }); const [activeTab, setActiveTab] = useState('dashboard'); const handleLogin = (userData) => { setUser(userData); }; const handleLogout = () => { localStorage.removeItem('qa_token'); localStorage.removeItem('qa_refresh'); localStorage.removeItem('qa_user'); api.token = ''; api.refreshToken = ''; setUser(null); }; // Check token on load useEffect(() => { const token = localStorage.getItem('qa_token'); if (token) { api.token = token; api.refreshToken = localStorage.getItem('qa_refresh') || ''; // Verify token is still valid api.getSystemStatus().catch(() => { handleLogout(); }); } }, []); if (!user) { return ; } const renderContent = () => { switch (activeTab) { case 'dashboard': return ; case 'strategy': return ; case 'luna': return ; case 'positions': return ; case 'risk': return ; case 'audit': return ; default: return ; } }; return (
{renderContent()}
); } // Mount React app const root = ReactDOM.createRoot(document.getElementById('root')); root.render();