|
|
|
@@ -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>
|
|
|
|
|