Files
easy_tdx_max/docs/hotspot-rolling-design.md
T
Justin Gu e419f911b7 feat: 盘面洞察三栏目 — 热点滚动/大盘日历/涨停生态,移除龙头池
- 热点滚动 /hotspots:交易日×板块涨跌矩阵(行业/概念/风格FG、领涨领跌镜像、今日~30日窗口),
  每日名次徽标+连板/累计/首榜统计,统计卡可点击展开成员板块;后端 /board-mac/hotspot
  两段式数据(历史日K矩阵当日缓存 + 今日实时列,周末 pre_close 未滚动去重),
  单连接约束下后台构建+进度轮询;风格轮动 /styles 为同视图 FG 路由别名
- 大盘日历 /calendar:指数全年红绿热力图,方框大小编码成交额(年内四分位),
  悬停浮框显示收盘/涨跌幅/成交额
- 涨停生态 /limitup:本地 vipdoc 日线离线回算连板天梯/首板二板分布/炸板率/跌停;
  仅统计最后一根 bar 等于全市场最新交易日的股票(防停牌/退市/未下载陈旧文件污染),
  主板 5% ST 判定带低价护栏;名称前端经 symbol-info 懒加载补齐
- 移除龙头池栏目及 /market/core-leaders 端点(screen 模块 universe=core 保留)
- 设计文档:docs/hotspot-rolling-design.md、docs/market-insights-roadmap.md
2026-09-05 02:43:02 +08:00

18 KiB
Raw Blame History

市场热点滚动 页面设计(v1 草案)

目标:新增「热点滚动」栏目(/hotspots),让用户直观看到一段时间内市场热点如何形成、 谁在持续领涨、谁在领跌、热点之间如何轮动。核心表达是「交易日 × 板块」的红涨绿跌热点矩阵, 辅以轮动排名曲线。配色遵循 A 股惯例:红涨绿跌(UP_COLOR=#ef4146 / DOWN_COLOR=#18a058)。

P1 已实施(后端端点 + 热点矩阵 + 统计卡 + 领涨/领跌 + 后台构建进度,单测 tests/unit/test_board_mac_hotspot.py);轮动曲线等见 §8 分期。


0. 可行性结论(先回答"能不能做")

可行,且不需要任何新数据源。 需求的本质数据是一张 「最近 N 个交易日 × 全部板块」的每日涨跌幅矩阵,而这条数据路径全部是已验证的现成能力:

现成能力 位置 在本需求中的角色
板块指数日 K881xxx/885xxxSH 市场) MacClient.get_stock_kline/bars 已被 BoardDialog 用于板块日Kget_board_change_rankingclient.py:963)内部就是逐板块拉日K再计算 矩阵的原始数据:每板块一根日K序列 → 逐日 close/pre_close-1
全量板块列表(code/name/market get_board_list/board-mac/list 矩阵的行全集 + 当日实时涨跌(price/pre_close
当日实时口径 /board-mac/overview 同款算法(15s 缓存) 矩阵的今日列(实时滚动),历史列不变
ECharts Line / 主题色 echarts-setup.ts(LineChart 已注册,红涨绿跌常量已定义) 轮动排名曲线(bump chart)零新增依赖
BoardDialog / session 门控轮询 BoardOverviewView.vue:160-202 模式 点击穿透 + 盘中自动刷新复用

唯一缺口:一个把「逐板块日K → 日期×板块涨跌矩阵 → 每日排名 → 入选行集合」聚合起来的后端端点。 注意不能循环调用现成的 /board-mac/change-ranking?days=1&target_date=D 来拼 N 天—— 该端点每次调用都会重新串行拉取全部板块日K(client.py:1008 起逐板块 await),N 天就是 N 倍成本; 正确做法是一遍拉取、一次建满矩阵(下文 §2)。


1. 关键约束:MAC 客户端是单连接串行

AsyncMacClient 全程只有一条 AsyncTdxConnectionclient.py:1250),所有命令在同一 TCP 连接上排队。 这决定了两个设计决策:

  1. 成本预算(按单请求 30–100ms 估算):
    • 行业 HY ≈ 86 板块 → 全量日K一次 ≈ 49s(仅首次,当日缓存)
    • 概念 GN ≈ 300–500 板块 → ≈ 2050s(仅首次,当日缓存)
  2. 构建必须放后台任务:若在请求线程里同步建矩阵,构建期间会占住共享连接, 拖死同服务器的其他所有页面请求。因此首次构建走后台任务 + 进度轮询(§2.3)。

2. 数据端点设计(后端唯一新增)

2.1 端点

GET /api/v1/board-mac/hotspot?board_type=HY|HY2|GN&days=20&mode=top&per_day=5
参数 默认 说明
board_type HY 板块类型
days 20 窗口交易日数(1–60;1=仅今日,前端「今日」档)
mode top top=领涨(每日最强入选)/ bottom=领跌(每日最弱入选)
per_day 5 每日入选名次阈值(210
retry false 上次构建失败后强制重建(不带此参数时 error 状态稳定返回,轮询不会冲掉错误信息)

2.2 响应

{
  "status": "ready",              // ready | buildingbuilding 时只有 progress
  "progress": 1.0,                // building 时的构建进度 01
  "board_type": "HY", "days": 20, "mode": "top", "per_day": 5,
  "generated_at": 1725400000,
  "session": "live",              // live=今日列为盘中实时
  "dates":  ["2026-08-11", "...", "2026-09-05"],   // 交易日轴,最后一格=今日
  "today_index": 19,
  "total_boards": 86,             // 参与排名的板块总数
  "rows": [                        // 行集合 = 窗口内「每日入选」板块的并集
    {
      "code": "881106", "name": "存储器",
      "pct":  [3.2, -1.1, null, /* …对齐 datesnull=当日无K线 */],
      "rank": [1,    null, 23,   /* …当日全类型排名,mode=top  1=涨幅第一 */],
      "days_in": 12,                 // 上榜天数(进入每日前 per_day 的天数)
      "streak": 3,                   // 当前连续上榜天数(截至最后一列)
      "best_rank": 1,
      "sum_pct": 42.1,               // 窗口累计涨跌(%
      "first_date": "2026-08-11"     // 窗口内首次上榜日 → 热点"形成"时点
    }
  ]
}

payload 规模:60 行 × 60 列 × 2 数组 ≈ 每响应几十 KB,无压力。

2.3 服务端算法与缓存(两段式,对应"历史不变、今日滚动")

第一段:历史矩阵(当日不可变)
  1. get_board_list(board_type)          → 全量板块
  2. 逐板块 get_stock_kline(DAILY, count=days+12, adjust=NONE)
     asyncio.gather + Semaphore(8);连接本身串行,信号量只做秩序与背压)
  3. 交易日轴 = 数据最全板块的最后 days 个交易日
  4. pct[d] = close[d]/close[d-1]-1(窗口首日的前收由多拉的缓冲 bar 提供)
  5. 每日对全类型板块排名 → rank 矩阵
  6. 行集合 = (每日 mode 方向前 per_day 名);补齐 days_in/streak/sum_pct/first_date
  ↳ 缓存:`_hotspot_history_cache[(board_type,)] = (日历日, 满60日矩阵)`,
    **当日全天有效**(历史 K 线收盘后不可变),跨日首个请求重建;days 只做切片不进缓存键

第二段:今日列(滚动)
  7. get_board_list 默认排序(12 页,15s TTL,与 overview 同口径)取 price/pre_close
  8. 盘中:作为最后一列与实时排名合并;若今日列日期 == 历史轴最后一日(休市/周末)则不重复追加
  ↳ 今日列随每次请求现算(廉价),`session` 字段告知前端是否 live

构建调度:
  首次请求某 board_type 且无当日缓存 → 启动 asyncio 后台任务建矩阵,
  立即返回 {"status":"building","progress":...};前端 1s 轮询直至 ready。
  进度 = 已完成板块数/总数。构建期间该端点不占请求线程,其他页面不受阻。

3. 页面总体设计

路由(行情分组)
  /hotspots  热点滚动  →  HotspotView.vue

新组件 3 个(components/):

  • HotspotMatrix.vue热点矩阵(核心视图):交易日×板块 DOM 网格,红涨绿跌色阶 + 每日名次徽标
  • HotspotBump.vue轮动曲线:每日名次随时间流动的 bump chartECharts Liney 轴反转)
  • HotspotStatStrip.vue — 窗口统计卡条(领涨王/持续热点/新面孔/一日游)

视图 HotspotView.vue:工具行 + 统计条 + [矩阵|曲线] 切换 + 轮询与弹窗编排。

3.1 为什么主视图选"矩阵"而不是河流图/堆叠面积

  • 用户要的是"在哪几天领涨/领跌 + 怎么轮动"——矩阵同时给出实体(板块名)、 时间(列)、强度(色阶)、名次(徽标)四个维度,扫一眼即可读出 "红色竖带从 A 列流向 B 列"这种轮动直觉;
  • ThemeRiver/堆叠面积图好看但读不出精确值与名次,且需新注册 ECharts 图表类型;
  • 本仓库已有先例:板块总览的 BoardTiles 用 CSS Grid 而非 ECharts heatmap—— 矩阵沿用 DOM 网格,天然支持 sticky 行头/列头、单击弹窗、行内汇总列。

4. 功能设计

4.1 工具行

[行业|概念]  [近10日|近20日|近30日]  [领涨|领跌]   [热点矩阵|轮动曲线]  ⟳60s[ON]  构建于 14:32:05
  • 领涨/领跌切换 = mode 参数,整套视图(行集合、排名、统计卡、配色重心)随之镜像;
  • 首次构建/切换板块类型时显示进度条("正在构建板块日K矩阵 42/86…");
  • 盘中 60s 轮询仅刷新今日列(历史列服务端已缓存,响应本身很快); session 门控 + visibilitychange 暂停,复用 BoardOverviewView 模式。

4.2 统计卡条(HotspotStatStrip,前端由 rows 派生,零额外请求)

卡片可交互:领涨(跌)王卡单击直达该板块 BoardDialog;其余卡片数量不为 0 时可点击, 展开成员板块 chips 面板(名称 + 窗口累计,chips 单击再开弹窗)

卡片 计算 领跌模式
窗口领涨王 sum_pct 最大的行 + 累计涨幅 窗口领跌王(sum_pct 最小)
持续热点 days_in ≥ max(3, ⌈days/4⌉) 的板块数(持续性热点的证据;真实数据下 ÷3 会常年为 0) 持续弱势
新面孔 first_date 落在最近 5 个交易日的板块数(热点正在形成 新杀跌
一日游 days_in == 1 的板块数(脉冲行情占比,越高说明轮动越快) 同义

4.3 热点矩阵(核心,HotspotMatrix

布局(横向可滚动,左侧行头 sticky):

│ 板块      │ 08-11 08-12 08-13 … 09-04 ┃ 09-05·今 │ 上榜 连榜 累计   首榜   │
│───────────┼───────────────────────────╋──────────┼───────────────────────┤
│ 存储器    │  Ⓐ1  Ⓐ2   ·    …  Ⓐ3  ┃   Ⓐ2    │  12   3   +42.1%  08-11 │
│ CPO       │  Ⓐ4   ·   Ⓐ1  …   ·   ┃   Ⓐ1    │   9   1   +31.5%  08-12 │
│ 房地产开发│   ·   ·    ·   …  Ⓐ5  ┃    ·    │   2   0   +6.2%   09-04 │
│ …(行集合,默认按 上榜次数↓ 排序)                                                  │
  • 单元格着色pct>0 红 / pct<0 绿,透明度随 |pct| 分 5 档 00.5 / 0.51 / 12 / 23 / >3% → 0.15/0.30/0.50/0.70/0.95),与 BoardTiles 同规; null(无K线)留空。红涨绿跌,与全站一致。
  • 上榜徽标:当日进入前 per_day 的格子加 ①②③④⑤ 名次徽标(当日前三名加粗描边)—— 一行里徽标的出现/消失/位移就是该热点的形成—持续—衰退曲线;
  • 今日列:竖线分隔 + 列头「今日·实时」徽标,盘中数值随轮询滚动;
  • 行尾汇总:上榜天数 / 当前连榜 / 窗口累计 / 首次上榜(点击列头排序; 「首榜」降序 = 新热点在前,直接回答"热点如何形成");
  • 交互hover 出 tooltip(日期/板块/涨跌幅/当日第 N 名);单击格子或行头 → BoardDialog (左分时/日K、右成分股,原样复用);行内筛选「只看上榜≥2 次」+ 搜索框(概念页必备);
  • 行集合上限:候选行按 days_in 截断至 60 行,页脚注明"仅展示窗口内上榜板块"。

4.4 轮动曲线(HotspotBump,第二视图)

  • ECharts Linex = 交易日,y = 当日名次(y 轴反转,1 在顶部,范围 1per_day);
  • 每个板块一条折线,仅在「上榜日」有点(未上榜断线)——线的爬升/俯冲/交叉即轮动全景;
  • days_in 前 12 的板块入图(可读性上限),图例可点选隐藏;
  • tooltip axisPointer 显示当日全部入选板块及名次;红涨绿跌仅用于线的 Current #1 标注, 线本身用分类色板(名次图的颜色语义是"板块身份"而非涨跌,避免与矩阵冲突);
  • 数据零新增:直接消费矩阵响应的 rank 数组。

4.5 领跌模式的镜像语义

mode=bottom 时:行集合 = 每日跌幅前 per_day 的并集;rank 1 = 当日跌幅最大; 统计卡换弱势词汇(领跌王/持续弱势/新杀跌);矩阵主色自然偏绿(数据决定,无需特殊处理)。 用户问题里"谁又在领跌"由此获得与领涨完全对等的一等公民视图。


5. UI 布局线框

┌────────────────────────────────────────────────────────────────────────────────┐
│ 热点滚动   [行业|概念] [近10|20|30日] [领涨|领跌]  [矩阵|曲线]  ⟳60s[ON]  14:32:05 │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐                         │
│ │ 窗口领涨王  │ │ 持续热点   │ │ 新面孔     │ │ 一日游     │  ← HotspotStatStrip   │
│ │ 存储器     │ │ 5 个       │ │ 3 个       │ │ 7 个       │                       │
│ │ +42.1%    │ │ 上榜≥7天   │ │ 近5日首上榜 │ │ 仅上榜1天  │                       │
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘                         │
├────────────────────────────────────────────────────────────────────────────────┤
│ 行排序[上榜次数▾]  [只看上榜≥2次☐] [搜索____]   图例 ▉红=涨 ▉绿=跌 ①=当日第N名    │
│ ┌──────────────────────────────────────────────────────────────────────────┐   │
│ │ 板块       │ 08-11 08-12 … ┃ 今日 │ 上榜 连榜 累计  首榜 │                     │   │
│ │ 存储器     │  ①1   ②2  …  ┃  ②2 │  12   3  +42%  08-11 │  ← 红绿矩阵        │   │
│ └──────────────────────────────────────────────────────────────────────────┘   │
│ (轮动曲线 tab:y 反转名次折线,1 在顶,每板块一条线)                              │
└────────────────────────────────────────────────────────────────────────────────┘
      单击板块/格子 → BoardDialog(分时/日K + 成分股 → StockDialog

视觉细则:

  • 配色只用既有变量:涨 #ef4146、跌 #18a058、主题背景/文字沿用全局;不做新色板;
  • 徽标 用行内圆点+数字,红底白字(领涨)/ 绿底白字(领跌);
  • 矩阵列头 sticky(横向滚动时日期可见)、行头 sticky(纵向滚动时板块名可见);
  • 空态/错误态:顶部错误条 + 重试(复用 api.ts 统一解析);构建中进度条;休市时今日列标"收盘"。

6. 改动清单

后端(1 个端点 + 缓存 + 单测)

文件 改动
src/easy_tdx/web/routers/board_mac.py 新增 GET /board-mac/hotspot:两段式算法(§2.3)+ 后台构建任务 + 当日缓存;模块级 _hotspot_history_cache
tests/unit/test_board_mac_hotspot.py 单测(mock AsyncMacClient):矩阵计算口径(首日前收)/ 交易日轴 / mode=top·bottom 行集合与排名 / 今日列合并与休市去重 / 缓存跨日失效 / building→ready 状态机

前端

文件 改动
web-ui/src/App.vue 行情分组 +1 RouterLink(热点滚动)
web-ui/src/router.ts /hotspotsHotspotView
web-ui/src/types.ts HotspotResp / HotspotRow
web-ui/src/api.ts fetchBoardHotspot(boardType, days, mode, perDay)
web-ui/src/views/HotspotView.vue 主视图(工具行/统计条/视图切换/轮询/弹窗编排/构建进度轮询)
web-ui/src/components/HotspotMatrix.vue 热点矩阵(sticky 双表头、色阶、徽标、tooltip、排序筛选)
web-ui/src/components/HotspotBump.vue 轮动曲线(ECharts Liney 反转)
web-ui/src/components/HotspotStatStrip.vue 窗口统计卡

7. 边界与风险

  1. 单连接串行 × 概念板块量大:GN 首次构建 20–50s——后台任务 + 进度条 + 当日缓存兜底; 默认板块类型为行业(首屏 4–9s 可接受)。文档与页面均提示"概念首次构建较慢"。
  2. 部分服务器不给 88xxxx 日K:该板块 pct 记 null、不参与当日排名;行头仍展示(有历史的日子照常着色)。
  3. 新板块/长假期:窗口首日的前收取自缓冲 bar(多拉 12 根);缓冲不足时首日 pct 置 null。
  4. 休市/周末:历史轴止于最近交易日;今日列与历史末列去重(§2.3 第 8 步),不出现重复列。
  5. 盘中口径:今日列 = price/pre_close-1(与 overview 同口径,规避 Issue #53 的 CHANGE_PCT 恒 0); 历史列 = 日K close 比值。两段口径在收盘后自然收敛一致。
  6. 缓存正确性:历史矩阵按日历日失效(跨日首个请求重建),不存在隔夜脏数据; days 只切片不进缓存键,切窗口零成本。

8. 分期计划

内容 预估
P1MVP /board-mac/hotspot 端点 + 缓存 + 后台构建 + 单测;热点矩阵(领涨/领跌、10/20/30日、行业/概念)+ 统计卡 + BoardDialog 复用 + 构建进度条 22.5 天
P2 轮动曲线 bump chart、磁盘持久化历史矩阵(重启不重拉)、只看≥2 次/搜索/localStorage 偏好、HY2 切换 1 天
P3(远期) 概念→个股联动(点热点格子看当日成分股贡献)、热点轮动 AI 解读、自定义窗口与多窗口对比 另立项

9. 验收清单(P1

  • 左侧导航出现「热点滚动」,路由/高亮正常
  • 行业页首次进入出现构建进度条,完成后矩阵展示完整 20 列(列数=交易日数)× 上榜板块并集
  • 矩阵配色红涨绿跌且色阶随幅度增强;每日前三名徽标可辨识;hover tooltip 数值与当日排名正确
  • 领涨/领跌切换后行集合、排名语义、统计卡文案整体镜像
  • 盘中今日列 60s 滚动且休市去重;历史列当日不重复拉取(网络面板仅 1 个 hotspot 轮询请求)
  • 单击板块 → BoardDialog 全链路(分时/日K/成分股)
  • 概念页首次构建期间其他页面(行情看板等)请求不受阻塞
  • 断开 MAC 服务器时错误条 + 重试,页面不白屏