7f93d6b0f5
- Introduced ActiveMoneyflowSource to fetch optional active-order flow, enhancing the sector radar's data capabilities. - Updated StockFactRecord and DailyAggregateRecord to include pct_change and active_buy_net_amount_yuan for improved financial insights. - Modified the build process to incorporate active moneyflow data without invalidating main rankings on failure. - Enhanced the HTTP API to return detailed sector history and metrics, including pct_change and active buy metrics for members. - Updated tests to validate the new functionality and ensure data integrity across various scenarios.
5.3 KiB
5.3 KiB
选股页面迭代:上搜索 + 左列表/右详情布局 & 板块筛选
现状与关键结论
- 布局:
selection-results-workbench.tsx目前是左右两栏 grid(左 320px 筛选+列表 / 右详情),筛选栏嵌在左栏顶部。 - 板块数据:选股结果表(
selection_run_item)没有板块字段;板块归属在sector_radar_membership表,现有 membership 接口只支持单股查询。板块聚合必须由后端新增(前端逐股请求既慢又只覆盖已加载分页,计数不准)。 - 已确认口径(用户未答,按推荐执行):概念板块(
sector_type='concept',与详情面板"板块"标签一致),单选;接口保留sector_type参数,后续扩展行业零成本。排序 = 按stock_count倒序(选项旁展示数量)。
一、后端(zhixing-server)
遵循 bounded-context-first(ADR 0001):selection 不直接 join sector_radar 表,通过应用层端口调用 sector_radar 的读服务。
- sector_radar 模块(
application/read.py+infrastructure/postgres.py):- 仓储新增两个只读方法(复用已有的
get_last_good_publication解析快照日):load_sector_counts(stock_codes, snapshot_date, sector_type):SELECT sector_code, sector_name, COUNT(*) FROM sector_radar_membership WHERE stock_code = ANY(%s) AND trade_date = %s AND sector_type = %s AND membership_status='available' GROUP BY 1,2load_sector_member_codes(snapshot_date, sector_code, sector_type):返回该板块成员股票代码列表
- 读服务
ReadSectorRadar新增:sector_counts(stock_codes, trade_date, sector_type)(内部解析 last-good publication,返回按 stock_count 降序、名称升序)和sector_member_codes(trade_date, sector_code, sector_type)。
- 仓储新增两个只读方法(复用已有的
- selection 模块:
domain/runs.py:SelectionResultQuery增加sector: str | None;新增SelectionSectorCount(sector_code, sector_name, stock_count)值对象;定义端口协议SectorMembershipReader(sector_counts/sector_member_codes两个方法)。application/run.py:新增list_sector_counts(strategy, target_trade_date, sector_type)—— 取 latest run,收集status='selected' AND signal_count>0的 ts_code,调端口聚合;get_latest/get结果查询在query.sector有值时先调端口取成员代码,空则直接返回空页,否则把代码数组传入仓储。infrastructure/postgres_runs.py:_stock_filter增加子句item.ts_code = ANY(%s)(参数由应用层传入)。presentation/http.py:- 新端点
GET /api/v1/selection/sectors?strategy=&target_trade_date=§or_type=→{ sector_type, snapshot_trade_date, sectors: [{sector_code, sector_name, stock_count}] } GET /results与GET /runs/{id}增加sectorquery 参数校验(去空格、限长)。
- 新端点
- 组合根(router/依赖装配处)把 sector_radar 的读服务适配为 selection 的端口注入。
- 测试:
tests/unit/sector_radar/(新仓储方法)、tests/unit/selection/test_postgres_runs.py(sector 过滤、ANY 数组、空成员空页)、tests/test_selection_http.py(新端点契约 + results 带 sector)。跑./dev.sh check(ruff/pyright/pytest)。
二、前端(zhixing-web)
- 布局重构
selection-results-workbench.tsx:- 外层改为
flex flex-col:顶部一个全宽 section 放搜索栏(关键词 / 信号分类 / 板块(新增) / 排序 / 筛选结果计数),带rounded-md border bg-card与现有一致; - 下方
md:grid md:grid-cols-[320px_minmax(0,1fr)]左列表右详情;移动端纵向堆叠为 搜索 → 列表 → 详情。
- 外层改为
- 类型与 API(
selection.types.ts/selection.api.ts):新增SelectionSectorAggregate;SelectionResultsQuery加sector?;getSelectionResultSectors();buildSelectionQueryParams带 sector。 - 查询层(
selection.query.ts):useSelectionResultSectors(key:strategy+date,结果就绪后启用);selectionResultsQueryKey/selectionRunQueryKey加入 sector 使筛选变化触发重新请求。 - 路由(
routes/route-tree.tsx):selectionRoutevalidateSearch增加sector(string,限长,默认 undefined)。 - 页面接线(
selection-results-page.tsx):把 URL 中的 sector 传入useSelectionResults;页面层调用useSelectionResultSectors并把聚合结果传给 workbench。 - workbench 板块下拉:选项 = "全部板块" + 聚合数据,item 渲染
名称 + 数量徽标(数量 tabular-nums,倒序由后端保证);选择写 URL;聚合加载后若当前 sector 不在列表中(如切换策略/日期)自动重置为全部;板块无数据时下拉仅剩"全部板块"并禁用。
三、执行方式
- 按仓库 Trellis 工作流建任务目录并加载
.trellis/spec/backend(selection.md 契约:查询不触发重算、错误矩阵等)与前端规范后再动手;先后端(接口+测试)再前端接线,最后./dev.sh check+ 前端 lint/tsc 全量质量门禁。 - 语义说明:板块数量 = 当次 run 全部选中股票中归属该板块的数量(不随关键词/信号分类变化);列表"筛选结果 N 只" = 包含板块在内的全部过滤叠加后的
stocks_total。