mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 16:54:20 +08:00
选 2-4 个已完成的回测 task,叠加净值曲线 + 横向指标对比。 后端(最小增量): - task_runner.list_recent(limit):返回最近 N 个任务摘要(LRU 倒序) - GET /backtest/tasks?limit=20:任务摘要列表端点(不含完整 result) - TaskSummary / TaskListResponse schema 前端(/compare 对比页): - CompareView:左栏勾选已完成 task,右栏叠加对比 - CompareChart:多 task 净值叠加图(归一化为初始=1) - CompareTable:多 task 指标横向对比表(8 项指标) - 按 result 结构判断可对比性(仅单标的 BacktestResult 可对比) 复用现有 task_runner LRU 表,无新增存储。测试:823 passed(+2 列表端点)
87 lines
2.2 KiB
Vue
87 lines
2.2 KiB
Vue
<script setup lang="ts">
|
|
// 多 task 净值叠加对比图(归一化为初始=1)。
|
|
|
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
|
|
import echarts from '../echarts-setup'
|
|
import type { BacktestResult } from '../types'
|
|
|
|
const props = defineProps<{
|
|
items: Array<{ label: string; result: BacktestResult }>
|
|
}>()
|
|
|
|
const container = ref<HTMLDivElement>()
|
|
let chart: echarts.ECharts | null = null
|
|
|
|
function render() {
|
|
if (!container.value || props.items.length === 0) return
|
|
chart ??= echarts.init(container.value, 'dark')
|
|
chart.setOption(buildOption(), true)
|
|
}
|
|
|
|
function buildOption(): echarts.EChartsCoreOption {
|
|
const seriesData = props.items.map((item) => {
|
|
const ec = item.result.equity_curve
|
|
const initial = ec[0]?.total || 1
|
|
return {
|
|
name: item.label,
|
|
dates: ec.map((e) => e.datetime.slice(0, 10)),
|
|
values: ec.map((e) => e.total / initial),
|
|
}
|
|
})
|
|
|
|
const allDates = Array.from(new Set(seriesData.flatMap((s) => s.dates))).sort()
|
|
|
|
return {
|
|
backgroundColor: 'transparent',
|
|
tooltip: { trigger: 'axis' },
|
|
legend: { top: 0, data: seriesData.map((s) => s.name) },
|
|
grid: { left: '8%', right: '5%', top: 30, bottom: 50 },
|
|
xAxis: { type: 'category', data: allDates, boundaryGap: false },
|
|
yAxis: { type: 'value', scale: true, name: '归一化净值' },
|
|
dataZoom: [{ type: 'inside', start: 0, end: 100 }],
|
|
series: seriesData.map((s) => {
|
|
const valMap = new Map(s.dates.map((d, i) => [d, s.values[i]]))
|
|
let last = 1
|
|
const aligned = allDates.map((d) => {
|
|
const v = valMap.get(d)
|
|
if (v !== undefined) last = v
|
|
return last
|
|
})
|
|
return {
|
|
name: s.name,
|
|
type: 'line',
|
|
data: aligned,
|
|
symbol: 'none',
|
|
lineStyle: { width: 1.5 },
|
|
}
|
|
}),
|
|
}
|
|
}
|
|
|
|
function resize() {
|
|
chart?.resize()
|
|
}
|
|
onMounted(() => {
|
|
render()
|
|
window.addEventListener('resize', resize)
|
|
})
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('resize', resize)
|
|
chart?.dispose()
|
|
chart = null
|
|
})
|
|
watch(() => props.items, render)
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="container" class="compare-chart"></div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.compare-chart {
|
|
width: 100%;
|
|
height: 380px;
|
|
}
|
|
</style>
|