fix(backtest): 结果页4个显示bug—成交枚举/时间/浮点精度/日志空

1. 交易详情方向/开平显原始枚举: Direction.LONG/Offset.OPEN → 买/卖、开仓/平仓/平今/平昨
   (TradesTable 加 fmtDir/fmtOff, 方向带涨跌色)
2. 交易详情时间 2024-06-25T00:00:00+08:00 → 2024-06-25 (日内带时分则保留)
3. 每日收益浮点精度 0.6533000000054017 → 0.65 (toFixed2, 涨跌色), 日期去 00:00:00
4. 日志tab硬编码"暂无日志数据": 前端接 getLog(/task/:id/log) + 后端 cta_engine 加
   stdout tee 把引擎 load/run/stats 输出落盘到 {task_id}.log (worker 进程内隔离)
   → 新回测日志 tab 显示真实引擎输出(2117字)

验证: 交易 买/开仓/2024-06-25, 收益 0.15, 日志 2117字真实内容
This commit is contained in:
2026-07-11 23:31:50 +08:00
parent 0c7b030773
commit 10508db11d
3 changed files with 128 additions and 18 deletions
+53 -6
View File
@@ -2,15 +2,62 @@
import type { Trade } from '@/api/backtest'
defineProps<{ trades: Trade[] }>()
// vnpy 枚举 repr → 中文(兼容已是中文的情况)
function fmtDir(d: string): string {
if (!d) return ''
const s = String(d)
if (s.includes('LONG') || s === '多') return '买'
if (s.includes('SHORT') || s === '空') return '卖'
if (s.includes('NET')) return '净'
return s
}
function fmtOff(o: string): string {
if (!o) return ''
const s = String(o)
if (s.includes('CLOSETODAY')) return '平今'
if (s.includes('CLOSEFIRST')) return '平昨'
if (s.includes('CLOSE')) return '平仓'
if (s.includes('OPEN')) return '开仓'
return s
}
// 2024-06-25T00:00:00+08:00 → 2024-06-25(日内带时分则保留)
function fmtDate(dt: string): string {
if (!dt) return ''
const s = String(dt).slice(0, 19).replace('T', ' ')
return s.endsWith(' 00:00:00') ? s.slice(0, 10) : s
}
function dirClass(d: string): string {
const v = fmtDir(d)
return v === '买' ? 'up' : v === '卖' ? 'down' : ''
}
</script>
<template>
<el-table :data="trades" stripe size="small" empty-text="无成交">
<el-table-column prop="datetime" label="时间" width="220" />
<el-table-column prop="direction" label="方向" width="80" />
<el-table-column prop="offset" label="开平" width="80" />
<el-table-column prop="price" label="价格" width="100" />
<el-table-column prop="volume" label="数量" width="100" />
<el-table-column prop="vt_symbol" label="标的" />
<el-table-column label="时间" width="150">
<template #default="{ row }"><span class="mono">{{ fmtDate(row.datetime) }}</span></template>
</el-table-column>
<el-table-column label="方向" width="70">
<template #default="{ row }"><span :class="dirClass(row.direction)">{{ fmtDir(row.direction) }}</span></template>
</el-table-column>
<el-table-column label="开平" width="70">
<template #default="{ row }">{{ fmtOff(row.offset) }}</template>
</el-table-column>
<el-table-column label="价格" width="100" align="right">
<template #default="{ row }"><span class="mono">{{ row.price }}</span></template>
</el-table-column>
<el-table-column label="数量" width="90" align="right">
<template #default="{ row }"><span class="mono">{{ row.volume }}</span></template>
</el-table-column>
<el-table-column label="标的" min-width="100">
<template #default="{ row }"><span class="mono">{{ row.vt_symbol }}</span></template>
</el-table-column>
</el-table>
</template>
<style scoped>
:deep(.mono) { font-family: var(--mono); font-size: 12px; color: var(--text); }
.up { color: var(--up); }
.down { color: var(--down); }
</style>
+31 -4
View File
@@ -3,7 +3,7 @@ import { ref, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import {
getResult, getEquityCurve, getDailyPnl, getTrades, getKline,
getRelativeMetrics, getBenchmarkCurve, getRiskSeries,
getRelativeMetrics, getBenchmarkCurve, getRiskSeries, getLog,
type EquityPoint, type PnlPoint, type Trade, type KlineBar,
type RelativeMetrics, type BenchmarkCurveData, type RiskSeriesData,
} from '@/api/backtest'
@@ -42,6 +42,7 @@ const equity = ref<EquityPoint[]>([])
const pnl = ref<PnlPoint[]>([])
const trades = ref<Trade[]>([])
const kline = ref<KlineBar[]>([])
const logText = ref('')
// statEntries is no longer used in the new layout but kept for potential future use
// const statEntries = computed(() =>
@@ -135,10 +136,32 @@ onMounted(async () => {
kline.value = []
}
}
try {
logText.value = await getLog(taskId)
} catch {
logText.value = ''
}
} finally {
loading.value = false
}
})
// 每日收益格式化:浮点精度 → 2 位;日期去 00:00:00
function fmtPnl(v: number | string): string {
const n = typeof v === 'number' ? v : parseFloat(v)
if (!Number.isFinite(n)) return String(v)
return n.toFixed(2)
}
function fmtPnlClass(v: number | string): string {
const n = typeof v === 'number' ? v : parseFloat(v)
if (!Number.isFinite(n) || n === 0) return ''
return n > 0 ? 'up' : 'down'
}
function fmtDate(d: string): string {
if (!d) return ''
const s = String(d).slice(0, 19).replace('T', ' ')
return s.endsWith(' 00:00:00') ? s.slice(0, 10) : s
}
</script>
<template>
@@ -219,8 +242,12 @@ onMounted(async () => {
<template #header><span class="section-title">每日收益</span></template>
<div class="daily-pnl-section">
<el-table :data="pnl" stripe size="small" empty-text="无数据">
<el-table-column prop="date" label="日期" width="120" />
<el-table-column prop="pnl" label="收益" width="120" />
<el-table-column label="日期" width="140">
<template #default="{ row }"><span class="mono">{{ fmtDate(row.date) }}</span></template>
</el-table-column>
<el-table-column label="收益" width="120" align="right">
<template #default="{ row }"><span class="mono" :class="fmtPnlClass(row.pnl)">{{ fmtPnl(row.pnl) }}</span></template>
</el-table-column>
</el-table>
</div>
</el-card>
@@ -230,7 +257,7 @@ onMounted(async () => {
<el-tab-pane label="日志输出">
<el-card>
<template #header><span class="section-title">系统日志</span></template>
<pre class="log-output">暂无日志数据</pre>
<pre class="log-output">{{ logText || '暂无日志数据' }}</pre>
</el-card>
</el-tab-pane>
</el-tabs>