多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

估值模型实战:从DCF到相对估值,参数假设与数据质量决定准确度

估值模型实战:从DCF到相对估值,参数假设与数据质量决定准确度 简介估值模型是企业投资分析、财务尽调和投研面试中的核心工具其本质并非套用复杂公式而是基于对现金流、折现率和增长率的合理假设。常见的收益基础法如DCF通过将未来自由现金流折现来评估内在价值而相对估值法则以可比公司倍数提供市场锚点。理解三张报表的勾稽关系、准确计算WACC、识别异常数据并借助敏感性分析定位关键驱动因子是提升估值可靠性的关键。本文结合Python代码系统讲解资产基础法、收益基础法、市场法的适用场景演示两阶段DCF建模、永续增长终值计算及二维敏感性矩阵并强调数据质量校验与交叉验证方法帮助读者从单点估值走向区间决策形成一套可落地的财务建模与分析框架。1. 为什么说估值模型的价值在于假设而非公式拿到《估值与财务模型中金公司.pdf》之前我以为它只是券商报告汇编。翻完发现它是一份投行估值方法的骨架先讲估值目的再铺开资产基础法、收益基础法和市场法然后接上 DCF、相对估值和资产定价模型最后反复强调数据质量。这个顺序与多数实践者的习惯相反——大多数人急着套公式结果模型越复杂估值越离谱。真正的问题从来不是公式而是公式背后的参数假设以及支撑假设的数据。这份材料适合三类人做投资分析与财务尽调、准备投研面试、以及会用 Excel 但缺少边界意识的开发和数据从业者。先给一个反直觉结论同一个标的用同一个 DCF 模型不同分析师的结果可能相差两倍差异几乎全部来自终值增长率、折现率和自由现金流假设。因此与其纠结选哪种方法不如花时间验证假设。2. 三大估值方法资产基础法、收益基础法与市场法的适用范围2.1 资产基础法从资产负债表出发的重置逻辑资产基础法的核心是企业价值等于调整后的总资产减去总负债。它不是简单地把资产负债表上的数字抄过来而是要逐项做公允价值调整。中金公司的PDF里把这种方法排在最前面是因为它最容易被理解也最容易被误用——许多从业者以为净资产等于价值但账面价值和公允价值之间的差异往往巨大。适用场景上资产基础法比较适合重资产企业如地产、制造、金融控股平台、控股收购后准备拆分出售的场景以及企业陷入经营困境时做的清算估值。它的缺陷也很明显完全忽略了企业的协同效应、品牌、客户关系和未来增长潜力。举个例子一家做工业软件的公司账面固定资产可能不到 2000 万但客户关系和研发团队内化的价值可能值 20 亿资产基础法对这种轻资产公司毫无意义。下面这段是我常用的调整逻辑实现用最简单的 Python 把一个账面净资产转为调整后净资产def asset_based_value(book_assets, book_liabilities, adjustments): 资产基础法估值调整后净资产 book_assets: 账面总资产 book_liabilities: 账面总负债 adjustments: 逐项列出的公允价值差额例如: [(应收账款坏账, -5000), (存货跌价, -12000), (固定资产重估, 30000)] total_adjustment sum(amount for _, amount in adjustments) adjusted_assets book_assets total_adjustment return adjusted_assets - book_liabilities # 示例账面总资产5亿总负债2.8亿三项调整 result asset_based_value( book_assets500_000_000, book_liabilities280_000_000, adjustments[(应收账款减值, -2_000_000), (存货减值, -5_000_000), (土地增值, 20_000_000)] ) print(f调整后净资产: {result/1e8:.2f} 亿元)这里的关键参数是 adjustments 列表。实际尽调中应收账款要看账龄和回款情况存货要看可变现净值固定资产要看重置成本而非账面历史成本。我一般会要求团队把每一项调整都留底稿因为后续做财务模型时这些调整会直接影响折旧摊销和营运资本进而影响 DCF 中的自由现金流。2.2 收益基础法把未来收益折现到当下收益基础法是一大类方法的总称常见的是股利折现模型DDM和自由现金流折现模型DCF。它们背后的思想是一致的资产的价值等于未来所有预期收益的现值。中金公司的PDF里把 DCF 单独列为一个财务模型但本质上 DCF 的数学基础就是收益基础法。最简化的形式是永续增长模型价值 第1期自由现金流 /折现率 - 永续增长率。这个公式看起来简单但使用有严格前提永续增长率 g 必须小于折现率 r且 r - g 本身不能过小否则估值会趋近无穷大。在实战中我通常要求 r - g 至少在 2 个百分点以上否则结果对假设极度敏感。用代码实现这个一步估值的函数很短但参数敏感性才是重点def growing_perpetuity(fcf1, discount_rate, growth_rate): 永续增长模型单阶段估值 fcf1: 下一期的自由现金流 discount_rate: 折现率WACC 或要求的回报率 growth_rate: 永续增长率 if discount_rate growth_rate: raise ValueError(折现率必须大于永续增长率) return fcf1 / (discount_rate - growth_rate) # 示例下一期FCF1.2亿, WACC8%, 永续增长3% val growing_perpetuity(120_000_000, 0.08, 0.03) print(f永续价值: {val/1e8:.2f} 亿元)在调用这个函数时discount_rate 和 growth_rate 的单位要保持一致。很多人直接把一个百分之八、一个百分之三放进去而忽略小数错误这在 Excel 里相当常见。此外这里的 fcf1 是下一期现金流而不是当期现金流——如果你只有当期自由现金流要先乘上1预期增长率。2.3 市场法寻找可比公司的相对锚市场法通过同行业可比公司的交易数据来推断目标公司价值。最常见的倍数包括市盈率P/E、市净率P/B、企业价值/EBITDAEV/EBITDA和市销率P/S。中金公司的材料里强调同行业或同类型这个同类型往往比同行业更重要——同样是软件公司SaaS 订阅模式和项目制外包的估值逻辑完全不同。下表是三种方法的快速对比也方便我在建模时做第一步筛选方法核心变量适用场景主要缺陷资产基础法调整后净资产重资产、清算、控股并购不考虑成长和协同收益基础法FCF、折现率、增长率现金流稳定、可持续经营对假设过度敏感市场法可比公司倍数IPO定价、二级市场参考可比性差会失真市场法在实操中的关键不是找十个同行业公司然后平均而是要排除那些增长、毛利率、资本结构和盈利质量差异过大的样本。我会先做一个初步筛选用四分位数去截断异常值再取中位数而不是平均数。以下是用 pandas 实现的一个简单筛选逻辑适合手头有同行业财务指标的场景import pandas as pd # 假设拿到一批可比公司的P/E数据 peer_pe pd.Series([15.2, 18.7, 12.6, 45.3, 17.1, 9.2, 16.4], index[A, B, C, D, E, F, G]) q1, q3 peer_pe.quantile([0.25, 0.75]) iqr q3 - q1 valid_pe peer_pe[(peer_pe q1 - 1.5 * iqr) (peer_pe q3 1.5 * iqr)] print(中位数 P/E:, valid_pe.median()) print(保留样本:, valid_pe.index.tolist())这里用了 1.5 倍 IQR 的规则来剔除极端值。作用在公司 D 那 45 倍数据上时它会被自动排除。需要留意的是IQR 规则只是统计上的初步过滤不能替代基本面判断。如果一家公司与目标公司极度相似即使它的倍数显得很高也不应该被随手删掉——你可以单独做一项标注而不是简单剔除。3. DCF与相对估值模型实战从现金流预测到终值计算3.1 构建预测期五年现金流怎么给DCF 模型的第一步是把公司的自由现金流拆成两段显性预测期通常是 5 年和终值期。显性预测期中你要分别预测收入、成本、营运资本、资本开支和税收最终得到每一年的自由现金流。中金公司的 PDF 里反复强调数据质量在这个环节表现得最明显——预测所用的基期数据如果来自注水报表后面所有折现都是浪费。我一般的做法是先从过去三年财报中提取自由现金流再基于增长率假设外推。下面是一个用字典模拟五期自由现金流的简单结构def project_fcf(base_fcf, growth_rates): 按给定增长率序列预测未来五年自由现金流 fcf_series [] current base_fcf for g in growth_rates: current current * (1 g) fcf_series.append(current) return fcf_series # 假设基期FCF0.8亿未来五年增长率分别为12%、10%、9%、8%、7% fcf_list project_fcf(80_000_000, [0.12, 0.10, 0.09, 0.08, 0.07]) print([round(x/1e8, 2) for x in fcf_list])这段代码的核心参数是 growth_rates。它必须是一组逐年递减或至少能自洽的数字。如果出现 12%、-30%、20% 这样的锯齿形先回去查业务逻辑而不是让它通过模型。增长率序列的来源通常是公司指引、行业增速和市占率变化投行里常见做法是用自上而下法先看行业空间再假设公司份额。3.2 WACC折现率不是拍脑袋折现率在 DCF 里通常是 WACC加权平均资本成本只有股权投资人视角才会用权益成本 Re 做股权自由现金流FCFE折现。WACC 的公式是WACC E/(ED) × Re D/(ED) × Rd × (1 - 税率)其中 Re 用资本资产定价模型CAPM计算Re Rf β ×Rm - Rf。这里的无风险利率 Rf 通常取十年期国债收益率风险溢价 Rm - Rf 在中国市场常见取 6%8%β 取可比公司或回归计算的中位数。下面是一个可复用的 WACC 计算函数def wacc(equity, debt, risk_free, beta, market_premium, debt_cost, tax_rate): 计算加权平均资本成本 equity: 股权市值 debt: 有息负债市值 risk_free: 无风险利率如十年期国债 beta: 系统性风险系数 market_premium: 市场风险溢价 debt_cost: 税前债务成本 tax_rate: 有效税率 total equity debt re risk_free beta * market_premium rd_after_tax debt_cost * (1 - tax_rate) wacc_value equity / total * re debt / total * rd_after_tax return wacc_value # 参数为股权市值80亿债务20亿无风险利率2.8%beta1.1风险溢价6.5%债务成本4%税率25% w wacc(8_000_000_000, 2_000_000_000, 0.028, 1.1, 0.065, 0.04, 0.25) print(fWACC {w*100:.2f}%)这里要特别注意 equity 应该是市值而不是账面净资产debt 应该使用有息负债无息负债如应付账款不进入权重的分母。税率方面尽量用有效税率而不是法定税率因为递延税收和亏损结转会让实际税率偏离 25% 或 15% 的法定值。CAPM 中 beta 的预测周期至少要用 3 年的周度或月度数据否则波动太大。3.3 完整DCF估值模型两阶段法的代码骨架把自由现金流预测和 WACC 合在一起就是一个标准的五期 DCF 加终值的实现。终值我通常用永续增长法而不是退出倍数法因为永续增长直接对应收益基础法的逻辑中金公司的材料里也提到未来收益折现是最核心的财务模型。def dcf_valuation(fcf_series, wacc, terminal_growth): 两阶段DCF估值 fcf_series: 显性预测期的自由现金流列表 wacc: 加权平均资本成本 terminal_growth: 永续增长率 if wacc terminal_growth: raise ValueError(WACC 必须大于永续增长率) present_value 0.0 n len(fcf_series) for i, fcf in enumerate(fcf_series, start1): present_value fcf / (1 wacc) ** i last_fcf fcf_series[-1] terminal_value last_fcf * (1 terminal_growth) / (wacc - terminal_growth) pv_terminal terminal_value / (1 wacc) ** n return present_value pv_terminal, present_value, pv_terminal total_ev, pv_cashflow, pv_tv dcf_valuation(fcf_list, w, 0.03) print(f企业价值: {total_ev/1e8:.2f} 亿元) print(f显性期PV占比: {pv_cashflow/total_ev*100:.1f}%)逻辑说明整个函数先对显性期每一年 FCF 做折现再用最后一期 FCF 计算终值并同样折现到现值。返回的三个值里pv_terminal 在成熟企业里通常占到总企业价值的 60% 以上这就是为什么永续增长率哪怕只变动 0.5 个百分点估值也会大幅波动。如果 pv_cashflow 占比很高说明公司增长主要靠近年的现金流兑现这种公司估值相对可靠。参数说明fcf_series 的单位是货币单位wacc 和 terminal_growth 都用小数表示。常见错误是增长率用名义值、折现率用实际值或者反过来导致估值系统性偏高或偏低。此外显性预测期只有 5 年但终端增长率和资本开支的匹配度要考虑清楚如果永续期资本开支不足以维持增长那么终端 FCF 可能会被高估。3.4 相对估值模型P/E与EV/EBITDA的取数差异相对估值模型不需要预测未来现金流但需要找到合适倍数。P/E 受资本结构影响很大而 EV/EBITDA 可以跨资本结构比较因为 EBITDA 是付息前税前的利润近似。下面这张表是我在模型里经常用来做可比公司筛选的标准指标分子含义分母含义排除干扰注意问题P/E股价×股数归母净利润考虑税后利润一次性损益、非经常项目EV/EBITDA市值净债务EBITDA资本结构与折旧政策营运资本变化P/B市值净资产银行、保险适用杠杆差异大时失效在选取可比公司时我会先排除那些近期有重大并购、会计政策随意变更、或者被 ST 的公司。下面这段代码用来计算一组可比公司 EV/EBITDA 的中位数peers pd.DataFrame({ company: [公司A, 公司B, 公司C, 公司D], ev: [150, 220, 90, 300], # 单位亿元 ebitda: [12, 18, 8, 25] # 单位亿元 }) peers[ev_ebitda] peers[ev] / peers[ebitda] median_ev_ebitda peers[ev_ebitda].median() print(f可比公司 EV/EBITDA 中位数: {median_ev_ebitda:.2f})这里的 ev 是市值加净债务net debt 通常是短期借款加长期借款减去现金及等价物。ebitda 要从利润表里找到营业利润加回折旧摊销并剔除投资收益等非经营性项目。中位数的价值在于平滑极端值但如果样本只有三四个公司中位数也不可靠这时我会手动检查每个公司的增长和盈利水平是否和目标公司有显著差异。4. 财务数据质量与估算陷阱三张报表的拆解与校验4.1 从三张报表到自由现金流勾稽关系的实用拆解自由现金流不是财务报表上的一个现成科目而是通过利润表、资产负债和现金流量表加工出来的。中金公司的PDF里把数据质量单独列为一个重点我认为原因在于模型的数学很简单但错误地取数会让结果完全失真的概率远大于模型本身。公司自由现金流FCFF最常见的一种手动算法是从 EBIT 出发FCFF EBIT ×1 - 税率 折旧与摊销 - 资本开支 - 营运资本增加另一种算法是从经营现金流出发FCFF 经营现金流 税后利息费用 - 资本开支我建议手工作坊至少同时算这两条路径如果差值超过 5%就去核对报表附注。下面是一个直接用 Python 算两条路径差的函数def fcff_cross_check(ebit, tax_rate, depreciation, capex, working_capital_change, ocf, interest): 交叉验证两种口径的FCFF fcff_from_ebit ebit * (1 - tax_rate) depreciation - capex - working_capital_change fcff_from_cash_flow ocf interest * (1 - tax_rate) - capex return fcff_from_ebit, fcff_from_cash_flow, fcff_from_ebit - fcff_from_cash_flow a, b, diff fcff_cross_check( ebit2_000_000, tax_rate0.25, depreciation500_000, capex700_000, working_capital_change-80_000, ocf1_500_000, interest120_000 ) print(fEBIT口径FCFF: {a/1e4:.2f}万元) print(f现金流口径FCFF: {b/1e4:.2f}万元) print(f差异: {diff/1e4:.2f}万元)上面代码里working_capital_change 是应收账款增加存货增加-应付账款增加符号方向很容易错。如果营运资本增加为正说明资金被占用FCFF 减少所以是减号。ocf 是现金流量表第一栏的经营活动现金流量净额interest 是利息费用金额。两者的差如果很大要先看是否有非同一控制下的并表、资产处置或汇兑损益等特殊项目。4.2 用pandas做异常值检测报表数据不能直接堆进模型许多下载回来的财报数据是 Excel 或 CSV直接用会踩坑。第一件事是用 pandas 读入并检查缺失值和极值。我一般写一个统一的校验脚本把收入、净利润、经营现金流、总资产、总负债全部扫一遍。import pandas as pd def check_financial_data(df): 检查财报数据的缺失和异常 missing df.isnull().sum() abnormal [] for col in df.columns: if col.startswith(revenue) or col.startswith(profit): growth df[col].pct_change() * 100 large_growth growth[abs(growth) 100].dropna() if not large_growth.empty: abnormal.append((col, large_growth.to_dict())) return {missing: missing, abnormal: abnormal} # 模拟一个财报数据框 raw pd.DataFrame({ revenue: [100, 110, 300, 140, 145], net_profit: [10, 12, 42, 14, 15], ocf: [9, 11, 20, 13, 14] }) report check_financial_data(raw) print(缺失值:, report[missing].sum()) print(增长率超100%的异常:, report[abnormal])该脚本中pct_change() 计算的是环比收入增长率。当增长率超过 100% 时不能直接判定造假但必须追问原因。比如收入从 110 跳到 300可能是并购合并了报表也可能是提前确认收入。在把数据放入估值模型之前我会把这类年份标记出来在预测期里剔除或单独处理。4.3 通货膨胀、利率与风险溢价宏观参数如何进入模型DCF 中的现金流和折现率必须是同一个口径。如果现金流用名义币值包含通胀折现率就必须是名义折现率如果现金流是实际币值折现率就用实际折现率。实际折现率约等于名义折现率减通货膨胀率但用公式算价格时不能简单做减法要分解成 (1名义)/(1通胀) - 1。在投行实务中通常的做法是全部用名义口径因为预测的报表本身是包含价格的。但在做恶性通胀国家项目时比如历史通胀超过 20%名义口径会失控此时用实际币值建模更可行。下面是一个简单的名义转实际折现率函数def real_rate(nominal_rate, inflation_rate): 名义折现率转实际折现率 return (1 nominal_rate) / (1 inflation_rate) - 1 nominal 0.10 inflation 0.02 r_real real_rate(nominal, inflation) print(f实际折现率: {r_real*100:.2f}%)利率和风险溢价是 WACC 的两个上游参数。利率上行会同时提高无风险利率和债务成本风险溢价则反映市场情绪和公司特定风险。中金公司的材料里把风险列为估值的重要考虑因素我的理解是不仅要不低估风险还要说明风险参数调整的路径比如在模型中增加一个小概率情景而不是把所有风险打包进一个高不可攀的折现率后者会让模型变得无法解释。5. 估值结果交叉验证把单点估值变成可决策的区间5.1 三种方法的结果归一化与加权用三种方法对同一家公司估值结果往往不会一致。与其花大量时间争论哪种方法正确不如把它们放到一个统一框架里做交叉验证。资产基础法的结果看作地板价市场法给出市场情绪化的参考收益法给出基本面价值。如果收益法结果低于资产基础法可能说明公司在亏损或资产价值被严重低估。我的习惯是先给每种方法赋予权重权重不拍脑袋而是基于数据可靠性。比如一家现金流稳定的公用事业公司收益法权重 50%市场法 30%资产基础法 20%一家处于困境中的重资产公司资产基础法权重直接提到 50% 以上。下面是一个简单的加权估值代码同时输出最大值和最小值形成区间。def weighted_valuation(methods, weights): 按权重计算加权估值并给出区间 total 0 for value, weight in zip(methods, weights): total value * weight return total, min(methods), max(methods) vals [5.0, 7.5, 6.2] # 资产基础法、收益法、市场法单位亿元 weights [0.2, 0.5, 0.3] estimate, low, high weighted_valuation(vals, weights) print(f加权估值: {estimate:.2f} 亿元区间: {low:.1f} ~ {high:.1f} 亿元)这里的 weights 必须满足和为 1。如果某种方法的输入数据质量差就减少权重而不是直接删掉——保留它可以作为检查极端值的哨卡。5.2 敏感性分析用二维矩阵定位估值驱动因子最后一个具体技巧是网格扫描 WACC 和永续增长率。这两个参数对 DCF 的影响最大我每次在交付模型前都会做一张二维表让领导或客户一眼看出估值在什么假设组合下会变成多少。def sensitivity_matrix(base_value_func, wacc_range, growth_range): 对wacc和永续增长率做二维敏感性分析 import numpy as np result np.zeros((len(wacc_range), len(growth_range))) for i, wacc in enumerate(wacc_range): for j, growth in enumerate(growth_range): if wacc growth: result[i, j] base_value_func(wacc, growth) else: result[i, j] float(nan) return result # 使用第3章的fcf_list计算不同参数组合下的企业价值 def calc_value(wacc, g): total_ev, _, _ dcf_valuation(fcf_list, wacc, g) return total_ev wacc_list [0.07, 0.08, 0.09, 0.10] growth_list [0.02, 0.025, 0.03, 0.035] s_matrix sensitivity_matrix(calc_value, wacc_list, growth_list) print(s_matrix / 1e8) # 转换为亿元逻辑说明二维矩阵的行是 WACC列是永续增长率。当 wacc 小于等于 growth 时置为 nan确保不进入无效参数区。输出矩阵中每个单元格代表对应参数组合下的企业价值亿元。通过观察矩阵区域变化梯度找出估值对哪个参数更敏感——如果横向变化远大于纵向说明估值主要由增长率决定反之则由折现率主导。实际使用时把 wacc_range 换成目标公司所在行业上下四分位区间同时把 growth_range 控制在 0.5% 的步长以内敏感性矩阵就能直接当决策看板用。本文还有配套的精品资源点击获取
返回列表