5.7 KiB
选股模块图表展示技术设计
1. 边界与总体方案
本任务继续使用现有 selection bounded context,不新建业务上下文。分页结果接口继续只承载列表与目标日摘要;新增独立只读详情接口按 ts_code + target_trade_date 返回最多 250 个升序 qfq 日线点及对应 KDJ,避免每页股票都携带大数组。
前端 selection feature 用独立 React Query 缓存详情图表。从现有 md 双栏断点起,工作台统一使用左 1fr、右 3fr,删除更大断点的旧比例覆盖;移动端沿用现有记录卡和展开交互,不在本次增加移动端复合图表。右侧详情依次展示股票摘要、复合图表、最佳案例 JPG、评分分解和信号指标。
2. 后端数据流与契约
新增 application 用例 GetSelectionChart,依赖已有 MarketDataReader。用例接收 ts_code、target_trade_date 和固定上限 250,读取目标日前完整 qfq 历史,在完整历史上复用 compute_kdj 计算递归 K/D/J,再把 OHLCV 与指标对齐并截取末尾最多 250 条。这样返回量受控,同时 KDJ 初值与策略计算保持一致。
新增接口:
GET /api/v1/selection/stocks/{ts_code}/chart?target_trade_date=YYYY-MM-DD
成功响应:
{
"ts_code": "603259.SH",
"name": "药明康德",
"target_trade_date": "2026-08-31",
"source_adj": "qfq",
"points": [
{
"trade_date": "2026-08-31",
"open": 1.0,
"high": 1.0,
"low": 1.0,
"close": 1.0,
"volume": 1.0,
"k": 50.0,
"d": 50.0,
"j": 50.0
}
]
}
OHLCV 或 KDJ 不可用的点用 null 保留日期对齐,不制造数值。前端遇到 OHLC 不完整的日期时保留 category 但不绘制该蜡烛,volume/K/D/J 也保持空点,KDJ 禁止跨空点连线。没有任何目标日前行情时返回 404 chart_data_not_found;数据库读取失败返回 503 selection_storage_unavailable。接口继续使用 FastAPI Pydantic response model 和同源 /api/v1 路径。
3. 最佳案例评分与图片
不改变十案例评分算法、阈值、排序或持久化。调整 HTTP 映射:只要评分状态是 matched 或 below_threshold 且内部结果完整,都返回实际 value、threshold、version、case 和 breakdown;状态仍保留原值,因此 UI 可以区分“达到阈值”和“最接近但低于阈值”。评分失败返回 status="failed" 且无 case;评分未执行沿用现有 score: null 契约,前端明确显示“本次运行未执行图形评分”,两者均不展示案例图。契约测试锁定 null 的既有语义,避免把字段遗漏误当成功结果。
把用户提供的十张 JPG 复制到 zhixing-web/public/patterns/b1-cases/ 并保留原文件名。selection feature 维护穷举的 case.id -> public URL 映射;案例 ID 是稳定业务键,中文名只用于展示。若响应出现未知 case id,页面显示“案例图片暂不可用”,不猜测文件名。
4. 前端数据与组件
新增 SelectionChart、SelectionChartPoint 类型,API 函数通过 requestJson 调用详情接口并传递 AbortSignal,query key 包含股票代码和目标交易日。仅当存在选中股票时启用查询;切换股票时 React Query 取消或隔离旧请求,详情分别呈现加载、失败、空数据和成功状态。
新增 selection feature 内的复合图表组件,直接使用 echarts/core,按需注册 candlestick、bar、line、grid、tooltip、legend、dataZoom 和 Canvas renderer。三组 category x 轴共享相同交易日数组,candlestick、volume、K/D/J series 分别绑定三个 grid;inside 与 slider dataZoom 同时控制三组 x 轴。后端最多返回 250 点,前端通过 category startValue/endValue 精确选择末尾 120 点,不足 120 点时展示全部,并允许缩放查看返回的全部数据。
ECharts 初始化只发生在图表 DOM 挂载后;数据变化调用 setOption,容器变化通过 ResizeObserver 调用 resize,卸载时 dispose。option 构造与 API 数据转换保持为纯函数,避免在 jsdom 中依赖 Canvas 像素渲染。
最佳案例组件从 score.case.id 查找静态 JPG,使用语义标题、描述性 alt、固定 2:1 比例和 loading="lazy"。图像加载失败时保留案例名称和可读错误状态。
5. 兼容性与取舍
- 不把历史序列扩展到
/selection/results,避免分页和轮询响应膨胀。 - 不新增图片数据库、上传接口或动态生成逻辑;案例库固定,因此静态资源映射是最小充分方案。
- 不引入 React ECharts wrapper,减少 React 19 兼容面;只增加
echarts一个运行时依赖。 - 移动端暂不加载大图表,维持现有详情展开行为;桌面端按需求提供完整详情。
- 不新增迁移。最佳案例字段已经持久化,图表读取复用现有 market data。
6. 风险、回滚与验证重点
主要风险是 KDJ 窗口被错误截断、股票切换显示上一只股票的数据、ECharts 容器未 resize/dispose、缺失行情点错误连线、未知案例 ID 产生错误图片,以及低于阈值的最佳案例仍被 HTTP 丢弃。测试分别锁定完整历史计算后截断、query key、加载/错误/空状态、三轴同步 option、缺失点处理、静态映射完整性和 below-threshold 契约。案例图片行为测试必须断言详情中恰有一张 <img>,其 src 由返回的 case.id 映射,案例名称和评分与同一响应一致;低于阈值也执行同一断言。
回滚可以按层撤销:删除新增详情接口与用例、恢复评分 HTTP 映射、移除 ECharts 组件/依赖和静态 JPG,再恢复原 grid class;没有数据库迁移或不可逆数据写入。