专题:估值分位计算方法

一、为什么需要这个专题

01-估值指标公式与计算.md 中,我们解决了"PE/PB/PS 当前值是多少"的问题。但"当前 PE = 25 倍"这个数字本身没有意义——它到底是贵还是便宜,必须放到历史区间中比较。

估值分位(Valuation Percentile)就是把当前估值放在历史样本中的"百分位排名",回答"现在的估值,比历史上多少时候更贵"。

本专题解决的核心问题:

  1. 历史样本窗口选 5 年还是 10 年?为什么?
  2. 基准周期怎么选——日频、周频、月频?
  3. 亏损股、极端值如何处理,避免污染分位?
  4. 等权 PE 分位 vs 市值加权 PE 分位 哪个更"诚实"?
  5. 如何用 Python(numpy/pandas)从原始数据计算分位?

本专题是项目主线 /01-路线图/01-路线图 中"估值驱动定投"的量化基础——定投加减码的信号,全部来自分位阈值(如分位 < 20% 加码,> 90% 止盈)。

二、核心概念与公式

2.1 分位数的数学定义

给定一组历史样本 $X = {x_1, x_2, \dots, x_n}$(已排序),当前值 $x$ 的分位 $p$ 定义为:

$$ p = \frac{#{x_i \leq x}}{n} \times 100% $$

即"历史样本中小于等于当前值的比例"。例如 PE 分位 = 15%,表示当前 PE 比历史上 85% 的时间都低。

2.2 分位计算的三种方法

方法计算公式特点
最近邻法(lower)$p = \frac{\text{rank}(x)}{n}$简单,分立值
线性插值(默认)在两相邻样本间线性插值平滑,numpy 默认
正态分布假设$p = \Phi\left(\frac{x-\mu}{\sigma}\right)$假设正态,金融数据常偏态

定投实务中默认用线性插值法(numpy 的 percentile 函数即此实现)。

2.3 时间窗口选择

窗口优点缺点适用场景
5 年反映近期估值中枢,对市场结构变化敏感样本量小,可能整段处于牛/熊市行业结构剧变的科技、新能源
10 年覆盖一个完整牛熊周期,样本充足包含已过时的历史(如 2014 年前的创业板结构)主流宽基(沪深300、中证500)
全历史最完整早期数据质量差,行业结构已变仅作为辅助参考

定投默认口径:10 年滚动窗口 + 全历史辅助参考。10 年覆盖一个完整周期,且数据质量较好。

2.4 基准周期选择

周期样本数(10年)噪音推荐度
日频~2430高(含很多无效波动)仅用于高频策略
周频~520推荐,平衡噪音与样本量
月频~120数据少,分位粒度粗

推荐周频:每周收盘 PE 构成样本,既过滤了日内噪音,又保证足够的样本量。

2.5 等权 vs 市值加权 PE 分位

这是估值分位中最容易被忽视、却影响巨大的一个选择。

市值加权 PE(指数官方口径):

$$ \text{PE}_{\text{市值加权}} = \frac{\sum_i \text{市值}_i}{\sum_i \text{净利润}_i} $$

等权 PE

$$ \text{PE}_{\text{等权}} = \frac{1}{n}\sum_i \text{PE}_i $$

差异本质:市值加权 PE 受大市值成分股主导。例如沪深300中贵州茅台、宁德时代等少数巨头占权重极高,它们的估值变化会"绑架"整个指数的 PE。而等权 PE 给每只成分股相同权重,更反映"平均公司"的估值水平。

实际影响案例

  • 2021 年初,沪深300 市值加权 PE 分位 ~85%,但等权 PE 分位仅 ~60%。原因是茅台、宁德等少数龙头估值极高,拉高了市值加权 PE,而大部分成分股估值仍合理。
  • 这种情况下,市值加权 PE 会给出"高估止盈"信号,但等权 PE 显示"仍有空间"。两者结合判断更稳健。
维度市值加权 PE等权 PE
代表性代表"指数本身"的估值代表"平均公司"的估值
大股权重影响
对龙头泡沫敏感度
适用跟踪指数基金的定投判断市场整体热度

实务建议两者都看。市值加权 PE 用于判断"跟踪指数本身"贵不贵;等权 PE 用于判断"市场整体广度"贵不贵。若两者背离严重,说明市场分化极端。

三、实操方法(含步骤与决策规则)

3.1 异常值处理

问题 1:亏损股 PE 为负

亏损股 PE 为负或无意义。处理方式:

  • 剔除法(推荐):从样本中剔除亏损股,分母净利润合计也相应减少;
  • 截断法:将 PE 为负的设为 NA,不参与统计。

问题 2:极端高 PE(如 > 500 倍)

某只成分股因净利润接近 0,PE 可能高达上千倍,会严重扭曲市值加权 PE。处理方式:

  • 缩尾处理(Winsorize):将 PE > 99 分位的样本截断为 99 分位值;
  • 剔除极端值:直接剔除 PE > 300 倍的样本。

问题 3:成分股变更

指数定期调整成分股,历史 PE 序列需要回溯调整。正规数据源(理杏仁、Wind)会提供"调整后历史 PE",直接使用即可。若自行计算,需注意成分股变更带来的口径跳变。

3.2 分位阈值与定投信号

PE 分位估值状态定投信号操作建议
< 10%极度低估强加码定投金额 × 2.0
10% - 30%低估加码定投金额 × 1.5
30% - 70%正常正常定投定投金额 × 1.0
70% - 90%高估减码定投金额 × 0.5
> 90%极度高估止盈分批止盈(详见 12-定投止盈止损具体策略.md

决策规则

  • 分位每变化 20% 调整一次定投倍数,避免频繁操作;
  • 分位信号以周频确认,避免日频噪音导致的误判;
  • 若等权与市值加权分位背离 > 20%,取较高者作为止盈信号(更保守)。

3.3 Python 实现代码

import numpy as np
import pandas as pd

def calc_percentile(current_value, history_series):
    """
    计算当前值在历史序列中的分位
    :param current_value: 当前 PE/PB 值
    :param history_series: 历史样本序列(pd.Series)
    :return: 分位百分比(0-100)
    """
    history = history_series.dropna().values
    if len(history) < 50:
        return np.nan  # 样本不足
    return np.percentile(history, current_value * 100 / current_value)  # 占位,见下方修正


def calc_percentile_correct(current_value, history_series):
    """
    正确实现:当前值在历史序列中的百分位
    """
    history = history_series.dropna().values
    if len(history) < 50:
        return np.nan
    # 方法1:用 sum 比例
    rank = (history <= current_value).sum() / len(history)
    return rank * 100


def calc_percentile_numpy(current_value, history_series):
    """
    用 numpy.percentile 的反向计算更简洁
    """
    history = history_series.dropna().values
    if len(history) < 50:
        return np.nan
    # scipy.stats.percentileofscore 的等效实现
    return 100.0 * np.sum(history <= current_value) / len(history)


def calc_index_pe_percentile(pe_series, window_years=10, freq='W'):
    """
    计算指数 PE 的历史分位(完整流程)
    :param pe_series: 日频 PE 序列,index 为日期
    :param window_years: 滚动窗口年数
    :param freq: 重采样周期 'W'=周 'M'=月
    :return: 当前分位
    """
    # 1. 重采样到周频(取每周收盘值)
    pe_resampled = pe_series.resample(freq).last()

    # 2. 取最近 window_years 年作为窗口
    cutoff_date = pe_resampled.index.max() - pd.DateOffset(years=window_years)
    window = pe_resampled[pe_resampled.index >= cutoff_date]

    # 3. 异常值处理:剔除负值与极端值
    window = window[(window > 0) & (window < 300)]

    # 4. 计算当前值的分位
    current_pe = pe_resampled.iloc[-1]
    percentile = 100.0 * np.sum(window.values <= current_pe) / len(window)

    return {
        'current_pe': current_pe,
        'percentile': percentile,
        'window_samples': len(window),
        'window_min': window.min(),
        'window_max': window.max(),
        'window_median': window.median()
    }


# 等权 PE 分位计算(多成分股)
def calc_equal_weight_pe_pe_percentile(stock_pe_df, date_col='date',
                                        code_col='code', pe_col='pe_ttm'):
    """
    计算等权 PE 的历史分位
    :param stock_pe_df: 包含 date/code/pe_ttm 的 DataFrame
    :return: 每个日期的等权 PE 序列
    """
    # 1. 按日期分组,计算每日等权 PE
    daily_eq_pe = (
        stock_pe_df.dropna(subset=[pe_col])
        .groupby(date_col)[pe_col]
        .mean()  # 等权 = 算术平均
    )

    # 2. 重采样到周频
    daily_eq_pe = daily_eq_pe.resample('W').last()

    # 3. 计算分位(同上)
    return calc_index_pe_percentile(daily_eq_pe)


# 使用示例
if __name__ == '__main__':
    # 模拟 10 年周频 PE 数据
    np.random.seed(42)
    dates = pd.date_range('2014-01-01', '2024-01-01', freq='W')
    pe_values = np.random.lognormal(mean=3.3, sigma=0.2, size=len(dates))
    pe_series = pd.Series(pe_values, index=dates)

    result = calc_index_pe_percentile(pe_series)
    print(f"当前 PE: {result['current_pe']:.2f}")
    print(f"历史分位: {result['percentile']:.1f}%")
    print(f"窗口样本数: {result['window_samples']}")
    print(f"区间: [{result['window_min']:.2f}, {result['window_max']:.2f}]")
    print(f"中位数: {result['window_median']:.2f}")

3.4 完整定投决策流程

1. 每周五收盘后,获取目标指数 PE (TTM)
   ├─ 来源:理杏仁 / Wind / 中证指数官网
   └─ 口径:市值加权 + 等权(两者均取)

2. 计算当前 PE 的 10 年周频分位
   ├─ 市值加权分位 P1
   └─ 等权分位 P2

3. 分位信号判定
   ├─ 取 P = max(P1, P2)(保守原则)
   ├─ P < 20%:定投金额 × 1.5
   ├─ 20% ≤ P < 80%:正常定投
   ├─ 80% ≤ P < 90%:定投金额 × 0.5
   └─ P ≥ 90%:启动分批止盈

4. 记录与复盘
   └─ 每次操作记入定投日志,含 PE/分位/操作/原因

四、常见误区

误区 1:用日频数据算分位

日频数据噪音大,单日极端值(如 2015 年股灾单日暴跌)会扭曲分位。周频或月频更稳健。

误区 2:窗口选 1 年

1 年样本量不足(约 250 个交易日),且可能整段处于牛市或熊市,分位失真。至少 5 年,推荐 10 年

误区 3:不处理亏损股

亏损股 PE 为负,若直接纳入算术平均,会拉低等权 PE,给出"低估"假象。必须剔除

误区 4:只看市值加权 PE 分位

市值加权 PE 受龙头股主导,可能"指数高估但广度合理"。等权 PE 分位是必要的交叉验证

误区 5:分位低 = 绝对便宜

分位是相对历史而言。若行业进入衰退期(如地产 2021 年后),PE 中枢永久下移,低分位可能是"价值陷阱"。必须结合行业生命周期(见 /03-中间段/02-行业分析/01-行业生命周期分析)。

误区 6:分位信号频繁触发

分位在 30%-70% 区间内小幅波动是正常的,不应每次都调整定投金额。建议以 20% 为阈值间隔,避免过度交易。

五、与项目其他文档的关联

  • 01-估值指标公式与计算.md:本专题的输入(PE/PB 当前值)来自该专题的计算结果,形成"指标 → 分位 → 信号"的完整链路。
  • /01-路线图/01-路线图:定投加减码的核心信号来自分位阈值,路线图中的"估值驱动定投"以本专题为量化基础。
  • 03-指数编制规则详解.md:指数的加权方式决定了"市值加权 PE"的计算口径,需配合理解。
  • 12-定投止盈止损具体策略.md:止盈的核心信号"PE 分位 > 90%"直接使用本专题的分位计算结果。
  • 13-数据源使用手册.md:理杏仁、Wind 等数据源提供"历史 PE 序列",本专题的代码基于此类数据。
  • /03-中间段/02-行业分析/01-行业生命周期分析:分位信号的解读需结合行业生命周期——衰退期的低分位是陷阱,不是机会。
  • /03-中间段/04-经济指标分析/01-宏观经济指标影响:宏观环境变化会影响整体估值中枢,分位需结合宏观背景判断。

小结:估值分位是把"当前 PE"转化为"贵还是便宜"信号的关键工具。10 年周频窗口、等权与市值加权双口径、异常值剔除,是分位计算的三项核心规范。Python 实现提供了从原始数据到定投信号的完整管道。下一专题将进入指数编制规则,理解指数本身是如何"构造"出来的。