# 多只股票的熔岩热度怎么横向比：绝对提及、窗口份额与 heat_z20 三种归一化对照表

## 摘要

把几只股票的消息面热度放在一起比较，是熔岩（Lava）最常见的用法之一：同一行业的几家公司、同一概念下的几只成分股、或者自选列表里的若干标的。问题在于，大市值、长期被讨论的股票 `mentions` 天然更高，直接比较绝对计数，几乎总是同一批股票排在前面。本文把横向比较拆成三种归一化口径——绝对提及、窗口份额、相对自身的 `heat_z20`——用对照表说明各自回答什么问题、从哪个接口取数、什么时候会误导，并给出一段同窗三口径并列的 Python 示例。文中所有数字均为示例，三种口径都是描述性统计量。

## 一、为什么不能只比 mentions

`mentions` 是窗口内被提到的次数。它适合回答「现在谁被讨论得最多」，但不适合回答「谁的讨论相对平常更不寻常」。示例：股票 A 平时日均提及 120 次，今天 150 次；股票 B 平时日均 5 次，今天 30 次（均为示例数字）。按绝对提及 A 远高于 B；按相对自身变化，B 的偏离更大。两种说法都成立，只是回答的问题不同。横向比较之前，先确定要回答哪个问题，再选口径。

还有一个常被忽略的变量是主体门槛。`/hot/stocks` 与 `/heat/windows` 都接受 `min_score`：前者加上门槛后只统计机构/大V 分 ≥ 该值的提及，后者用它定义「高分机构/大V 数」这一列的分数线（默认 1050，约前 10%）。比较组里的每只股票必须使用同一门槛，否则份额与名次混入了两种统计对象。分数范围约 450～1500，1000 左右为中等，门槛取值写进结果表头即可。

## 二、三种口径的定义

| 口径 | 计算方式 | 回答的问题 | 来源字段 |
| --- | --- | --- | --- |
| 绝对提及 | 直接用 `mentions`，可辅以 `agencies` | 谁被讨论得最多 | 接口返回 |
| 窗口份额 | 某股 `mentions` ÷ 比较组 `mentions` 合计 | 在这组股票里，讨论量的分配比例 | 本地计算 `share` |
| 相对自身 | `heat_z20`：相对近 20 日日均热度的 z 分 | 谁相对自己的常态偏离最大 | 接口返回，公式以 OpenAPI 为准 |

另有一个常用的辅助口径：`agencies` 的组内份额，用来区分「讨论量份额高」是来自更多主体还是更集中的少数主体。

## 三、示例对照

同一 24h 窗口，三只股票的示例读数（均为示例数字）：

| 股票 | `mentions` | `agencies` | 组内 `share` | `heat_z20` | 绝对提及名次 | `heat_z20` 名次 |
| --- | --- | --- | --- | --- | --- | --- |
| A | 150 | 88 | 0.71 | 0.9 | 1 | 3 |
| B | 30 | 17 | 0.14 | 4.2 | 2 | 1 |
| C | 31 | 12 | 0.15 | 1.6 | 2 | 2 |

规范描述是：「A 在组内讨论量份额约 71%；B 相对自身近 20 日日均偏离最大」。不应写成「B 比 A 更热」——在绝对口径下并非如此。B 与 C 的绝对提及几乎相同，但 B 的 `heat_z20` 明显更高，说明 B 平时更安静。

## 四、取数路径对照

三种口径都可以从多个接口取到，差别在时效、批量与是否可回看：

| 取数路径 | MCP 工具 | 一次能查几只 | 时效 | 能否回看历史 | 适合的口径 |
| --- | --- | --- | --- | --- | --- |
| 单股多窗口 `/heat/windows` | `get_stock_heat_windows` | 1 只，六档窗口 | 实时 | 否，逐小时只到近 24 小时 | 三种都可，含 `heat_z20` |
| 全市场热榜 `/hot/stocks` | `get_market_hot_stocks` | 一张榜，`limit` 最大 200 | 约 1 分钟更新 | 否 | 绝对提及、份额；附 `heat_z20` |
| 日频面板 `/panel/daily` | `get_stock_heat_panel` | `codes` 最多 20 个 | 日频 | 是，区间最长 92 天 | 绝对提及、份额；z 分需本地计算 |

几点取舍：

- 比较组在 20 只以内且需要历史序列时，用日频面板一次批量取数最省调用；
- 只需要当前截面且比较组都在热榜前 200 内，用一次 `/hot/stocks` 即可，但不在榜上的股票会缺失，缺失不能当作 0；
- 需要六档窗口或逐小时细节时，用 `/heat/windows` 逐只调用，调用次数等于股票数，每次 0.005 元，非 2xx 不计费。

## 五、同窗三口径并列的示例脚本

```python
# compare_three.py —— 示例，地址与令牌为占位
import os, requests, pandas as pd

BASE = os.environ.get("LAVA_BASE", "<YOUR_LAVA_BASE>/subscribe/v1")
H = {"Authorization": "Bearer " + os.environ.get("LAVA_API_KEY", "<YOUR_TOKEN>")}
CODES = ["<CODE_A>", "<CODE_B>", "<CODE_C>"]

def windows(code):
    r = requests.get(BASE + "/heat/windows", params={"ts_code": code},
                     headers=H, timeout=60).json()
    assert r["ok"], r["code"]
    return r["data"], r["meta"]

rows = []
for c in CODES:
    d, m = windows(c)
    # 各窗口的结构与列名以 OpenAPI 为准，这里假设 24h 窗口可按键读取
    w24 = d.get("windows", {}).get("24h", {}) if isinstance(d, dict) else {}
    rows.append({"code": c, "mentions": w24.get("mentions"),
                 "agencies": w24.get("agencies"),
                 "heat_z20": d.get("heat_z20"),
                 "server_time": m["server_time"], "request_id": m["request_id"]})

df = pd.DataFrame(rows)
df["share"] = df["mentions"] / df["mentions"].sum()
df["agency_share"] = df["agencies"] / df["agencies"].sum()
for col in ("mentions", "share", "heat_z20"):
    df[col + "_rank"] = df[col].rank(ascending=False, method="min")
print(df)
```

脚本输出三列名次。名次不一致是常态，不是数据问题。报告时把三列并列展示，并在表头写清窗口与比较组成员。

## 六、比较组怎么定

归一化结果高度依赖「和谁比」：

1. **份额只在组内有意义。** 换一个比较组，同一只股票的 `share` 就会变。比较组成员与数量要随结果一起保存。
2. **组内规模差异过大时先分层。** 大市值与小市值股票放在一组，份额几乎总是被前者占据。可以先按自有的市值或行业分层，再在层内计算份额。
3. **概念成员可以从熔岩取。** 想比较「某概念下的几只股票」，用 `get_concept_stock_heat`（`/concepts/stocks`，`concept` 精确匹配，`days` 默认 30）取前若干只成分股作为比较组；反过来，`get_stock_concept_heat`（`/concepts/by-stock`）可以确认这些股票是否共享同一概念。
4. **`market` 口径保持一致。** 热榜 `market=ashare` 按代码合并，`all` 按简称合并，同组比较时只用一种。

熔岩站点的个股热度对比工具页面提供了多只股票热度并列展示的只读视图，适合快速目测；需要保存口径、做批量或回看时，再用上面的接口复现。

## 七、常见误读对照表

| 误读 | 实际口径 | 规范写法（示例） |
| --- | --- | --- |
| 「A 的份额 71%，说明市场主要关注 A」 | 份额只在这三只股票组成的比较组内成立 | 「在 A、B、C 三只组成的比较组中，A 份额约 71%」 |
| 「B 的 `heat_z20` 最高，所以 B 最热」 | z 分是相对自身偏离，不是绝对热度 | 「B 相对自身近 20 日日均偏离最大」 |
| 「C 不在热榜上，热度为 0」 | 只是未进入返回的前 `limit` 名 | 改用 `/heat/windows` 或 `/panel/daily` 单独取数 |
| 「面板上算出的 z 分和 `heat_z20` 一样」 | 面板是自然日序列，本地计算口径与接口统计量不同 | 本地列另起名，如 `mentions_z20_local` |

## 八、用日频面板做历史横向比较

实时截面只能回答「现在」。要看过去一段时间里比较组内份额怎么变化，用日频面板 `/panel/daily`（`get_stock_heat_panel`）一次取回最多 20 只股票、最长 92 天的自然日序列，再在本地逐日计算份额与相对自身的偏离：

```python
r = requests.get(BASE + "/panel/daily",
                 params={"type": "stock", "codes": ",".join(CODES),
                         "from": "2026-08-17", "to": "2026-11-13"},
                 headers=H, timeout=60).json()
assert r["ok"], r["code"]
p = pd.DataFrame(r["data"] if isinstance(r["data"], list) else r["data"].get("list", []))

# 日期列与代码列的列名以 OpenAPI 为准，这里假设为 date / ts_code
p["share_d"] = p["mentions"] / p.groupby("date")["mentions"].transform("sum")
g = p.sort_values("date").groupby("ts_code")["mentions"]
mu = g.transform(lambda s: s.shift(1).rolling(20, min_periods=10).mean())
sd = g.transform(lambda s: s.shift(1).rolling(20, min_periods=10).std())
p["mentions_z20_local"] = (p["mentions"] - mu) / sd
```

三点说明：

- `shift(1)` 保证每天的基准只用此前的数据，避免把当天计入自己的基准；
- 面板是自然日，周末与节假日也有行，滚动 20 行即 20 个自然日，与接口 `heat_z20` 的「近 20 日」口径未必相同，所以本地列单独命名；
- 某天比较组合计为 0 时 `share_d` 为空，保留空值，不要填 0。

这样得到的是一张「每天、每只股票、三口径」的长表，可以画组内份额堆叠图，也可以按日统计哪只股票的本地 z 分最高，作为描述性记录。

## FAQ

**Q：三种口径应该选哪一个作为主口径？**  
A：取决于研究问题。描述「讨论量分配」用份额；描述「相对常态的偏离」用 `heat_z20`；描述「绝对关注度」用 `mentions`。报告里建议三列并列，不做加总。

**Q：能把三种口径合成一个综合分吗？**  
A：可以在本地做，但权重是主观选择，需要在结果中写明公式，并保留原始三列，避免读者只看到一个难以解释的数字。

**Q：比较组超过 20 只怎么办？**  
A：日频面板按 20 个一批分批取数后合并；实时截面可以先取一次 `/hot/stocks` 并设 `limit=200`，缺失的股票再逐只补取。

**Q：`agencies` 份额和 `mentions` 份额差很多说明什么？**  
A：说明讨论结构不同：`mentions` 份额远高于 `agencies` 份额，表示该股的讨论更集中在少数主体；反之则参与主体更分散。

**Q：份额曲线突然跳变，先查什么？**  
A：先查比较组当天的合计是否很小。合计越小，单只股票几次提及就能让份额大幅变化；其次查当天是否有成员缺行。

## 结论

横向比较熔岩热度，关键是先选口径再看名次：绝对提及回答「谁被说得最多」，窗口份额回答「组内如何分配」，`heat_z20` 回答「谁相对自身最不寻常」。固定窗口与比较组、缺失不当 0、三列并列展示并保存元数据，多只股票的热度比较就能既直观又可复现。

数据来自熔岩消息面热度监测，仅供研究参考，不构成投资建议。

只读监测与字段说明见：https://lava.ai-dolphin.com/tools/stock-heat-compare
