Add demo trading analyst with plan validation and sizing

The analyst prompts the model with trading rules, published skills,
account sizing inputs and prefetched market data, exposes read-only
tools plus submit_trade_plan, and validates and sizes the submitted plan
in pure functions covered by pytest. Manual analyses run as dry runs
while trading is stopped; the trading page shows the latest plan and an
analysis detail drawer.
This commit is contained in:
eezd committed 2026-10-15 10:40:29 +08:00
1 parent 7536c9c5de
commit 78ca4422af
16 files changed
+1465 -151

No files matched your search

@@ -0,0 +1,68 @@
<script lang="ts" setup>
import type { AnalysisDetail } from "@/apis/trading"
import { getAnalysis } from "@/apis/trading"
import { renderMarkdown } from "@/utils/markdown"
import { formatTime } from "@/utils/text"
const props = defineProps<{ analysisId: number | null }>()
const visible = defineModel<boolean>({ required: true })
const detail = ref<AnalysisDetail>()
watch(() => [visible.value, props.analysisId] as const, async ([open, id]) => {
detail.value = undefined
if (open && id)
detail.value = await getAnalysis(id)
})
const toolResults = computed(() => {
const results = new Map<string, string>()
for (const m of detail.value?.transcript ?? []) {
if (m.role === "tool")
results.set(m.tool_call_id, m.result)
}
return results
})
function pretty(raw: string | undefined) {
if (raw === undefined)
return "无结果"
try {
return JSON.stringify(JSON.parse(raw), null, 2)
} catch {
return raw
}
}
</script>
<template>
<el-drawer v-model="visible" :title="detail ? `分析 #${detail.id} · ${detail.symbol}` : '分析'" size="55%">
<div v-if="detail" class="flex flex-col gap-3 text-sm">
<div class="text-xs text-[var(--el-text-color-secondary)]">
{{ formatTime(detail.started_at) }} – {{ formatTime(detail.finished_at) }} · {{ detail.reasons.join(";") }}
· {{ detail.model }}{{ detail.reasoning_effort ? ` / ${detail.reasoning_effort}` : "" }}
· {{ detail.usage?.input_tokens.toLocaleString() }} / {{ detail.usage?.output_tokens.toLocaleString() }} token
{{ detail.dry_run ? "· 试运行" : "" }}
</div>
<el-alert v-if="detail.error" type="error" :closable="false" :title="detail.error" />
<!-- markdown-it 已关闭 html,模型输出中的标签按文本显示 -->
<div v-if="detail.text" class="markdown-body" v-html="renderMarkdown(detail.text)" />
<el-collapse>
<template v-for="(m, i) in detail.transcript ?? []" :key="i">
<el-collapse-item v-if="m.role === 'user'" :name="`u${i}`" :title="i === 0 ? '发给模型的数据(账户、持仓、预取行情)' : '程序提示'">
<pre class="m-0 max-h-120 overflow-auto whitespace-pre-wrap break-all text-xs">{{ m.text }}</pre>
</el-collapse-item>
<template v-else-if="m.role === 'assistant'">
<el-collapse-item v-for="call in m.tool_calls" :key="call.id" :name="call.id">
<template #title>
<span class="inline-block max-w-[90%] truncate text-xs font-mono">{{ call.name }} {{ JSON.stringify(call.arguments) }}</span>
</template>
<pre class="m-0 max-h-120 overflow-auto whitespace-pre-wrap break-all text-xs">{{ pretty(toolResults.get(call.id)) }}</pre>
</el-collapse-item>
</template>
</template>
</el-collapse>
</div>
<div v-else v-loading="true" class="h-40" />
</el-drawer>
</template>