fix(frontend): CTA结果页补净值曲线图(零成交也出图)
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:
@@ -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>
|
||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user