VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > python数据分析 >
  • 财务数据可视化:杜邦分析图、营收利润趋势图

第27章 基本面量化实战(续)
27.4 财务数据可视化:杜邦分析图、营收利润趋势图
27.4.1 先讲个真实需求:老板要我把枯燥的财务数据做成能看懂的图
之前做基本面分析,给老板发几十页的财务数据表格,老板看一眼就扔回来,说“你就告诉我这家公司好不好,好在哪,别给我看数字”。后来我把财务数据做成两张图:一张杜邦分析图,把ROE拆解成盈利能力、运营效率、杠杆水平三个维度,一眼就能看出公司赚的是什么钱;一张营收利润趋势图,把近5年的营收、净利润、现金流走势放在一起,一眼就能看出公司的增长稳不稳。老板看了直接说以后所有分析报告都要配这两张图,省得他猜。今天我就把这两张最vb.net教程C#教程python教程SQL教程access 2010教程实用的财务可视化图表的制作方法讲清楚,帮你把财务分析做的直观易懂。

27.4.2 核心概念:两张图的分析逻辑

  1. 杜邦分析图
    把核心指标ROE层层拆解,找到公司盈利的核心驱动因素:
    ROE = 销售净利率 × 总资产周转率 × 权益乘数
    销售净利率高:说明公司产品赚钱,是“茅台型”公司,靠高毛利盈利
    总资产周转率高:说明公司运营效率高,是“沃尔玛型”公司,靠薄利多销盈利
    权益乘数高:说明公司用杠杆赚钱,是“银行型”公司,靠借别人的钱赚钱
  2. 营收利润趋势图
    把近3-5年的营收、净利润、经营现金流的同比增速放在一张图里,看增长的一致性和质量:
    三者同步增长:说明增长质量高,真金白银赚到钱
    净利润增速远高于营收增速:可能是靠压缩费用、卖资产,增长不可持续
    现金流增速远低于净利润增速:说明盈利有水分,可能是大量赊销,钱没收回来

27.4.3 实战:用Python制作财务可视化图表

  1. 实战代码:财务可视化工具
    python
	# 1. 导入需要的库
	import tushare as ts
	import pandas as pd
	import numpy as np
	from datetime import datetime
	import plotly.graph_objects as go
	from plotly.subplots import make_subplots
	import os
	
	# 2. 初始化Tushare
	pro = ts.pro_api()
	
	# 3. 定义财务可视化类
	class FinancialVisualizer:
	def __init__(self):
	self.save_dir = '财务可视化图表'
	os.makedirs(self.save_dir, exist_ok=True)
	
	# 3.1 获取单只股票的财务数据(近5年年报)
	def get_financial_data_for_vis(self, ts_code, year_range=5):
	"""
	获取最近N年的年报财务数据,用于可视化
	"""
	print(f'正在获取{ts_code}近{year_range}年的财务数据...')
	end_year = datetime.now().year - 1 # 取到上一年的年报
	start_year = end_year - year_range + 1
	years = list(range(start_year, end_year + 1))
	all_data = []
	
	for year in years:
	end_date = f'{year}1231'
	# 获取财务指标
	fina = pro.fina_indicator(
	ts_code=ts_code, end_date=end_date,
	fields='end_date,roe,roic,gross_margin,net_profit_margin,turnover_rate,current_ratio,assets_liab_ratio'
	)
	# 获取利润表
	income = pro.income(
	ts_code=ts_code, end_date=end_date,
	fields='end_date,total_revenue,net_profit'
	)
	# 获取现金流量表
	cashflow = pro.cashflow(
	ts_code=ts_code, end_date=end_date,
	fields='end_date,net_operate_cash_flow'
	)
	# 合并数据
	df = fina.merge(income, on='end_date', how='left')
	df = df.merge(cashflow, on='end_date', how='left')
	all_data.append(df)
	
	all_data = pd.concat(all_data, ignore_index=True)
	all_data['end_date'] = pd.to_datetime(all_data['end_date'], format='%Y%m%d')
	all_data['year'] = all_data['end_date'].dt.year
	all_data.sort_values('year', inplace=True)
	
	# 单位转换:营收、净利润、现金流从元转成亿元
	all_data['total_revenue'] = all_data['total_revenue'] / 1e8
	all_data['net_profit'] = all_data['net_profit'] / 1e8
	all_data['net_operate_cash_flow'] = all_data['net_operate_cash_flow'] / 1e8
	
	# 计算同比增速
	all_data['revenue_yoy'] = all_data['total_revenue'].pct_change() * 100
	all_data['profit_yoy'] = all_data['net_profit'].pct_change() * 100
	all_data['cashflow_yoy'] = all_data['net_operate_cash_flow'].pct_change() * 100
	
	# 计算杜邦三因子
	all_data['权益乘数'] = 1 / (1 - all_data['assets_liab_ratio'] / 100)
	all_data['总资产周转率'] = all_data['turnover_rate'] / 100 # 接口返回的是百分比
	all_data['销售净利率'] = all_data['net_profit_margin'] / 100
	
	# 获取股票名称
	self.stock_name = pro.stock_basic(ts_code=ts_code, fields='name').iloc[0]['name']
	self.ts_code = ts_code
	print(f'{self.stock_name}财务数据获取完成,共{len(all_data)}年数据')
	return all_data
	
	# 3.2 绘制杜邦分析图(瀑布图+拆解条形图)
	def plot_dupont_analysis(self, df, save_html=True, show_plot=True):
	"""
	杜邦分析图:展示最近一年ROE的拆解逻辑
	"""
	latest_year = df['year'].iloc[-1]
	latest_data = df[df['year'] == latest_year].iloc[0]
	
	# 杜邦分解计算
	roe = latest_data['roe']
	net_profit_margin = latest_data['net_profit_margin']
	asset_turnover = latest_data['turnover_rate']
	equity_multiplier = (1 / (1 - latest_data['assets_liab_ratio'] / 100)) * 10 # 放大10倍方便展示
	
	# 构建瀑布图数据
	waterfall_measure = ['absolute', 'relative', 'relative', 'relative', 'total']
	waterfall_x = ['净资产收益率(ROE)', '销售净利率', '总资产周转率', '权益乘数', 'ROE(验证)']
	waterfall_y = [
	0,
	net_profit_margin,
	asset_turnover - net_profit_margin,
	equity_multiplier - asset_turnover,
	roe
	]
	waterfall_text = [
	f'{roe:.2f}%',
	f'{net_profit_margin:.2f}%',
	f'{asset_turnover:.2f}次/年',
	f'{equity_multiplier/10:.2f}倍',
	f'{roe:.2f}%'
	]
	
	# 构建杜邦三因子对比条形图(近3年)
	recent_3y = df.tail(3)
	bar_x = recent_3y['year'].astype(str).tolist()
	bar_y1 = recent_3y['net_profit_margin'].tolist()
	bar_y2 = recent_3y['turnover_rate'].tolist()
	bar_y3 = (recent_3y['权益乘数'] * 10).tolist() # 同比例放大
	
	# 组合子图
	fig = make_subplots(
	rows=2, cols=1,
	subplot_titles=(
	f'{latest_year}年杜邦分析拆解(ROE驱动因素)',
	'近3年杜邦三因子变化趋势'
	),
	vertical_spacing=0.2,
	row_heights=[0.6, 0.4]
	)
	
	# 第一层:瀑布图
	fig.add_trace(
	go.Waterfall(
	name='杜邦拆解',
	orientation='v',
	measure=waterfall_measure,
	x=waterfall_x,
	y=waterfall_y,
	text=waterfall_text,
	textposition='outside',
	connector={'line': {'color': 'rgb(63, 63, 63)'}},
	increasing={'marker': {'color': '#FF4B4B'}},
	decreasing={'marker': {'color': '#2ECC71'}},
	totals={'marker': {'color': '#3498DB'}}
	),
	row=1, col=1
	)
	
	# 第二层:条形图
	fig.add_trace(
	go.Bar(name='销售净利率(%)', x=bar_x, y=bar_y1, marker_color='#FF4B4B'),
	row=2, col=1
	)
	fig.add_trace(
	go.Bar(name='总资产周转率(次)', x=bar_x, y=bar_y2, marker_color='#3498DB'),
	row=2, col=1
	)
	fig.add_trace(
	go.Bar(name='权益乘数(×10)', x=bar_x, y=bar_y3, marker_color='#9B59B6'),
	row=2, col=1
	)
	
	# 布局调整
	fig.update_layout(
	title_text=f'{self.stock_name}({self.ts_code}) 杜邦分析报告',
	title_font_size=18,
	height=800,
	width=1000,
	showlegend=True,
	barmode='group'
	)
	fig.update_yaxes(title_text='数值', row=1, col=1)
	fig.update_yaxes(title_text='数值(同比放大)', row=2, col=1)
	
	# 保存与展示
	if save_html:
	filename = f'{self.save_dir}/{self.stock_name}_{latest_year}年杜邦分析.html'
	fig.write_html(filename)
	print(f'杜邦分析图已保存到{filename}')
	if show_plot:
	fig.show()
	return fig
	
	# 3.3 绘制营收利润趋势图(双轴折线+柱状组合图)
	def plot_revenue_profit_trend(self, df, save_html=True, show_plot=True):
	"""
	营收利润趋势图:展示近N年营收、净利润、现金流的绝对值和增速
	"""
	years = df['year'].astype(str).tolist()
	# 绝对值数据(亿元)
	revenue = df['total_revenue'].tolist()
	profit = df['net_profit'].tolist()
	cashflow = df['net_operate_cash_flow'].tolist()
	# 增速数据(%)
	revenue_yoy = df['revenue_yoy'].fillna(0).tolist()
	profit_yoy = df['profit_yoy'].fillna(0).tolist()
	cashflow_yoy = df['cashflow_yoy'].fillna(0).tolist()
	
	# 构建双轴子图
	fig = make_subplots(
	rows=2, cols=1,
	subplot_titles=(
	'营收、净利润、经营现金流绝对值(亿元)',
	'同比增速(%)'
	),
	vertical_spacing=0.15,
	specs=[[{"secondary_y": False}], [{"secondary_y": False}]]
	)
	
	# 第一层:绝对值柱状图
	fig.add_trace(
	go.Bar(name='总营收', x=years, y=revenue, marker_color='#3498DB', opacity=0.8),
	row=1, col=1
	)
	fig.add_trace(
	go.Bar(name='净利润', x=years, y=profit, marker_color='#FF4B4B', opacity=0.8),
	row=1, col=1
	)
	fig.add_trace(
	go.Bar(name='经营现金流', x=years, y=cashflow, marker_color='#2ECC71', opacity=0.8),
	row=1, col=1
	)
	
	# 第二层:增速折线图
	fig.add_trace(
	go.Scatter(name='营收增速', x=years, y=revenue_yoy, mode='lines+markers', line=dict(color='#3498DB', width=3)),
	row=2, col=1
	)
	fig.add_trace(
	go.Scatter(name='净利润增速', x=years, y=profit_yoy, mode='lines+markers', line=dict(color='#FF4B4B', width=3)),
	row=2, col=1
	)
	fig.add_trace(
	go.Scatter(name='现金流增速', x=years, y=cashflow_yoy, mode='lines+markers', line=dict(color='#2ECC71', width=3)),
	row=2, col=1
	)
	
	# 添加增速为0的参考线
	fig.add_hline(y=0, line_dash="dash", line_color="gray", row=2, col=1)
	
	# 布局调整
	fig.update_layout(
	title_text=f'{self.stock_name}({self.ts_code}) {min(years)}-{max(years)}年营收利润趋势',
	title_font_size=18,
	height=700,
	width=1000,
	barmode='group',
	legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
	)
	fig.update_yaxes(title_text='金额(亿元)', row=1, col=1)
	fig.update_yaxes(title_text='增速(%)', row=2, col=1)
	
	# 保存与展示
	if save_html:
	filename = f'{self.save_dir}/{self.stock_name}_{min(years)}-{max(years)}年营收利润趋势.html'
	fig.write_html(filename)
	print(f'营收利润趋势图已保存到{filename}')
	if show_plot:
	fig.show()
	return fig
	
	# 3.4 一键生成完整财务分析报告
	def generate_full_report(self, ts_code, year_range=5):
	"""
	一键生成杜邦分析+营收利润趋势的完整报告
	"""
	df = self.get_financial_data_for_vis(ts_code, year_range)
	self.plot_dupont_analysis(df)
	self.plot_revenue_profit_trend(df)
	print(f'{self.stock_name}完整财务分析报告生成完成!')
	return df
	
	# 4. 执行可视化
	if __name__ == '__main__':
	visualizer = FinancialVisualizer()
	# 生成贵州茅台的财务分析报告
	moutai_data = visualizer.generate_full_report(ts_code='600519.SH', year_range=5)
	# 也可以单独生成某一张图
	# visualizer.plot_dupont_analysis(moutai_data)
	# visualizer.plot_revenue_profit_trend(moutai_data)

逐行讲解:
get_financial_data_for_vis():获取指定股票最近N年的年报数据,自动计算同比增速和杜邦三因子,统一单位为亿元,避免数值过大不好展示。
plot_dupont_analysis():绘制杜邦分析图,分上下两层:
上层是瀑布图,直观展示ROE是怎么从三个因子乘出来的,一眼就能看出哪个因子是主要驱动
下层是近3年的三因子对比条形图,看驱动因素有没有变化,比如是不是靠加杠杆拉高了ROE
注意权益乘数放大了10倍,不然数值太小在图上看不到,标注里会说明放大倍数,不影响理解
plot_revenue_profit_trend():绘制营收利润趋势图,也是上下两层:
上层是柱状图,看每年的营收、净利润、现金流绝对值规模,直观看到公司的体量变化
下层是折线图,看三者的增速,重点看是不是同步增长,有没有背离的情况
加了增速为0的参考虚线,低于虚线就是负增长,一眼就能看到哪年增长下滑了
generate_full_report():一键生成两张图,不用单独调用,适合批量生成多个公司的分析报告。
运行结果:
生成两个交互式HTML文件,打开后可以放大缩小,鼠标悬停能看到具体数值,汇报的时候投影出来非常直观,不用再解释一堆数字。

27.4.4 基础知识拓展:财务可视化的分析技巧

  1. 杜邦分析图的三种典型模式
模式 特征 代表行业/公司 风险点
高利润型 销售净利率>20%,周转率和乘数低 白酒、医药(茅台、恒瑞) 毛利率下滑风险,一旦竞争加剧利润会快速下跌
高周转型 总资产周转率>1次/年,净利率低 零售、物流(沃尔玛、顺丰) 运营效率下滑风险,一旦成本上升利润会被挤压
高杠杆型 权益乘数>3倍,净利率和周转率低 银行、地产、基建 债务风险,一旦利率上升或者行业下行,会出现资不抵债

重点警惕:三个因子都不突出,ROE却很高的公司,大概率是财务造假,比如早年的康美药业,杜邦三因子都很普通,ROE却常年保持20%+,后来证明是造假。
2. 营收利润趋势图的危险信号
连续两年净利润增速低于营收增速:说明公司的盈利能力在下降,可能是行业竞争加剧,降价促销,或者成本上升。
现金流连续两年低于净利润:说明公司的钱都压在应收账款和存货上,赚的是纸面利润,一旦下游客户倒闭,会出现大额坏账。
营收增速突然跳升:比往年增速高2-3倍,除非有重大业务突破,否则大概率是关联交易或者虚增收入,要小心。
净利润增速突然为负:第一次出现亏损,尤其是周期股,很可能是行业下行的开始,不要轻易抄底。
3. 可视化的注意事项
不要过度美化:重点是清晰传递信息,不要加太多花里胡哨的特效,反而干扰判断。
单位要统一:所有金额单位要么都是亿元,要么都是万元,不要混着来,容易误导。
异常值要标注:如果某一年增速特别高或者特别低,要在图上标注原因,比如是并购、资产出售还是行业周期。

27.4.5 实战优化:添加财务健康度评分

  1. 实战代码:添加财务评分功能
    python
	# 在FinancialVisualizer类中添加财务评分方法
	def calculate_financial_score(self, df):
	"""
	从盈利能力、成长能力、现金流质量、偿债能力四个维度打分,满分100分
	"""
	latest = df.iloc[-1]
	score = 0
	# 1. 盈利能力(30分):ROE>15%得满分,每低1%扣2分
	roe_score = max(0, min(30, 30 - (15 - latest['roe']) * 2))
	score += roe_score
	# 2. 成长能力(30分):近3年净利润复合增速>20%得满分,每低2%扣3分
	cagr = (df['net_profit'].iloc[-1] / df['net_profit'].iloc[-4]) ** (1/3) - 1
	cagr_score = max(0, min(30, 30 - (20 - cagr*100)/2 *3))
	score += cagr_score
	# 3. 现金流质量(20分):近3年平均现金流/净利润>1得满分,每低0.1扣2分
	cash_ratio = (df['net_operate_cash_flow'].tail(3) / df['net_profit'].tail(3)).mean()
	cash_score = max(0, min(20, 20 - (1 - cash_ratio)/0.1 *2))
	score += cash_score
	# 4. 偿债能力(20分):资产负债率<50%得满分,每高5%扣2分
	debt_score = max(0, min(20, 20 - (latest['assets_liab_ratio'] - 50)/5 *2))
	score += debt_score
	
	# 生成评级
	if score >= 80:
	rating = '优秀'
	elif score >= 60:
	rating = '一般'
	else:
	rating = '较差'
	
	print(f'{self.stock_name}财务健康度评分:{score:.1f}分,评级:{rating}')
	# 保存评分结果
	pd.DataFrame({
	'维度': ['盈利能力', '成长能力', '现金流质量', '偿债能力', '总分', '评级'],
	'得分': [roe_score, cagr_score, cash_score, debt_score, round(score,1), rating]
	}).to_csv(f'{self.save_dir}/{self.stock_name}_财务评分.csv', index=False, encoding='utf-8-sig')
	return score, rating
	
	# 修改generate_full_report方法,加入评分
	def generate_full_report(self, ts_code, year_range=5):
	df = self.get_financial_data_for_vis(ts_code, year_range)
	self.calculate_financial_score(df) # 新增评分
	self.plot_dupont_analysis(df)
	self.plot_revenue_profit_trend(df)
	print(f'{self.stock_name}完整财务分析报告生成完成!')
	return df

优化效果:
生成报告的时候会自动给公司打个分,80分以上的是优秀公司,60分以下的要小心,不用自己再去对比各个指标,快速筛选优质公司。

27.4.6 总结:财务可视化的应用场景
1.个股研究:研究一家公司的时候,先看这两张图,5分钟就能搞懂公司的盈利模式和增长情况,比看几十页财报快很多。
2.行业对比:把同一个行业的几家公司的图放在一起对比,一眼就能看出哪个是行业龙头,哪个竞争力弱。
3.投资汇报:给客户或者老板汇报的时候,用这两张图代替枯燥的表格,对方一眼就能看懂,不用你费半天劲解释。
4.持仓跟踪:每个季度给持仓股生成一次报告,看基本面有没有变化,一旦出现危险信号及时卖出。
通过这个实战,你应该已经掌握了最实用的财务可视化方法,以后做财务分析再也不用发一堆没人看的表格了。接下来我们就讲如何把这些基本面分析工具整合起来,做一个自动化的基本面选股系统。
下一节咱们就讲自动化基本面研究平台的搭建。

转载请注明出处:https://www.xin3721.com/ArticlePrograme/csharp49726.html


相关教程