
过热拥挤是金融领域经常会讨论和探索的话题可能与未来收益有一定关系。这里尝试通过12 月涨幅、波动率、量比、偏离 120 日均线探索其相关的统计检验方法和原理。1 过热/拥挤指标这里选择经常使用的过热拥挤监测指标梳理期计算过程和分析原理。1.1 ret12ret12表示过去约 12 个月价格的涨幅如果数据长度 ≥ 252 个交易日即过去约 12 个月的涨幅。如果不足 252 天就用序列第一天代替ret12 float(s.iloc[-1] / s.iloc[-252] - 1) if len(s) 252 else float(s.iloc[-1] / s.iloc[0] - 1)ret12含义解释如下- 代表过去一年的动量/涨幅。- 涨幅越大可能是强势趋势也可能是拥挤、炒作、超买。1.2 vol60vol60表示最近 60 日年化波动率取最近 60 个日收益率计算标准差年化vol60 float(r.tail(60).std() * np.sqrt(252))vol60含义解释如下vol60衡量近期价格波动/投机程度。高波动通常代表高关注、高换手、情绪化交易、彩票偏好。低波动异象认为高波动股票未来收益可能偏低在顶部区域高波动也可能是见顶信号。1.3 pmapma表示偏离 120 日均线首先计算截至T的 120 日简单移动平均然后计算当前价相对均线偏离pma float(s.iloc[-1] / s.rolling(120).mean().iloc[-1] - 1)pma含义解释如下pma类似乖离率。pma 0当前价高于 120 日均线短期强势/超买pma越大说明价格离中期成本区越远均值回归压力越大。如果与后续比如2026收益负相关说明顶部偏离越大后续跌得越多。1.4 vol_ratiovol_ratio表示量比近期成交量 / 长期成交量首先计算如果成交量数据 ≥ 250 天即最近 20 日平均成交量 ÷ 最近 250 日平均成交量。vv v[c].loc[:T].dropna() if c in v.columns else pd.Series(dtypefloat) vr float(vv.tail(20).mean() / vv.tail(250).mean()) if len(vv) 250 else float(nan)vol_ratio含义解释如下vol_ratio 1近期放量资金涌入交易拥挤或情绪升温vol_ratio 1近期缩量关注度下降。高位放量可能是主力出货、分歧加大、情绪顶点。需要注意的是分子20日包含在分母250日里有一定重叠不是完全独立的短期/长期比较。2 统计检验方法2.1 Spearman 秩相关Spearman 相关不是直接用数值而是用排序后的秩1衡量两个变量的单调关系2对极端值不敏感适合小样本、非正态、非线性场景3若结果为负表示该指标越高2026 收益排名越靠后即未来表现越差。df[ret12].corr(df[ret26], methodspearman)针对之前提到的指标可以分别检验其与未来收益df[ret26]的关系。- ret12 过去 12 月涨幅- vol60 60 日波动- vol_ratio 量比- pma 偏离 120 均线。2.2 三分位分组三分位分组分析过程如下1按某指标从小到大分成三组低、中、高2看每组的 2026 平均收益3如果“高”组平均收益明显低于“低”组说明该指标越高未来表现越差。q pd.qcut(df[col], 3, labels[低, 中, 高])df.groupby(q)[ret26].agg([mean, count])同样可以使用三分位分组对ret12、vol_ratio、pma、vol60分别进行监测。2.3 综合过热分 overheat综合过热分 overheat计算过程如下1对四个指标分别做 z-score 标准化2然后每只股票取四个 z-score 的平均df[overheat] (df[[ret12, vol_ratio, pma, vol60]].apply(lambda x: (x - x.mean()) / (x.std() 1e-12)).mean(axis1))综合过热分 overheat含义解释如下1综合分越高说明该股票在“涨幅、量比、均线偏离、波动”四个维度上整体越热、越挤、越贵。2等权处理避免量纲不同。3再用三分位看 2026 表现并取最高 8 只和最低 8 只对比。2.4 分析原理这里分析的核心逻辑是顶部过热/拥挤反转。1动量与反转- ret12高过去一年涨幅大。- 在主题炒作中高涨幅可能吸引跟风盘但也意味着获利盘多、估值高、拥挤。- 当买盘衰竭容易出现反转。2波动率异象- vol60高近期波动大投机氛围强。- 高波动股票常被过度定价未来收益可能偏低。3量价拥挤- vol_ratio高近期放量。- 高位放量可能是资金加速入场也可能是主力派发、分歧加剧。- 放量后若不能继续上涨容易形成顶部。4乖离率/超买- pma高价格远离 120 日均线。- 短期涨太快偏离中期成本区均值回归压力大。5综合过热分- 单指标可能噪声大- 多指标共振时顶部特征更可靠。- 如果overheat高组 2026 平均收益显著低说明“顶部拥挤”有横截面解释力。6Spearman 分档目的是分析收益热度分组的关联关系。- Spearman 看单调关系- 分三档看低/中/高组的未来收益差异- 头尾 8 只看极端组对比。- 若结果为负则支持假设前一年底越热、越挤、越贵的股票下一年表现越差。3 代码示例这里进一步给出完整脚本供参考。构造一个顶部过热/拥挤因子用过去涨幅、波动率、量比、均线偏离和综合 z-score检验它们与 2026年收益的关系。若相关为负、高分组表现更差、最过热组弱于最不过热组就说明 2025 年底越拥挤的xx股票2026 年越容易跑输或下跌。如果需要实际测试可能需要提前准备对应的pv测试数据。import numpy as np import pandas as pd from pathlib import Path from mfm.data.loader import load_qlib_h5 ROOT Path(__file__).resolve().parent.parent def _path(p): return str(ROOT / p) H5 _path(data/daily_pv.h5) def main(): p, v load_qlib_h5(H5, start2024-06-01, end2026-12-31) perf pd.read_csv(_path(tmp/ingest2026/aerospace_perf.csv), dtype{code: str}) extra pd.read_csv(_path(tmp/ingest2026/aerospace_2026_extra.csv), dtype{code: str}) ret26 {} for _, r in perf.iterrows(): if pd.notna(r[2026%]): ret26[r[code]] float(r[2026%]) / 100 for _, r in extra.iterrows(): if pd.notna(r[2026_qfq%]): ret26[r[code]] float(r[2026_qfq%]) / 100 codes [c for c in perf[code] if c in p.columns and c in ret26] T p.index.asof(pd.Timestamp(2025-12-31)) nm pd.read_csv(_path(data/cn_data/stock_names.csv), dtype{code: str}) nmap dict(zip(nm[code].apply(lambda x: (SH if x[0] 6 else SZ) x.zfill(6)), nm[name])) rows [] for c in codes: s p[c].loc[:T].dropna() if len(s) 130: continue r s.pct_change() ret12 float(s.iloc[-1] / s.iloc[-252] - 1) if len(s) 252 else float(s.iloc[-1] / s.iloc[0] - 1) vol60 float(r.tail(60).std() * np.sqrt(252)) pma float(s.iloc[-1] / s.rolling(120).mean().iloc[-1] - 1) vv v[c].loc[:T].dropna() if c in v.columns else pd.Series(dtypefloat) vr float(vv.tail(20).mean() / vv.tail(250).mean()) if len(vv) 250 else float(nan) rows.append({code: c, name: nmap.get(c, ?), ret12: ret12, vol60: vol60, vol_ratio: vr, pma: pma, ret26: ret26[c]}) df pd.DataFrame(rows).dropna(subset[vol_ratio, ret26]) print(f样本 {len(df)} 只2025-12-31 顶部截面 → 2026 YTD\n) print( 顶部过热指标 与 2026 跌幅 的 spearman 相关 ) print( ret12(12月涨幅) : %.3f % df[ret12].corr(df[ret26], methodspearman)) print( vol60(60日波动) : %.3f % df[vol60].corr(df[ret26], methodspearman)) print( vol_ratio(量比) : %.3f % df[vol_ratio].corr(df[ret26], methodspearman)) print( pma(偏离120均线) : %.3f % df[pma].corr(df[ret26], methodspearman)) def tertiles(col): q pd.qcut(df[col], 3, labels[低, 中, 高]) return df.groupby(q, observedTrue)[ret26].agg([mean, count]).assign(meanlambda x: (x[mean] * 100).round(1)) for col in [ret12, vol_ratio, pma, vol60]: print(f\n 按 {col} 分三档的 2026 平均涨跌 ) print(tertiles(col).to_string()) df[overheat] (df[[ret12, vol_ratio, pma, vol60]] .apply(lambda x: (x - x.mean()) / (x.std() 1e-12)) .mean(axis1)) print(\n 综合过热分(越高越挤/越贵) 三档 2026 表现 ) print(tertiles(overheat).to_string()) hi df.nlargest(8, overheat) lo df.nsmallest(8, overheat) print(f\n最过热组 2026 均值 {hi[ret26].mean()*100:.1f}% ; 最不过热组 {lo[ret26].mean()*100:.1f}%) print(\n最过热8只:); print(hi[[name, ret12, vol_ratio, pma, ret26]].round(3).to_string(indexFalse)) df.round(4).to_csv(_path(tmp/ingest2026/aerospace_crowding.csv), indexFalse) if __name__ __main__: main()reference---