From fa87135a443982b80dbfc30678c7a8d45da18b32 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Tue, 25 Aug 2026 00:33:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E5=9B=A0=E5=AD=90=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E5=8F=B0=E6=8E=A5=E7=BA=BF=E2=80=94=E2=80=94eval=20AP?= =?UTF-8?q?I=20=E5=AE=A2=E6=88=B7=E7=AB=AF+3=E8=B7=AF=E7=94=B1+=E6=8A=95?= =?UTF-8?q?=E7=A0=94=E7=BB=84=E8=8F=9C=E5=8D=95/=E9=9D=A2=E5=8C=85?= =?UTF-8?q?=E5=B1=91=20[nas]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/factor.ts | 86 +++++++++++++++++++++++++++++++++++ frontend/src/router/index.ts | 3 ++ frontend/src/views/Layout.vue | 7 +++ 3 files changed, 96 insertions(+) diff --git a/frontend/src/api/factor.ts b/frontend/src/api/factor.ts index b3e403e..b5d2337 100644 --- a/frontend/src/api/factor.ts +++ b/frontend/src/api/factor.ts @@ -33,3 +33,89 @@ export function reportUrl(taskId: string, factor: string): string { const auth = useAuthStore() return `/api/v1/task/${taskId}/report/${factor}?token=${encodeURIComponent(auth.token ?? '')}` } + +// —— Factor Evaluation API —— + +export interface EvalRun { + run_id: string + label: string + universe: string + symbols_count: number + factors_total: number + factors_done: number + start: string + end: string + status: string + created_at: string +} + +export interface MonthlyIcPoint { month: string; ic: number } + +export interface LeaderboardRow { + rank: number + factor: string + category: string + expression: string + ic_mean: number | null + icir: number | null + t_stat: number | null + win_rate: number | null + ls_annual: number | null + turnover: number | null + conclusion: string + monthly_ic: MonthlyIcPoint[] + error?: string +} + +export interface LeaderboardTiles { + factors_total: number + effective: number + watch: number + top_ls_annual: number | null + top_ls_factor: string | null +} + +export interface EvalDetail { + factor: string + category: string + expression: string + metrics: Record +} + +export interface BatchEvalSubmit { + categories: string[] + factors: string[] + symbols: string[] + start: string + end: string + label: string +} + +export async function getEvalRuns(): Promise { + const { data } = await apiClient.get<{ runs: EvalRun[] }>('/factor/eval/runs') + return data.runs +} + +export async function getLeaderboard(params: { + run_id?: string + period?: string + category?: string + search?: string + sort?: string + order?: string +}): Promise<{ tiles: LeaderboardTiles; rows: LeaderboardRow[] }> { + const { data } = await apiClient.get('/factor/eval/leaderboard', { params }) + return data +} + +export async function getEvalDetail(factor: string, runId?: string): Promise { + const { data } = await apiClient.get('/factor/eval/detail', { + params: { factor, ...(runId ? { run_id: runId } : {}) }, + }) + return data +} + +export async function submitBatchEval(req: BatchEvalSubmit): Promise { + const { data } = await apiClient.post<{ task_id: string }>('/factor/eval/submit', req) + return data.task_id +} diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 4df85cc..bfa8fe1 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -22,6 +22,9 @@ const routes: RouteRecordRaw[] = [ { path: 'backtest/optimize', name: 'bt-optimize', component: () => import('@/views/backtest/Optimize.vue') }, { path: 'backtest/optimize-result/:id', name: 'bt-optimize-result', component: () => import('@/views/backtest/OptimizeResult.vue') }, { path: 'backtest/history', name: 'bt-history', component: () => import('@/views/backtest/History.vue') }, + { path: 'factor/leaderboard', name: 'fc-leaderboard', component: () => import('@/views/factor/Leaderboard.vue') }, + { path: 'factor/leaderboard/:factor', name: 'fc-leaderboard-detail', component: () => import('@/views/factor/LeaderboardDetail.vue') }, + { path: 'factor/batch', name: 'fc-batch', component: () => import('@/views/factor/BatchEval.vue') }, { path: 'factor/new', name: 'fc-new', component: () => import('@/views/factor/New.vue') }, { 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') }, diff --git a/frontend/src/views/Layout.vue b/frontend/src/views/Layout.vue index 8db8fdb..9d40f26 100644 --- a/frontend/src/views/Layout.vue +++ b/frontend/src/views/Layout.vue @@ -31,6 +31,9 @@ const PAGE_TITLE: Array<{ match: RegExp; group: string; title: string }> = [ { match: /^\/factor\/new$/, group: '投研', title: '因子分析' }, { match: /^\/factor\/progress\//, group: '投研', title: '因子分析进度' }, { match: /^\/factor\/result\//, group: '投研', title: '因子分析结果' }, + { match: /^\/factor\/leaderboard\/.+$/, group: '投研', title: '因子详情' }, + { match: /^\/factor\/leaderboard$/, group: '投研', title: 'IC 排行榜' }, + { match: /^\/factor\/batch$/, group: '投研', title: '批量评估' }, { match: /^\/paper\/new$/, group: '模拟盘', title: '新建模拟盘' }, { match: /^\/paper\/result\//, group: '模拟盘', title: '模拟盘结果' }, { match: /^\/paper\/live\//, group: '模拟盘', title: '实走监控' }, @@ -66,6 +69,8 @@ const CMD_ITEMS: CmdItem[] = [ { label: '组合回测', group: '回测', path: '/backtest/portfolio' }, { label: '参数优化', group: '回测', path: '/backtest/optimize' }, { label: '任务列表', group: '回测', path: '/backtest/history' }, + { label: 'IC 排行榜', group: '投研', path: '/factor/leaderboard' }, + { label: '批量评估', group: '投研', path: '/factor/batch' }, { label: '因子分析', group: '投研', path: '/factor/new' }, { label: '模拟盘列表', group: '模拟', path: '/paper' }, { label: '双轨对账', group: '模拟', path: '/paper/reconcile' }, @@ -170,6 +175,8 @@ function onLogout(): void { + IC 排行榜 + 批量评估 因子分析