mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 20:24:16 +08:00
对单个策略的 1-2 个参数做网格搜索,遍历用户指定的取值列表笛卡尔积, 每个组合跑一次回测,按 total_return 排序,返回排名表 + 热力图。 后端: - ParamGridOptimizer(backtest/optimizer.py):itertools.product 遍历网格, 每点 entry.build(params) + BacktestEngine.run(df),复用同一 DataFrame - 网格大小上限 200 防组合爆炸,单点失败容错(跳过不中断) - 2 参数时生成热力图矩阵(x/y 轴取值 + cell 收益率) - POST /backtest/optimize/run/async 端点(后台任务) - OptimizeBacktestRequest schema(param_grid 1-2 参数) 前端(/optimize 寻优页): - ParamGridPicker:勾选 1-2 个寻优参数,逗号分隔填取值列表 - OptimizeResultTable:网格点排名表(按收益降序,最优高亮) - OptimizeHeatmap:2 参数热力图(ECharts heatmap,绿→红映射收益) - 最优点「查看」按钮跳转单标的页用该参数回测 测试:821 passed(+10 寻优器单测 + 3 寻优路由测试)
102 lines
2.3 KiB
Vue
102 lines
2.3 KiB
Vue
<script setup lang="ts">
|
|
// 网格点排名表,按 total_return 降序,最优高亮。
|
|
|
|
import type { GridPointResult } from '../types'
|
|
|
|
defineProps<{
|
|
results: GridPointResult[]
|
|
bestIndex?: number
|
|
}>()
|
|
|
|
defineEmits<{ select: [params: Record<string, number | string>] }>()
|
|
|
|
function pct(v: number | null): string {
|
|
return v !== null && Number.isFinite(v) ? `${(v * 100).toFixed(2)}%` : '-'
|
|
}
|
|
function num(v: number | null, d = 2): string {
|
|
return v !== null && Number.isFinite(v) ? v.toFixed(d) : '-'
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<table class="opt-table">
|
|
<thead>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>参数</th>
|
|
<th class="num">总收益</th>
|
|
<th class="num">夏普</th>
|
|
<th class="num">最大回撤</th>
|
|
<th class="num">交易数</th>
|
|
<th class="num">胜率</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(r, i) in results" :key="i" :class="{ best: i === bestIndex }">
|
|
<td class="rank">{{ i + 1 }}</td>
|
|
<td class="params">{{ JSON.stringify(r.params) }}</td>
|
|
<td class="num" :class="r.total_return !== null && r.total_return > 0 ? 'pos' : 'neg'">
|
|
{{ pct(r.total_return) }}
|
|
</td>
|
|
<td class="num">{{ num(r.sharpe) }}</td>
|
|
<td class="num neg">{{ pct(r.max_drawdown) }}</td>
|
|
<td class="num">{{ r.total_trades }}</td>
|
|
<td class="num">{{ pct(r.win_rate) }}</td>
|
|
<td><button class="view-btn" @click="$emit('select', r.params)">查看</button></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.opt-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 13px;
|
|
}
|
|
.opt-table th,
|
|
.opt-table td {
|
|
padding: 6px 10px;
|
|
border-bottom: 1px solid var(--border);
|
|
text-align: left;
|
|
}
|
|
.opt-table th {
|
|
color: var(--text-dim);
|
|
font-size: 12px;
|
|
position: sticky;
|
|
top: 0;
|
|
background: var(--bg-panel);
|
|
}
|
|
.num {
|
|
text-align: right;
|
|
font-family: var(--font-mono);
|
|
}
|
|
.params {
|
|
font-family: var(--font-mono);
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
}
|
|
.rank {
|
|
color: var(--text-dim);
|
|
width: 32px;
|
|
}
|
|
.best {
|
|
background: rgba(74, 158, 255, 0.08);
|
|
}
|
|
.best .rank {
|
|
color: var(--accent);
|
|
font-weight: 700;
|
|
}
|
|
.pos {
|
|
color: var(--up);
|
|
}
|
|
.neg {
|
|
color: var(--down);
|
|
}
|
|
.view-btn {
|
|
font-size: 11px;
|
|
padding: 2px 8px;
|
|
}
|
|
</style>
|