From 557479b848c76c24271707eac753f0849b1a6a96 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sun, 30 Aug 2026 09:59:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(factor):=20New/Result=E4=B8=A4=E9=A1=B5?= =?UTF-8?q?=E7=BB=88=E7=AB=AF=E9=A3=8E=E9=87=8D=E6=9E=84=E8=90=BD=E5=9C=B0?= =?UTF-8?q?(=E6=96=B9=E6=A1=88B=E8=AE=BE=E8=AE=A1=E7=A8=BFv2=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8B=8D=E6=9D=BF'=E5=BC=80=E5=B7=A5')=20[nas]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端3小端点(sanguo_factor/universe_pools.py+routes):/factor/universe/pools 池清单(白名单6指数+当前成分数,in_current=1池语义≠回测并集口径)/pool/{key} 成分(code+name读constituent_unified)/search?q=(代码前缀OR名称子句DISTINCT LIMIT10);cfg=None兜底同analyzer;+6单测+2端点测(⚠️首版误覆盖batch_eval的 universe.py,git恢复后改名universe_pools,292测全绿) 前端:①New.vue终端风重写——FactorPicker(搜索+三色分组点选+已选托盘, 240因子弃下拉)+UniversePicker(三层输入:预设池一键选[跨行业30前端常量+ 6指数池走后端]/搜索combobox防抖250ms带名称候选/chips池可删+复制+清空, 批量粘贴折叠兜底)+原生date input禁未来+跨度显示+校验前置(禁用+原因文案); 要素零变化(query.factor预填/hydrate回填/≥1因子+≥2标的/混合分隔)。 ②Result.vue外壳——5格metric-strip(最优ICIR正红负绿/最优因子青)+IC明细 终端表格(右对齐tabular-nums,|t|≥2加粗,最优因子行青标+左青条)+tears区 tabs终端化(TearsPanel本体不动)。③factorSamples.ts常量:跨行业30只 (code+name)详情页一键直达与New页预设共用同源(LeaderboardDetail改引, 删本地重复清单)。npm run build绿(vue-tsc+rolldown) --- frontend/src/api/factor.ts | 29 +++ frontend/src/constants/factorSamples.ts | 40 +++ frontend/src/views/factor/FactorPicker.vue | 136 ++++++++++ .../src/views/factor/LeaderboardDetail.vue | 9 +- frontend/src/views/factor/New.vue | 172 +++++++------ frontend/src/views/factor/Result.vue | 144 +++++++---- frontend/src/views/factor/UniversePicker.vue | 240 ++++++++++++++++++ sanguo_api/routes.py | 26 ++ sanguo_factor/universe_pools.py | 82 ++++++ tests/api/test_factor_routes.py | 28 ++ tests/factor/test_universe.py | 99 -------- tests/factor/test_universe_pools.py | 76 ++++++ 12 files changed, 851 insertions(+), 230 deletions(-) create mode 100644 frontend/src/constants/factorSamples.ts create mode 100644 frontend/src/views/factor/FactorPicker.vue create mode 100644 frontend/src/views/factor/UniversePicker.vue create mode 100644 sanguo_factor/universe_pools.py delete mode 100644 tests/factor/test_universe.py create mode 100644 tests/factor/test_universe_pools.py diff --git a/frontend/src/api/factor.ts b/frontend/src/api/factor.ts index 7dff52b..1e84057 100644 --- a/frontend/src/api/factor.ts +++ b/frontend/src/api/factor.ts @@ -153,3 +153,32 @@ export async function submitBatchEval(req: BatchEvalSubmit): Promise { const { data } = await apiClient.post<{ task_id: string }>('/factor/eval/submit', req) return data.task_id } + + +// —— 标的池(New 页三层输入) —— + +export interface UniversePool { + key: string + name: string + count: number +} + +export interface UniverseStock { + code: string + name: string +} + +export async function getUniversePools(): Promise { + const { data } = await apiClient.get('/factor/universe/pools') + return data +} + +export async function getUniversePool(key: string): Promise { + const { data } = await apiClient.get(`/factor/universe/pool/${key}`) + return data +} + +export async function searchUniverse(q: string): Promise { + const { data } = await apiClient.get('/factor/universe/search', { params: { q } }) + return data +} diff --git a/frontend/src/constants/factorSamples.ts b/frontend/src/constants/factorSamples.ts new file mode 100644 index 0000000..5e1185f --- /dev/null +++ b/frontend/src/constants/factorSamples.ts @@ -0,0 +1,40 @@ +/** 跨行业 30 只样本(详情页「生成 tears 报告」与 New 页标的池预设共用同一份) */ +export interface SampleStock { + code: string + name: string +} + +export const TEARS_SAMPLE: SampleStock[] = [ + { code: '600519', name: '贵州茅台' }, + { code: '000858', name: '五粮液' }, + { code: '600036', name: '招商银行' }, + { code: '601318', name: '中国平安' }, + { code: '000001', name: '平安银行' }, + { code: '600000', name: '浦发银行' }, + { code: '300750', name: '宁德时代' }, + { code: '002594', name: '比亚迪' }, + { code: '600276', name: '恒瑞医药' }, + { code: '000538', name: '云南白药' }, + { code: '600887', name: '伊利股份' }, + { code: '000651', name: '格力电器' }, + { code: '000333', name: '美的集团' }, + { code: '600690', name: '海尔智家' }, + { code: '000725', name: '京东方A' }, + { code: '002415', name: '海康威视' }, + { code: '600030', name: '中信证券' }, + { code: '601166', name: '兴业银行' }, + { code: '600028', name: '中国石化' }, + { code: '601857', name: '中国石油' }, + { code: '600585', name: '海螺水泥' }, + { code: '000625', name: '长安汽车' }, + { code: '601633', name: '长城汽车' }, + { code: '002352', name: '顺丰控股' }, + { code: '600104', name: '上汽集团' }, + { code: '601398', name: '工商银行' }, + { code: '600941', name: '中国移动' }, + { code: '300059', name: '东方财富' }, + { code: '002230', name: '科大讯飞' }, + { code: '601888', name: '中国中免' }, +] + +export const TEARS_SAMPLE_CODES = TEARS_SAMPLE.map((s) => s.code) diff --git a/frontend/src/views/factor/FactorPicker.vue b/frontend/src/views/factor/FactorPicker.vue new file mode 100644 index 0000000..b20dedb --- /dev/null +++ b/frontend/src/views/factor/FactorPicker.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/frontend/src/views/factor/LeaderboardDetail.vue b/frontend/src/views/factor/LeaderboardDetail.vue index b8b7120..ba562d0 100644 --- a/frontend/src/views/factor/LeaderboardDetail.vue +++ b/frontend/src/views/factor/LeaderboardDetail.vue @@ -2,6 +2,7 @@ import { ref, onMounted, watch, nextTick, computed } from 'vue' import { useRoute, useRouter } from 'vue-router' import { getEvalDetail, submitFactor, type EvalDetail } from '@/api/factor' +import { TEARS_SAMPLE_CODES } from '@/constants/factorSamples' import { useChart } from '@/composables/useChart' import { darkTitle, darkTooltip, darkGrid, darkAxis, BRAND } from '@/utils/echartsDark' import { useFactorCompareStore } from '@/stores/factorCompare' @@ -51,12 +52,6 @@ function toggleCompare() { // —— 一键生成 tears(方案A):排行榜数据没有分层序列,tears 要跑一次 alphalens 分析 // (固定 30 只跨行业样本 × 近 2.5 年,分钟级),跑完结果页即新版 ECharts tears。 -const TEARS_SAMPLE_SYMBOLS = [ - '600519', '000858', '600036', '601318', '000001', '600000', '300750', '002594', - '600276', '000538', '600887', '000651', '000333', '600690', '000725', '002415', - '600030', '601166', '600028', '601857', '600585', '000625', '601633', '002352', - '600104', '601398', '600941', '300059', '002230', '601888', -] const TEARS_SAMPLE_SPAN = { start: '2024-01-01', end: '2026-06-30' } const tearsSubmitting = ref(false) @@ -66,7 +61,7 @@ async function generateTears() { tearsSubmitting.value = true try { const tid = await submitFactor({ - symbols: TEARS_SAMPLE_SYMBOLS, + symbols: TEARS_SAMPLE_CODES, factor_names: [detail.value.factor], start: TEARS_SAMPLE_SPAN.start, end: TEARS_SAMPLE_SPAN.end, diff --git a/frontend/src/views/factor/New.vue b/frontend/src/views/factor/New.vue index 4fa80d3..f760917 100644 --- a/frontend/src/views/factor/New.vue +++ b/frontend/src/views/factor/New.vue @@ -1,46 +1,56 @@ diff --git a/frontend/src/views/factor/Result.vue b/frontend/src/views/factor/Result.vue index 7dc6b13..1908d8e 100644 --- a/frontend/src/views/factor/Result.vue +++ b/frontend/src/views/factor/Result.vue @@ -1,4 +1,9 @@ + +