1:本文主要站在分析师的角度对利润表进行建模,并以A股某公司为例进行展示;
2:本文主要为理念的讲解,模型也是笔者自建;
3:本文主要数据均通过Tushare(ID:444829)金融大数据平台接口获取;
4:文中假设与观点是基于笔者对模型及数据的一孔之见,若有不同见解欢迎随时留言交流;
5:模型实现基于R语言;
通过财报数据我们可以勾勒出上市公司的经营状况,因此很多基本面分析都是建立在财务数据的基础上。 出于好奇笔者试着搜了搜网上的大哥们都是怎么建模的,发现要么是用excel,要么说得不清不楚的吸引你买服务,还有的纯粹是站在会计角度对企业报表编制流程进行优化,总之都与笔者所想差之甚远。既然没有咱就整活儿!
目录
1.为什么要进行财务建模?
正式开始前先说为什么。财报的重要性就不说了,巴菲特老爷爷晚上都会拿财报当睡前读物,它对基本面分析的意义不言而喻。网上其实很多excel的建模和关于企业报表编制的东西,但它们都不是一个分析师打开财报分析的优雅姿势。笔者更喜欢自己拿数据,自己分析,建模过程也用代码代替重复性工作,将更多精力放在估值和其它基本面的研究上。
1):为什么用R?
先说为什么不用excel或者python,我们不是进行财报数据录入或者编制财务报表,当进行财务数据预测时往往需要横向的同行业财务数据和纵向的历史财务数据,这一波下来就是好多张报表,如果碰上个同行业公司有几十个之多的行业,用excel的直接去世。
python下可爬数据,上可进行大量的数据操作,其实是个不错的选择。但在数据操作的方便程度上,笔者认为并没有R好用。鉴于目前很多数据可以直接用API拿,加上R语言体积小,轻量,变量调试方便,可视化操作又简洁,笔者多方面权衡下认为R最优(其实会用R,python也能写啦,区别不会很大)。
2):为什么要费力自己建模?
其实目前市面上很多行情软件还有券商研报都可以看财务指标和盈利预测,我们自己建模是不是多此一举?其实不然,一来使用第三方预测的可靠性问题,尽管券商一些公司的盈利预测准得不行,笔者还是喜欢亲历亲为,做到每个数据心中有底;二来自己亲历亲为更能理解和发现公司财务问题;最后,往往很多财务预测服务都需要收费,一些个人投资者也接触不到券商内部的系统。
2. 建模原则和方法
笔者遵循原则主要有两个:1):分析师不是会计师,分析师需要跳出准则,打破一些在分析师看来并不合理的会计估计和会计计量;2):建模是为了进行盈利预测,保证公允合理,为估值服务。
笔者主要思路主要有四个:1):自上而下或自下而上或综合考虑二者进行预测;2):借助财务指标运算和报表勾稽关系;3):纵向参考历史趋势,横向参考行业标准;4):立足于未来视角,凡遇不合理之处,一个字——调。
对大多数公司来说,其价值主要体现在商业活动所带来的利润上,最直接的反映便是销售收入,因此笔者也往往从利润表开始进行建模。其次,三大报表存在联系,将利润表预测出来后可以通过财务指标运算,勾稽关系等方式将其它两张表推出来。
3. 利润表建模
利润表大家都熟悉,主要科目一收四支加个税,但细分科目也比较麻烦,笔者下面以某公司(以下称为”A公司“)利润表为例进行建模(结果仅作展示)。
3.1 营业收入
图一为A公司营收及增速,呈现了一定的周期波动规律,对比图一增速走势与图二房地产板块走势不难发现,近十年来,A公司营收状况与房地产行业联系较为紧密,且存在一定的滞后关系。因而,我们可以利用房地产行业的预期增速对未来营收进行判断。
图一:A公司营收及增速
图二:同花顺房地产开发板块2007-2022年月线
根据房地产景气指数,目前位于比疫情还低的位置,考虑到房地产本身的周期属性及国家政策上的宽松(有一定安全边际),笔者判断该公司这部分业务(以下称为“业务a")未来仍具有一定上升空间,但增速不会很高。从板块指数上看,目前走得也还算稳定,没有出现很大波动。因此可以初步判断业务a营收增速也很难经历大起大落,但我们显然不能直接利用房地产增速代替公司这部分业务增速。
上面对房地产那一通分析仅仅只是为了明确行业未来发展趋势,下面还需要自下而上考虑公司本身产品,技术及品牌等因素为增速带来的额外加成。A公司是行业巨头,从历史数据看公司业务a收入波动虽受房地产行业影响,但其增速的绝对数值都高于房地产。因此,笔者在疫情前9%左右增速上调整,给该业务a 8%左右的增速。
但是,这还远远不够,A公司业务结构较为复杂,还有近40%的业务(以下称为”业务b")与消费有关。这业务b受GDP波动影响较大且其数值与GDP增速基本趋同,刨去20,21两年的经济波动带来的异常数据,由于疫情前GDP增速与目前增速较为可比,因此笔者按疫情前增速给该板块业务增速9%。
2019 | 2020 | 2021 | |
GDP增速% | 5.8 | -6.9 | 18.3 |
b板块业务增速% | 9.4 | 0.04 | 15.8 |
该公司还有一部分机器人业务,虽然历史表现不佳,但笔者较为看好未来发展。不过鉴于笔者本次只进行未来一年的财务预测,还是按历史增速取平均,保守的给2%。
最后营收项目采用加权平均的方式获得总体增速:7.2%
下期营收=本期营收×(1+增速)
可以看到,仅一项营收就动用很多种工具,属于综合分析。在自上而下分析中,宏观层面主要利用GDP增速法和行业判断。事实上还可以利用市场增速和份额法,只是笔者没有资源进行大规模市场调研,且对该公司采用GDP和行业判断的方式笔者认为依旧是公允的,实务中可以利用不同方法进行交叉验证。微观层面主要借助公司基本面分析,历史数据进行时间序列预测。
最后,采用自下而上的方式将公司各部分业务加总。这里如果再进一步加总整个行业,部门,甚至经济体可以得到行业,部门和整个经济体的宏观数据。不管用什么方式估计,总的原则要保证公允合理。
笔者花费这么多笔墨在营收上就是因为它是建模的起点,甚至可以说是估值的起点,一步错后面就算算得天准那也是步步错。尤其是对一些难以预测数据又难搞的公司,绝对是个技术活儿。不过预测出了营收,其它一系列的东西都是水到渠成,除了一些项目还是需要人为判断,大部分的活都可以交给电脑完成,下面开始带着代码走。
数据方面笔者选择Tushare金融大数据接口,注册即可使用自己的Token获取数据,不管是python还是R都可以非常便捷。首先导入模块,输入自己的Token,最后调用相关API获取数据。R的语法和python稍微有些不一样, python里是将需要调用的API写括号外面当方法调用,R语言是把API名字放括号里面当初参数。
library(Tushare)
library(ggplot2)
api <- Tushare::pro_api("Token")
code <- code # 公司代码
indicator <- api(api_name='fina_indicator', ts_code='000333.SZ', end_type=4) # 财务指标表
incom_stat <- api(api_name='income', ts_code=code, end_type=4) # 利润表
把分析半天算出来的收入赋值吧:
rev_growth <- 0.072
rev <- incom_stat$revenue[1] * (1+rev_growth)
3.2 经营成本
1):销货成本(COGS)= (历史销货成本/历史营业收入) 预期营业收入
历史销货成本/历史营业收入= 1-历史毛利率
首先观察一下历史毛利率:(由于Tushare请求的数据都是按日期从大到小排,这里将计算出来的新表格倒一下顺序方便再图表上展示)
gross_profit = data.frame(incom_stat$ann_date, (incom_stat$revenue-incom_stat$oper_cost)/incom_stat$revenue)
names(gross_profit)[1] <- "date"
names(gross_profit)[2] <- "gp_margin"
gross_profit <- gross_profit[order(gross_profit$date),]
ggplot(gross_profit, aes(x=(1:nrow(gross_profit)), y=gross_profit$gp))+
geom_point(size=1, color="blue")+geom_line(color="blue")+
labs(x="period", y="ratio%")+
ggtitle("gross profit margin")+
theme(plot.title = element_text(hjust = 0.5))
图三:毛利率走势
可以看到,该公司以前毛利走势较强,这几年的下滑受上游大宗商品涨价影响,下游价格又有粘性。毕竟是制造业,两头挤压,毛利率下滑较为明显。今年通胀高挂,上游大宗价格依旧不容乐观,笔者认为毛利率仍有下滑空间。于是笔者以过去三年平均降幅作为预测期降幅:
gross_profit = data.frame(incom_stat$end_date, (incom_stat$revenue-incom_stat$oper_cost)/incom_stat$revenue)
names(gross_profit)[1] <- "date"
names(gross_profit)[2] <- "gp_margin"
gross_profit <- gross_profit[order(gross_profit$date,decreasing = T),]
gross_profit <- gross_profit$gp_margin
total_dif <- 0
for (i in c(-3:-1){ # 前面把表格倒序了,所以写负
total_dif <- total_dif + his_data[i-1] - gross_profit[i]}
ratio <- his_data[-1]+total_dif/3
wait,从可视化到计算,那岂不是说每个会计科目都写上那么十几行代码?不可能,绝对不可能。
于是封装函数,后面直接传参调用就好了:
mean_mod <- function(his_data,current_number, periods, full_ratio){
names(his_data)[1] <- "date"
names(his_data)[2] <- "ratio"
ratio <- mean(his_data$ratio[1:periods])
if (full_ratio == 1){
exp_num <- ratio*current_number
}else{
exp_num <- (1-ratio) * current_number}
his_data <- his_data[order(his_data$date,decreasing = F),]
visual_mod(his_data)
return(exp_num)
}
trend_mod <- function(his_data, current_number, periods, full_ratio){
names(his_data)[1] <- "date"
names(his_data)[2] <- "ratio"
ratio <- his_data$ratio[1] + (his_data$ratio[1] - his_data$ratio[periods]) / periods
if (full_ratio == 1){
exp_num <- ratio*current_number
}else{
exp_num <- (1-ratio) * current_number}
his_data <- his_data[order(his_data$date,decreasing = F),]
visual_mod(his_data)
return(exp_num)
}
再写个可视化插进这些模块里去,这样运行到某个指标都能随时将历史走势显示在R的可视化窗口中,实乃分析财报之必备利器也:
visual_mod <- function(data){
plot <- ggplot(data, aes(x=(1:nrow(data)), y=ratio))+
geom_point(size=1, color="blue")+geom_line(color="blue")+
labs(x="period", y="ratio%")+
ggtitle("Index")+
theme(plot.title = element_text(hjust = 0.5))
print(plot)
}
做完了这些,营业成本就两行代码:
gross_profit <- data.frame(incom_stat$end_date, (incom_stat$revenue-incom_stat$oper_cost)/incom_stat$revenue)
cogs <- dcrs_mod(gross_profit, rev, 3, 0)
甚至:
cogs <- dcrs_mod(data.frame(incom_stat$end_date, (incom_stat$revenue-incom_stat$oper_cost)/incom_stat$revenue), rev, 3, 0)
3.3: 经营成本
1):利息支出、手续费及佣金支出、税金及附加
利息支出项目主要和公司付息债务水平有关,鉴于A公司这个项目占比不大,集团债务水平也减少了很多,笔者直接以上期支出代替。手续费及佣金支、税金及附加出也一样处理(这里简化了一下,毕竟仅作举例)。
int_exp <- incom_stat$int_exp[1]
comm_exp <- incom_stat$comm_exp[1]
biz_tax_surchg <- incom_stat$biz_tax_surchg[1]
2):销售费用
这部分支出与公司营收业务相关度较大,与销售人员与销售业绩直接挂钩。笔者这里借用财务比率(和COGS一样如法炮制):
sales_exp <- data.frame(incom_stat$end_date, incom_stat$sell_exp/incom_stat$revenue)
sales_exp <- trend_mod(sales_exp, rev, 3, 1)
可以看到,近几年的费用比率其实是在下降的,但总的还是在一个较窄的区间内波动:
图四:销售费率走势
这里其实trend_mod不太可靠,具体估计应该看公司销售人员和营收状况判断接下来是反弹还是会继续下跌,这个历史数据的走法一看就是需要要人为调的,模型很难判断。不过出于模型普适性的考虑笔者还是用了trend_mod,仅作举例和展示。
3):管理费,财务费,研发费
一般来说管理费,财务费与销售关联度较小,主要受公司规模影响。研发费就需要看具体公司属性了,A公司这三项都处于稳定上升,笔者选择用自回归进行预测, 不借助财务指标。
新建自回归模块, 带上可视化一起飞(笔者随意写的,没有让模型做假设检验,仅作展示和举例):
auto_mod <- function(his_data){
names(his_data)[1] <- "date"
names(his_data)[2] <- "ratio"
seri_1 <- his_data$ratio[1:nrow(his_data)-1]
seri_2 <- his_data$ratio[2:nrow(his_data)]
result <- data.frame(coef(lm(seri_1~seri_2)))
names(result)[1] <- "parameter"
intercept <- result$parameter[1]
beta <- result$parameter[2]
exp_num <- intercept + beta*his_data$ratio[1]
his_data <- his_data[order(his_data$date,decreasing = F),]
visual_mod(his_data)
return(exp_num)
}
这种走法笔者肉眼一看:自回归yyds
图五:管理费走势
于是又到了开心的两行代码环节:
管理费:
admin_exp <- data.frame(incom_stat$end_date, incom_stat$admin_exp)
admin_exp <- auto_mod(admin_exp)
财务费、研发费:
fin_exp <- data.frame(incom_stat$end_date, incom_stat$fin_exp)
fin_exp <- auto_mod(fin_exp)
rd_exp <- data.frame(incom_stat$end_date, incom_stat$rd_exp)
rd_exp <- auto_mod(rd_exp)
有意思的是这个公司财务费用逐年降低,而且是负值。好家伙您是找公司财务收利息了吗:
图六:财务费用走势
小结一下:
operating_exp <- cogs + int_exp + comm_exp + biz_tax_surchg + sales_exp + admin_exp + fin_exp + rd_exp
4):其它损益类项目及非营业收入
什么也不说了,一锅端,最后小结一下会计科目。有些数据API上没有,用往年数代替了。
oth_income <- data.frame(incom_stat$end_date, incom_stat$oth_income)
oth_income <- auto_mod(oth_income)
ass_invest_income <- data.frame(incom_stat$end_date, incom_stat$ass_invest_income)
ass_invest_income <- auto_mod(ass_invest_income)
fv_value_chg_gain <- incom_stat$fv_value_chg_gain[1]
#credit_impa_loss <- data.frame(incom_stat$end_date, incom_stat$credit_impa_loss)
credit_impa_loss <- -383451 #auto_mod(credit_impa_loss)
assets_impair_loss <- data.frame(incom_stat$end_date, incom_stat$assets_impair_loss)
assets_impair_loss <- auto_mod(assets_impair_loss)
#asset dispossal
nca_disploss <- 58257
non_oper_income <- data.frame(incom_stat$end_date, incom_stat$non_oper_income)
non_oper_income <- auto_mod(non_oper_income)
non_oper_exp <- data.frame(incom_stat$end_date, incom_stat$non_oper_exp)
non_oper_exp <- auto_mod(non_oper_exp)
non_operating <- oth_income + ass_invest_income + fv_value_chg_gain + credit_impa_loss + assets_impair_loss + nca_disploss + non_oper_income + non_oper_exp
3.4 利润总额及净利润
有了所有的数据就可以算利润总额:
tatal_earnings <- rev - operating_exp + non_operating
最后看用效税率算净利润:
tax_rate <- data.frame(incom_stat$end_date, incom_stat$income_tax/incom_stat$operate_profit)
net_incom <- mean_mod(tax_rate, tatal_earnings, nrow(tax_rate), 0)
图七:有效税率走势
哇,这个税率是认真的吗,19年还竟然只有不到10%,税盾狗,这么高明的会计,我或许有点明白之前会计费用为什么是正的了。。
笔者模型给出预计净利润为:¥ 27145022057
print(net_incom)
[1] 27145022057
刷一波历史净利润走势,最后一个点为预测值:
图八:历史近利润走势及预测
这意味着如果模型认为该公司将迎来近十年来的第一次净利润下滑,笔者对一些科目给的还是比较保守的,不过对于净利润下滑笔者也不可置否。
RStudio还可以拉出所有数据及表格,非常方便检查,这点比起pycharm和jupytor都友好很多很多。
表二:会计科目变量值
后面如果碰到陌生的会计科目,只需要将代码加入,没有数据的科目以空值代替,模型完善后便可以用代码一撸到底。剩下导入到Excel还有一些表格的完善工作就不放了。
4. 注意事项
本来想随便写写,笔者估计C站估计也没多少会计,写着写着不知不觉还是写了这么多。没办法,财报建模本来就是个复杂的工作,对一些会计科目的调整和假设绝对是技术活中的技术活。本文中笔者只是举了个例子,实际上要注意的细节多如牛毛,上市公司规模较大,业务结构较为复杂,文中这个公司还是家跨国企业,例如报表合并产生的长期股权投资,少数股东损益,存货计量,收入确认,减值计提等等,对于利润表还要考虑未来会不会有损益项目从资产负债表中出来,变成已实现损益影响当期利润。不仅需要分析师对会计了如指掌,更重要的还要对公司经营状况,行业未来作出专业的判断,知道为什么不是随随便便拉个会计都能当分析师了吧。
虽然很多会计科目细究起来非常麻烦,很多时候不是计算机能处理的。但我们借助代码可以将一些繁复的数据收集和处理工作交给计算机,而我们则只需要专注于更有意义的事情上。
感觉财报分析可以出一个系列的专栏了,后面有机会继续出其它几张报表建模及更多会计相关内容。您若不弃,我们风雨共济。