feat(front+api): #79+#94+#95验收三连——#79:策略库卡片收益格移除(用户'回测·最新10.44%没啥用',回测数字口径易误导,收益看各列表/全景;latestRet/retInfoOf孤儿函数随删,pct/retClass巡检仍在);#94:实盘列表标的列接poolLabel(all→全市场)+实例列建instanceNames映射显实例名(原绑row.instance但后端list_lives从未填恒'—',对齐模拟盘#71口径,兜底实例#id);#95:标的池默认值统一改all全市场——前端三表单(paper/live/PortfolioBacktest poolForm)+后端四处默认(routes_live setdefault/routes_portfolio Field/routes_paper/orchestrator runner)同步对齐防口径分裂,标签'全市场(慢,非MVP)'简化'全市场';936绿+build绿 [vps]
CI/CD / test (push) Successful in 11s
CI/CD / nas-deploy (push) Successful in 25s
CI/CD / nas-verify (push) Successful in 10s

This commit is contained in:
2026-08-16 22:29:29 +08:00
parent 17c51c44c7
commit 6ecdaab959
9 changed files with 31 additions and 32 deletions
@@ -55,7 +55,7 @@ const dr = loadDateRange()
// §12.6 选档案(跳转带入或页面下拉),提交带 instance_id 回写四格
const selectedInstanceId = ref<number | null>(null)
const form = reactive({
pool: 'hs300_subset',
pool: 'all',
strategy: 'all_weather',
max_pool: 30,
start: dr.start,
@@ -81,7 +81,7 @@ watch(
const poolOptions = [
{ label: 'HS300 子集(小范围验证)', value: 'hs300_subset' },
{ label: '全市场(慢,非 MVP)', value: 'all' },
{ label: '全市场', value: 'all' },
]
const strategyOptions = [
{ label: '全天候轮动', value: 'all_weather' },
+20 -3
View File
@@ -2,6 +2,8 @@
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import CodeSnapshotDrawer from '@/views/strategy/CodeSnapshotDrawer.vue'
import { poolLabel } from '@/constants/strategy'
import { getInstances } from '@/api/strategy'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
listLives, startLive, stopLive, deleteLive, updateLive,
@@ -73,6 +75,21 @@ function num(v: number | null | undefined): string {
return Math.round(v).toLocaleString('zh-CN')
}
// #94 实例名映射(instance_id → 实例名,对齐模拟盘列表口径;后端 list_lives 不填 instance 字段)
const instanceNames = ref<Record<number, string>>({})
onMounted(async () => {
try {
const ins = await getInstances()
instanceNames.value = Object.fromEntries(ins.map((i) => [i.id, i.name]))
} catch {
/* 映射失败退化为 实例 #id */
}
})
function instanceNameOf(instanceId: number | null | undefined): string {
if (!instanceId) return '—'
return instanceNames.value[instanceId] || `实例 #${instanceId}`
}
function goMonitor(a: LiveAccount): void {
router.push(`/live/monitor/${a.id}`)
}
@@ -253,7 +270,7 @@ async function onStop(a: LiveAccount): Promise<void> {
</template>
</el-table-column>
<el-table-column label="标的" min-width="110">
<template #default="{ row }"><span class="mono">{{ row.vt_symbol }}</span></template>
<template #default="{ row }"><span class="mono">{{ poolLabel(row.vt_symbol) }}</span></template>
</el-table-column>
<el-table-column label="策略实例" min-width="160">
<template #default="{ row }">
@@ -261,8 +278,8 @@ async function onStop(a: LiveAccount): Promise<void> {
<div class="cell-id mono muted">{{ row.strategy_class }}</div>
</template>
</el-table-column>
<el-table-column prop="instance" label="实例" min-width="160">
<template #default="{ row }"><span class="mono">{{ row.instance || '—' }}</span></template>
<el-table-column label="实例" min-width="160">
<template #default="{ row }"><span class="mono">{{ instanceNameOf(row.instance_id) }}</span></template>
</el-table-column>
<el-table-column label="频率" width="70">
<template #default="{ row }"><span class="mono">{{ row.interval }}</span></template>
+2 -2
View File
@@ -19,10 +19,10 @@ const isPortfolio = computed(() => strategyType.value === 'portfolio')
const portfolioOptions = ref<string[]>([])
const portfolioStrategy = ref('all_weather')
const poolForm = ref({ pool: 'hs300_subset', max_pool: 30, benchmark: '000300.XSHG' })
const poolForm = ref({ pool: 'all', max_pool: 30, benchmark: '000300.XSHG' })
const POOL_OPTIONS = [
{ label: 'HS300 子集(小范围验证)', value: 'hs300_subset' },
{ label: '全市场(慢,非 MVP)', value: 'all' },
{ label: '全市场', value: 'all' },
]
const BENCH_OPTIONS = [
{ label: '沪深300', value: '000300.XSHG' },
+2 -2
View File
@@ -27,10 +27,10 @@ const instancesOfCta = computed(() => {
const file = classToFile.value[form.value.strategies[0]?.name]
return file ? instanceOptions.value.filter((i) => i.code_file === file) : instanceOptions.value
})
const poolForm = reactive({ pool: 'hs300_subset', max_pool: 30, benchmark: '000300.XSHG' })
const poolForm = reactive({ pool: 'all', max_pool: 30, benchmark: '000300.XSHG' })
const POOL_OPTIONS = [
{ label: 'HS300 子集(小范围验证)', value: 'hs300_subset' },
{ label: '全市场(慢,非 MVP)', value: 'all' },
{ label: '全市场', value: 'all' },
]
const BENCH_OPTIONS = [
{ label: '沪深300', value: '000300.XSHG' },
+1 -19
View File
@@ -115,23 +115,6 @@ const lampOf = (s: string): string => {
}
const pct = (v: number | null | undefined): string => (v == null ? '—' : (v * 100).toFixed(2) + '%')
const retClass = (v: number | null | undefined): string => (v == null ? '' : v >= 0 ? 'up' : 'down')
// #75 ret / run_returns
interface RetInfo { v: number | null; label: string }
function retInfoOf(i: Instance): RetInfo {
const runs = (i.running_accounts || []).filter((a) => a.ret != null)
if (runs.length) {
const last = runs[runs.length - 1]
const label = last.kind === 'live' ? '实盘 · 最新' : last.kind === 'shadow' ? '影子 · 最新' : '模拟实走 · 最新'
return { v: last.ret, label }
}
const rr = (i as Instance & { run_returns?: Record<string, number> }).run_returns || {}
if (rr.backtest != null) return { v: rr.backtest, label: '回测 · 最新' }
if (rr.replay != null) return { v: rr.replay, label: '回放 · 最新' }
return { v: null, label: '未运行' }
}
function latestRet(i: Instance): number | null {
return retInfoOf(i).v
}
const paramSummary = (p: Record<string, unknown>): string =>
Object.entries(p).map(([k, v]) => `${k}=${v}`).join(' · ')
@@ -327,8 +310,7 @@ const runningRows = computed(() =>
<span class="sub mono">#{{ i.id }} · {{ i.interval }} · 更新 {{ i.updated_at }}</span>
</div>
<div class="params mono muted">{{ paramSummary(i.params) }}</div>
<!-- #91: 标的行移除#86a 定稿实例不承载标的(发起时定),老实例存量值显示只会误导 -->
<div class="ret mono" :class="retClass(latestRet(i))">{{ pct(latestRet(i)) }}<span class="sub">{{ retInfoOf(i).label }}</span></div>
<!-- #91: 标的行移除;#79: 收益格移除回测数字口径易误导,收益看各列表/全景(口径干净) -->
<div class="runs">
<button class="run-btn" @click="runGo(i, 'backtest')"><span class="lamp" :class="lampOf(i.status.backtest)"></span>回测{{ badge(i, 'backtest') }}</button>
<button v-if="fileTypeOf(i) !== 'portfolio'" class="run-btn" @click="runGo(i, 'replay')"><span class="lamp" :class="lampOf(i.status.replay)"></span>回放{{ badge(i, 'replay') }}</button>