refactor(paper): 影子升级为第三种运行模式(回放/实走/影子,用户拍板CTA和组合都可走影子): 模式卡三选一替代组合卡内撮合引擎单选; engine由mode推导(shadow→shadow否则eod_replay,CTA/组合统一); 日终20:30 job只遍历mode=live天然隔离影子账户; 列表徽标mode=shadow亮'影子'+组合live标'日终'; 2新测试 [vps]
CI/CD / test (push) Successful in 13s
CI/CD / nas-deploy (push) Successful in 27s
CI/CD / nas-verify (push) Successful in 9s

This commit is contained in:
2026-08-14 12:55:26 +08:00
parent 8e7e7282de
commit 6220fc5943
4 changed files with 64 additions and 33 deletions
+3 -5
View File
@@ -44,7 +44,7 @@ const stats = computed(() => ({
failed: accounts.value.filter((a) => a.status === 'failed').length,
}))
const modeLabel: Record<string, string> = { replay: '回放', live: '实走' }
const modeLabel: Record<string, string> = { replay: '回放', live: '实走', shadow: '影子' }
const statusLabel: Record<string, string> = { running: '运行中', done: '完成', failed: '失败', created: '已创建', pending: '排队', stopped: '已停止' }
function pct(v: number | null | undefined): string {
@@ -189,10 +189,8 @@ async function saveEdit(): Promise<void> {
</el-table-column>
<el-table-column label="模式" width="80">
<template #default="{ row }">
<span class="chip chip-paper">{{ modeLabel[row.mode] || row.mode }}</span>
<span v-if="row.strategy_type === 'portfolio'" class="chip chip-shadow" title="影子柜台=盘中实时本地撮合 / 日终回放=每晚全量重放">
{{ row.engine === 'shadow' ? '影子' : '日终' }}
</span>
<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">
+12 -22
View File
@@ -84,7 +84,8 @@ watch(
const modes = [
{ value: 'replay', label: '回放', desc: '历史重放,立即出结果' },
{ value: 'live', label: '实走', desc: '每日定时 step,长期跟踪' },
{ value: 'live', label: '实走', desc: '每日 20:30 定时结算,长期跟踪' },
{ value: 'shadow', label: '影子', desc: 'VPS 影子柜台盘中实时本地撮合' },
]
const sessions = [
{ value: 'next_open', label: '次日开盘', desc: '收盘型信号,T+1 开盘撮合' },
@@ -115,19 +116,17 @@ onMounted(async () => {
const isPortfolio = computed(() => strategyType.value === 'portfolio')
const portfolioStrategy = ref('all_weather')
// 撮合引擎:日终回放(NAS 每晚 20:30) / 影子柜台(VPS 盘中实时本地撮合)
const engine = ref<'eod_replay' | 'shadow'>('eod_replay')
function setMode(m: string): void {
// 组合类型锁定实走:回放卡片不可选(历史回放走「组合回测」页)
// 组合类型不支持回放(历史回放走「组合回测」页)
if (isPortfolio.value && m === 'replay') return
form.value.mode = m
}
// 组合类型锁定实走(历史回放走「组合回测」页)
// 组合类型默认实走(历史回放走「组合回测」页)
watch(strategyType, (t) => {
if (t === 'portfolio') {
form.value.mode = 'live'
if (form.value.mode === 'replay') form.value.mode = 'live'
if (!portfolioStrategy.value && portfolioOptions.value.length) {
portfolioStrategy.value = portfolioOptions.value[0]
}
@@ -140,7 +139,6 @@ async function onSubmit(): Promise<void> {
const payload: PaperCreate = { ...form.value }
if (strategyType.value === 'portfolio') {
payload.strategy_type = 'portfolio'
payload.mode = 'live'
payload.symbols = [poolForm.pool]
payload.strategies = [{
name: portfolioStrategy.value,
@@ -151,13 +149,16 @@ async function onSubmit(): Promise<void> {
payload.pool = poolForm.pool
payload.max_pool = Number(poolForm.max_pool)
payload.benchmark = poolForm.benchmark
payload.engine = engine.value
}
// 影子模式:engine=shadow(VPS 柜台盘中实时撮合);其余 eod_replay(回放/日终)
payload.engine = payload.mode === 'shadow' ? 'shadow' : 'eod_replay'
const aid = await createPaper(payload)
ElMessage.success(strategyType.value === 'portfolio' && engine.value === 'shadow'
ElMessage.success(payload.mode === 'shadow'
? `已创建影子柜台模拟盘 #${aid}VPS 柜台运行期间盘中实时结算)`
: `已创建模拟盘 #${aid}(今晚 20:30 起每日结算)`)
router.push(payload.mode === 'live' ? `/paper/live/${aid}` : `/paper/result/${aid}`)
: payload.mode === 'live'
? `已创建模拟盘 #${aid}(今晚 20:30 起每日结算)`
: `已创建回放模拟盘 #${aid}`)
router.push(payload.mode === 'replay' ? `/paper/result/${aid}` : `/paper/live/${aid}`)
} catch (e: unknown) {
ElMessage.error(e instanceof Error ? e.message : '创建失败')
} finally {
@@ -250,17 +251,6 @@ function onSymbols(v: string): void {
<el-option v-for="b in BENCH_OPTIONS" :key="b.value" :label="b.label" :value="b.value" />
</el-select>
</el-form-item>
<el-form-item label="撮合引擎">
<el-radio-group v-model="engine">
<el-radio-button value="eod_replay">日终回放</el-radio-button>
<el-radio-button value="shadow">影子柜台</el-radio-button>
</el-radio-group>
<span class="muted form-hint">
{{ engine === 'shadow'
? 'VPS 盘中实时行情本地撮合(需 VPS 影子柜台进程运行中)'
: '每晚 20:30 全量重放结算(NAS)' }}
</span>
</el-form-item>
</el-form>
<el-form v-else :model="form" label-width="120px">
<el-form-item label="标的(逗号分隔)">