fix(frontend): CTA结果页补净值曲线图(零成交也出图)
CI/CD / test (push) Successful in 8s
CI/CD / nas-deploy (push) Successful in 21s
CI/CD / nas-verify (push) Successful in 3s

Result.vue 拉了 equity-curve 数据却未画净值曲线;且 benchmark-curve/risk-series 依赖 *_metrics.json,CTA 引擎当前未生成该文件,致收益概述图全空。新增 EquityCurve.vue 走 equity-curve 接口(独立于 metrics.json,数据源 BacktestResult.equity_curve),零成交时画 N 天平线(初始资金不变),满足'有结果就有图'。vue-tsc RC=0。
This commit is contained in:
2026-08-02 19:10:55 +08:00
parent 9588a77a01
commit 857d43c6c2
2 changed files with 69 additions and 0 deletions
@@ -0,0 +1,59 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import type { EChartsCoreOption } from 'echarts'
import { useChart } from '@/composables/useChart'
import { darkTitle, darkTooltip, darkGrid, darkAxis } from '@/utils/echartsDark'
// 净值曲线(独立组件)。数据源 equity-curve 接口 = BacktestResult.equity_curve,
// 不依赖 *_metrics.json(CTA 引擎当前未生成该文件,致 BenchmarkCurve 等图全空)。
// 零成交时净值是 N 天平线(初始资金不变),也要画出而非空白。
const props = defineProps<{ dates: string[]; equity: number[] }>()
const el = ref<HTMLDivElement>()
const { setOption } = useChart(el)
const isEmpty = computed(() => !props.dates.length || !props.equity.length)
function render(): void {
if (isEmpty.value) return
const option: EChartsCoreOption = {
title: darkTitle('净值曲线'),
tooltip: darkTooltip(),
grid: darkGrid(),
xAxis: { type: 'category', data: props.dates, ...darkAxis() },
yAxis: { type: 'value', scale: true, name: '净值(元)', ...darkAxis() },
series: [
{
type: 'line',
name: '净值',
smooth: true,
showSymbol: false,
lineStyle: { color: '#c23531', width: 1.6 },
areaStyle: { color: '#c23531', opacity: 0.12 },
data: props.equity,
},
],
}
setOption(option)
}
onMounted(render)
watch(() => [props.dates, props.equity], render, { deep: true })
</script>
<template>
<div v-if="isEmpty" class="chart-empty">暂无净值数据</div>
<div v-else ref="el" class="chart-box" />
</template>
<style scoped>
.chart-box { width: 100%; height: 320px; }
.chart-empty {
width: 100%;
height: 320px;
display: flex;
align-items: center;
justify-content: center;
color: #8b949e;
font-size: 13px;
}
</style>
+10
View File
@@ -14,6 +14,7 @@ import AlphaChart from '@/components/backtest/AlphaChart.vue'
import BetaChart from '@/components/backtest/BetaChart.vue' import BetaChart from '@/components/backtest/BetaChart.vue'
import DrawdownChart from '@/components/backtest/DrawdownChart.vue' import DrawdownChart from '@/components/backtest/DrawdownChart.vue'
import VolatilityChart from '@/components/backtest/VolatilityChart.vue' import VolatilityChart from '@/components/backtest/VolatilityChart.vue'
import EquityCurve from '@/components/backtest/EquityCurve.vue'
const route = useRoute() const route = useRoute()
const taskId = String(route.params.id) const taskId = String(route.params.id)
@@ -109,6 +110,12 @@ function filterDataByTimeRange<T extends { dates: string[] }>(data: T): T {
const filteredBenchmarkCurve = computed(() => filterDataByTimeRange(benchmarkCurve.value)) const filteredBenchmarkCurve = computed(() => filterDataByTimeRange(benchmarkCurve.value))
const filteredRiskSeries = computed(() => filterDataByTimeRange(riskSeries.value)) const filteredRiskSeries = computed(() => filterDataByTimeRange(riskSeries.value))
// 净值曲线(走 equity-curve 接口,独立于 benchmark-curve)。benchmark-curve/risk-series
// 依赖 *_metrics.json,CTA 引擎当前未生成该文件 → 收益概述图全空;equity-curve 直接
// 来自 BacktestResult.equity_curve(零成交也有 N 天平线),单独画保证"有结果就有图"。
const equityDates = computed(() => equity.value.map((e) => fmtDate(e.date)))
const equityValues = computed(() => equity.value.map((e) => e.balance))
onMounted(async () => { onMounted(async () => {
// 请求隔离:任一接口失败(如 benchmark-curve/risk-series 数据缺失)不得阻塞 // 请求隔离:任一接口失败(如 benchmark-curve/risk-series 数据缺失)不得阻塞
// 其余请求。statistics/equity/trades 有数据时必须正常渲染。用 Promise.allSettled // 其余请求。statistics/equity/trades 有数据时必须正常渲染。用 Promise.allSettled
@@ -228,6 +235,9 @@ function fmtDate(d: string): string {
<!-- Tab 1: 收益概述 --> <!-- Tab 1: 收益概述 -->
<el-tab-pane label="收益概述"> <el-tab-pane label="收益概述">
<div class="charts-container"> <div class="charts-container">
<el-card>
<EquityCurve :dates="equityDates" :equity="equityValues" />
</el-card>
<el-card> <el-card>
<BenchmarkCurve <BenchmarkCurve
:dates="filteredBenchmarkCurve.dates" :dates="filteredBenchmarkCurve.dates"