35  上市公司净利润增长率的计算

35.1 引言增长率的价值发现功能

净利润增长率的重要性:

净利润增长率是衡量上市公司盈利能力和成长性的核心指标,在价值投资和成长投资中都具有关键作用。

理论背景:企业价值与增长

根据戈登增长模型(Gordon Growth Model),股票价值为:

\[ P_0 = \frac{D_1}{r - g} \]

其中: - \(P_0\): 当前股票价格 - \(D_1\): 下一期预期股息 - \(r\): 必要收益率(股权成本) - \(g\): 股息永续增长率

关键洞见: - 增长率越高,企业价值越大: g越接近r,股价越高 - 增长质量重于数量: 可持续的增长优于短期爆发 - 增长率递减: 随着企业成熟,增长率会逐步下降

净利润增长率的金融意义:

  1. 成长性评估:
    • 识别高增长企业(成长股)
    • 区分周期性增长与趋势性增长
    • 评估增长可持续性
  2. 估值依据:
    • DCF模型的关键输入
    • PEG比率的核心要素
    • 相对估值的基础
  3. 投资决策:
    • 价值投资:低增长+低估值
    • 成长投资:高增长+合理估值
    • GARP策略:合理价格增长
  4. 业绩预警:
    • 增长率放缓可能预示业绩拐点
    • 负增长需警惕企业衰退
    • 波动率反映经营风险

35.2 数据读取与预处理

35.2.1 财务数据获取

补充说明:上市公司财务报告体系

中国上市公司财务报告披露周期:

  1. 季度报告(Q1, Q2, Q3):
    • 每季度结束后1个月内披露
    • 包含简化的资产负债表、利润表
    • 不需要审计
  2. 年度报告(Q4/年报):
    • 会计年度结束后4个月内披露
    • 包含完整的三张报表
    • 必须经过审计
    • 附详细的管理层讨论与分析
  3. 业绩快报与预告:
    • 业绩快报:实际数据但未经审计
    • 业绩预告:预计数据范围
    • 时间早于正式报告

利润表的层次结构:

一、营业收入
减:营业成本
    营业税金及附加
    销售费用
    管理费用
    财务费用
-----------------------------------
二、营业利润(Operating Profit) ← 本章使用
加:营业外收入
减:营业外支出
-----------------------------------
三、利润总额(Total Profit)
减:所得税费用
-----------------------------------
四、净利润(Net Profit) ← 更常用
列表 35.1
# =============================================================================
# 题目: 读取上市公司财务数据
# =============================================================================
# 本代码块演示如何从Excel文件读取上市公司财务数据。上市公司财务数据
# 通常以Excel格式公开披露,包含多个年度的利润表、资产负债表和现金流量表。
# 数据读取是财务分析的第一步,需要正确处理表头、索引和数据类型。

# ==================== 导入必要的库 ====================
import pandas as pd  # 导入pandas库,用于数据处理和分析
import numpy as np  # 导入numpy库,用于数值计算
import matplotlib.pyplot as plt  # 导入matplotlib库,用于可视化

# ==================== 设置中文字体 ====================
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置中文字体为黑体,避免中文乱码
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示为方块的问题

# ==================== 读取Excel文件 ====================
url = 'https://huoran.oss-cn-shenzhen.aliyuncs.com/20230410/xls/1645298865123385344.xls'  # 数据文件URL
df = pd.read_excel(url)  # 使用pandas读取Excel文件,默认读取第一个sheet

# ==================== 输出原始数据预览 ====================
print('原始数据:')  # 打印标题
print(df.head(10))  # 打印前10行数据,快速了解数据结构
print(f'\n数据类型: {type(df)}')  # 确认数据类型为DataFrame
print(f'\n数据形状: {df.shape}')  # 打印数据维度(行数,列数)

代码深度解析:

  1. pd.read_excel()参数:
    • 默认读取第一个sheet
    • header=0: 第一行为列名
    • index_col: 指定索引列
  2. 在线数据读取:
    • 支持HTTP/HTTPS URL
    • 自动处理编码
    • 适合快速原型开发
  3. 数据结构检查:
    • shape: (行数, 列数)
    • head(): 前几行数据预览
    • type(): 确认DataFrame类型

35.2.2 数据提取与转换

补充说明:时间序列的索引处理

时间序列分析的关键步骤:

  1. 索引设置:
    • set_index(): 将某列设为索引
    • 索引必须是唯一的
    • 时间索引便于切片操作
  2. 索引类型:
    • 字符串: ‘2020’, ‘2021’
    • 整数: 2020, 2021
    • Datetime: 2020-01-01(推荐)
  3. 索引排序:
    • sort_index(): 按索引排序
    • 升序或降序
    • 确保时间顺序正确
列表 35.2
# 注:该代码块读取远程平台数据文件,且变量名与下方代码不匹配,渲染时无法正确执行
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库

#读取excel文件
df = pd.read_excel('https://huoran.oss-cn-shenzhen.aliyuncs.com/20230410/xls/1645298865123385344.xls')

#把index设置为Unnamed: 0
df.set_index('Unnamed: 0', inplace=True)

#提取index为“四、营业利润(亿元)”的行
df = df.loc['四、营业利润(亿元)']

#输出数据格式
print(type(df))

#修改index为当前index的前4个字符
df.index = df.index.str[:4]

#将index转换为int类型
df.index = df.index.astype(int)

#将index从小到大排序
df.sort_index(inplace=True)

#计算净利润增长率
result = df.pct_change()

#输出结果
print(result)

代码深度解析:

  1. loc[]索引器:
    • 基于标签的索引
    • df.loc[row_label]: 选择行
    • 支持切片和布尔索引
  2. 字符串处理:
    • .str[:4]: 切片前4个字符
    • .astype(int): 类型转换
    • 链式操作提高效率
  3. 数据清洗:
    • pd.to_numeric(): 转换为数值
    • errors='coerce': 无法转换的设为NaN
    • 便于后续处理缺失值

35.3 计算增长率

35.3.1 增长率计算方法

理论背景:增长率的各种计算方式

  1. 简单增长率: \[ g_t = \frac{X_t - X_{t-1}}{X_{t-1}} \]
    • 适用于:任意相邻两期
    • 优点:计算简单
    • 缺点:受基数影响
  2. 复合年均增长率(CAGR): \[ \text{CAGR} = \left(\frac{X_n}{X_0}\right)^{\frac{1}{n}} - 1 \]
    • 适用于:长期平均增长
    • 优点:平滑短期波动
    • 缺点:忽略中间路径
  3. 对数差分增长率: \[ g_t = \ln(X_t) - \ln(X_{t-1}) \]
    • 适用于:连续复利假设
    • 优点:对称性,可加性
    • 缺点:近似值
列表 35.3
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 计算净利润增长率
# =============================================================================
# 本代码块演示如何使用pandas计算时间序列的增长率。增长率计算是财务分析
# 的核心技能,我们需要掌握不同的计算方法并理解它们的应用场景。

# ==================== 方法1:使用pct_change()计算增长率 ====================
# pct_change()计算百分比变化:(当前期 - 上一期) / 上一期
# 简单增长率公式: R_t = (X_t - X_{t-1}) / X_{t-1}
# periods参数默认为1,表示与前一期相比
growth_rate = profit.pct_change()  # 计算百分比变化,返回增长率序列

# ==================== 输出增长率(小数形式) ====================
print('净利润增长率(小数形式):')  # 打印标题
print(growth_rate)  # 打印增长率序列,第一个值为NaN(因为没有上期数据)

# ==================== 转换为百分比形式 ====================
growth_rate_pct = growth_rate * 100  # 将小数转换为百分比形式

# ==================== 输出增长率(百分比形式) ====================
print('\n净利润增长率(百分比形式):')  # 打印标题
for year, rate in growth_rate_pct.items():  # 遍历每一年的增长率
    if not pd.isna(rate):  # 跳过NaN值(第一年)
        # 使用格式化字符串输出:+号表示正数,-号表示负数
        # :+.2f表示带符号,保留2位小数
        print(f'{year}年: {rate:+.2f}%')  # 格式化输出增长率

# ==================== 方法2:手动计算验证 ====================
# 手动实现增长率计算,验证pct_change()的结果
growth_rate_manual = pd.Series(index=profit.index, dtype=float)  # 创建空Series存储结果
for i in range(1, len(profit)):  # 从第二个数据点开始(索引为1)
    # 手动计算增长率: (本期 - 上期) / 上期
    growth_rate_manual.iloc[i] = (profit.iloc[i] - profit.iloc[i-1]) / profit.iloc[i-1]

# ==================== 输出手动计算结果 ====================
print('\n验证-手动计算:')  # 打印标题
for year, rate in growth_rate_manual.items():  # 遍历手动计算的结果
    if not pd.isna(rate):  # 跳过NaN值
        print(f'{year}年: {rate*100:+.2f}%')  # 格式化输出

# ==================== 验证两种方法一致性 ====================
# np.allclose()比较两个数组是否在容差范围内相等
# 这用于验证pct_change()和手动计算的结果是否一致
print(f'\n两种方法一致: {np.allclose(growth_rate.dropna(), growth_rate_manual.dropna())}')  # 验证结果

代码深度解析:

  1. pct_change()函数:
    • 计算百分比变化
    • 默认periods=1
    • 第一个值为NaN(无上期数据)
  2. 格式化输出:
    • {:+.2f}: 带符号,2位小数
    • +表示正数
    • -表示负数
  3. NaN处理:
    • 第一个值为NaN
    • pd.isna()检查
    • dropna()删除

35.3.2 复合年均增长率(CAGR)

补充说明:长期增长率的度量

CAGR(Compound Annual Growth Rate)假设投资按恒定增长率复利增长,更符合长期投资视角。

\[ \text{CAGR} = \left(\frac{V_{\text{终值}}}{V_{\text{初值}}}\right)^{\frac{1}{n}} - 1 \]

其中: - \(V_{\text{终值}}\): 期末值 - \(V_{\text{初值}}\): 期初值 - \(n\): 年数

列表 35.4
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 计算复合年均增长率(CAGR)
# =============================================================================
# 本代码块演示如何计算复合年均增长率(CAGR)。CAGR是衡量长期投资表现
# 的标准指标,它假设投资按恒定增长率复利增长,消除了短期波动的影响。

# ==================== 删除NaN值 ====================
profit_valid = profit.dropna()  # 删除缺失值,确保计算准确

# ==================== 计算CAGR ====================
if len(profit_valid) >= 2:  # 确保至少有2个数据点
    # 提取期初值和期末值
    start_value = profit_valid.iloc[0]  # 期初值(第一个数据点)
    end_value = profit_valid.iloc[-1]  # 期末值(最后一个数据点)
    n_years = len(profit_valid) - 1  # 年份数=数据点数-1

    # CAGR公式: (期末值 / 期初值)^(1/年份数) - 1
    cagr = (end_value / start_value) ** (1 / n_years) - 1  # 计算复合年均增长率

    # ==================== 输出CAGR计算结果 ====================
    print('复合年均增长率(CAGR):')  # 打印标题
    print(f'期初值: {start_value:.2f}亿元')  # 打印期初值,保留2位小数
    print(f'期末值: {end_value:.2f}亿元')  # 打印期末值,保留2位小数
    print(f'年份数: {n_years}年')  # 打印年份数
    print(f'CAGR: {cagr*100:.2f}%')  # 打印CAGR,转换为百分比

    # ==================== 对比平均增长率 ====================
    # 对比CAGR和简单平均增长率
    avg_growth_rate = growth_rate.mean()  # 计算简单平均增长率
    print(f'\n简单平均增长率: {avg_growth_rate*100:.2f}%')  # 打印简单平均
    print(f'差异: {(cagr - avg_growth_rate)*100:.2f}个百分点')  # 计算两者差异

    # ==================== 解读CAGR ====================
    print('\n解读:')  # 打印标题
    print('- CAGR假设每年按相同增长率复利增长')  # CAGR的含义
    print('- 简单平均忽略复利效应')  # 简单平均的特点
    print('- 通常CAGR < 简单平均(除非增长率恒定)')  # 两者关系

35.4 结果分析与可视化

35.4.1 增长率统计分析

列表 35.5
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 增长率统计分析
# =============================================================================
# 本代码块演示如何对增长率进行统计分析,包括均值、最大值、最小值、标准差
# 等。这些统计量帮助我们了解企业增长的稳定性和波动性。

# ==================== 统计分析 ====================
valid_years = growth_rate.dropna()  # 删除NaN值,得到有效的增长率序列
print(f'有效年份: {len(valid_years)}')  # 打印有效年份数量
print(f'平均增长率: {valid_years.mean()*100:.2f}%')  # 打印平均增长率
print(f'最大增长率: {valid_years.max()*100:.2f}%')  # 打印最大增长率
print(f'最小增长率: {valid_years.min()*100:.2f}%')  # 打印最小增长率
print(f'增长率标准差: {valid_years.std()*100:.2f}%')  # 打印增长率标准差(波动性)

# ==================== 增长率分布 ====================
print('\n增长率分布:')  # 打印标题
# 统计正增长年份和负增长年份
positive_years = (valid_years > 0).sum()  # 计算正增长年份数量
negative_years = (valid_years < 0).sum()  # 计算负增长年份数量
print(f'正增长年份: {positive_years}年 ({positive_years/len(valid_years)*100:.1f}%)')  # 打印正增长占比
print(f'负增长年份: {negative_years}年 ({negative_years/len(valid_years)*100:.1f}%)')  # 打印负增长占比

# ==================== 高增长年份统计 ====================
# 高增长年份(>20%)
high_growth = (valid_years > 0.20).sum()  # 统计增长率大于20%的年份数
print(f'高增长年份(>20%): {high_growth}年')  # 打印高增长年份数

# ==================== 亏损年份统计 ====================
# 亏损年份(<0)
loss_years = (valid_years < 0).sum()  # 统计负增长年份数
print(f'亏损年份: {loss_years}年')  # 打印亏损年份数

35.4.2 增长率可视化

列表 35.6
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 净利润与增长率双轴图
# =============================================================================
# 本代码块演示如何绘制双y轴图,同时展示营业利润和增长率。双y轴图适用于
# 两个变量量纲不同但需要对比分析的情况,例如绝对值(利润)和相对值(增长率)。

# ==================== 创建图形 ====================
fig, ax1 = plt.subplots(figsize=(14, 8))  # 创建图形,尺寸14×8英寸

# ==================== 左轴:营业利润 ====================
color1 = 'steelblue'  # 定义左轴颜色
ax1.set_xlabel('年份', fontsize=12)  # 设置x轴标签
ax1.set_ylabel('营业利润(亿元)', fontsize=12, color=color1)  # 设置左y轴标签和颜色
# 绘制营业利润折线图
line1 = ax1.plot(profit.index, profit.values, color=color1, marker='o',
                 linewidth=2.5, label='营业利润')
ax1.tick_params(axis='y', labelcolor=color1)  # 设置左y轴刻度标签颜色
ax1.grid(True, alpha=0.3, linestyle='--')  # 显示网格,虚线样式

# ==================== 右轴:增长率 ====================
ax2 = ax1.twinx()  # 创建共享x轴的第二个y轴
color2 = 'coral'  # 定义右轴颜色
ax2.set_ylabel('增长率(%)', fontsize=12, color=color2)  # 设置右y轴标签和颜色
# 绘制增长率折线图
line2 = ax2.plot(growth_rate_pct.index, growth_rate_pct.values, color=color2,
                 marker='s', linewidth=2.5, label='增长率')
ax2.tick_params(axis='y', labelcolor=color2)  # 设置右y轴刻度标签颜色
ax2.axhline(y=0, color='gray', linestyle='--', linewidth=1, alpha=0.7)  # 添加0参考线

# ==================== 添加阴影区域标记负增长 ====================
# fill_between()在两个y值之间填充颜色
ax2.fill_between(growth_rate_pct.index, 0, growth_rate_pct.values,
                 where=(growth_rate_pct.values < 0),  # 条件:增长率小于0
                 color='red', alpha=0.2, label='负增长区间')  # 红色半透明填充

# ==================== 标题和图例 ====================
plt.title('上市公司营业利润与增长率趋势', fontsize=16, pad=20)  # 设置主标题
lines = line1 + line2  # 合并两条线的图例句柄
labels = [l.get_label() for l in lines]  # 提取图例标签
ax1.legend(lines, labels, loc='upper left', fontsize=11)  # 显示图例,位置在左上角

# ==================== 标注最高和最低点 ====================
max_year = growth_rate_pct.idxmax()  # 找到最大增长率对应的年份
min_year = growth_rate_pct.idxmin()  # 找到最小增长率对应的年份
max_value = growth_rate_pct[max_year]  # 获取最大增长率值
min_value = growth_rate_pct[min_year]  # 获取最小增长率值

# 标注最高点
ax2.annotate(f'最高: {max_value:.1f}%',  # 注释文本
            xy=(max_year, max_value),  # 箭头指向的坐标
            xytext=(max_year, max_value+5),  # 文本位置
            arrowprops=dict(facecolor='black', shrink=0.05, width=1.5, headwidth=8),  # 箭头样式
            fontsize=10, ha='center')  # 字体大小和对齐方式

# 标注最低点
ax2.annotate(f'最低: {min_value:.1f}%',  # 注释文本
            xy=(min_year, min_value),  # 箭头指向的坐标
            xytext=(min_year, min_value-5),  # 文本位置
            arrowprops=dict(facecolor='black', shrink=0.05, width=1.5, headwidth=8),  # 箭头样式
            fontsize=10, ha='center')  # 字体大小和对齐方式

fig.tight_layout()  # 自动调整布局,避免元素重叠
plt.show()  # 显示图形

代码深度解析:

  1. 双y轴图(twinx):
    • ax1: 主y轴(利润)
    • ax2: 次y轴(增长率)
    • 解决量纲不同的问题
  2. 填充区域(fill_between):
    • where: 条件判断
    • 标记负增长区域
    • 视觉强调亏损年份
  3. 注释(annotate):
    • 标注极值点
    • 箭头指向
    • 文字说明

35.4.3 增长趋势分解

理论背景:增长的趋势-周期分解

时间序列可以分解为: \[ Y_t = T_t + C_t + S_t + \varepsilon_t \]

其中: - \(T_t\): 趋势项(Trend): 长期方向 - \(C_t\): 循环项(Cycle): 多年波动 - \(S_t\): 季节项(Seasonal): 年内波动 - \(\varepsilon_t\): 随机项(Noise): 不可预测成分

列表 35.7
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 增长趋势分解
# =============================================================================
# 本代码块演示如何使用移动平均分解增长率的趋势和波动成分。移动平均是
# 时间序列分析的基础工具,可以平滑短期波动,揭示长期趋势。

# ==================== 计算移动平均(平滑短期波动) ====================
ma_window = 3  # 设置移动平均窗口为3年
# rolling()创建滚动窗口,center=True使窗口居中
growth_ma = growth_rate_pct.rolling(window=ma_window, center=True).mean()  # 计算3年移动平均

# ==================== 可视化趋势分解 ====================
fig, axes = plt.subplots(2, 1, figsize=(14, 10))  # 创建2×1子图布局

# 子图1:原始增长率vs移动平均
axes[0].plot(growth_rate_pct.index, growth_rate_pct.values,
             marker='o', linewidth=2, label='原始增长率', alpha=0.7)  # 绘制原始增长率
axes[0].plot(growth_ma.index, growth_ma.values,
             linewidth=3, label=f'{ma_window}年移动平均', color='red')  # 绘制移动平均线
axes[0].axhline(y=0, color='gray', linestyle='--', linewidth=1)  # 添加0参考线
axes[0].set_title('增长率趋势分解', fontsize=14)  # 设置标题
axes[0].set_ylabel('增长率(%)', fontsize=12)  # 设置y轴标签
axes[0].legend(fontsize=11)  # 显示图例
axes[0].grid(True, alpha=0.3)  # 显示网格

# 子图2:利润趋势
axes[1].plot(profit.index, profit.values, marker='s', linewidth=2.5, color='steelblue')  # 绘制营业利润
axes[1].set_title('营业利润趋势', fontsize=14)  # 设置标题
axes[1].set_xlabel('年份', fontsize=12)  # 设置x轴标签
axes[1].set_ylabel('营业利润(亿元)', fontsize=12)  # 设置y轴标签
axes[1].grid(True, alpha=0.3)  # 显示网格

# ==================== 添加趋势线 ====================
# 使用二次多项式拟合利润趋势
z = np.polyfit(profit.index, profit.values, 2)  # 二次多项式拟合
p = np.poly1d(z)  # 创建多项式函数
axes[1].plot(profit.index, p(profit.index), '--', color='red', linewidth=2,
             label='趋势线(二次)')  # 绘制趋势线
axes[1].legend(fontsize=11)  # 显示图例

plt.tight_layout()  # 调整布局
plt.show()  # 显示图形

# ==================== 增长阶段识别 ====================
print('\n增长阶段分析:')  # 打印标题
if len(growth_ma.dropna()) > 0:  # 确保有有效的移动平均数据
    recent_ma = growth_ma.dropna().iloc[-1]  # 获取最近的移动平均值
    print(f'最近{ma_window}年平均增长率: {recent_ma:.2f}%')  # 打印近期平均增长率

    # ==================== 判断增长阶段 ====================
    if recent_ma > 20:  # 平均增长率大于20%
        stage = '高速增长期'  # 判断为高速增长期
    elif recent_ma > 10:  # 平均增长率大于10%
        stage = '稳定增长期'  # 判断为稳定增长期
    elif recent_ma > 0:  # 平均增长率大于0
        stage = '缓慢增长期'  # 判断为缓慢增长期
    else:  # 平均增长率小于等于0
        stage = '衰退期'  # 判断为衰退期

    print(f'当前阶段: {stage}')  # 打印当前增长阶段

35.5 增长率预测

35.5.1 简单趋势预测

补充说明:外推预测的局限性

基于历史增长率预测未来需要注意:

  1. 均值回归:
    • 极高增长率难以持续
    • 负增长可能反转
    • 长期趋于GDP增速
  2. 基数效应:
    • 基数小,增长率高
    • 基数大,增长率低
    • 需要结合绝对值
  3. 行业周期:
    • 成长期:高增长
    • 成熟期:稳定增长
    • 衰退期:负增长
列表 35.8
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 基于历史数据的增长率预测
# =============================================================================
# 本代码块演示如何基于历史增长率进行简单的外推预测。需要注意的是,
# 这种预测方法假设历史模式会延续,实际应用中需要结合行业分析和
# 宏观环境判断。

# ==================== 方法1:使用最近3年平均增长率预测 ====================
forecast_years = 3  # 预测未来3年
recent_growth = growth_rate_pct.dropna().tail(forecast_years).mean()  # 计算最近3年平均增长率

# ==================== 方法2:使用整体CAGR预测 ====================
last_value = profit.dropna().iloc[-1]  # 获取最后一年的利润值
forecast_values = []  # 创建空列表,存储预测值

print('未来' + str(forecast_years) + '年预测:')  # 打印标题
print(f'\n方法1(最近{forecast_years}年平均):')  # 打印方法说明
print(f'  预测增长率: {recent_growth:.2f}%')  # 打印预测增长率

# ==================== 计算预测值 ====================
for i in range(1, forecast_years + 1):  # 循环预测未来3年
    # 使用复利公式: 预测值 = 期初值 × (1 + 增长率)^年数
    forecast = last_value * (1 + recent_growth / 100) ** i  # 计算第i年的预测值
    forecast_values.append(forecast)  # 将预测值添加到列表
    print(f'  第{i}年: {forecast:.2f}亿元')  # 打印第i年的预测值

# ==================== 可视化预测 ====================
fig, ax = plt.subplots(figsize=(12, 7))  # 创建图形

# 历史数据
ax.plot(profit.index, profit.values, marker='o', linewidth=2.5,
        label='历史数据', color='steelblue')  # 绘制历史数据折线图

# 预测数据
forecast_index = np.arange(profit.index[-1] + 1, profit.index[-1] + forecast_years + 1)  # 生成预测年份索引
ax.plot(forecast_index, forecast_values, marker='s', linewidth=2.5,
        linestyle='--', label='预测数据', color='coral')  # 绘制预测数据折线图(虚线)

# 置信区间(假设±20%误差)
upper_bound = [v * 1.2 for v in forecast_values]  # 计算上界(预测值×1.2)
lower_bound = [v * 0.8 for v in forecast_values]  # 计算下界(预测值×0.8)
ax.fill_between(forecast_index, lower_bound, upper_bound,
                color='coral', alpha=0.2, label='预测区间(±20%)')  # 填充置信区间

ax.set_title('营业利润趋势外推预测', fontsize=14)  # 设置标题
ax.set_xlabel('年份', fontsize=12)  # 设置x轴标签
ax.set_ylabel('营业利润(亿元)', fontsize=12)  # 设置y轴标签
ax.legend(fontsize=11)  # 显示图例
ax.grid(True, alpha=0.3)  # 显示网格

plt.tight_layout()  # 调整布局
plt.show()  # 显示图形

# ==================== 风险提示 ====================
print('\n警告:')  # 打印标题
print('- 预测基于历史数据,未来可能不同')  # 风险提示1
print('- 假设增长率恒定,实际会有波动')  # 风险提示2
print('- 未考虑宏观环境和行业变化')  # 风险提示3
print('- 仅供参考,不构成投资建议')  # 免责声明

35.6 投资应用案例

35.6.1 PEG比率分析

理论背景:PEG比率

PEG比率(Price/Earnings-to-Growth Ratio)由彼得·林奇提出,用于评估成长股的估值合理性:

\[ \text{PEG} = \frac{\text{PE}}{\text{增长率}} = \frac{P/E}{g} \]

解读标准: - PEG < 1: 被低估 - PEG = 1: 合理估值 - PEG > 1: 被高估

优势: - 考虑了增长因素 - 适合比较不同成长性的公司 - 避免”价值陷阱”

局限: - 增长率预测困难 - 对负增长不适用 - 忽略了增长质量

列表 35.9
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: PEG比率计算与分析
# =============================================================================
# 本代码块演示如何计算PEG比率并进行估值判断。PEG比率是成长股估值的核心
# 指标,它将市盈率与增长率结合起来,更全面地评估股票的估值水平。

# ==================== 假设当前股价和每股收益(EPS) ====================
stock_price = 50  # 假设当前股价为50元
current_eps = 2  # 假设每股收益为2元

# ==================== 计算PE比率 ====================
# PE比率 = 股价 / 每股收益
pe_ratio = stock_price / current_eps  # 计算市盈率

# ==================== 使用CAGR作为预期增长率 ====================
expected_growth = cagr * 100  # 将CAGR转换为百分比形式

# ==================== 计算PEG比率 ====================
# PEG = PE比率 / 增长率
# 注意:当增长率为负时,PEG比率无意义
peg_ratio = pe_ratio / expected_growth if expected_growth > 0 else np.nan  # 计算PEG比率

# ==================== 输出PEG分析结果 ====================
print('PEG比率分析:')  # 打印标题
print(f'股价: {stock_price}元')  # 打印股价
print(f'每股收益(EPS): {current_eps}元')  # 打印每股收益
print(f'PE比率: {pe_ratio:.2f}')  # 打印市盈率
print(f'预期增长率(CAGR): {expected_growth:.2f}%')  # 打印预期增长率
# 如果PEG不是NaN,打印PEG值;否则打印"无定义"
print(f'PEG比率: {peg_ratio:.2f}' if not np.isnan(peg_ratio) else 'PEG比率: 无定义(负增长)')

# ==================== 估值判断 ====================
if not np.isnan(peg_ratio):  # 确保PEG比率有效
    print('\n估值判断:')  # 打印标题
    if peg_ratio < 0.8:  # PEG小于0.8
        valuation = '被低估'  # 判断为低估
        advice = '建议买入'  # 建议买入
    elif peg_ratio < 1.2:  # PEG在0.8到1.2之间
        valuation = '合理估值'  # 判断为合理估值
        advice = '建议持有'  # 建议持有
    else:  # PEG大于等于1.2
        valuation = '被高估'  # 判断为高估
        advice = '建议谨慎'  # 建议谨慎

    print(f'估值水平: {valuation}')  # 打印估值水平
    print(f'投资建议: {advice}')  # 打印投资建议

# ==================== 情景分析 ====================
print('\n情景分析(不同增长率下的PEG):')  # 打印标题
# 定义不同的增长率情景
growth_scenarios = [5, 10, 15, 20, 25, expected_growth]  # 增长率情景(%)列表
for g in growth_scenarios:  # 遍历各情景
    peg_scenario = pe_ratio / g  # 计算该情景下的PEG比率
    # 根据PEG值判断估值状态
    status = '低估' if peg_scenario < 1 else '合理' if peg_scenario < 1.2 else '高估'  # 判断估值状态
    print(f'增长率={g:5.1f}%: PEG={peg_scenario:.2f} ({status})')  # 打印情景分析结果

35.6.2 增长质量评估

补充说明:如何评估增长质量

高质量增长的特征: 1. 可持续性: 来源于核心竞争力 2. 盈利性: 增长带来利润率提升 3. 现金流: 利润增长伴随现金流改善 4. 稳定性: 增长率波动较小 5. 内源性: 主要靠自身造血,而非并购

列表 35.10
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行
# =============================================================================
# 题目: 增长质量综合评估
# =============================================================================
# 本代码块演示如何从多个维度评估企业增长质量。增长质量比增长率本身更重要,
# 高质量的增长可持续,低质量的增长难以维持。

# ==================== 收集增长相关指标 ====================
growth_metrics = {
    '平均增长率': growth_rate.mean() * 100,  # 计算平均增长率
    '增长率标准差': growth_rate.std() * 100,  # 计算增长率标准差(波动性)
    '正增长概率': (growth_rate > 0).sum() / len(growth_rate.dropna()) * 100,  # 计算正增长年份占比
    'CAGR': cagr * 100,  # 复合年均增长率
    '最新增长率': growth_rate_pct.iloc[-1]  # 最近的增长率
}

# ==================== 转换为DataFrame ====================
df_metrics = pd.DataFrame(list(growth_metrics.items()),  # 将字典转换为列表
                          columns=['指标', '值(%)'])  # 设置列名

# ==================== 输出增长质量指标 ====================
print('增长质量评估:')  # 打印标题
print(df_metrics.to_string(index=False))  # 打印指标表,不显示索引

# ==================== 计算综合得分 ====================
# 1. 稳定性得分(标准差越小,得分越高)
# 标准差最大按50考虑,标准差越小得分越高
stability_score = 100 - min(growth_metrics['增长率标准差'], 50) / 50 * 100  # 计算稳定性得分

# 2. 持续性得分(正增长概率)
consistency_score = growth_metrics['正增长概率']  # 正增长概率即持续性得分

# 3. 动能得分(最新增长率)
# 最新增长率最大按30考虑,超过30按30算
momentum_score = max(min(growth_metrics['最新增长率'], 30) / 30 * 100, 0)  # 计算动能得分

# ==================== 综合得分(加权平均) ====================
quality_score = (stability_score * 0.3 +  # 稳定性权重30%
                 consistency_score * 0.4 +  # 持续性权重40%
                 momentum_score * 0.3)  # 动能权重30%

# ==================== 输出得分 ====================
print(f'\n增长质量得分:')  # 打印标题
print(f'稳定性得分: {stability_score:.1f}/100')  # 打印稳定性得分
print(f'持续性得分: {consistency_score:.1f}/100')  # 打印持续性得分
print(f'动能得分: {momentum_score:.1f}/100')  # 打印动能得分
print(f'综合得分: {quality_score:.1f}/100')  # 打印综合得分

# ==================== 评级 ====================
if quality_score >= 80:  # 综合得分>=80
    grade = '优秀 (A)'  # 评级为优秀
elif quality_score >= 60:  # 综合得分>=60
    grade = '良好 (B)'  # 评级为良好
elif quality_score >= 40:  # 综合得分>=40
    grade = '一般 (C)'  # 评级为一般
else:  # 综合得分<40
    grade = '较差 (D)'  # 评级为较差

print(f'增长质量评级: {grade}')  # 打印最终评级