fix(live): 监控页三修——①收益率改首快照基线(与列表同口径,治共用QMT账户下cap兜底的假900%)②组合实盘成交落库_sync_trades(轮询broker当日成交→live_trades,此前完全没人写成交表)③updated_at北京时间显示+策略参数JSON改表格 [vps]
CI/CD / test (push) Successful in 15s
CI/CD / nas-deploy (push) Successful in 42s
CI/CD / nas-verify (push) Successful in 14s

This commit is contained in:
2026-08-14 22:07:30 +08:00
parent f73810a6da
commit f49154266d
4 changed files with 97 additions and 24 deletions
+23 -23
View File
@@ -47,21 +47,27 @@ const settingParsed = computed<Record<string, unknown>>(() => {
})
const totalReturnPct = computed(() => {
const total = balance.value.total
const cap = account.value?.initial_capital
if (total == null || !cap) return null
return (total - cap) / cap
// 后端算好的首快照基线收益率(与列表页同口径)。
// 不再用 initial_capital 兜底:共用 QMT 账户时账户总额≠实例资金 → 假 900%。
const r = account.value?.total_return
if (r != null && Number.isFinite(r)) return r
return null
})
/** UTC ISO(库内存的是 +00:00) → 北京时间显示 */
function fmtTime(v: string | null | undefined): string {
if (!v) return '—'
const d = new Date(v)
if (Number.isNaN(d.getTime())) return v
return new Date(d.getTime() + 8 * 3600 * 1000)
.toISOString().slice(0, 19).replace('T', ' ')
}
const todayStr = new Date().toISOString().slice(0, 10)
const todayTrades = computed(() =>
trades.value.filter((t) => String(t.traded_at).slice(0, 10) === todayStr),
)
function orDash(v: string | null | undefined): string {
return v && v !== 'None' ? v : '—'
}
function num(v: number | null | undefined): string {
if (v == null || !Number.isFinite(v)) return '—'
return Math.round(v).toLocaleString('zh-CN')
@@ -191,7 +197,7 @@ async function onStop(): Promise<void> {
</div>
<div class="stat">
<span class="stat-label">上次更新</span>
<span class="stat-value num">{{ orDash(status?.updated_at ?? account?.updated_at) }}</span>
<span class="stat-value num">{{ fmtTime(status?.updated_at ?? account?.updated_at) }}</span>
</div>
</div>
@@ -231,7 +237,9 @@ async function onStop(): Promise<void> {
<el-table-column prop="volume" label="总持仓" width="100" class-name="num" />
<el-table-column prop="frozen" label="冻结" width="90" class-name="num" />
<el-table-column prop="avg_price" label="均价" width="100" class-name="num" />
<el-table-column prop="updated_at" label="更新时间" min-width="160" />
<el-table-column prop="updated_at" label="更新时间" min-width="160">
<template #default="{ row }">{{ fmtTime(row.updated_at) }}</template>
</el-table-column>
</el-table>
</el-card>
@@ -278,7 +286,11 @@ async function onStop(): Promise<void> {
<!-- 策略参数 -->
<el-card shadow="never">
<template #header><span class="section-title">策略参数</span></template>
<pre class="setting-pre mono">{{ JSON.stringify(settingParsed, null, 2) }}</pre>
<el-descriptions :column="2" border size="small">
<el-descriptions-item
v-for="(v, k) in settingParsed" :key="k" :label="String(k)"
>{{ typeof v === 'object' ? JSON.stringify(v) : String(v) }}</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
</template>
@@ -312,18 +324,6 @@ async function onStop(): Promise<void> {
.metric-label { font-size: 12px; color: var(--text-3); margin-bottom: 6px; }
.metric-value { font-size: 22px; font-weight: 700; color: var(--text); }
.setting-pre {
background: var(--bg);
border: 1px solid var(--border-2);
border-radius: var(--r-sm);
padding: 10px 14px;
margin: 0;
font-size: 12px;
color: var(--text-2);
white-space: pre-wrap;
word-break: break-all;
}
@media (max-width: 1200px) {
.stat-row { grid-template-columns: repeat(3, 1fr); }
}