8月13日,常士杉的一场私募直播,把两个词重新推到了讨论中心:泡沫、市场温度。标题写得很直接,“中国大爱泡沫中洗澡”这种说法本身就有强烈的情绪色彩。但无论直播间里具体说了什么,真正值得留意的,是“如何判断当前市场是冷是热”这个方法论问题。
我的判断很直接:市场温度必须有数字,而且最好由你自己算出来,而不是听别人报一个结论。“感觉市场很热”“估值太高了”这类表述,本质上停留在情绪层面,无法验证,也无法回溯。估值分位、成交活跃度、换手率、杠杆资金规模,这些数据都是公开的,用 Python 写一个几十行的温度计算脚本,就能把“市场很热”变成“当前指标处于历史 83% 分位”,这才是可讨论、可验证、可复用的表达方式。
这篇文章会沿着一条完整的技术路径展开:先解释市场温度是什么、需要哪些指标;再准备 Python 环境,用模拟数据演示从数据清洗到温度打分的完整代码;最后给出解读方法、常见问题和工程化建议。文中的代码不依赖任何收费数据源,拿到就能跑,逻辑也可以直接迁移到 A 股、港股,或者你自己的投资研究工作流里。
先说明边界:本文是数据分析方法演示,不构成任何投资建议。所有计算只用来演示“如何把市场冷热量化”,不构成买卖依据,也不对任何市场涨跌做预测。读完你会发现,重要的不是那个温度数值,而是建立一套属于自己的、可验证、可回溯的判断框架。
1. 市场温度到底是什么?为什么值得自己算
先给一个能直接落地的定义:市场温度,是把一组反映市场状态的数据指标压缩成一个 0 到 100 的分数,用来衡量当前市场相对于历史区间的冷热位置。这个分数不是绝对值,而是相对位置。比如“市盈率温度 80”,意思是当前市盈率比历史上 80% 的时间都高。
它解决的问题,是投资讨论中最常见的无效沟通。两个人争论“市场到底是不是泡沫”,一个说估值太高,另一个说业绩还在增长,结果说的根本不是同一套数据。市场温度先把争论对象统一掉:你用的是哪几个指标、看的是多长历史、权重怎么配,全部摊开检查。讨论就从“我觉得”变成了“我们看的是同一张表,只是结论不同”。
为什么单看指数涨跌不够?因为指数点位本身没有标尺。上证指数 3000 点和 6000 点相差一倍,但如果上市公司盈利总额也增长了一倍,市场估值其实并没有翻倍。这也是为什么做温度分析时,第一优先是估值类指标,成交额、换手率、杠杆资金作为辅助验证。
真正适合自己算市场温度的人,并不是想预测明天的 K 线的人,而是需要把决策流程固定下来的研究者、写量化策略的开发者、负责资产配置的投研人员。如果你在做定投计划,温度可以帮助你在偏冷区域多积累份额;如果你在管理一个自选组合,温度可以帮助你提前为波动做准备。它的价值不是预测涨跌,而是让你在市场情绪极端时,有一个可以依赖的坐标系。
2. 核心指标与口径:市场温度用到哪些数据
温度不是一个单一指标,而是由多个维度组成的系统。下面按照“估值、交易活跃度、资金面”三个层次来选指标。
2.1 估值类指标
市盈率(PE)是最常见的温度指标。它等于总市值除以净利润,可以通俗地理解成“按当前盈利水平,回本需要多少年”。对指数而言,PE 温度反映的是当前估值在历史上的位置。
市净率(PB)适合与 PE 配合使用。当企业盈利波动大时,PE 会失真,比如周期股在盈利低谷时 PE 反而偏高;PB 衡量的是股价相对净资产的位置,能提供另一个独立视角。
这里有一个容易踩坑的地方:A 股历史估值的中位数水平和美股不同,不能简单套用“PE 小于 15 就是便宜”。更麻烦的是,不同指数覆盖的行业结构差异很大,创业板和银行指数的 PE 根本没有可比性,必须分开计算、分开看。
2.2 交易活跃度指标
成交额代表资金参与的规模,换手率代表筹码交换的速度。市场温度升高时,通常伴随成交额放大和换手率抬升,这是情绪最直接的体现。
需要注意的是,成交额绝对值会随市场扩容而增长。十年前的单日成交额和今天相比,本身就不在同一基准线上,直接比较会产生明显噪音。更稳妥的方式是用换手率,或者把成交额除以市场总市值来消除规模影响。
2.3 资金类指标
融资余额代表杠杆资金的参与程度。A 股市场的融资余额处于高位时,往往意味着市场情绪乐观,但也意味着潜在的去杠杆风险。港股方面,可以关注恒生指数的估值分位、主板成交额等公开数据。资金类指标最大的价值,是提前暴露出市场中的“脆弱性”积累程度。
2.4 指标汇总与窗口选择
综合来看,一套相对完整的温度系统可以包含五类指标:指数 PE、指数 PB、换手率、成交额/市值比、融资余额在成交额中的占比。下表给出推荐字段和说明。
| 指标 | 含义 | 数据来源类型 | 温度方向 |
|---|---|---|---|
| PE 分位 | 估值绝对水平 | 指数官网 / 数据接口 | 越高越热 |
| PB 分位 | 净资产估值水平 | 指数官网 / 数据接口 | 越高越热 |
| 换手率 | 交易活跃度 | 交易所 / 行情库 | 越高越热 |
| 成交额 / 总市值 | 资金参与浓度 | 行情库计算 | 越高越热 |
| 融资余额 / 成交额 | 杠杆资金活跃度 | 交易所 / 公开数据 | 越高越热 |
窗口选择也有讲究。看 3 年窗口,代表观察的是中短期情绪;看 10 年窗口,代表观察的是长期估值中枢。窗口越短,温度越容易被极端行情推向两头;窗口越长,新股上市和指数编制规则变化带来的扰动越大。实际项目中,建议至少同时计算 3 年与 5 年两个窗口,不要只给一个数字。
3. 环境准备:Python 与依赖安装
本文代码只需要标准的数据分析环境,没有复杂框架依赖。
3.1 运行环境
- 操作系统:Windows / macOS / Linux 均可
- Python 版本:3.8 及以上
- 包管理:pip 或 conda
3.2 安装依赖
pip install pandas numpy matplotlibpandas 用于数据处理,numpy 用于数值计算,matplotlib 不是必须,但如果想画温度曲线,建议一并安装。
3.3 数据来源说明
真实的 A 股估值数据可以从指数官网、交易所网站,或者 Tushare Pro、AkShare 这类社区数据接口获取。需要提醒的是,这些接口的字段名和调用方式会频繁调整,本文示例代码不绑定具体接口,也不会给出一个“今天能用、下个月就过期”的 API 拼装方案。
为什么这里选择用模拟数据而不是直接调接口?因为接口请求需要注册权限,数据结构随时可能调整,如果代码直接依赖具体接口,很可能过几个月就跑不通。先把温度计算的算法彻底讲清楚,数据获取部分可以后续单独扩展。
4. 数据准备与清洗:从原始数据到指标序列
不管是来自付费接口还是官方 Excel,最终输入都应该整理成一个统一的 DataFrame,至少包含:日期、PE、PB、换手率、成交额、融资余额。下面用一段代码生成模拟数据,模拟一个“估值中枢缓慢抬升、成交逐步活跃”的市场状态。
import pandas as pd import numpy as np np.random.seed(42) dates = pd.bdate_range("2022-01-03", periods=500) # 模拟一条先冷后热的行情 t = np.linspace(0, 1, len(dates)) pe = 18 + t * 14 + np.random.normal(0, 0.9, len(dates)) pb = 1.5 + t * 1.2 + np.random.normal(0, 0.08, len(dates)) turnover = 0.8 + t * 1.8 + np.random.normal(0, 0.12, len(dates)) amount = 700 + t * 900 + np.random.normal(0, 60, len(dates)) margin = 15000 + t * 5000 + np.random.normal(0, 400, len(dates)) df = pd.DataFrame({ "date": dates, "pe": pe, "pb": pb, "turnover": turnover, "amount": amount, "margin": margin, }) print(df.head()) print(df.tail())生成的数据量是 500 个交易日,差不多两年。PE 从约 18 逐步抬升到 32 附近,PB 从 1.5 升到 2.7 附近,成交额和融资余额同步放大。这种形态模拟的就是一个从“偏冷”走向“偏热”的市场过程。
真实数据清洗时,还需要关注三类问题。第一,停牌导致的缺失值,可以用前值填充,或者直接丢弃;第二,除权除息后,价格和估值数据是否做了复权处理,不同数据源的复权方式不一样,这会影响分位数计算;第三,数据源里偶尔会出现异常大单或者错误字段,需要做基本的范围校验。演示数据已避开这些问题,换成真实数据时,清洗这一步不能跳过。
5. 温度计算:百分位排名与综合打分
温度计算的核心思想是百分位排名:把当前值放进历史序列中,看它压过了多少历史数据。如果当前 PE 比过去 250 个交易日里 80% 的 PE 都高,那 PE 温度就是 80 分。
5.1 单指标温度函数
def indicator_temperature(series, window=None): valid = series.dropna() if window is not None: valid = valid.iloc[-window:] current = series.iloc[-1] rank = (valid < current).mean() * 100 return round(rank, 2)这里有一个细节:采用<而不是<=,避免当前值在数据密集区域时温度反复跳变。window 参数用于控制历史窗口,比如 250 表示最近一年,500 表示最近两年。
5.2 综合温度
单指标温度容易误判,因为单一指标可能受到行业结构、短期政策等因素干扰。更稳的办法是把多个指标加权成综合温度。权重怎么定?没有绝对正确答案,但有一个原则可以参考:估值类权重合计不低于一半,因为市场温度的基础是估值;交易活跃度和杠杆资金属于辅助验证,权重建议各占 20% 左右。
def market_temperature(df, window=250, weights=None): if weights is None: weights = {"pe": 0.3, "pb": 0.2, "turnover": 0.2, "amount": 0.15, "margin": 0.15} temps = {} for col, w in weights.items(): temps[col] = indicator_temperature(df[col], window) total = sum(temps[k] * weights[k] for k in weights) temps["total"] = round(total, 2) return temps weights = {"pe": 0.30, "pb": 0.20, "turnover": 0.20, "amount": 0.15, "margin": 0.15} result = market_temperature(df, window=250, weights=weights) print(result)这段代码把五个单指标温度算出来,再按权重加总。实际使用中,权重不要频繁调整。一旦你觉得“这个权重不顺眼”就改一下,温度序列就会失去跨时期可比性。
5.3 温度区间映射
拿到综合温度后,还需要一个便于交流和执行的口径。下面是一套常用的区间映射:
def temperature_level(score): if score < 20: return "冰点区" elif score < 40: return "偏冷区" elif score < 60: return "中性区" elif score < 80: return "偏热区" else: return "过热/泡沫提示区"这个映射本身也是一种判断口径,可以根据自己的风险偏好调整,但建议固定下来,不要因为行情变化而临时修改。否则温度系统就成了一个可以随时“解释”的工具,失去了客观性。
6. 运行结果与解读:一张表看清市场冷热
把前面的函数合并,执行完整流程:
last_date = df["date"].iloc[-1].date() result = market_temperature(df, window=250, weights=weights) result["level"] = temperature_level(result["total"]) print(f"观察日期: {last_date}") for k, v in result.items(): print(f"{k}: {v}")预期输出类似下面这样(模拟数据,固定随机种子后结果一致):
观察日期: 2023-12-29 pe: 89.20 pb: 85.40 turnover: 84.00 amount: 83.60 margin: 88.00 total: 86.23 level: 过热/泡沫提示区这段输出说明,在两年模拟数据里,最新的估值、换手和杠杆指标都处于历史较高位置,综合温度超过 80,系统给出的提示是“泡沫提示区”。
如何解读这个结果?温度高不意味着第二天就会跌,温度低也不意味着马上会涨。它真正的作用是提示概率环境:在偏热区间,历史上继续向上的空间和时间通常更有限,波动率也会上升;在偏冷区间,长期投资的安全边际相对更高。
实际生产环境中,建议把每天的温度记录成历史序列,再回看“温度 80 分之后半年市场发生了什么”“温度 20 分之后半年又发生了什么”。这种回溯,比单看一个当天数值更能建立直觉,也是检验温度系统是否有价值的可靠方法。
如果遇到 A 股与港股温度不对称的情况,比如 A 股综合温度 70 而港股只有 30,不能直接得出“港股更便宜所以买入”的结论。两个市场的指数构成、估值中枢和参与者结构都不同,温度只能用于自身历史比较,不能跨市场直接比较绝对值。
7. 常见问题与排查思路
用 Python 做市场温度时,问题主要集中在数据源、窗口设置、权重和结果解读四类。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 温度常年接近 100 或 0 | 数据没清洗,存在极端离群值 | 查看指标的 min/max 和分布图 | 去除异常值,或用分位数截断 |
| 不同数据源计算出的 PE 差异明显 | 指数口径、复权方式不同 | 对照官方披露的 PE 数值 | 固定使用一个数据源,不混用 |
| 温度跳变剧烈 | 窗口过短,或当前值处于密集区 | 画出温度曲线检查波动 | 增加窗口长度,或做平滑处理 |
| 综合温度与真实感受不符 | 权重设置不合理,指标共线性强 | 输出每个单指标温度对比 | 调整权重并记录决策理由 |
| 真实接口请求失败 | 接口权限、字段名或频率限制 | 查看返回错误信息 | 按官方文档更新字段,或接入备用数据源 |
| 代码运行报错 | pandas 版本差异或 API 变更 | 查看完整报错堆栈 | 固定依赖版本,写入 requirements.txt |
这里最容易被忽略的是“记录假设”。温度系统不是一次性脚本,而是长期使用的决策工具。如果今天用 250 日窗口,明天改成 60 日,后天又换一个数据源,温度就没有比较意义。涉及窗口、权重、数据源的所有选择,都应写进配置,固定下来。
8. 最佳实践与工程化建议
一个看起来再简单的数据分析系统,放到真实项目里长期运行,都会遇到数据源不稳定、口径变化、结果难以复现的问题。市场温度也一样,把它当成一个正经的工程系统来维护,才能真正发挥作用。
第一,把计算函数和配置分离。温度计算逻辑应该是纯函数,输入 DataFrame,输出温度结果;窗口、权重、数据源都放在配置文件里,不要散落在代码中。这样以后调整参数、做回归测试,才能知道每次改动影响的是什么。
第二,结果要留痕。每次计算后,把日期、温度、各个分项、参数版本保存下来。即使之后算法改进了,也能对比新旧结果。推荐用 CSV 或轻量数据库追加记录,这样回溯“三个月前为什么显示偏热”就有据可查。
第三,警惕指标之间的共线性。PE、PB、换手率、融资余额在牛熊周期里往往是同涨同跌的,权重加总后可能放大同一个情绪因子。更好的做法是把相关性高的指标放在同一组,先看组内温度,再看综合温度,避免被“同一个因素重复计分”。
第四,不要机械执行温度信号。温度从 80 回落到 70,不代表应该立刻卖出;温度从 20 升到 30,也不代表应该立刻买入。温度应该结合估值趋势、政策环境和自身资金期限来使用。它是一个风控参考坐标,不是自动交易信号。
再说一次风险和合规。本文所有内容只是数据分析方法演示,不构成投资建议。普通人做市场温度分析,一定要基于公开、合法获取的数据源,不要使用非公开数据,更不能用温度概念宣传“稳赚”“确定性收益”。把温度当作观察市场的工具,而不是承诺收益的指标,才能长期稳定地使用下去。
9. 写在最后:带走方法,而不是结论
回到最初的话题。8 月 13 日那场直播,直播间里具体说了什么,可能很快就会被遗忘,但“市场温度”这个问题不会过时。与其记住某个人的结论,不如带走一套方法:用公开数据、用 Python、用自己的逻辑给市场测温。
代码已经给出,窗口和权重需要按自己的研究口径调整;模拟数据也已经给出,下一步就是替换成真实数据。建议先跑通文中的最小示例,再逐步接入 A 股指数、港股指数和行业指数。过程中遇到问题,优先检查数据和清洗逻辑,其次再怀疑算法本身。
市场永远会有泡沫的争论,也永远会有“这次不一样”的声音。温度计不会告诉你泡沫什么时候破裂,但它能告诉你,当前处在历史区间的什么位置。这套方法,值得建立一个属于自己的版本。