260 lines
8.1 KiB
JavaScript
260 lines
8.1 KiB
JavaScript
// 导出功能模块
|
||
|
||
// 导出为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);
|
||
}
|