Files
strategy_backtest/static/js/export.js

260 lines
8.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 导出功能模块
// 导出为Excel使用纯JS实现CSV导出
function exportToExcel(data, filename) {
// 将数据转换为CSV格式
const csv = convertToCSV(data);
// 创建Blob并下载
const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function convertToCSV(data) {
if (!data || data.length === 0) return '';
// 获取表头
const headers = Object.keys(data[0]);
// 构建CSV内容
const csvContent = [
headers.join(','), // 表头行
...data.map(row =>
headers.map(header => {
let cell = row[header];
// 处理特殊字符
if (typeof cell === 'string' && (cell.includes(',') || cell.includes('"'))) {
cell = `"${cell.replace(/"/g, '""')}"`;
}
return cell;
}).join(',')
)
].join('\n');
return csvContent;
}
// 绑定导出按钮事件
document.addEventListener('DOMContentLoaded', function() {
// 延迟绑定,确保元素已加载
setTimeout(() => {
// 参数优化结果导出
const optExportBtn = document.getElementById('opt-export-btn');
if (optExportBtn) {
optExportBtn.addEventListener('click', () => {
if (currentOptData && currentOptData.data) {
const filename = `参数优化结果_${new Date().toISOString().slice(0,10)}.csv`;
exportToExcel(currentOptData.data, filename);
} else {
alert('请先加载数据');
}
});
}
// 交易明细导出
const tradesExportBtn = document.getElementById('trades-export-btn');
if (tradesExportBtn) {
tradesExportBtn.addEventListener('click', () => {
if (currentTradesData && currentTradesData.data) {
const filename = `交易明细_${new Date().toISOString().slice(0,10)}.csv`;
exportToExcel(currentTradesData.data, filename);
} else {
alert('请先加载数据');
}
});
}
}, 1000);
});
// 生成完整报告HTML格式
function generateFullReport() {
if (!currentOptData && !currentTradesData && !currentEquityData) {
alert('请先加载数据');
return;
}
let reportHTML = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>回测报告</title>
<style>
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #667eea;
border-bottom: 3px solid #667eea;
padding-bottom: 10px;
}
h2 {
color: #333;
margin-top: 30px;
border-left: 4px solid #667eea;
padding-left: 10px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}
th {
background: #667eea;
color: white;
}
tr:nth-child(even) {
background: #f8f9fa;
}
.positive {
color: #28a745;
font-weight: bold;
}
.negative {
color: #dc3545;
font-weight: bold;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin: 20px 0;
}
.stat-box {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
}
.stat-box h3 {
margin: 0 0 10px 0;
font-size: 0.9em;
}
.stat-box p {
margin: 0;
font-size: 1.5em;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<h1>📊 A股回测报告</h1>
<p>生成时间:${new Date().toLocaleString('zh-CN')}</p>
`;
// 添加优化结果部分
if (currentOptData) {
reportHTML += `
<h2>参数优化结果</h2>
<div class="stats-grid">
<div class="stat-box">
<h3>参数组合总数</h3>
<p>${currentOptData.stats.total_combinations || 0}</p>
</div>
<div class="stat-box">
<h3>最佳夏普比率</h3>
<p>${currentOptData.stats.best_sharpe ? currentOptData.stats.best_sharpe.toFixed(4) : 'N/A'}</p>
</div>
<div class="stat-box">
<h3>最佳总收益</h3>
<p>${currentOptData.stats.best_return ? (currentOptData.stats.best_return * 100).toFixed(2) + '%' : 'N/A'}</p>
</div>
</div>
<table>
<thead>
<tr>
<th>排名</th>
<th>短期均线</th>
<th>长期均线</th>
<th>持有天数</th>
<th>总收益率</th>
<th>夏普比率</th>
<th>最大回撤</th>
</tr>
</thead>
<tbody>
`;
currentOptData.data.slice(0, 10).forEach((row, index) => {
reportHTML += `
<tr>
<td>${index + 1}</td>
<td>${row.param_ma_short || '-'}</td>
<td>${row.param_ma_long || '-'}</td>
<td>${row.param_hold_days || '-'}</td>
<td class="${row.total_return > 0 ? 'positive' : 'negative'}">${(row.total_return * 100).toFixed(2)}%</td>
<td>${row.sharpe ? row.sharpe.toFixed(4) : '-'}</td>
<td class="negative">${(row.max_drawdown * 100).toFixed(2)}%</td>
</tr>
`;
});
reportHTML += `
</tbody>
</table>
`;
}
// 添加交易明细部分
if (currentTradesData) {
reportHTML += `
<h2>交易统计</h2>
<div class="stats-grid">
<div class="stat-box">
<h3>总交易次数</h3>
<p>${currentTradesData.stats.total_trades || 0}</p>
</div>
<div class="stat-box">
<h3>胜率</h3>
<p>${currentTradesData.stats.win_rate || '0%'}</p>
</div>
<div class="stat-box">
<h3>总盈亏</h3>
<p>¥${currentTradesData.stats.total_profit ? currentTradesData.stats.total_profit.toFixed(2) : '0'}</p>
</div>
</div>
`;
}
reportHTML += `
</div>
</body>
</html>
`;
// 下载HTML报告
const blob = new Blob([reportHTML], { type: 'text/html;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `回测报告_${new Date().toISOString().slice(0,10)}.html`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}