新建回测系统,并提交
This commit is contained in:
259
static/js/export.js
Normal file
259
static/js/export.js
Normal file
@@ -0,0 +1,259 @@
|
||||
// 导出功能模块
|
||||
|
||||
// 导出为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);
|
||||
}
|
||||
Reference in New Issue
Block a user