// 全局变量 let currentOptData = null; let currentTradesData = null; let currentEquityData = null; let equityChart = null; let comparisonChart = null; let profitDistChart = null; let drawdownChart = null; let holdingPeriodChart = null; let monthlyReturnChart = null; let heatmapChart = null; // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { initTabs(); loadOptimizationFiles(); loadTradesFiles(); loadEquityFiles(); // 绑定事件 document.getElementById('opt-file-select').addEventListener('change', loadOptimizationData); document.getElementById('opt-sort-select').addEventListener('change', renderOptimizationTable); document.getElementById('opt-limit').addEventListener('change', renderOptimizationTable); document.getElementById('opt-refresh-btn').addEventListener('click', () => { loadOptimizationFiles(); loadOptimizationData(); }); document.getElementById('trades-file-select').addEventListener('change', loadTradesData); document.getElementById('trades-filter').addEventListener('change', renderTradesTable); document.getElementById('trades-refresh-btn').addEventListener('click', () => { loadTradesFiles(); loadTradesData(); }); document.getElementById('equity-file-select').addEventListener('change', loadEquityData); document.getElementById('equity-refresh-btn').addEventListener('click', () => { loadEquityFiles(); loadEquityData(); }); }); // 标签页切换 function initTabs() { const tabBtns = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); tabBtns.forEach(btn => { btn.addEventListener('click', () => { const tabName = btn.getAttribute('data-tab'); // 更新按钮状态 tabBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); // 更新内容显示 tabContents.forEach(content => { content.classList.remove('active'); if (content.id === tabName) { content.classList.add('active'); } }); }); }); } // ===== 参数优化部分 ===== async function loadOptimizationFiles() { try { const response = await fetch('/api/optimization/list'); const result = await response.json(); if (result.success) { const select = document.getElementById('opt-file-select'); select.innerHTML = ''; result.files.forEach(file => { const option = document.createElement('option'); option.value = file.filename; option.textContent = `${file.time_display} (${file.num_results}组参数)`; select.appendChild(option); }); } } catch (error) { console.error('加载优化文件列表失败:', error); } } async function loadOptimizationData() { const filename = document.getElementById('opt-file-select').value; if (!filename) return; try { const response = await fetch(`/api/optimization/${filename}`); const result = await response.json(); if (result.success) { currentOptData = result; updateOptimizationStats(result.stats); renderOptimizationTable(); } } catch (error) { console.error('加载优化数据失败:', error); } } function updateOptimizationStats(stats) { document.getElementById('opt-total').textContent = stats.total_combinations || '-'; document.getElementById('opt-best-sharpe').textContent = stats.best_sharpe !== null ? stats.best_sharpe.toFixed(4) : '-'; document.getElementById('opt-best-return').textContent = stats.best_return !== null ? (stats.best_return * 100).toFixed(2) + '%' : '-'; document.getElementById('opt-worst-dd').textContent = stats.worst_drawdown !== null ? (stats.worst_drawdown * 100).toFixed(2) + '%' : '-'; } function renderOptimizationTable() { if (!currentOptData) return; const sortBy = document.getElementById('opt-sort-select').value; const limit = parseInt(document.getElementById('opt-limit').value); // 排序数据 let data = [...currentOptData.data]; data.sort((a, b) => { if (sortBy === 'max_drawdown') { return (b[sortBy] || -999) - (a[sortBy] || -999); // 回撤越小越好 } else { return (b[sortBy] || -999) - (a[sortBy] || -999); // 其他指标越大越好 } }); // 限制显示数量 data = data.slice(0, limit); // 根据排序方式确定需要加粗的列索引 const sortColumnMap = { 'total_return': 5, 'annual_return': 6, 'sharpe': 7, 'max_drawdown': 8 }; const boldColumnIndex = sortColumnMap[sortBy] || -1; // 渲染表格 const tbody = document.getElementById('opt-tbody'); tbody.innerHTML = ''; data.forEach((row, index) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${index + 1} ${row.param_ma_short || '-'} ${row.param_ma_long || '-'} ${row.param_hold_days || '-'} ${row.param_position_pct_per_stock || '-'} ${(row.total_return * 100).toFixed(2)}% ${(row.annual_return * 100).toFixed(2)}% ${row.sharpe !== null ? row.sharpe.toFixed(4) : '-'} ${(row.max_drawdown * 100).toFixed(2)}% ${(row.avg_capital_utilization * 100).toFixed(2)}% ${row.total_trades || 0} `; tbody.appendChild(tr); }); } // ===== 交易明细部分 ===== async function loadTradesFiles() { try { const response = await fetch('/api/trades/list'); const result = await response.json(); if (result.success) { const select = document.getElementById('trades-file-select'); select.innerHTML = ''; result.files.forEach(file => { const option = document.createElement('option'); option.value = file.filename; option.textContent = `${file.time_display} - ${file.strategy_params}`; select.appendChild(option); }); } } catch (error) { console.error('加载交易文件列表失败:', error); } } async function loadTradesData() { const filename = document.getElementById('trades-file-select').value; if (!filename) return; try { const response = await fetch(`/api/trades/${filename}`); const result = await response.json(); if (result.success) { currentTradesData = result; updateTradesStats(result.stats); renderTradesTable(); } } catch (error) { console.error('加载交易数据失败:', error); } } function updateTradesStats(stats) { document.getElementById('trades-total').textContent = stats.total_trades || '-'; document.getElementById('trades-win-rate').textContent = stats.win_rate || '-'; document.getElementById('trades-profit').textContent = stats.total_profit !== null ? '¥' + stats.total_profit.toFixed(2) : '-'; document.getElementById('trades-avg').textContent = stats.avg_profit !== null ? '¥' + stats.avg_profit.toFixed(2) : '-'; document.getElementById('trades-max-win').textContent = stats.max_profit !== null ? '¥' + stats.max_profit.toFixed(2) : '-'; document.getElementById('trades-max-loss').textContent = stats.max_loss !== null ? '¥' + stats.max_loss.toFixed(2) : '-'; } function renderTradesTable() { if (!currentTradesData) return; const filter = document.getElementById('trades-filter').value; // 筛选数据 let data = [...currentTradesData.data]; if (filter === 'win') { data = data.filter(row => row.is_win === true); } else if (filter === 'loss') { data = data.filter(row => row.is_win === false); } // 渲染表格 const tbody = document.getElementById('trades-tbody'); tbody.innerHTML = ''; data.forEach((row, index) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${index + 1} ${row.ts_code} ¥${row.buy_price.toFixed(2)} ¥${row.sell_price.toFixed(2)} ${row.quantity} ${(row.profit_pct * 100).toFixed(2)}% ¥${row.profit_amount.toFixed(2)} ${row.is_win ? '盈利' : '亏损'} `; tbody.appendChild(tr); }); } // ===== 资金曲线部分 ===== async function loadEquityFiles() { try { const response = await fetch('/api/equity/list'); const result = await response.json(); if (result.success) { const select = document.getElementById('equity-file-select'); select.innerHTML = ''; result.files.forEach(file => { const option = document.createElement('option'); option.value = file.filename; option.textContent = `${file.strategy} (${file.total_return})`; select.appendChild(option); }); } } catch (error) { console.error('加载资金曲线文件列表失败:', error); } } async function loadEquityData() { const filename = document.getElementById('equity-file-select').value; if (!filename) return; try { const response = await fetch(`/api/equity/${filename}`); const result = await response.json(); if (result.success) { currentEquityData = result; updateEquityStats(result.stats); renderEquityChart(result.data); } } catch (error) { console.error('加载资金曲线数据失败:', error); } } function updateEquityStats(stats) { document.getElementById('equity-initial').textContent = stats.initial_asset !== null ? '¥' + stats.initial_asset.toLocaleString() : '-'; document.getElementById('equity-final').textContent = stats.final_asset !== null ? '¥' + stats.final_asset.toLocaleString() : '-'; document.getElementById('equity-return').textContent = stats.total_return !== null ? (stats.total_return * 100).toFixed(2) + '%' : '-'; document.getElementById('equity-dd').textContent = stats.max_drawdown !== null ? (stats.max_drawdown * 100).toFixed(2) + '%' : '-'; } function renderEquityChart(data) { const ctx = document.getElementById('equity-chart').getContext('2d'); // 销毁旧图表 if (equityChart) { equityChart.destroy(); } // 准备数据(每隔10个点取一个,避免数据过多) const step = Math.max(1, Math.floor(data.length / 200)); const labels = data.filter((_, i) => i % step === 0).map(row => row.trade_date); const assets = data.filter((_, i) => i % step === 0).map(row => row.total_asset); const cash = data.filter((_, i) => i % step === 0).map(row => row.cash); const marketValue = data.filter((_, i) => i % step === 0).map(row => row.market_value); // 创建新图表 equityChart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [ { label: '总资产', data: assets, borderColor: '#667eea', backgroundColor: 'rgba(102, 126, 234, 0.1)', borderWidth: 2, fill: true, tension: 0.4 }, { label: '现金', data: cash, borderColor: '#28a745', backgroundColor: 'rgba(40, 167, 69, 0.1)', borderWidth: 2, fill: true, tension: 0.4 }, { label: '持仓市值', data: marketValue, borderColor: '#ffc107', backgroundColor: 'rgba(255, 193, 7, 0.1)', borderWidth: 2, fill: true, tension: 0.4 } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top', }, title: { display: true, text: '资金曲线走势图', font: { size: 16 } }, tooltip: { mode: 'index', intersect: false, } }, scales: { x: { display: true, title: { display: true, text: '交易日期' }, ticks: { maxTicksLimit: 20 } }, y: { display: true, title: { display: true, text: '金额 (元)' } } } } }); }