fix(portfolio): 历史组合结果可查看(PortfolioBacktest查看模式+History跳转/筛选/标签); 回测等待加已耗时提示(治卡顿错觉) [nas]
This commit is contained in:
@@ -9,7 +9,7 @@ const tasks = ref<TaskListItem[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 筛选
|
||||
const typeFilter = ref<'all' | 'cta' | 'optimize' | 'factor'>('all')
|
||||
const typeFilter = ref<'all' | 'cta' | 'portfolio' | 'optimize' | 'factor'>('all')
|
||||
const statusFilter = ref<'all' | 'done' | 'failed'>('all')
|
||||
const keyword = ref('')
|
||||
// 分页
|
||||
@@ -19,6 +19,7 @@ const pageSize = 20
|
||||
const typeOptions = [
|
||||
{ key: 'all', label: '全部' },
|
||||
{ key: 'cta', label: '回测' },
|
||||
{ key: 'portfolio', label: '组合' },
|
||||
{ key: 'optimize', label: '参数优化' },
|
||||
{ key: 'factor', label: '因子分析' },
|
||||
] as const
|
||||
@@ -77,10 +78,11 @@ async function load(): Promise<void> {
|
||||
function open(row: TaskListItem): void {
|
||||
if (row.type === 'factor') router.push(`/factor/result/${row.task_id}`)
|
||||
else if (row.type === 'optimize') router.push(`/backtest/optimize-result/${row.task_id}`)
|
||||
else if (row.type === 'portfolio') router.push(`/backtest/portfolio?task_id=${row.task_id}`)
|
||||
else router.push(`/backtest/result/${row.task_id}`)
|
||||
}
|
||||
|
||||
const typeLabelMap: Record<string, string> = { cta: '回测', optimize: '优化', factor: '因子' }
|
||||
const typeLabelMap: Record<string, string> = { cta: '回测', portfolio: '组合', optimize: '优化', factor: '因子' }
|
||||
function typeLabel(t: string): string {
|
||||
return typeLabelMap[t] || t
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { EChartsCoreOption } from 'echarts'
|
||||
import { useChart } from '@/composables/useChart'
|
||||
import { darkTitle, darkTooltip, darkGrid, darkAxis } from '@/utils/echartsDark'
|
||||
@@ -109,8 +110,29 @@ function renderEquity(): void {
|
||||
}
|
||||
setEquityOption(option)
|
||||
}
|
||||
const route = useRoute()
|
||||
const viewTaskId = computed(() => (route.query.task_id as string) || '')
|
||||
const elapsedSec = ref(0)
|
||||
let elapsedTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
async function loadResult(tid: string): Promise<void> {
|
||||
try {
|
||||
const r = await getPortfolioResult(tid)
|
||||
result.value = r
|
||||
equityCurve.value = r.equity_curve || []
|
||||
stocks.value = r.stocks_selected || []
|
||||
trades.value = r.trades || []
|
||||
metrics.value = r.metrics || null
|
||||
period.value = r.period || null
|
||||
await nextTick()
|
||||
renderEquity()
|
||||
} catch {
|
||||
ElMessage.error('加载历史结果失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 等 dom 挂载后渲染(若有初始数据)
|
||||
if (viewTaskId.value) loadResult(viewTaskId.value)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
if (pollTimer) clearInterval(pollTimer)
|
||||
@@ -130,6 +152,10 @@ async function onSubmit(): Promise<void> {
|
||||
submitting.value = true
|
||||
errorMsg.value = ''
|
||||
stageHint.value = ''
|
||||
elapsedSec.value = 0
|
||||
elapsedTimer = setInterval(() => {
|
||||
elapsedSec.value += 1
|
||||
}, 1000)
|
||||
result.value = null
|
||||
equityCurve.value = []
|
||||
stocks.value = []
|
||||
@@ -185,6 +211,10 @@ async function onSubmit(): Promise<void> {
|
||||
ElMessage.error('回测失败,详见页面提示')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
if (elapsedTimer) {
|
||||
clearInterval(elapsedTimer)
|
||||
elapsedTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -193,14 +223,14 @@ async function onSubmit(): Promise<void> {
|
||||
<div class="page bt-portfolio">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="page-title">组合策略回测</h2>
|
||||
<h2 class="page-title">{{ viewTaskId ? '组合回测结果' : '组合策略回测' }}</h2>
|
||||
<p class="page-subtitle">
|
||||
BulletTrade + {{ strategyLabel }} · 本地执行回测 · MVP 验证链路
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card class="blk" shadow="never">
|
||||
<el-card v-if="!viewTaskId" class="blk" shadow="never">
|
||||
<template #header><span class="section-title">回测参数</span></template>
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="标的池">
|
||||
@@ -267,7 +297,7 @@ async function onSubmit(): Promise<void> {
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="submit-bar">
|
||||
<div v-if="!viewTaskId" class="submit-bar">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@@ -277,7 +307,7 @@ async function onSubmit(): Promise<void> {
|
||||
{{ submitting ? `回测中${stageHint ? '(' + stageHint + ')' : '...'}` : '开始回测' }}
|
||||
</el-button>
|
||||
<span v-if="submitting" class="muted form-hint">
|
||||
异步执行,请勿关闭页面
|
||||
异步执行 · 已等待 {{ elapsedSec }}s · 组合回测加载大库数据预计 1-3 分钟,请勿关闭
|
||||
</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
Reference in New Issue
Block a user