格雷厄姆 股票估值模型

  • 摘要
  • 估值模型思路
  • 项目结构
  • 模块介绍
    • fetch_stock_data.py 提取数据模块
    • mysql_drivers.py 自定义MySQL驱动模块
    • valuation_model.py 股票估值模块

摘要

在阅读美国经济学家和投资思想家本杰明·格雷厄姆的《聪明投资者》的书籍时,发现有一个成长股的资本化率公式,这个公式是假定上市公司恒定增长,在给定一个合理的市盈率和预期增长率的情况下,预计公司未来7~10年的合理价格。

估值模型思路

获取数据
数据提取
数据清洗
计算估值
存储到数据库

1、获取数据
数据的获取方法有很多,可以直接从同花顺、东方财富网、萝卜投研上获取,这里我是直接从萝卜投研上获取。
2、数据提取
获取数据后,我们从获取的数据中提取需要的财务指标。
3、数据清洗
数据中有一些缺失值,我们使用0进行填充,并保存到MySQL数据库中。
4、计算估值
依据格雷厄姆的成长公式计算股票的估值。
5、存储到数据库
将计算的股票估值存储到MongoDB数据库中。
关于上面第3点,为什么要将清洗后财务数据存储到MySQL数据库中,是因为计算出来的估值仅用于参考,还需要横向对比同行业不同公司的财务指标,选出同行业的龙头企业。

项目结构

如下图所示的项目文件结构

  • main.py 主文件
  • fetch_stock_data.py 提取数据和清洗数据的模块
  • mysql_drivers.py 在MySQL驱动基础上二次开发的操作函数
  • stockinfo.py 股票的基础数据,例如:股票名称对应的代码
  • valuation_model.py 股票估值模型

模块介绍

fetch_stock_data.py 提取数据模块

数据提取模块包含fillna_value()和fetch_data_one()函数
fillna_value() 用于将异常值 NaN,’-’, np.inf, -np.inf 替换成 -1
fetch_data_one() 函数提出关键的财务数据并返回
代码如下:

def fetch_data_one(self, name):''' 提取某支股票的财务数据参数----------name : str公司名称返回值 ----------DataFrame提取的财务数据'''try:# 文件列表cw_file = name + r'-财务摘要表.xls'fz_file = name + r'-资产负债表.xls'# 读取文件cw_pd = pd.read_excel(cw_file, index_col=0, header=0)   # 读取 财务摘要表fz_pd = pd.read_excel(fz_file, index_col=0, header=0)   # 读取 资产负债表except FileNotFoundError:print('=== ERROR: 文件 {} 或者 {} 不存在!'.format(cw_file, fz_file))# 返回空的DataFrame对象return pd.DataFrame()print('提取 {} 公司数据'.format(name))# 提取关键数据cw_pd1 = cw_pd.reindex(['营业收入', '营业收入同比(YOY)', '归属母公司股东的净利润', '归属母公司股东的净利润同比(YOY)','经营活动现金净流量','投资活动产生的现金流量净额', 'ROE(摊薄)', 'ROIC', 'EPS(摊薄)', '股息率', '资产负债率', '折旧与摊销', '销售毛利率', '销售净利润率', 'EBIT', 'EBITDA'])fz_pd1 = fz_pd.reindex(['商誉', '非流动资产合计', '负债合计', '货币资金', '应收账款', '存货'])concat_data = pd.concat([cw_pd1, fz_pd1], sort=False).T   # 合并图表,并转换行列位置self.fillna_value(concat_data)   # 填充缺失值# 计算指标concat_data['归属母公司股东的净利润 / 经营活动现金净流量'] = concat_data['归属母公司股东的净利润'] / concat_data['经营活动现金净流量']# 对列标签重新排序finall_data = concat_data.reindex(['营业收入', '营业收入同比(YOY)', '归属母公司股东的净利润', '归属母公司股东的净利润同比(YOY)', '经营活动现金净流量', '归属母公司股东的净利润 / 经营活动现金净流量', '投资活动产生的现金流量净额', '货币基金','应收账款', '存货', '商誉', '折旧与摊销', '非流动资产合计', '资产负债率', '销售毛利率', '销售净利润率', 'EPS(摊薄)', 'ROE(摊薄)', 'ROIC', '股息率', 'EBIT', 'EBITDA'], axis='columns')# 对行标签进行排序finall_data.sort_index(inplace=True)return finall_data

这里我们传入一个公司名称到fetch_data_one()函数,函数会打开对应公司的“财务摘要表”和“资产负债表”Excel文件(我们前面提前下载好,放在工作目录下的),打开文件后,我们提取关键字段,然后将两个DataFrame表对象合并成一个,在处理异常值后返回最终的DataFrame对象。

流程图如下:

打开Excel
提取字段
合并DataFame
行列转置
填充异常值
索引排序
返回DataFrame

mysql_drivers.py 自定义MySQL驱动模块

这个模块自定义了驱动MySQL的函数,包括如下的函数和常量:

SQL_FIELDS 常量:数据库中的变量对照表

MysqlDrivers类说明
init(self, db) 方法
说明:连接数据库

isexists_table(self, table) 方法
说明:查询表是否存在

create_table(self, table) 方法
说明:传入表名table,创建固定结构的表。

query_value(self, table, fields=None)方法
说明:查询表的数据。

insert_values(self, table, params) 方法
说明:插入一组或多组数据

close_connet(self) 方法
说明:关闭打开的数据库连接。

部分代码如下:

# 数据库中的变量对照表
SQL_FIELDS = {'公司名称': 'company','报告日期': 'report_date','营业收入': 'taking','营业收入同比': 'taking_growth_rate','净利润': 'retained_profits','净利润同比': 'profits_growth_rate','经营活动现金净流量': 'ONCF','投资活动产生的现金流量净额': 'IONCF','货币基金': 'money_fund','存货': 'inventory','应收账款': 'receivables','商誉': 'goodwill','非流动资产合计': 'Non_Total_current_assets','折旧与摊销': 'depreciation_amortization','负债合计': 'total_liability','资产负债率': 'LEV','销售毛利率': 'gross_profit','销售净利润率': 'sales_profit_margin','EPS': 'EPS','净资产收益率': 'ROE','ROE': 'ROE','ROIC': 'ROIC','股息率': 'DYR','企业主营业务的盈利能力': 'EBIT','企业主营业务产生现金流的能力': 'EBITDA','归属母公司股东的净利润 / 经营活动现金净流量': 'profits_to_cash','非流动资产收益率': 'NCROA',
}

SQL_FIELDS 常量是一个字典,定义了财务指标对应数据库的字段名

def create_table(self, table):''' 创建表根据表结构创建数据库的表参数----------table :str表名返回值----------无'''# 通用的表结构genernal_table = ("""
CREATE TABLE IF NOT EXISTS {} (
company varchar(10) COMMENT '公司名称',
report_date varchar(20) COMMENT '报告日期',
taking decimal(16, 6) COMMENT '营业收入',
taking_growth_rate varchar(10) COMMENT '营业收入同比(YOY)',
retained_profits decimal(16, 6) COMMENT '归属母公司股东的净利润',
profits_growth_rate varchar(10) COMMENT '归属母公司股东的净利润同比(YOY)',
ONCF decimal(16, 6) COMMENT '经营活动现金净流量',
profits_to_cash float COMMENT '归属母公司股东的净利润 / 经营活动现金净流量',
IONCF decimal(16, 6) COMMENT '投资活动产生的现金流量净额',
money_fund decimal(16, 6) COMMENT '货币基金',
receivables decimal(16, 6) COMMENT '应收账款',
inventory decimal(16, 6) COMMENT '存货',
goodwill decimal(16, 6) COMMENT '商誉',
depreciation_amortization decimal(16, 6) COMMENT '折旧与摊销',
Non_Total_current_assets decimal(16, 6) COMMENT '非流动资产合计',
LEV varchar(10) COMMENT '资产负债率',
gross_profit varchar(10) COMMENT '销售毛利率',
sales_profit_margin varchar(10) COMMENT '销售净利润率',
EPS varchar(10) COMMENT 'EPS(摊薄)',
ROE varchar(10) COMMENT 'ROE(摊薄)',
ROIC varchar(10) COMMENT '投资资本回报率',
DYR varchar(10) COMMENT '股息率',
EBIT float COMMENT '企业主营业务的盈利能力',
EBITDA float COMMENT '企业主营业务产生现金流的能力',
update_date date COMMENT '当前的时间',
primary key (company, report_date)
)""")# 创建表 table# print(type((table)))self.cursor.execute(genernal_table.format(table))self.cnx.commit()   # 确定修改

create_table()函数创建一个固定结构的表,表的名称以股票代码来命名,例如:深圳主板上市的格力电器,代码:000651,则表的名称为sz_000651
股票名称对对应表的名称存在于 stockinfo.py 模块的STOCK_INFO 常量中。

def query_value(self, table, fields=None):''' 查询表数据查询表table字段fields的数据;如果fields为None,则查询表table所有数据参数----------table :str表名fields :str,list,tuple查询的字段,默认为None,查询所有字段的数据返回值----------list查询到的数据'''# print(fields)if fields is None:   # 如果fields为None,则查询所有数据query = ("SELECT * FROM {}".format(STOCK_INFO[table]))elif isinstance(fields, (tuple, list)):  # 如果fields是列表或者元组, 则查询多个字段fields1 = ','.join([SQL_FIELDS[key] for col in fields for key in SQL_FIELDS.keys() if re.match('.*{}.*'.format(col), key)])    # 拼接查询内容query = ("SELECT {} FROM {}".format(fields1, STOCK_INFO[table]))else:  # 查询一个字段field = [SQL_FIELDS[key] for key in SQL_FIELDS.keys() if re.match('.*{}.*'.format(fields), key)][0]query = ("SELECT {} FROM {}".format(field, STOCK_INFO[table]))self.cursor.execute(query)return self.cursor.fetchall()

query_value() 函数查询数据库的内容,默认查询某个表的所有字段。
流程图如下:

None
tuple or list
其余情况
fields
查询所有字段
查询tuple or list中的字段
查询一个字段
返回查询值

valuation_model.py 股票估值模块

这个模块计算股票的估值,例如:PE、PB、内生增长率等。主要包括如下的函数:
to_float(self, string) 函数
说明:将带%的字符串转换成浮点型数值

correction_growth(self, growth) 函数
说明:修正净利润同比增长率

collection_struct(self, *args) 函数
说明:转换成MongoDB 数据结构

calculate_value(self, name, table) 函数
说明:计算企业价值,包括PE, PB, 股价,预期股价,净利润同比增长率,内生增长率

股票的估值指标主要有PE、PB、PS等
市盈率(PE)是指股票价格除以每股收益(每股收益,EPS)的比率
市净率(PB)指的是每股股价与每股净资产的比率
PE(市盈率)、PB(市净率)的计算方法参照雪球大V的 坤鹏论:用ROE推算合理的PE和PB 用盈再率看企业投资效率 文章提到的公式进行计算,
它的方法考虑了ROE(净资产收益率)和股息支付率,更具有参考意义。
公式如下:
PE=(1-股利支付率)×ROE×100+股利支付率×10
PB=((ROE^2)×(1-股利支付率))×100+ROE×股利支付率×10

优秀的企业都应该是持续成长的,这也就是巴菲特的“具有可持续竞争优势”。
自我维持增长率是衡量公司在不增加外部权益时最大的增长能力的指标,它也叫企业内生收益增长率,或是所有者权益增长率。
计算方法参照雪球大V的坤鹏论:自强才是真强!企业的自我维持增长率怎么算?
公式如下:
自我维持增长率=(1-股利支付率)×权益回报率(ROE)

格雷厄姆的《聪明投资者 第四版》著作中第七章中记载的成长股的资本化率公式:
当期利润是每股盈利EPS,预期年增长率不需要百分号(如:20%就是20),8.5是认为一家成长型公司的合理市盈率。
例如:某股票最新的EPS为1.0,预期年增长率为20%
那么现在的 合理股价 = 1.0 x (8.5 + 2 x 20) = 48.5元

部分代码如下:

def calculate_value(self, name, table):''' 计算企业价值包括PE, PB, 股价,预期股价,净利润同比增长率,内生增长率参数----------name :str公司名称table : PrettyTable object漂亮输出的对象返回值 ----------dict企业估值集合'''report = self.mysql.query_value(name, '报告日期')  # 报告日期列表# 如果最新的为季度报数据,则使用上一年年报数据N = -1 if re.match('.*季报', report[-1][0]) else 0report_date = report[-1 + N][0]  # 报告日期roe = np.average([self.to_float(roe[0])for roe in self.mysql.query_value(name, 'ROE')][-6 + N: -1 + N])  # 近5年平均ROEroic = np.average([self.to_float(roic[0])for roic in self.mysql.query_value(name, 'ROIC')][-6 + N: -1 + N])  # 近5年平均ROICgrowth = np.average([self.to_float(roe[0])for roe in self.mysql.query_value(name, '净利润同比')][-6 + N: -1 + N])  # 近5年平均净利润同比增长率eps = float(self.mysql.query_value(name, 'EPS')[-1 + N][0])  # EPSpayouts = PAYOUTS_INFO[name]  # 股利支付率# 合理PE = (1 - 股利支付率) * ROE * 100 + 股利支付率 * 100  ; 公式来源于:雪球——坤鹏论pe = (1 - payouts) * roe * 100 + payouts * 10# 合理PB = (ROE)^2 * (1 - 股利支付率) * 100 + ROE * 股利支付率 * 10  ; 公式来源于:雪球——坤鹏论pb = np.power(roe, 2) * (1 - payouts) * 100 + roe * payouts * 10# 自我维持增长率 = (1 - 股利支付率) * ROE  ; 公式来源于:雪球——坤鹏论self_growth = (1 - payouts) * roe# 格雷厄姆公式合理股价 = EPS * (PE + 2 * G),对应于复利公式:EPS * PE * (1+G)^10;股价 = EPS * PEif re.match('.*银行', name):graham = eps * (pe + 2 * self.correction_growth(growth)* 0.8 * 100)  # 银行板块,预期增长率G打8折else:graham = eps * (pe + 2 * self.correction_growth(growth)* 0.5 * 100)  # 其余板块,预期增长率G打5折# 如果ROIC不为-1,则格式化roicroic_1 = '{:.2f} %'.format(roic * 100) if roic != -1 else roic # 添加输出的数据 ['公司名称', '报告日期', EPS, 'ROE', 'ROIC', PE', 'PB', '合理股价','预期股价',  '净利润同比增长率', '内生增长率']table.add_row([name, report_date, eps, '{:.2f} %'.format(roe * 100), roic_1, pe, pb, pe * eps, graham, '{:.2f} %'.format(growth * 100), '{:.2f} %'.format(self_growth * 100)])# 转换成 MongoDB 数据结构coll = self.collection_struct(name, report_date, eps,pe, pb, graham, roe, roic, growth, self_growth, payouts)return coll

calculate_value()函数传入公司的名称,然后从数据库中提取公司的财务数据用于计算股票估值。

def correction_growth(self, growth):''' 修正净利润同比增长率平均增长率 >= 40%, 修正为 40%平均增长率 >= 30%, 修正为 原来的80%平均增长率 < 0%, 修正为 0%其余情况,平均增增长率保持不变参数----------growth :float平均增长率返回值 ----------float修正后净利润同比增长率'''if growth >= 0.4:  # 平均增长率 >= 40%return 0.4elif growth >= 0.3:  # 平均增长率 >= 30%return growth * 0.8elif growth < 0:  # 平均增长率 < 0%return 0else:return growth

correction_growth()函数修正平均净利润增增长率。对于一家公司来说,保持5年以上保持40%的同比增长率的可能性是很小的,因此对于平均增长率大于40%的改为40%;对于大于30%的,我们打8折;对于增长率为负数的,认为不增长;其余情况保持不变。
流程图如下:

>=0.4
>=0.3 且 <0.4
<0.3
<0
growth
growth=0.4
gowth*0.8
growth
growth=0
修正growth

这个项目的运行结果如下:
为了避免推荐股票的嫌疑,我将公司名称用xxxx来替代。

x:\test_python\财务报表
+----------+----------+------+---------+---------+-------+------+----------+--------------+------------------+------------+
| 公司名称 | 报告日期 | EPS  |   ROE   |   ROIC  |   PE  |  PB  | 合理股价 | 预期10年股价 | 净利润同比增长率 | 内生增长率 |
+----------+----------+------+---------+---------+-------+------+----------+--------------+------------------+------------+
| xxxxxxx | 2018年报 | 4.36 | 30.55 % | 22.12 % | 24.38 | 7.45 |  106.31  |    223.43    |     26.86 %      |  21.38 %   |
| xxxxxxx | 2018年报 | 3.05 | 23.22 % | 16.91 % | 17.67 | 4.10 |  53.88   |    175.88    |     42.98 %      |  13.47 %   |
| xxxxxxx | 2019年报 | 6.34 | 16.52 % | 14.92 % | 14.56 | 2.40 |  92.32   |    267.77    |     34.59 %      |  11.56 %   |
| xxxxxxx | 2018年报 | 2.20 | 12.42 % | 11.84 % | 11.69 | 1.45 |  25.73   |    66.77     |     18.65 %      |   8.69 %   |
| xxxxxxx | 2018年报 | 0.66 | 15.05 % | 14.75 % | 10.51 | 1.58 |   6.93   |    18.04     |     16.83 %      |   1.51 %   |
+----------+----------+------+---------+---------+-------+------+----------+--------------+------------------+------------+
写入 MongoDB 数据库完成!PS x:\程序开发\应用案例\股票估值案例>

以上我只是选取了部分核心的模块和函数进行说明,其余的可以直接看程序的注释,如有不懂的欢迎评论,如有侵权联系立删。

以上转载内容如下:

  1. 雪球作者:坤鹏论 链接:https://xueqiu.com/5992486099/140360538

  2. 雪球作者:坤鹏论
    链接:https://mp.weixin.qq.com/s?__biz=MzIyMzE3MzgxMw==&mid=2650996055&idx=1&sn=9fb5ef869e517c54d9be6011264135e5&chksm=f3d4108dc4a3999bdc181bd13e9d294a5c33fea440e079f65ad815d4692afa0bdadd3ee08467&scene=21#wechat_redirect

  3. 《聪明的投资者(第四版)》第七章

基于Python3的格雷厄姆 股票估值模型相关推荐

  1. 聪明的投资者 by 格雷厄姆

    聪明的投资者 by 格雷厄姆 <聪明的投资者>1949年第一次出版,是格雷厄姆的第一部专著<有价证券分析>的普及版,专门写给个体投资者,被称为"简单的圣经" ...

  2. 保罗.格雷厄姆:如何获得创业Ideas | How to Get Startup Ideas

    保罗.格雷厄姆:如何获得创业Ideas |  How to Get Startup Ideas 译者注: 创业.创新有很多共通之处.不是拉出一支人马单干才是创业,在公司内部干全新的项目也是创业.   ...

  3. 硅谷创业教父保罗·格雷厄姆给的创业建议书

    本文来自硅谷知名创业孵化器Y Combinator创始Paul Graham(保罗·格雷厄姆)的博客: 作者注:这篇文章源于本人在斯坦福创业课程上的客座讲座.它本来是面向大学生,不过它大部份内容也适用 ...

  4. 格雷厄姆和他的9大门徒

    本杰明·格雷厄姆(Benjamin Graham)被誉为 "现代证券分析之父""华尔街教父",价值投资理论奠基人,其在投资界的地位,相当于物理学界的爱因斯坦,生 ...

  5. 【翻译】硅谷创业教父保罗·格雷厄姆:做不可拓展的事 Do things that don't scale...

    原文地址:http://www.paulgraham.com/ds.html 我们给 YC 投资者提供的最常见的意见类型是做难以扩展的事.一大批潜在的创始人认为,创业不成功便成仁.你做出一些东西,让它 ...

  6. [聪明的投资者]读书笔记1--关于本杰明.格雷厄姆

    第一次知道这本书籍,是在天涯的股市论坛里面,一个又见小包子的网友推荐的,之后看到飞泥也推荐看这本书籍,所以就在拼多多上以低廉的价格买下了这本书籍,纸质书籍质量一般(页薄写注解会透过纸背形成笔迹的凹凸痕 ...

  7. 浅谈格雷厄姆的《证券分析》

    本杰明-格雷厄姆是价值投资的祖师爷,是巴菲特的师傅,我丝毫不敢对他不敬.他的两本书<证券分析>和<聪明的投资者>是每位做投资者必读的经典之书. 国内读者对<证券分析> ...

  8. 7位格雷码计算风向_七哥特刊|从二轮秀到队内得分王 格雷厄姆会新的蜂王吗?...

    乔丹老爷子贵为篮球之神,在球场上的辉煌功绩无需多述,但其选人的眼光与随意开出的垃圾合同,足以证明其并不是个拥有好眼光的的伯乐. 从早前的夸梅-布朗到山猫时期的迈克尔-吉尔克里斯特:从顶薪续约巴图姆最终 ...

  9. 拒绝平庸 保罗格雷厄姆_保罗·格雷厄姆(Paul Graham):微软无法处理Y组合器

    拒绝平庸 保罗格雷厄姆 Earlier this month we wrote about a plan put forth by former Microsoft employee and curr ...

最新文章

  1. C#之windows桌面软件第一课:倒时器软件
  2. SCN Headroom与时光倒流到1988年的Oracle数据库
  3. Linux系统编程之--守护进程的创建和详解【转】
  4. JavaScript——易班优课YOOC课群在线测试自动答题解决方案(二十)整理维护
  5. Linux系统编程之进程控制(进程创建,fork函数,进程中止,进程等待,程序替换)
  6. mybatis学习(17):列名与属性名不一致的情况(使用别名)
  7. shell脚本作为保证PHP脚本不挂掉的守护进程实例
  8. 重新分析connection reset by peer, socket write error错误原因
  9. 第五届山东理工大学ACM网络编程擂台赛
  10. 阿里云域名如何拍卖?
  11. 孤独的根号三 (Missing NUMBER)
  12. cad重新加载php命令,cad撤回命令是什么
  13. VM虚拟机桥接模式的复制物理网络连接状态是什么意思
  14. 关于合格设计师的30条冷知识
  15. (最新最详细)ADT安装和Eclipse的window-->preferences(首选项)没Android
  16. 神经元的结构图手绘,神经元图片手绘图
  17. RN入门基础1:第一个RN项目-hello World
  18. qemu qcow2镜像如何缩小
  19. Spring Boot 静态资源修改立即生效
  20. 贪吃蛇游戏设计准备(实验准备)

热门文章

  1. docer启动一个容器时的过程
  2. [渝粤教育] 中国地质大学 工业通风及除尘 复习题 (2)
  3. https证书格式转换(cer转bks)
  4. 关于站内信的开发思路
  5. 睿瞳车牌识别测试总结
  6. 生物化学 核磁共振 氢谱 n+1定律 邻碳耦合 同碳耦合
  7. vue.js 动态合并单元格问题
  8. E0 S0 SVL接口配置
  9. 《Java程序设计》实验指导——项目3 类与对象
  10. xp自定义桌面中ie图标消失解决办法