From 5aec4996c63142e89e5f01b6fe1edde821751acc Mon Sep 17 00:00:00 2001 From: Justin Gu <97915@qq.com> Date: Mon, 6 Jul 2026 00:59:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(web-ui):=20=E7=AD=96=E7=95=A5=E5=BA=93?= =?UTF-8?q?=E7=BB=84=E5=90=88=E5=9B=9E=E6=B5=8B=E7=BB=93=E6=9E=9C=E5=8A=A0?= =?UTF-8?q?=E7=BB=84=E5=90=88=E8=AF=84=E7=BA=A7=20+=20=E5=B8=82=E5=9C=BA?= =?UTF-8?q?=E5=89=8D=E7=BC=80=E7=BA=A0=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StrategiesView: 组合回测结果区新增「组合评级」详情块,复用 gradePortfolio(与 /portfolio 页和单标的回测页同口径的 5 维度评分) - StrategiesView: 新增 normalizeSymbol,发请求前用 detectMarket 重算 市场前缀,纠正历史保存策略的错配(如 SZ:515030 → SH:515030) - BacktestView: fullSymbol 改用 market.ts 的 detectMarket,修复 5 开头 沪市基金/ETF 被错判为深市的问题(与 SymbolPicker/StocksPicker 统一) --- web-ui/src/views/BacktestView.vue | 9 ++++---- web-ui/src/views/StrategiesView.vue | 33 +++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/web-ui/src/views/BacktestView.vue b/web-ui/src/views/BacktestView.vue index 0d7d0d3..e9e56fa 100644 --- a/web-ui/src/views/BacktestView.vue +++ b/web-ui/src/views/BacktestView.vue @@ -14,6 +14,7 @@ import StrategyPicker from '../components/StrategyPicker.vue' import SymbolPicker from '../components/SymbolPicker.vue' import TradeTable from '../components/TradeTable.vue' import { formatError, saveStrategy } from '../api' +import { detectMarket } from '../market' import { gradePerformance } from '../grading' import type { Category, ExecutionMode } from '../types' import { useBacktestStore } from '../stores/backtest' @@ -121,12 +122,10 @@ const grade = computed(() => ) // 当前股票完整代码(市场:6位),从 SymbolPicker 同步来的 code 是纯数字, -// 需要带上市场前缀。复用 SymbolPicker 内部已经算好的前缀更稳妥——这里简单按 -// 交易所规则推断(6 位代码:6/9 开头 SH,其余 SZ;8/4 开头 BJ)。 +// 需要带上市场前缀。复用 market.ts 的 detectMarket(与 SymbolPicker / +// StocksPicker 同一套规则),避免分叉导致 ETF/基金(5 开头)等被错判市场。 function fullSymbol(code6: string): string { - if (/^(6|9)/.test(code6)) return `SH:${code6}` - if (/^(8|4)/.test(code6)) return `BJ:${code6}` - return `SZ:${code6}` + return `${detectMarket(code6)}:${code6}` } function openSaveForm() { diff --git a/web-ui/src/views/StrategiesView.vue b/web-ui/src/views/StrategiesView.vue index 39b7ab1..8561551 100644 --- a/web-ui/src/views/StrategiesView.vue +++ b/web-ui/src/views/StrategiesView.vue @@ -7,6 +7,7 @@ import { computed, nextTick, onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import EquityChart from '../components/EquityChart.vue' +import GradeDetails from '../components/GradeDetails.vue' import MetricTable from '../components/MetricTable.vue' import PortfolioCompareChart from '../components/PortfolioCompareChart.vue' import PortfolioSummaryTable from '../components/PortfolioSummaryTable.vue' @@ -16,6 +17,8 @@ import { formatError, saveStrategy, } from '../api' +import { gradePortfolio } from '../grading' +import { detectMarket } from '../market' import type { MultiStrategyItem, Performance, SavedStrategy } from '../types' import { useBacktestStore } from '../stores/backtest' @@ -78,6 +81,17 @@ function clearSelection() { selectedIds.value = new Set() } +/** 纠正历史保存策略的市场前缀。 + * 早期 BacktestView.fullSymbol 硬编码市场判断(漏判 5 开头的沪市基金/ETF), + * 导致部分历史保存的策略 symbol 被错标(如 SZ:515030,应为 SH:515030), + * 后端按错配市场取到 0 根 K 线被静默跳过。 + * 这里在发请求前用 detectMarket 重算前缀,纠正历史数据 + 兜底未来。 */ +function normalizeSymbol(raw: string): string { + if (!raw) return raw + const code = raw.includes(':') ? raw.split(':').pop()! : raw + return `${detectMarket(code)}:${code}` +} + /** 组合回测:把勾选的策略组装成 MultiStrategyItem[],各跑原标的,资金均分。 */ async function onComboBacktest() { if (selectedStrategies.value.length === 0) return @@ -93,7 +107,7 @@ async function onComboBacktest() { strategy: s.strategy, strategy_label: s.strategy_label || s.strategy, params: s.params, - symbol: s.context.symbol as string, + symbol: normalizeSymbol(s.context.symbol as string), category: (s.context.category as MultiStrategyItem['category']) || 'DAY', start_date: (s.context.start_date as string) || undefined, end_date: (s.context.end_date as string) || undefined, @@ -190,7 +204,11 @@ async function onLoadMulti(s: SavedStrategy) { `结果区会显示截至今天的策略信号("哪些该买/该卖")。`, )) return const today = isoToday() - const items = rawItems.map((it) => ({ ...it, end_date: today })) + const items = rawItems.map((it) => ({ + ...it, + symbol: normalizeSymbol(it.symbol), + end_date: today, + })) const cash = typeof ctx.cash === 'number' ? ctx.cash : 1_000_000 lastComboItems.value = items lastComboCash.value = cash @@ -393,6 +411,12 @@ const comboPerf = computed(() => { volatility: get('volatility'), } }) + +// 组合评级:从 combined_equity 重算夏普/卡玛/回撤/波动率等 5 维度评分, +// 与 /portfolio 页和单标的回测页同口径(复用 gradePortfolio)。 +const comboGrade = computed(() => + store.multiStrategyResult ? gradePortfolio(store.multiStrategyResult) : null, +)