【编程】DCF自由现金流贴现模型——基于python的实现

简介:这是一个DCF自由现金流贴现模型,是对郭永清老师的《财务报表分析与股票估值》这本书内容的实现

在这里插入图片描述

注意事项

  1. 代码是基于《财务报表分析与股票估值》的,其中自由现金流的口径与大众认知略有出入,建议使用前先阅读该书第14、15章;
  2. 本人非计算机专业,模型代码可能存在部分错误;
  3. 银行股暂时无法估值,因为其财报形式和其他种类公司相比略有不同;
  4. 数据采用的是邢不行老师整理的股票历史日线数据和新浪财务数据;
  5. 本人非财务、会计专业,尽管过程中请教了CPA大神,但财务数据口径依然可能存在问题。

DCF介绍

自由现金流贴现法是绝对估值法的一种,理论基础是现值原理:任何资产的价值都等于其预期未来全部现金流的现值总和,对公司而言就是自由现金流。

由于准确预测未来所有自由现金流是不可能的,而且股票并没有固定的生命周期,因此将模型简化为以下四种:

零 增 长 模 型 : V = F C F W A C C 不 变 增 长 模 型 : V = F C F ( 1 + g ) W A C C − g 两 阶 段 模 型 : V = ∑ t = 1 n F C F t ( 1 + W A C C ) t + T V ( 1 + W A C C ) n ,    其 中 T V = F C F n ( 1 + g 2 ) W A C C − g 2 三 阶 段 模 型 : V = ∑ t = 1 n F C F 0 ( 1 + g 1 ) ( 1 + W A C C ) t + ∑ t = n + 1 m F C F n ( 1 + g 2 ) ( 1 + W A C C ) t + F C F n + m ( 1 + g 3 ) ( W A C C − g 3 ) ( 1 + W A C C ) n + m \begin{aligned} &零增长模型:V=\frac{FCF}{WACC}\\ &不变增长模型:V=\frac{FCF(1+g)}{WACC-g}\\ &两阶段模型:V=\sum_{t=1}^n\frac{{FCF}_t}{(1+WACC)^t}+\frac{TV}{(1+WACC)^n},\ \ 其中TV=\frac{FCF_n(1+g_2)}{WACC-g_2}\\ &三阶段模型:V=\sum_{t=1}^n\frac{{FCF}_0(1+g_1)}{(1+WACC)^t}+\sum_{t=n+1}^m\frac{{FCF}_n(1+g_2)}{(1+WACC)^t}+\frac{FCF_{n+m}(1+g_3)}{(WACC-g_3)(1+WACC)^{n+m}}\\ \end{aligned} V=WACCFCFV=WACCgFCF(1+g)V=t=1n(1+WACC)tFCFt+(1+WACC)nTV  TV=WACCg2FCFn(1+g2)V=t=1n(1+WACC)tFCF0(1+g1)+t=n+1m(1+WACC)tFCFn(1+g2)+(WACCg3)(1+WACC)n+mFCFn+m(1+g3)

  • 零增长模型适用于成熟稳定、没有增长的公司,每年的自由现金流也保持在一个稳定的金额水平,类似于永续年金;如果该类公司的自由现金流全部用于发放股利现金,那么其得出的结果与股利贴现模型非常接近。
  • 不变增长模型适用于成熟的公司,未来的自由现金流以非常缓慢的速度增长。
  • 两阶段模型中,投资者的预期回报WACC至少要高于总体的经济增长率;不变增长率g2通常小于WACC,反之,意味着很长时间以后公司的规模将超过总体经济规模。
  • 三阶段模型中,假设所有的公司经历三个阶段:成长阶段、过渡阶段和稳定阶段。三个阶段的成长率由高到低,稳定阶段保持较低增长率的不变增长。

具体计算步骤:

  1. 计算自由现金流并依据相应的方法折现( ⋆ ⋆ ⋆ ⋆ ⋆ \star\star\star\star\star )
  2. 计算股权价值= 折现后的自由现金流+金融资产+长期股权投资-公司债务
  3. 计算少数股东比例
  4. 归属于上市公司股东的价值=股权价值 × \times ×(1-少数股东比例)
  5. 每股内在价值=归属于上市公司股东的价值/股本

其中,

  • 经营资产自由现金流=公司维持原有生产经营规模前提下的增量现金流入=经营活动现金流量净额-保全性资本支出=经营活动现金流量净额-固定资产折旧-无形资产和长期待摊费用摊销-处置长期资产的损失
  • W A C C = k d × D D + E × ( 1 − t ) + k e × E D + E WACC=k_d\times\frac{D}{D+E}\times(1-t)+k_e\times\frac{E}{D+E} WACC=kd×D+ED×(1t)+ke×D+EE。其中债务资本成本率=债务资本总额/债务资本平均金额$\times$100%=(财务费用+汇兑收益)/(期初债务资本+期末债务资本)/2;股权资本成本率应该高于同期的国债利率,加上股票投资的风险溢价,我们普遍设置为9%;t为公司实际所得税税率=1-净利润/税前利润。
  • 公司债务=有息债务
  • 少数股东比例= 少 数 股 东 权 益 股 东 权 益 合 计 \frac{少数股东权益}{股东权益合计}
  • 股本=市值/股价

如何使用

  • 可直接复制末尾的代码使用;
  • 可进入github网址下载代码使用(ipynb格式);
  • 提供了一份浪潮信息的数据,位于github网址上。

变量说明

#=== 变量
file_name = 'sz000977'  # 股票代码
time = 4  # 采用最近n期的数据(季)
zero_change = False  # 是否为零增长模型
one_change = False  # 是否为不变增长模型
two_change = True   # 是否为两阶段模型
three_change = False  # 是否为三阶段模型
g1, g2, g3 = 0.2, 0.03, 0.01  # 增长速度,分别是后三个模型的g,如果使用不变增长模型,则不需要更改后两个
t1, t2 = np.arange(1, 3), np.arange(1,2)  # 某阶段的几年,两阶段与三阶段模型需要,注意最大值减一为实际值

以上部分是额可以修改的内容,即修改以上部分就足以运行。

file_name是指股票代码,即读取数据的文件名,本程序一共读取股票日线数据和财务数据两个文件,注意使用时将read_file函数中的地址改为自己电脑的地址。

time是指最近第n季的数据,由于财务数据分季,且季报、半年报的现金流净额容易出现负数,故默认值为4,采取的是现有数据倒数第四季,即2019年年报的数据,根据自己数据的不同所填的数不同。

change系列是四个模型,需要使用哪个模型后面写True, 不需要的记得写上False

g系列是增长率,不变增长模型下是g1,两阶段模型下g1是可预测期,g2是不变增长率,三阶段模型下g1、g2、g3分别是成长、过度、稳定阶段的增长率。

t系列代表预期时间具体年数,如两阶段模型下t1代表可预测期年数,三阶段模型下t1、t2分别代表成长、过度期年数;另外np.arange函数生成的数字小于第二个值,如np.arange(1,3)生成的是[1,2], np.arange(1,5)生成的是[1,2,3,4]

最终输出的结果是以下样子:

WACC is  0.07472217542810221
归属于上市公司股东的价值: 39579757869.00408 
 股票内在价值: 30.69977988736238
原PE:  48.261587273819586 估值PE: 49.223259346212316
前0个季度股票价格 30.39
前1个季度股票价格 39.18
前2个季度股票价格 38.78
前3个季度股票价格 30.1
前4个季度股票价格 25.7
前5个季度股票价格 25.02
前6个季度股票价格 26.96

代码

import pandas as pd
import numpy as np

#=== 变量
file_name = 'sz000977'  # 股票代码
time = 4  # 采用最近n期的数据(季)
zero_change = False  # 是否为零增长模型
one_change = False  # 是否为不变增长模型
two_change = True   # 是否为两阶段模型
three_change = False  # 是否为三阶段模型
g1, g2, g3 = 0.2, 0.03, 0.01  # 增长速度,分别是后三个模型的g,如果使用不变增长模型,则不需要更改后两个
t1, t2 = np.arange(1, 3), np.arange(1,2)  # 某阶段的几年,两阶段与三阶段模型需要,注意最大值减一为实际值



#=== functions
def read_file(file_name):
    # 读取股票基本数据
    df = pd.read_csv(r'your_address\%s.csv' % file_name, encoding='GBK', skiprows=1, parse_dates=['交易日期'])
    df = df[['股票代码', '股票名称', '交易日期', '总市值', '净利润TTM', '收盘价']]
    print(df.tail(5))
    # 读取股票财务数据
    finance_df = pd.read_csv(r'your_address\%s.csv' % file_name, parse_dates=['财报日期', '财报发布日期'], skiprows=1, encoding='gbk')
    finance_df = finance_df.resample('Q', on='财报日期').first()
    del finance_df['财报日期']
    finance_df.reset_index(inplace=True)
    finance_df.dropna(subset=['财报发布日期'], inplace=True)
    finance_df.sort_values(by='财报发布日期', inplace=True)
    return df, finance_df


def merge_data(df, finance_df):
    add_columns = ['B_货币资金',  
     'B_交易性金融资产',
     'B_衍生金融资产',
     'B_应收票据及应收账款',
     'B_应收票据',
     'B_应收账款',
     'B_应收款项融资',
     'B_应收利息',
     'B_应收股利',
     'B_其他应收款',
     'B_买入返售金融资产',
     'B_发放贷款及垫款',
     'B_可供出售金融资产',
     'B_持有至到期投资',
     'B_长期应收款',
     'B_长期股权投资',
     'B_投资性房地产',
     'B_所有者权益(或股东权益)合计',   
     'C_经营活动产生的现金流量净额', 
     'B_短期借款',
     'B_交易性金融负债',
     'B_应付利息',
     'B_应付短期债券',
     'B_一年内到期的非流动负债',
     'B_长期借款',
     'B_应付债券',
     'B_租赁负债',
     'B_长期应付款(合计)',
     'R_财务费用',
     'R_汇兑收益',
     'R_四、利润总额',
     'R_减:所得税费用',
     'C_固定资产折旧、油气资产折耗、生产性物资折旧', 'C_无形资产摊销', 'C_长期待摊费用摊销', 'C_处置固定资产、无形资产和其他长期资产的损失',
     'B_少数股东权益']
    col = ['财报发布日期', '财报日期'] + add_columns
    stock_df = pd.merge_asof(df, finance_df[col], left_on='交易日期', right_on='财报日期', direction='backward')
    print(stock_df.columns)
    return stock_df


def data_been_prepared(now_df, stock_df):
    now_df[['股票代码', '股票名称', '交易日期', '总市值', '财报发布日期', '财报日期', '净利润TTM', '收盘价']] = stock_df[['股票代码', '股票名称', '交易日期', '总市值', '财报发布日期', '财报日期', '净利润TTM', '收盘价']]
    now_df['金融资产'] = 0
    now_df['公司债务'] = 0
    for factor1 in ['B_货币资金',
     'B_交易性金融资产',
     'B_衍生金融资产',
     'B_应收票据及应收账款',
     'B_应收票据',
     'B_应收账款',
     'B_应收款项融资',
     'B_应收利息',
     'B_应收股利',
     'B_其他应收款',
     'B_买入返售金融资产',
     'B_发放贷款及垫款',
     'B_可供出售金融资产',
     'B_持有至到期投资',
     'B_长期应收款',
     'B_投资性房地产',
     'B_长期股权投资']:
        now_df['金融资产'] += stock_df[factor1]
    for factor2 in ['B_短期借款',
     'B_交易性金融负债',
     'B_应付利息',
     'B_应付短期债券',
     'B_一年内到期的非流动负债',
     'B_长期借款',
     'B_应付债券',
     'B_租赁负债',
     'B_长期应付款(合计)']:
        now_df['公司债务'] += stock_df[factor2]   
    now_df['债务资本成本总额'] = stock_df['R_财务费用'] + stock_df['R_汇兑收益']
    now_df['经营资产自由现金流'] = stock_df['C_经营活动产生的现金流量净额'] - stock_df['C_固定资产折旧、油气资产折耗、生产性物资折旧'] - stock_df['C_无形资产摊销'] - stock_df['C_长期待摊费用摊销'] - stock_df['C_处置固定资产、无形资产和其他长期资产的损失']
    now_df['实际企业所得税税率'] = 1 - ((stock_df['R_四、利润总额'] - stock_df['R_减:所得税费用']) /  stock_df['R_四、利润总额'])
    now_df['少数股东权益比例'] = stock_df['B_少数股东权益'] / stock_df['B_所有者权益(或股东权益)合计']
    now_df['债务占比'] = now_df['公司债务'] / (stock_df['B_所有者权益(或股东权益)合计'] + now_df['公司债务'])
    now_df.drop_duplicates(subset=['财报日期'], inplace=True)
    now_df.reset_index(inplace=True)
    del now_df['index']
    print(now_df.tail(10))
    return now_df


def cal_WACC(now_df, time):
    WACC = (now_df['债务资本成本总额'] / ((now_df['公司债务'] + now_df['公司债务'].shift(time)) / 2) * now_df['债务占比'] * (1-now_df['实际企业所得税税率'])) + (0.09 * (1-now_df['债务占比']))
    return WACC.tolist()[-time]


def fcf_discounted(now_df, WACC, time, zero_change, one_change, two_change, three_change, g1, g2, g3, t1, t2):
    value = (now_df.loc[: ,'金融资产'].tolist()[-time] - now_df.loc[: ,'公司债务'].tolist()[-time])
    if zero_change == True:
        FCF = now_df.loc[: ,'经营资产自由现金流'].tolist()[-time] / WACC
    if one_change == True:
        FCF = (now_df.loc[: ,'经营资产自由现金流'].tolist()[-time] * (1+g1)) / (WACC - g1)
    if two_change == True: 
        temp_sum = 0
        for _ in t1:
            temp = now_df.loc[: ,'经营资产自由现金流'].tolist()[-time] * ((1+g1) ** _) / ((1+WACC) ** _)
            temp_sum = temp + temp_sum
        FCF = ((now_df.loc[: ,'经营资产自由现金流'].tolist()[-time] * ((1+g1) ** (t1[-1]-1)) * (1+g2)) / ((WACC-g2)*((1+WACC)**t1[-1]))) + temp_sum
    if three_change == True:
        temp_sum1, temp_sum2 = 0, 0
        for _ in t1:
            temp1 = now_df.loc[: ,'经营资产自由现金流'].tolist()[-time] * ((1+g1) ** _)
            temp =  temp1 / ((1+WACC) ** _)
            temp_sum1 = temp + temp_sum1
        for _ in t2:
            temp = temp1 * ((1+g2) ** _) / ((1+WACC) ** (_+t1[-1]))
            temp_sum2 = temp + temp_sum2
        FCF = (temp1 * ((1+g2) ** t2) * (1+g3)) / ((WACC-g3)*((1+WACC)**(t1[-1]+t2[-1]))) + temp_sum1 + temp_sum2
    FCF_plus_value =  (FCF + value)  * (1 - now_df.loc[: ,'少数股东权益比例'].tolist()[-time])
    result = FCF_plus_value / (now_df.loc[: ,'总市值'].tolist()[-time] / now_df.loc[: ,'收盘价'].tolist()[-time])  # 股票内在价值,价值/股数
    print('归属于上市公司股东的价值:', FCF_plus_value, '\n', '股票内在价值:', result)
    return FCF_plus_value, result


def statistics(now_df, time):
    PE1 = now_df.loc[: ,'总市值'].tolist()[-time] / now_df.loc[: ,'净利润TTM'].tolist()[-time]  # 市盈率
    PE2 = FCF_plus_value / now_df.loc[: ,'净利润TTM'].tolist()[-time] 
    print('原PE: ', PE1, '估值PE:', PE2)
    for time_n in [1, 2, 3, time, time+1, time+2, time+3]:
        print('前%s个季度股票价格' % (time_n-1), now_df.loc[: ,'收盘价'].tolist()[-time_n])  # 股票收盘价



#=== main
df, finance_df = read_file(file_name)
stock_df = merge_data(df, finance_df)
now_df = pd.DataFrame()
now_df = data_been_prepared(now_df, stock_df)
WACC = cal_WACC(now_df, time)
print('=============================')
print('WACC is ', WACC)
FCF_plus_value, result = fcf_discounted(now_df, WACC, time, zero_change, one_change, two_change, three_change, g1, g2, g3, t1, t2)
statistics(now_df, time)
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值