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 () => {