# 市场温度计数据源验证记录 (2026-08-13)

Bull-market thermometer (成交额/两融/换手率/涨跌家数广度) data sources, validated
against live APIs. Scripts: `market_temperature.py`, `market_temperature_v2.py`;
cache dir `market_temp/` (turnover.csv, margin.csv, turnover_rate.csv, breadth.csv, sz_float_mv.csv).

## ✅ Verified good sources (all tushare pro_api)

| 指标 | 接口 | 要点 |
|------|------|------|
| 两市成交额 | `index_daily('000001.SH')` + `index_daily('399106.SZ')` | ⚠️ 深市必须用**深证综指 399106.SZ**(全深市)，不能用深证成指 399001.SZ(仅500成分股)。`amount` 单位**千元**，/1e5→亿元。两表 merge on trade_date 后加总。历史自2005/2014。 |
| 两融余额 | `margin(start_date, end_date)` | 按交易所分行返回，全市场 = `groupby(trade_date)['rzye'].sum()`。单位元，/1e8→亿。**⚠️ 大范围请求静默截断**：一次拉 2014→today 只返回 2020-02+ 且不报错。修复 = **分年拉取再 concat**（`for year in range(2014, ...): pro.margin(f'{year}0101', min(f'{year}1231', today))`），拉完必核对日期 min/max。分年后可取全历史至2014。用户要求数据起点统一 **2014年**（百分位口径须覆盖2015杠杆牛完整周期）。加速度 = `rzye.pct_change(20)*100` 再取 percentileofscore。 |
| 换手率 | `index_dailybasic('000001.SH', fields='trade_date,turnover_rate')` | 直接给出，历史自2014。 |
| 涨跌家数广度 | `daily(trade_date=X)` 逐日统计 | 单次全市场 ~5500 行 ~0.5s；up=(pct_chg>0).sum()。历史 = trade_cal 遍历（~0.5s/日，加 sleep 限速，缓存 CSV）。 |
| 北向资金 | `moneyflow_hsgt` | 正常。 |
| ETF份额(宽基第5组候选) | `fund_share` | 现有框架可复用。 |

## ✅ akshare 可用（例外项）

- `stock_zt_pool_em(date)` 涨停池：历史 3/3 日可用。tushare `limit_list_d` **无权限**，涨停数据走这个。
- `stock_market_activity_legu()`：当日上涨/下跌/平/涨停/跌停/活跃度，用作涨跌家数**对账基准**。

## ❌ 已验证不可靠 / 剔除

- **东财个股快照 `stock_zh_a_spot_em()`**：反复被断连（`Connection aborted, Remote end closed`），IP限流。涨跌家数历史千万别指望它。
- **新增开户数 `stock_account_statistics_em()`**：数据只到 2023-08，严重滞后，剔除。
- `stock_market_pb_legu`：接口不存在（akshare 无此属性）。
- tushare `adb`：不是合法接口名（"请指定正确的接口名"）。
- csindex `index-basic-info` / `index-list` 搜索接口：404/空返回；但 `perf/index-perf?indexCode=X` 可用（可确认指数中文名/PE/成分数）。

## 口径对账（涨跌家数）

tushare daily vs 乐咕乐咕，2026-08-13：
- tushare: 上涨1143 下跌4317 平80（含B股/退市整理，多约50只）
- 乐咕: 上涨1085 下跌4041 平79
- 差异 ~5% 绝对值，趋势一致。够用；如需精确可剔除非主板代码。

## ⚠️ 成交额归一化（用户要求 2026-08-13）

绝对成交额做百分位**失真**：2014年1万亿是爆量，2026年1万亿是缩量，因为总市值涨了好几倍。用户明确要求改成"**成交额/流通市值比例**"再算百分位。

### 数据源：daily_info 接口

`pro.daily_info(trade_date=X)` 返回 SH_MARKET / SZ_MARKET 等市场汇总行，含 total_mv / float_mv / amount。

**⚠️ 沪市历史缺失**：`SH_MARKET` 行只从 **2019年** 起有数据，2014-2018 只有 `SZ_MARKET`。深市全历史可用。

### 沪市流通市值反推 — ❌ 已放弃（校准系数不稳定）

初版方案：用上证换手率反推 `sh_float_mv = sh_amount(亿) / turnover_rate(%) * 100`。
2026-08-13 多日期实测：daily_info沪市真实换手率 ÷ tushare turnover_rate 在
2019-06~2026-08 间漂移 **1.12 ~ 1.57**（20190614=1.12, 20200320=1.46, 20230407=1.56, 20260812=1.57），
tushare turnover_rate 分母口径与流通市值不一致且随时间漂移。**不要用 turnover_rate 反推市值做分母。**

### ✅ 最终方法：换手率加权平均（成交额/流通市值 本质 = 全市场换手率）

```
# 成交额口径（关键！）
沪市成交额 = index_daily('000001.SH').amount / 1e5   # 上证综指=全沪市 ✓
深市成交额 = index_daily('399106.SZ').amount / 1e5   # ⚠️ 深证综指(全深市)!
    # 绝不能用 399001.SZ 深证成指(仅500成分股): 6452亿 vs 全深市11675亿, 低估~45%
    # 对账: 深证综指 1.1663万亿 ≈ daily_info SZ_MARKET 1.1675万亿 ✓

# 换手率
沪市换手率 = index_dailybasic('000001.SH') turnover_rate      # 直接用, 全历史
深市换手率 = 深证综指成交额(亿) / daily_info深市float_mv(亿) * 100
全市场换手率 = w_sh*沪市换手率 + (1-w_sh)*深市换手率,  w_sh = 沪市成交额/两市成交额
```
amount_to_mv 即全市场换手率，百分位基于此（用户确认口径等价后，原独立的"上证换手率"面板被删除——信息冗余）。

**中证全指替代方案已测试不可行**：tushare index_dailybasic 对 000985.CSI/H00985.CSI/000985.SH 均返回空，akshare 亦无对应接口。维持加权方案。

### 数据源：daily_info 接口

### 深市流通市值拉取

`daily_info` 按日查询，需遍历 trade_cal 所有交易日（~3067天，约15分钟）。
**⚠️ 首轮会随机失败大量日期**（限流）：实测首轮仅 1656/3067 成功，缺失散布于每年（非历史覆盖问题）。
修复 = 写重试脚本只补 `missing = all_dates - have`，每天最多重试3次 + sleep(1)，再 sleep(0.5)/200条限速。
补拉后 3019/3067，剩 48 天 → `df['sz_float_mv'].interpolate()` 线性插值即可。
缓存到 `market_temp/sz_float_mv.csv`。脚本：`/tmp/fetch_sz_float_mv.py` + `/tmp/retry_sz_float_mv.py`。

## 绘图要点（v8 双层最终版, 5面板, 2026-08-14）

v7(MA10单层)被用户质疑"换手率/广度百分位与牛熊不正相关"后重构为双层。**v6章节描述的是旧单层结构（MA10），已被v8取代**。

**设计动机（数据验证）**：
- 广度 vs 沪深300涨幅：日度相关 0.002，MA10口径 -0.55 → 广度价值在背离而非跟随
- MA10百分位熊反弹误报：18超跌反弹96% / 20疫情反弹99% / 24年2月反弹100%（比牛市顶还高）
- ⚠️ MA60崩盘滞后：2016-01-27（崩盘后6周熊底）MA60温度仍92%（窗口装着崩盘前热度）
- MA20回测全通过：15顶100 / 16熔断底64 / 18熊底2 / 19熊底0 / 20反弹峰64 / 24低点31 / 24顶65

**结构**（market_temperature_v2.py, figsize (16,17.5), height_ratios [1,1,1,1.2,0.9]）：
1. 全市场换手率：MA10浅色细线+MA20深色粗线，右轴=MA20百分位曲线（draw_pct_axis）
2. 杠杆率（存量指标不光滑）+ 百分位右轴
3. 上涨家数占比：MA10浅色+MA20粗线，右轴=MA20百分位（红绿底色50%分区）
4. 沪深300独立面板：灰线+牛熊温度≥90红点/≤10绿点（scatter s=12）
5. 双温度曲线：牛熊温度(MA20,橙粗,lw1.6)+短期温度脉冲(MA10,浅橙细,lw0.7)+90/10阈值区

**计算口径**：SMOOTH_FAST=10/SMOOTH_SLOW=20；换手率/广度各自算 ma_fast/ma_slow → 全量rank百分位 pct_fast/pct_slow；杠杆率单百分位。牛熊温度 temp_slow = mean(tr_slow, lev_pct, br_slow) 再全量rank；短期温度 temp_fast = mean(tr_fast, lev_pct, br_fast) 再全量rank。红绿点只看 temp_slow。

**关键时点读数（牛熊/短期）**：15顶 100/100 | 21顶 39/40（抱团顶正确不报）| 24顶 55/68 | 16熔断底 65/88 | 18熊底 2/1 | 24年2月低点 30/32 | 当前2026-08-12 94/96（过热区+短期脉冲同高=高温持续形态，最接近2015顶）。

**"指数-广度差"诊断范式**（报温度时引用）：各顶部 沪深300点位全历史百分位 vs 广度MA百分位——15顶 99%/97%（差+2全面疯牛）、21顶 100%/46%（差+54抱团顶）、24顶 75%/100%（差-25脉冲顶）。指数高+广度低=抱团见顶；广度爆表+指数不高=脉冲持续性差。

## 绘图要点（v6, MA10单层）— 已被v8取代，留作历史

**结构**（用户逐步确认）：5面板 sharex, figsize (16,17.5), height_ratios [1,1,1,1.2,0.75]：
1. 全市场换手率（浅色原始+深色MA10）+ 百分位曲线右轴（draw_pct_axis helper: twinx画pct曲线+90/10虚线+淡色阈值区）
2. 杠杆率（融资余额/流通市值）+ 百分位曲线右轴
3. 上涨家数占比（原始+MA10）+ 百分位曲线右轴（红绿底色分区50%）
4. **沪深300独立面板**（用户要求单独成图）：灰色底线 + 温度≥90打红点 + ≤10打绿点（scatter s=12）
5. 复合温度百分位曲线（90/10阈值线）

历史版本演进：v3(3面板:换手率/两融+加速度/广度vs上证) → v4(百分位曲线+阈值映射) → v5(迟滞带状态机+着色) → **v6(用户简化为红绿点)**。用户明确去掉：迟滞带、三角标记、日期文字标注（"打红点就行...不需要再加那些三角或者文字"）。大盘曲线从广度面板拆出独立成图（"大盘曲线可以单独一个子图绘图"）。

**指标口径（v6）**：换手率/广度先 MA10 平滑再算百分位（用户要求，过滤单日噪音）；杠杆率不光滑；三个百分位全量窗口 `rank(pct=True)`（用户确认不用扩展窗口，"有未来数据无所谓"）；复合温度 = 三百分位均值 → 再全量 rank（均值分布收缩必须二次百分位）；阈值 HOT=90/COLD=10 直接打点。

**读数（2026-08-12/13）**：换手率 2.48% pct86 | 杠杆率 2.65% pct87 | 广度当日20.6% pct91 | 复合温度 96% | 沪深300 4691。红点296天/绿点295天（2014-）。

## 沪深300视角 (v7, 2026-08-14, 用户要求)

用户要求从沪深300角度重算温度计。数据源验证结果：

**✅ 沪深300换手率**：`index_dailybasic('000300.SH', fields='trade_date,turnover_rate')`，官方口径，2014-04~今全历史（3000行）。绝对水平远低于全市场（当前0.64% vs 2.48%；2015-06顶1.9% vs 3.22%；峰值3.09%@2015-04-20）——大盘股换手天然低。**跨视角只能比各自百分位，不能比绝对值**。缓存 `hs300_turnover.csv`。

**⚠️ 沪深300上涨家数占比**：`index_weight(index_code='399300.SZ')` 成分股历史**只到2024-08**，不够2014起点。替代方案 = **动态市值Top300**：逐日 `daily_basic(trade_date=X)` 按total_mv降序取前300 → join `daily(trade_date=X)` → up=(pct_chg>0)。~0.8s/日，3068天后台循环约25-40分钟，缓存 `breadth_hs300.csv`。动态Top300比固定成分股表更贴近真实指数构成（每天按当天市值排名）。

**杠杆率保持全市场口径**（两融无分指数数据）。脚本 `market_temperature_hs300.py`，输出 `market_temperature_hs300.png`。换视角的意义：2021抱团顶集中在沪深300成分股内，HS300视角的广度/换手率可能捕捉到全市场视角漏掉的顶——两版对比后再定去留。

**21顶诊断**（用户问"为什么感觉没过阈值"）：2021-02-18 换手率pct38.9/杠杆率pct40.5/广度pct46.0 → 温度39.5%。正确不报警——那是机构抱团顶（茅指数），非全面过热。对比15顶99.6%（三维度全极端）、2024-10-08 67.5%（杠杆pct仅0.7，政策脉冲）。当前96%形态最接近2015顶。报温度时须能解释"哪类顶不报警、为什么"。

**数据缓存**：cache_idx_sh/idx_sz/idx_hs300/tr_sh/sh_mv.csv + margin.csv，freshness检查 `date.max() >= today-7d` 则跳过tushare全量拉取（否则网络慢时超300s）。FORCE=True 强制重拉。

**性能教训**：数百个阈值切换点各挂一个带arrowprops的annotate → savefig卡死300s+（被kill两次）。改用纯scatter秒出。多点用scatter，annotate只留给≤5个关键标注。

## 绘图要点（v3, 3面板）— 已被v6取代，留作历史

最终结构（用户确认 2026-08-13）：**全市场换手率 / 两融余额+加速度 / 涨跌家数广度** 三面板。
原独立的"上证换手率"面板被用户删除——Panel 1 已是全市场换手率，上证单市场子集信息冗余
（用户："子图3似乎无存在必要性了"）。**教训：新增归一化/全市场指标后，主动检查旧面板是否变成冗余子集。**
sharex，figsize (16,14)，height_ratios [1.2,1.2,1.3]，每面板画历史 10/50/90% 百分位刻度线 +
末端 annotate（offset points）+ 历史大顶竖线（15顶/21顶/24顶）。广度面板双轴：上涨占比
（左，红绿底色分区50%）vs 上证指数（右轴蓝线），看**背离**（指数新高+占比走低=顶部特征）。
保存 `dpi=150, bbox_inches='tight'`。输出 `market_temperature.png`，脚本 `market_temperature_v2.py`。

### 关键读数（2026-08-13，供后续报告对照）

全市场换手率 2.48%（88%分位）；2015-06-12 顶 3.22%；2024-10-08 历史最大 4.88%；历史中位数 1.47%。
对比：旧绝对成交额口径给 98%分位——归一化后温度读数显著温和，这才是跨周期可比的刻度。

**⚠️ 07顶留白问题**：数据起点2014年但 axvline 画在2007年会把 x 轴撑到2007，
2007-2014之间全是空白。修复：去掉07顶标记 + 显式 `ax.set_xlim(data_start, data_end)`。
所有面板的数据起点必须一致（用户要求统一2014年）。

## 关键读数解读范式

成交额/换手率极热（98%/83% 百分位）但两融加速度趴地板（变化率百分位2%）
= 放量非杠杆驱动，与2015式两融爆量顶部形态不同。报温度时三维度一起读，
单看任一维都会误判。
