新建回测系统,并提交

This commit is contained in:
2026-01-17 21:37:42 +08:00
commit fe50ea935a
68 changed files with 108208 additions and 0 deletions

259
static/js/export.js Normal file
View 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);
}