feat(shadow-desk): 通路测试策略channel_test+模拟盘列表改造: 策略=每日等权轮换ETF/蓝筹(卖旧买新走全买卖通路)+T+1当日卖探针(两端应一致拒单,验证拒单通路),注册backtest/live/前端,默认universe 5只高流动ETF,4单测(坑:BrokerFacade是dataclass,子类方法被父类__init__实例属性遮蔽须实例注入); 模拟盘列表:加类型列(个股/组合)+创建时间列,标的去JSON中括号,列宽重排,模式筛选加影子 [vps]
This commit is contained in:
@@ -83,6 +83,7 @@ const strategyOptions = [
|
||||
{ label: '牛熊动量', value: 'momentum_timing' },
|
||||
{ label: '价值精选', value: 'value_selection' },
|
||||
{ label: '小市值轮动', value: 'small_cap' },
|
||||
{ label: '通路测试(影子vs实盘双轨)', value: 'channel_test' },
|
||||
]
|
||||
const BENCHMARK_OPTIONS = [
|
||||
{ label: '沪深300', value: '000300.XSHG' },
|
||||
|
||||
@@ -57,11 +57,22 @@ function num(v: number | null | undefined): string {
|
||||
}
|
||||
function symbolsShort(s: string | undefined): string {
|
||||
if (!s) return '—'
|
||||
const arr = typeof s === 'string' ? s.split(',') : []
|
||||
return arr.length > 3 ? arr.slice(0, 3).join(',') + ` +${arr.length - 3}` : s
|
||||
let arr: string[] = []
|
||||
try {
|
||||
arr = JSON.parse(s)
|
||||
} catch {
|
||||
arr = String(s).split(',').map((x) => x.trim())
|
||||
}
|
||||
if (!arr.length) return '—'
|
||||
return arr.length > 3 ? arr.slice(0, 3).join(',') + ` +${arr.length - 3}` : arr.join(',')
|
||||
}
|
||||
const typeLabel: Record<string, string> = { cta: '个股', portfolio: '组合' }
|
||||
function createdShort(v: string | undefined): string {
|
||||
if (!v) return '—'
|
||||
return v.replace('T', ' ').slice(0, 16)
|
||||
}
|
||||
function open(a: PaperAccount): void {
|
||||
router.push(a.mode === 'live' ? `/paper/live/${a.id}` : `/paper/result/${a.id}`)
|
||||
router.push(a.mode === 'replay' ? `/paper/result/${a.id}` : `/paper/live/${a.id}`)
|
||||
}
|
||||
function goNew(): void {
|
||||
router.push('/paper/new')
|
||||
@@ -170,6 +181,7 @@ async function saveEdit(): Promise<void> {
|
||||
<el-select v-model="modeFilter" placeholder="全部模式" clearable style="width: 130px">
|
||||
<el-option label="回放" value="replay" />
|
||||
<el-option label="实走" value="live" />
|
||||
<el-option label="影子" value="shadow" />
|
||||
</el-select>
|
||||
<el-select v-model="statusFilter" placeholder="全部状态" clearable style="width: 130px">
|
||||
<el-option label="运行中" value="running" />
|
||||
@@ -181,51 +193,55 @@ async function saveEdit(): Promise<void> {
|
||||
|
||||
<el-card class="table-card" shadow="never">
|
||||
<el-table :data="filtered" size="small" empty-text="暂无模拟盘">
|
||||
<el-table-column label="名称 / ID" min-width="160">
|
||||
<el-table-column label="名称 / ID" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<div class="cell-name">{{ row.name }}</div>
|
||||
<div class="cell-id mono">#{{ row.id }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模式" width="80">
|
||||
<el-table-column label="类型" width="76">
|
||||
<template #default="{ row }">
|
||||
<span class="chip" :class="row.strategy_type === 'portfolio' ? 'chip-portfolio' : 'chip-cta'">
|
||||
{{ typeLabel[row.strategy_type] || row.strategy_type || '个股' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模式" width="92">
|
||||
<template #default="{ row }">
|
||||
<span class="chip chip-paper" :class="{ 'chip-shadow': row.mode === 'shadow' }">{{ modeLabel[row.mode] || row.mode }}</span>
|
||||
<span v-if="row.strategy_type === 'portfolio' && row.mode === 'live'" class="chip chip-shadow" title="日终回放=每晚 20:30 全量重放结算">日终</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="频率" width="70">
|
||||
<template #default="{ row }"><span class="mono">{{ row.interval }}</span></template>
|
||||
<el-table-column label="频率" width="64">
|
||||
<template #default="{ row }"><span class="mono muted">{{ row.interval }}</span></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>
|
||||
<el-table-column label="标的" min-width="120">
|
||||
<el-table-column label="标的" min-width="130">
|
||||
<template #default="{ row }"><span class="mono">{{ symbolsShort(row.symbols) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收益率" width="100" align="right">
|
||||
<el-table-column label="收益率" width="86" align="right">
|
||||
<template #default="{ row }">
|
||||
<span class="mono" :class="(row.total_return ?? 0) >= 0 ? 'up' : 'down'">{{ pct(row.total_return) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最新净值" width="120" align="right">
|
||||
<el-table-column label="最新净值" width="104" align="right">
|
||||
<template #default="{ row }"><span class="mono">{{ num(row.latest_equity) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="起止" min-width="180">
|
||||
<template #default="{ row }"><span class="mono muted">{{ row.start_date }} ~ {{ row.end_date }}</span></template>
|
||||
<el-table-column label="创建时间" width="142">
|
||||
<template #default="{ row }"><span class="mono muted">{{ createdShort(row.created_at) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="84">
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="{ row }"><span class="chip" :class="`st-${row.status}`">{{ statusLabel[row.status] || row.status }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<el-table-column label="操作" width="176" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="open(row)">查看</el-button>
|
||||
<el-button
|
||||
v-if="row.mode === 'live' && row.status === 'running'"
|
||||
v-if="row.mode !== 'replay' && row.status === 'running'"
|
||||
link type="warning" :loading="actionLoading === row.id"
|
||||
@click="onStop(row)"
|
||||
>停止</el-button>
|
||||
<el-button
|
||||
v-if="row.mode === 'live' && row.status === 'stopped'"
|
||||
v-if="row.mode !== 'replay' && row.status === 'stopped'"
|
||||
link type="success" :loading="actionLoading === row.id"
|
||||
@click="onResume(row)"
|
||||
>恢复</el-button>
|
||||
|
||||
@@ -32,6 +32,7 @@ const PORTFOLIO_LABELS: Record<string, string> = {
|
||||
momentum_timing: '牛熊动量',
|
||||
value_selection: '价值精选',
|
||||
small_cap: '小市值轮动',
|
||||
channel_test: '通路测试(影子vs实盘双轨)',
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
Reference in New Issue
Block a user