From 41a788c431cd0d432d85ee633371704692654c6a Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sat, 11 Jul 2026 21:10:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20P2=E5=85=A8=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E8=81=9A=E5=AE=BD=E7=BA=A7=E5=A4=8D=E5=88=BB=E2=80=94=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F=E7=9B=98/=E5=9B=A0=E5=AD=90/=E5=9B=9E=E6=B5=8B?= =?UTF-8?q?=E8=A1=A8=E5=8D=95=E9=A1=B5=E9=87=8D=E5=81=9A+=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F=E7=9B=98=E5=88=97=E8=A1=A8=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 模拟盘模块: - 新增模拟盘列表页(List.vue):统计行+富表格(名称/模式/频率/收益/最新净值/状态/操作)+搜索筛选 - 后端加 GET /paper 列表端点(类比 GET /task,带最新净值+收益率) - paper/New 分区富表单(卡式模式选择+频率+撮合时点说明) - paper/Result 重做(收益/年化/回撤/夏普/波动指标卡+净值曲线+归因表+成交明细,净值客户端算指标) 因子模块: - factor/Result 重做(最优ICIR/平均IC/显著数指标卡+彩色IC表+tears报告tab化) - factor/New 重做(因子按类分组多选+标的批量+实时计数) 回测模块: - backtest/Progress 重做(步骤时间线+进度条+实时日志尾3s刷新) - backtest/New 重做(策略/参数/标的区间/基准分区富表单) - backtest/Optimize 重做(结果列头可排序+Top1高亮+按收益默认降序) - History 迁移scoped chip→全局chips.css(DRY清理) 全局:深色主题统一,复用 tokens.css + chips.css --- frontend/src/api/backtest.ts | 5 + frontend/src/api/paper.ts | 9 + frontend/src/router/index.ts | 1 + frontend/src/views/Layout.vue | 1 + frontend/src/views/backtest/History.vue | 62 ------- frontend/src/views/backtest/New.vue | 98 ++++++---- frontend/src/views/backtest/Optimize.vue | 164 ++++++++++++---- frontend/src/views/backtest/Progress.vue | 172 ++++++++++++++--- frontend/src/views/factor/New.vue | 100 +++++++--- frontend/src/views/factor/Result.vue | 96 ++++++++-- frontend/src/views/paper/List.vue | 152 +++++++++++++++ frontend/src/views/paper/New.vue | 145 +++++++++++---- frontend/src/views/paper/Result.vue | 226 +++++++++++++++++------ sanguo_api/routes_paper.py | 28 +++ 14 files changed, 963 insertions(+), 296 deletions(-) create mode 100644 frontend/src/views/paper/List.vue diff --git a/frontend/src/api/backtest.ts b/frontend/src/api/backtest.ts index e4ef2e5..cc981fc 100644 --- a/frontend/src/api/backtest.ts +++ b/frontend/src/api/backtest.ts @@ -176,3 +176,8 @@ export async function getRiskSeries(taskId: string): Promise { return data } +export async function getLog(taskId: string): Promise { + const { data } = await apiClient.get<{ log: string }>(`/task/${taskId}/log`) + return data.log +} + diff --git a/frontend/src/api/paper.ts b/frontend/src/api/paper.ts index 2dc2549..577e74c 100644 --- a/frontend/src/api/paper.ts +++ b/frontend/src/api/paper.ts @@ -32,6 +32,10 @@ export interface PaperAccount { next_run_at?: string | null checkpoint_date?: string | null error_msg?: string | null + /** 列表端点附带的最新净值 / 收益(单账户 GET 不含) */ + latest_equity?: number | null + latest_date?: string | null + total_return?: number | null } export interface BalancePoint { @@ -85,6 +89,11 @@ export async function createPaper(req: PaperCreate): Promise { return data.account_id } +export async function listPapers(): Promise { + const { data } = await apiClient.get<{ accounts: PaperAccount[] }>('/paper') + return data.accounts +} + export async function getPaper(aid: number): Promise { const { data } = await apiClient.get(`/paper/${aid}`) return data diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 9460e3e..3000f67 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -18,6 +18,7 @@ const routes: RouteRecordRaw[] = [ { path: 'factor/progress/:id', name: 'fc-progress', component: () => import('@/views/backtest/Progress.vue') }, { path: 'factor/result/:id', name: 'fc-result', component: () => import('@/views/factor/Result.vue') }, { path: 'paper/new', name: 'paper-new', component: () => import('@/views/paper/New.vue') }, + { path: 'paper', name: 'paper-list', component: () => import('@/views/paper/List.vue') }, { path: 'paper/result/:id', name: 'paper-result', component: () => import('@/views/paper/Result.vue') }, { path: 'paper/live/:aid', name: 'paper-live', component: () => import('@/views/paper/Live.vue') }, ], diff --git a/frontend/src/views/Layout.vue b/frontend/src/views/Layout.vue index cea7aff..dd77fe8 100644 --- a/frontend/src/views/Layout.vue +++ b/frontend/src/views/Layout.vue @@ -53,6 +53,7 @@ function onLogout(): void { + 模拟盘列表 新建模拟盘 diff --git a/frontend/src/views/backtest/History.vue b/frontend/src/views/backtest/History.vue index ff05d03..19de1fa 100644 --- a/frontend/src/views/backtest/History.vue +++ b/frontend/src/views/backtest/History.vue @@ -159,21 +159,6 @@ function statusLabel(s: string): string { diff --git a/frontend/src/views/backtest/Optimize.vue b/frontend/src/views/backtest/Optimize.vue index ff72011..af496e7 100644 --- a/frontend/src/views/backtest/Optimize.vue +++ b/frontend/src/views/backtest/Optimize.vue @@ -1,5 +1,5 @@ + + diff --git a/frontend/src/views/backtest/Progress.vue b/frontend/src/views/backtest/Progress.vue index 3a11f96..541a2dc 100644 --- a/frontend/src/views/backtest/Progress.vue +++ b/frontend/src/views/backtest/Progress.vue @@ -1,55 +1,183 @@ diff --git a/frontend/src/views/factor/New.vue b/frontend/src/views/factor/New.vue index b2c488b..bc8d574 100644 --- a/frontend/src/views/factor/New.vue +++ b/frontend/src/views/factor/New.vue @@ -1,5 +1,5 @@ + + diff --git a/frontend/src/views/factor/Result.vue b/frontend/src/views/factor/Result.vue index 9780038..5395727 100644 --- a/frontend/src/views/factor/Result.vue +++ b/frontend/src/views/factor/Result.vue @@ -22,6 +22,7 @@ const route = useRoute() const taskId = String(route.params.id) const loading = ref(true) const icSummary = ref>({}) +const activeReport = ref('') const rows = computed(() => { const out: Array> = [] @@ -36,14 +37,43 @@ const rows = computed(() => { const factors = computed(() => Object.keys(icSummary.value)) +// 汇总指标卡:所有因子里挑最优 ICIR / 平均 |IC| / 显著数 +const summary = computed(() => { + const all = rows.value + if (!all.length) return null + let bestIcir = -Infinity + let bestFactor = '—' + let sumAbsIc = 0 + let significant = 0 + for (const r of all) { + const icir = typeof r.icir === 'number' ? r.icir : 0 + if (icir > bestIcir) { bestIcir = icir; bestFactor = String(r.factor) } + sumAbsIc += Math.abs(typeof r.mean === 'number' ? r.mean : 0) + if (typeof r.t_stat === 'number' && Math.abs(r.t_stat) >= 2) significant++ + } + return { + factorCount: factors.value.length, + bestIcir: Number.isFinite(bestIcir) ? bestIcir : null, + bestFactor, + avgAbsIc: sumAbsIc / all.length, + significant, + total: all.length, + } +}) + function fmt(v: unknown): string { if (typeof v === 'number') return (Math.round(v * 10000) / 10000).toString() return v == null ? '' : String(v) } +function icClass(v: unknown): string { + if (typeof v !== 'number') return '' + return v >= 0 ? 'up' : 'down' +} onMounted(async () => { try { icSummary.value = (await getIcSummary(taskId)) as Record + if (factors.value.length) activeReport.value = factors.value[0] } catch { ElMessage.error('IC 摘要加载失败') } finally { @@ -53,39 +83,67 @@ onMounted(async () => {