// 全局状态管理 const AppState = { currentTab: 'screening', loading: false }; // 全局图表实例管理 const ChartInstances = { scoringChart: null, industryChart: null }; // DOM 加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { initializeApp(); }); // 应用初始化 function initializeApp() { setupNavigation(); setupEventListeners(); setupRangeSliders(); } // 导航设置 function setupNavigation() { const navLinks = document.querySelectorAll('.nav-link'); const tabContents = document.querySelectorAll('.tab-content'); navLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const targetTab = link.getAttribute('data-tab'); // 更新导航状态 navLinks.forEach(nav => nav.classList.remove('active')); link.classList.add('active'); // 显示对应内容 tabContents.forEach(content => content.classList.remove('active')); document.getElementById(targetTab).classList.add('active'); AppState.currentTab = targetTab; }); }); } // 事件监听器设置 function setupEventListeners() { // 股票筛选 document.getElementById('screening-btn').addEventListener('click', handleScreening); // 综合分析 document.getElementById('analysis-btn').addEventListener('click', handleComprehensiveAnalysis); // 键盘事件 document.addEventListener('keydown', handleKeyPress); } // 范围滑块设置 function setupRangeSliders() { // 移除范围滑块设置,因为筛选页面不再使用滑块 } // 股票筛选处理 let isScreeningInProgress = false; // 防重复提交标志 async function handleScreening() { // 防止重复点击 if (isScreeningInProgress) { showToast('筛选正在进行中,请稍候...', 'warning'); return; } const minScore = 60; // 固定最低评分为60分 const limit = 50; // 固定结果数量为50个 // 综合筛选:使用三种策略综合评估 const data = { strategy: 'comprehensive', min_score: minScore, limit }; isScreeningInProgress = true; const screeningBtn = document.getElementById('screening-btn'); const originalText = screeningBtn.innerHTML; screeningBtn.innerHTML = ' 筛选中...'; screeningBtn.disabled = true; showLoading('正在进行综合筛选...'); try { const response = await fetch('/api/screen', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (response.ok) { displayScreeningResults(result.results); showToast('筛选成功!', 'success'); } else { throw new Error(result.error || '筛选失败'); } } catch (error) { showToast(`筛选失败: ${error.message}`, 'error'); } finally { isScreeningInProgress = false; screeningBtn.innerHTML = originalText; screeningBtn.disabled = false; hideLoading(); } } // 显示筛选结果 function displayScreeningResults(results) { const resultsSection = document.getElementById('screening-results'); const tableContainer = document.getElementById('screening-table'); if (!results || results.length === 0) { tableContainer.innerHTML = '

未找到符合条件的股票

'; resultsSection.style.display = 'block'; return; } // 检查是否是综合评分结果 const isComprehensive = results[0].value_score !== undefined; // 创建表格 let headers, tableData; if (isComprehensive) { headers = ['股票代码', '名称', '行业', '综合评分', '价值评分', '成长评分', '技术评分', '建议']; tableData = results.map(stock => [ stock.ts_code, stock.name, stock.industry, stock.score.toFixed(1), stock.value_score.toFixed(1), stock.growth_score.toFixed(1), stock.technical_score.toFixed(1), stock.recommendation ]); } else { headers = ['股票代码', '名称', '行业', '评分', '建议']; tableData = results.map(stock => [ stock.ts_code, stock.name, stock.industry, stock.score.toFixed(1), stock.recommendation ]); } const table = createDataTable(headers, tableData); tableContainer.innerHTML = ''; tableContainer.appendChild(table); // 创建图表 createScoringChart(results.slice(0, 10)); createIndustryChart(results); resultsSection.style.display = 'block'; resultsSection.scrollIntoView({ behavior: 'smooth' }); } // 综合分析处理 - 合并多策略分析 async function handleComprehensiveAnalysis() { const tsCodeInput = document.getElementById('stock-code').value.trim(); if (!tsCodeInput) { showToast('请输入股票代码', 'warning'); return; } // 格式化股票代码:自动添加交易所后缀 const tsCode = formatStockCode(tsCodeInput); const strategies = ['value', 'growth', 'technical']; const results = []; showLoading('正在进行综合分析...'); try { // 并行获取三种策略的分析结果 for (const strategy of strategies) { const response = await fetch('/api/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ts_code: tsCode, strategy }) }); const result = await response.json(); if (response.ok) { results.push({ strategy: getStrategyName(strategy), rawStrategy: strategy, score: result.score, recommendation: result.recommendation, data: result }); } } if (results.length > 0) { displayComprehensiveResults(results, tsCode); showToast('综合分析完成!', 'success'); } else { throw new Error('无法获取分析数据'); } } catch (error) { showToast(`分析失败: ${error.message}`, 'error'); } finally { hideLoading(); } } // 显示股票基本信息 function displayStockBasicInfo(results, tsCode) { // 从价值策略结果中获取基本信息(因为价值策略通常有最完整的财务数据) const valueData = results.find(r => r.rawStrategy === 'value') || results[0]; const stockData = valueData.data; // 显示股票代码 document.getElementById('stock-code-value').textContent = tsCode; // 显示股票名称 const stockName = stockData.name || stockData.stock_name || '--'; document.getElementById('stock-name-value').textContent = stockName; // 显示行业信息 const industry = stockData.industry || '--'; document.getElementById('stock-industry-value').textContent = industry; // 显示当前价格 const currentPrice = stockData.current_price || stockData.price; if (currentPrice) { document.getElementById('stock-price-value').textContent = `¥${currentPrice.toFixed(2)}`; } else { document.getElementById('stock-price-value').textContent = '--'; } // 显示市值 const marketCap = stockData.market_cap; if (marketCap) { const marketCapBillion = (marketCap / 100000000).toFixed(2); document.getElementById('stock-market-cap-value').textContent = `${marketCapBillion}亿`; } else { document.getElementById('stock-market-cap-value').textContent = '--'; } // 显示市盈率 const peRatio = stockData.financial_ratios?.pe_ratio; if (peRatio && peRatio !== Infinity && peRatio > 0) { document.getElementById('stock-pe-value').textContent = peRatio.toFixed(2); } else { document.getElementById('stock-pe-value').textContent = '--'; } } // 显示综合分析结果 function displayComprehensiveResults(results, tsCode) { const resultsSection = document.getElementById('analysis-results'); // 显示股票基本信息 displayStockBasicInfo(results, tsCode); // 计算综合指标 const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length; const bestStrategy = results.reduce((best, current) => current.score > best.score ? current : best ); // 综合建议逻辑 const buyCount = results.filter(r => r.recommendation === 'BUY').length; const holdCount = results.filter(r => r.recommendation === 'HOLD').length; let finalRecommendation; let confidenceLevel; if (buyCount >= 2) { finalRecommendation = 'BUY'; confidenceLevel = buyCount === 3 ? 'HIGH' : 'MEDIUM'; } else if (buyCount + holdCount >= 2) { finalRecommendation = 'HOLD'; confidenceLevel = 'MEDIUM'; } else { finalRecommendation = 'SELL'; confidenceLevel = results.filter(r => r.recommendation === 'SELL').length >= 2 ? 'MEDIUM' : 'LOW'; } // 显示综合投资评级 displayInvestmentRating(avgScore, bestStrategy, finalRecommendation, confidenceLevel); // 显示分析总结(包含策略详情表格) displayAnalysisSummary(results, avgScore, finalRecommendation, bestStrategy); // 显示财务指标 (使用价值策略的数据) const valueData = results.find(r => r.rawStrategy === 'value'); if (valueData && valueData.data.financial_ratios) { displayFinancialRatios(valueData.data.financial_ratios); } // 检查是否有技术分析的交易信号 const technicalData = results.find(r => r.rawStrategy === 'technical'); if (technicalData && technicalData.data.trading_signals) { displayTradingSignals(technicalData.data.trading_signals); document.getElementById('trading-signals-section').style.display = 'block'; } else { document.getElementById('trading-signals-section').style.display = 'none'; } resultsSection.style.display = 'block'; resultsSection.scrollIntoView({ behavior: 'smooth' }); } // 显示综合投资评级 function displayInvestmentRating(avgScore, bestStrategy, finalRecommendation, confidenceLevel) { // 显示综合评级指标 document.getElementById('best-strategy-value').textContent = bestStrategy.strategy; document.getElementById('avg-score-value').textContent = avgScore.toFixed(1) + '分'; document.getElementById('final-recommendation-value').textContent = finalRecommendation; document.getElementById('confidence-level-value').textContent = confidenceLevel === 'HIGH' ? '高' : confidenceLevel === 'MEDIUM' ? '中' : '低'; } // 显示分析总结 function displayAnalysisSummary(results, avgScore, finalRecommendation, bestStrategy) { const container = document.getElementById('summary-content'); let summaryHtml = `

综合评分:${avgScore.toFixed(1)}分

投资建议:${finalRecommendation}

最佳策略:${bestStrategy.strategy} (${bestStrategy.score.toFixed(1)}分)


各策略分析:

'; // 添加投资建议说明 if (finalRecommendation === 'BUY') { summaryHtml += '

建议买入:多数策略看好,具有较好投资价值

'; } else if (finalRecommendation === 'HOLD') { summaryHtml += '

建议持有:策略结果分化,建议持有观望

'; } else { summaryHtml += '

建议卖出:多数策略不看好,存在较大风险

'; } // 添加策略详细评分表格 summaryHtml += `
`; container.innerHTML = summaryHtml; // 创建策略对比表格 createStrategyComparisonTable(results, 'strategy-comparison-table-inline'); } // 创建策略对比表格 function createStrategyComparisonTable(results, containerId = 'strategy-comparison-table') { const container = document.getElementById(containerId); const headers = ['投资策略', '评分', '投资建议', '策略特点']; const rows = results.map(r => [ r.strategy, r.score.toFixed(1) + '分', r.recommendation, getStrategyDescription(r.rawStrategy) ]); const table = createDataTable(headers, rows); container.innerHTML = ''; container.appendChild(table); } // 获取策略描述 function getStrategyDescription(strategy) { const descriptions = { 'value': '注重估值合理性,适合稳健投资', 'growth': '关注成长潜力,适合追求高收益', 'technical': '基于技术指标,适合短期交易' }; return descriptions[strategy] || ''; } // 显示财务指标 function displayFinancialRatios(ratios) { const container = document.getElementById('financial-ratios'); const ratioItems = [ { key: 'roe', label: 'ROE (净资产收益率)', suffix: '%' }, { key: 'roa', label: 'ROA (总资产收益率)', suffix: '%' }, { key: 'gross_margin', label: '毛利率', suffix: '%' }, { key: 'net_margin', label: '净利率', suffix: '%' }, { key: 'current_ratio', label: '流动比率', suffix: '' }, { key: 'debt_ratio', label: '负债比率', suffix: '%' } ]; const grid = document.createElement('div'); grid.className = 'metrics-grid'; ratioItems.forEach(item => { if (ratios[item.key] !== undefined) { const card = document.createElement('div'); card.className = 'metric-card'; card.innerHTML = `
${ratios[item.key].toFixed(2)}${item.suffix}
${item.label}
`; grid.appendChild(card); } }); container.innerHTML = ''; container.appendChild(grid); } // 显示交易信号分析 function displayTradingSignals(tradingSignals) { const entrySignals = tradingSignals.entry_signals || {}; const exitSignals = tradingSignals.exit_signals || {}; const stopLossTakeProfit = tradingSignals.stop_loss_take_profit || {}; const tradingAdvice = tradingSignals.trading_advice || {}; const marketTiming = tradingSignals.market_timing || {}; // 显示主要信号指标 document.getElementById('entry-signal-value').textContent = getSignalDisplayText(entrySignals.overall_signal) + ` (${entrySignals.signal_strength || 0}%)`; document.getElementById('exit-signal-value').textContent = getSignalDisplayText(exitSignals.overall_signal) + ` (${exitSignals.signal_strength || 0}%)`; document.getElementById('trading-action-value').textContent = getActionDisplayText(tradingAdvice.action); document.getElementById('risk-level-value').textContent = getRiskDisplayText(tradingAdvice.risk_assessment); // 显示买入价格和卖出价格建议 const currentPrice = tradingSignals.current_price || 0; const entryPriceData = tradingSignals.entry_price || {}; const exitPriceData = tradingSignals.exit_price || {}; // 根据操作建议显示相应的价格 const action = tradingAdvice.action; document.getElementById('entry-price-value').textContent = (action === 'BUY' || action === 'CONSIDER_BUY') && entryPriceData.recommended ? `¥${entryPriceData.recommended}` : (action === 'BUY' || action === 'CONSIDER_BUY') && currentPrice > 0 ? `¥${currentPrice.toFixed(2)}` : '--'; document.getElementById('exit-price-value').textContent = (action === 'SELL') && exitPriceData.recommended ? `¥${exitPriceData.recommended}` : (action === 'SELL') && currentPrice > 0 ? `¥${currentPrice.toFixed(2)}` : '--'; // 显示止盈止损信息(只有在非观望状态时才显示) const stopLoss = stopLossTakeProfit.stop_loss || {}; const takeProfit = stopLossTakeProfit.take_profit || {}; const isWaitAction = tradingAdvice.action === 'WAIT'; document.getElementById('stop-loss-value').textContent = (isWaitAction || !stopLoss.recommended) ? '--' : `¥${stopLoss.recommended}`; document.getElementById('take-profit-value').textContent = (isWaitAction || !takeProfit.recommended) ? '--' : `¥${takeProfit.recommended}`; document.getElementById('risk-reward-ratio-value').textContent = (isWaitAction || !stopLossTakeProfit.risk_reward_ratio) ? '--' : `1:${stopLossTakeProfit.risk_reward_ratio}`; document.getElementById('position-size-value').textContent = (isWaitAction || !stopLossTakeProfit.position_sizing_suggestion) ? '--' : `${stopLossTakeProfit.position_sizing_suggestion.suggested_position}%`; // 显示详细信号分析 displaySignalsDetails(entrySignals, exitSignals, tradingAdvice, marketTiming); } // 显示信号详情 function displaySignalsDetails(entrySignals, exitSignals, tradingAdvice, marketTiming) { const container = document.getElementById('signals-content'); let detailsHtml = '
'; // 买入信号详情 if (entrySignals.entry_reasons && entrySignals.entry_reasons.length > 0) { detailsHtml += '
'; detailsHtml += '
📈 买入信号原因
'; detailsHtml += '
'; } // 卖出信号详情 if (exitSignals.exit_reasons && exitSignals.exit_reasons.length > 0) { detailsHtml += '
'; detailsHtml += '
📉 卖出信号原因
'; detailsHtml += '
'; } // 交易建议详情 if (tradingAdvice.reasoning && tradingAdvice.reasoning.length > 0) { detailsHtml += '
'; detailsHtml += '
🎯 交易建议理由
'; detailsHtml += '
'; } // 市场时机分析 if (marketTiming.recommendations && marketTiming.recommendations.length > 0) { detailsHtml += '
'; detailsHtml += '
⏰ 市场时机分析
'; detailsHtml += `

市场状态: ${getMarketConditionText(marketTiming.market_condition)}

`; detailsHtml += `

流动性: ${getLiquidityText(marketTiming.liquidity)}

`; detailsHtml += `

趋势强度: ${getTrendStrengthText(marketTiming.trend_strength)}

`; detailsHtml += '
'; } // 风险提示 const warningFlags = entrySignals.warning_flags || []; const riskFlags = exitSignals.risk_flags || []; if (warningFlags.length > 0 || riskFlags.length > 0) { detailsHtml += '
'; detailsHtml += '
⚠️ 风险提示
'; detailsHtml += '
'; } detailsHtml += '
'; container.innerHTML = detailsHtml; } // 信号显示文本转换函数 function getSignalDisplayText(signal) { const signalMap = { 'STRONG_BUY': '强烈买入', 'BUY': '买入', 'WEAK_BUY': '弱买入', 'NEUTRAL': '中性', 'WEAK_SELL': '弱卖出', 'SELL': '卖出', 'STRONG_SELL': '强烈卖出' }; return signalMap[signal] || '未知'; } function getActionDisplayText(action) { const actionMap = { 'BUY': '买入', 'SELL': '卖出', 'CONSIDER_BUY': '考虑买入', 'WAIT': '观望', 'HOLD': '持有' }; return actionMap[action] || '观望'; } function getRiskDisplayText(risk) { const riskMap = { 'LOW': '低风险', 'MEDIUM': '中等风险', 'HIGH': '高风险' }; return riskMap[risk] || '中等风险'; } function getMarketConditionText(condition) { const conditionMap = { 'NORMAL': '正常', 'VOLATILE': '高波动', 'QUIET': '平静' }; return conditionMap[condition] || '正常'; } function getLiquidityText(liquidity) { const liquidityMap = { 'POOR': '流动性差', 'GOOD': '流动性良好', 'EXCELLENT': '流动性优秀' }; return liquidityMap[liquidity] || '流动性良好'; } function getTrendStrengthText(strength) { const strengthMap = { 'WEAK': '趋势较弱', 'MEDIUM': '趋势中等', 'STRONG': '趋势较强' }; return strengthMap[strength] || '趋势中等'; } // 策略对比处理 - 已移除,合并到综合分析中 // 此函数已被 handleComprehensiveAnalysis 替代 // 显示对比结果 - 已移除,合并到综合分析中 // 此函数已被 displayComprehensiveResults 替代 // 工具函数 function formatStockCode(code) { // 移除所有空格和非数字字符,保留原有后缀 const cleanCode = code.replace(/\s+/g, '').toUpperCase(); // 如果已经包含交易所后缀,直接返回 if (cleanCode.includes('.SZ') || cleanCode.includes('.SH')) { return cleanCode; } // 只包含数字的情况,自动判断交易所 const numericCode = cleanCode.replace(/[^0-9]/g, ''); if (numericCode.length === 6) { // 深交所:000xxx, 002xxx, 300xxx if (numericCode.startsWith('000') || numericCode.startsWith('002') || numericCode.startsWith('300')) { return numericCode + '.SZ'; } // 上交所:600xxx, 601xxx, 603xxx, 688xxx else if (numericCode.startsWith('60') || numericCode.startsWith('688')) { return numericCode + '.SH'; } } // 默认返回原输入加.SZ(深交所较多) return numericCode + '.SZ'; } function getStrategyName(strategy) { const names = { 'value': '价值投资', 'growth': '成长投资', 'technical': '技术分析' }; return names[strategy] || strategy; } function getStrategyIcon(strategy) { const icons = { '价值投资': '💰', '成长投资': '🚀', '技术分析': '📈' }; return icons[strategy] || '📊'; } // 创建数据表格 function createDataTable(headers, rows) { const table = document.createElement('table'); table.className = 'data-table'; // 创建表头 const thead = document.createElement('thead'); const headerRow = document.createElement('tr'); headers.forEach(header => { const th = document.createElement('th'); th.textContent = header; headerRow.appendChild(th); }); thead.appendChild(headerRow); table.appendChild(thead); // 创建表体 const tbody = document.createElement('tbody'); rows.forEach(row => { const tr = document.createElement('tr'); row.forEach(cell => { const td = document.createElement('td'); td.textContent = cell; tr.appendChild(td); }); tbody.appendChild(tr); }); table.appendChild(tbody); return table; } // 图表创建函数 function createScoringChart(data) { const ctx = document.getElementById('scoring-chart').getContext('2d'); // 销毁已存在的图表 if (ChartInstances.scoringChart) { ChartInstances.scoringChart.destroy(); } ChartInstances.scoringChart = new Chart(ctx, { type: 'bar', data: { labels: data.map(item => item.name), datasets: [{ label: '评分', data: data.map(item => item.score), backgroundColor: '#000000', /* 纯黑色 */ borderColor: '#000000', borderWidth: 1, borderRadius: 4 }] }, options: { responsive: true, plugins: { title: { display: true, text: '股票评分排行' }, legend: { display: false } }, scales: { y: { beginAtZero: true, max: 100 } } } }); } function createIndustryChart(data) { const ctx = document.getElementById('industry-chart').getContext('2d'); // 销毁已存在的图表 if (ChartInstances.industryChart) { ChartInstances.industryChart.destroy(); } const industries = {}; data.forEach(item => { industries[item.industry] = (industries[item.industry] || 0) + 1; }); ChartInstances.industryChart = new Chart(ctx, { type: 'doughnut', data: { labels: Object.keys(industries), datasets: [{ data: Object.values(industries), backgroundColor: [ '#000000', '#333333', '#666666', '#999999', '#cccccc', '#444444', '#777777', '#aaaaaa' ] }] }, options: { responsive: true, plugins: { title: { display: true, text: '行业分布' } } } }); } // UI 辅助函数 function showLoading(message = '加载中...') { const loading = document.getElementById('loading'); loading.querySelector('p').textContent = message; loading.style.display = 'flex'; AppState.loading = true; } function hideLoading() { document.getElementById('loading').style.display = 'none'; AppState.loading = false; } function showToast(message, type = 'info') { const container = document.getElementById('toast-container'); const toast = document.createElement('div'); toast.className = `toast ${type}`; const icon = getToastIcon(type); toast.innerHTML = `
${icon} ${message}
`; container.appendChild(toast); // 自动移除 setTimeout(() => { toast.style.animation = 'slideOutRight 0.3s ease'; setTimeout(() => container.removeChild(toast), 300); }, 3000); } function getToastIcon(type) { const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; return icons[type] || icons.info; } // 键盘快捷键处理 function handleKeyPress(e) { if (AppState.loading) return; // Ctrl + 数字键切换标签 if (e.ctrlKey) { switch(e.key) { case '1': e.preventDefault(); document.querySelector('[data-tab="screening"]').click(); break; case '2': e.preventDefault(); document.querySelector('[data-tab="analysis"]').click(); break; } } // Enter 键执行当前标签的主要操作 if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); switch(AppState.currentTab) { case 'screening': document.getElementById('screening-btn').click(); break; case 'analysis': document.getElementById('analysis-btn').click(); break; } } }