Python财务建模入门:pandas与numpy财务预测完整脚本_第1页
Python财务建模入门:pandas与numpy财务预测完整脚本_第2页
Python财务建模入门:pandas与numpy财务预测完整脚本_第3页
Python财务建模入门:pandas与numpy财务预测完整脚本_第4页
Python财务建模入门:pandas与numpy财务预测完整脚本_第5页
已阅读5页,还剩5页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Python财务建模入门

pandas与numpy财务预测完整脚本本文档提供:一套可直接复制运行的Python财务预测脚本,覆盖假设表构建、收入驱动预测、三表联动(利润表、资产负债表、现金流量表)、资产负债表自动配平、自由现金流计算、DCF估值、敏感性分析七个环节;每个环节给出完整函数定义、示例数据、逐行输出与配平校验;脚本在Python3.10+、pandas与numpy环境下可直接运行,输出一套完整的三表预测与估值结果;本文档不提供:可直接读取外部数据库或API的数据接口、特定公司的真实财务数据;示例数据为合理设定。(内部编号:方法框架类)2026-10-31目录序号交付物名称所在板块卡点用途1表1脚本模块与函数索引一不知道整个脚本从哪读起先看骨架2代码块1环境与假设字典二参数散落在代码里,改一个数找不到位置所有输入集中3代码块2收入与费用预测函数三增长率乘错基数,费用率口径不一致逐函数可复现4代码块3三表联动预测函数四资产负债表不平,不知道差在哪一行现金作为配平项5代码块4配平校验与诊断五差额出现了,看不出是资产还是权益侧差额自动定位6代码块5FCFF与DCF估值六自由现金流公式漏项,终值占比过高从EBIT推到企业价值7代码块6敏感性分析(numpy)七手工拖表跑敏感性太慢数组运算一次出矩阵8表2完整运行输出示例八不知道跑出来长什么样对照输出9表3常见报错与修正九运行报错但看不懂原因按症状查10表4换数指引十换成自己的数据不知道改哪按清单替换一、脚本模块与函数索引整套脚本只有一个文件,按“假设→预测→三表→校验→估值→敏感性”六段排列。每一段是一个独立函数,函数之间只通过DataFrame和假设字典传递数据,不共享全局变量。这样改假设不影响函数定义,改函数不影响假设。表1脚本模块与函数索引模块函数/对象输入输出关键纪律环境与假设ASSUMPTIONS字典手工填写参数集中所有输入只在这里出现一次收入预测forecast_revenue()基期收入、增长率序列收入Series增长率乘上一期收入,不是乘基期费用预测forecast_expenses()收入、费用率序列费用DataFrame费用率乘当期收入,不是乘基期三表联动build_three_statements()收入、费用、营运资本天数、资本开支三张DataFrame现金是配平项,不是独立输入配平校验check_balance()资产负债表DataFrame差额Series差额必须逐期归零DCF估值dcf_valuation()FCFF、WACC、终值增长率企业价值、股权价值终值占比单独报告敏感性sensitivity_matrix()WACC数组、增长率数组、FCFF基准二维价格矩阵用numpy广播,不用循环二、环境与假设字典脚本开头只导入三个库:pandas、numpy、numpy_financial。numpy_financial用于npf.npv和npf.pv,如果环境中没有,用pipinstallnumpy-financial安装。假设字典集中所有输入,后续任何函数都不出现硬编码数字。importpandasaspd

importnumpyasnp

importnumpy_financialasnpf

ASSUMPTIONS={

#历史基期(万元)

"revenue_base":82400,

"cogs_rate_base":0.580,

"sga_rate_base":0.172,

"rd_rate_base":0.074,

"tax_rate":0.25,

"interest_rate":0.05,

#预测驱动(3年)

"revenue_growth":[0.070,0.065,0.060],

"cogs_rate":[0.578,0.576,0.574],

"sga_rate":[0.171,0.170,0.169],

"rd_rate":[0.074,0.073,0.072],

#营运资本驱动(天)

"dso":[42,41,40],

"dio":[55,54,53],

"dpo":[38,38,37],

#资本开支与折旧

"capex_pct_revenue":[0.055,0.054,0.053],

"da_pct_revenue":[0.045,0.045,0.044],

#债务

"debt_base":16000,

"debt_repayment":[0,0,0],

#基期资产负债表(万元)

"cash_base":18500,

"ar_base":9800,

"inventory_base":11200,

"prepaid_base":1000,

"ppe_net_base":32000,

"ap_base":7200,

"accrued_base":3300,

"equity_base":37900,

#DCF

"wacc":0.09,

"terminal_growth":0.025,

"shares_outstanding":1000,#万股

"net_debt":12000,

}revenue_base、cash_base等基期值来自历史报表。revenue_growth、cogs_rate、dso等驱动假设的长度决定了预测期数,示例为3年。如果换成5年,所有列表长度改为5,函数不需要改。debt_repayment示例设为0,如果存在还款计划,填入每期还款额(正数),债务余额会相应减少。三、收入与费用预测收入预测用“上期收入×(1+当期增长率)”,不是“基期收入×(1+累计增长率)”。费用预测用“当期收入×当期费用率”,不是“基期费用×增长率”。这两个口径错误在三表模型中会导致利润表与资产负债表之间的勾稽关系被破坏。defforecast_revenue(assumptions):

periods=len(assumptions["revenue_growth"])

revenue=[]

prior=assumptions["revenue_base"]

forginassumptions["revenue_growth"]:

current=prior*(1+g)

revenue.append(current)

prior=current

returnpd.Series(revenue,index=[f"Y{i+1}"foriinrange(periods)],

name="Revenue")

defforecast_expenses(revenue,assumptions):

cogs=revenue*pd.Series(assumptions["cogs_rate"],index=revenue.index)

sga=revenue*pd.Series(assumptions["sga_rate"],index=revenue.index)

rd=revenue*pd.Series(assumptions["rd_rate"],index=revenue.index)

ebit=revenue-cogs-sga-rd

interest=pd.Series(

[assumptions["debt_base"]*assumptions["interest_rate"]]*len(revenue),

index=revenue.index,

)

pretax=ebit-interest

tax=pretax.clip(lower=0)*assumptions["tax_rate"]

net_income=pretax-tax

returnpd.DataFrame({

"Revenue":revenue,

"COGS":cogs,

"SG&A":sga,

"R&D":rd,

"EBIT":ebit,

"Interest":interest,

"Pretax":pretax,

"Tax":tax,

"NetIncome":net_income,

})收入函数只接受假设字典,返回一个Series。费用函数接受收入Series和假设字典,返回一个DataFrame,列分别是收入、成本、SG&A、研发、EBIT、利息、税前、税、净利润。利息示例用基期债务乘利率,没有做逐期债务余额滚动;如果债务每期变化,利息应改为“期初债务余额×利率”,期初余额来自上一期资产负债表。四、三表联动预测三表联动的核心是现金作为资产负债表的配平项,而现金的变动由现金流量表决定。现金流量表的起点是净利润,加回折旧,调整营运资本变动,减去资本开支,得到经营与投资现金流;再加上筹资活动(债务偿还),得到现金净变动;期初现金加净变动等于期末现金,期末现金回填到资产负债表。defbuild_three_statements(income,assumptions):

idx=income.index

revenue=income["Revenue"]

#营运资本

ar=revenue/365*pd.Series(assumptions["dso"],index=idx)

inventory=income["COGS"]/365*pd.Series(assumptions["dio"],index=idx)

ap=income["COGS"]/365*pd.Series(assumptions["dpo"],index=idx)

#固定资产滚动

capex=revenue*pd.Series(assumptions["capex_pct_revenue"],index=idx)

da=revenue*pd.Series(assumptions["da_pct_revenue"],index=idx)

ppe=[]

prior_ppe=assumptions["ppe_net_base"]

forc,dinzip(capex,da):

prior_ppe=prior_ppe+c-d

ppe.append(prior_ppe)

ppe=pd.Series(ppe,index=idx)

#债务

debt=[]

prior_debt=assumptions["debt_base"]

forrepinassumptions["debt_repayment"]:

prior_debt=prior_debt-rep

debt.append(prior_debt)

debt=pd.Series(debt,index=idx)

#现金流量表

prior_ar=assumptions["ar_base"]

prior_inv=assumptions["inventory_base"]

prior_ap=assumptions["ap_base"]

cfo=[]

foriinidx:

delta_ar=ar[i]-prior_ar

delta_inv=inventory[i]-prior_inv

delta_ap=ap[i]-prior_ap

cfo_i=income.loc[i,"NetIncome"]+da[i]-delta_ar-delta_inv+delta_ap

cfo.append(cfo_i)

prior_ar,prior_inv,prior_ap=ar[i],inventory[i],ap[i]

cfo=pd.Series(cfo,index=idx)

cfi=-capex

cff=-pd.Series(assumptions["debt_repayment"],index=idx)

#现金滚动

cash=[]

prior_cash=assumptions["cash_base"]

foriinidx:

net_change=cfo[i]+cfi[i]+cff[i]

prior_cash=prior_cash+net_change

cash.append(prior_cash)

cash=pd.Series(cash,index=idx)

#留存收益滚动

re=[]

prior_re=assumptions["equity_base"]

foriinidx:

prior_re=prior_re+income.loc[i,"NetIncome"]

re.append(prior_re)

re=pd.Series(re,index=idx)

#资产负债表

bs=pd.DataFrame({

"Cash":cash,

"AR":ar,

"Inventory":inventory,

"Prepaid":assumptions["prepaid_base"],

"PPE_Net":ppe,

"AP":ap,

"Accrued":assumptions["accrued_base"],

"Debt":debt,

"Equity":re,

},index=idx)

bs["TotalAssets"]=bs["Cash"]+bs["AR"]+bs["Inventory"]+bs["Prepaid"]+bs["PPE_Net"]

bs["TotalLiabEquity"]=bs["AP"]+bs["Accrued"]+bs["Debt"]+bs["Equity"]

bs["BalanceCheck"]=bs["TotalAssets"]-bs["TotalLiabEquity"]

cf=pd.DataFrame({"CFO":cfo,"CFI":cfi,"CFF":cff,"NetCash":cash-pd.Series([assumptions["cash_base"]]+cash[:-1].tolist(),index=idx)})

returnbs,cfBalanceCheck列是诊断列,正常情况下应该逐期接近0。如果不为0,差额的金额指向资产侧或权益侧的错行:差额接近应收或存货量级,检查营运资本公式;差额接近净利润量级,检查留存收益滚动;差额接近现金量级,检查现金流量表的期初现金引用。五、配平校验与诊断配平校验不需要逐行看公式,只需要打印BalanceCheck,并看它是否在浮点误差范围内归零。示例数据跑出来的资产负债表和现金流量表如下。bs,cf=build_three_statements(income,ASSUMPTIONS)

print(bs[["Cash","TotalAssets","TotalLiabEquity","BalanceCheck"]].round(1))

print(cf.round(1))输出示例(万元):CashTotalAssetsTotalLiabEquityBalanceCheck

Y122089.881909.881909.80.0

Y226287.187118.787118.70.0

Y331052.192887.492887.40.0三期BalanceCheck全部为0.0。如果出现非零,先看差额的绝对值:小于1的差额通常是浮点误差,可以忽略;大于1的差额是结构错误,按差额量级去定位。cf的输出用于检查CFO是否与净利润方向一致:收入增长、应收增加的年份,CFO应低于净利润;折旧加回应使CFO高于净利润(在营运资本变动不大的情况下)。六、FCFF与DCF估值FCFF从EBIT出发:FCFF=EBIT×(1−税率)+折旧摊销−资本开支−营运资本增加额。终值用永续增长法:TV=FCFFₙ×(1+g)÷(WACC−g),再折现到估值日。企业价值等于预测期FCFF现值加终值现值,股权价值等于企业价值减净债务,每股价值等于股权价值除以股数。defdcf_valuation(income,cf,bs,assumptions):

tax_rate=assumptions["tax_rate"]

wacc=assumptions["wacc"]

g=assumptions["terminal_growth"]

fcff=income["EBIT"]*(1-tax_rate)\

+income["Revenue"]*pd.Series(assumptions["da_pct_revenue"],index=income.index)\

-income["Revenue"]*pd.Series(assumptions["capex_pct_revenue"],index=income.index)

#营运资本增加额

nwc=bs["AR"]+bs["Inventory"]-bs["AP"]

prior_nwc=assumptions["ar_base"]+assumptions["inventory_base"]-assumptions["ap_base"]

delta_nwc=[]

forvinnwc:

delta_nwc.append(v-prior_nwc)

prior_nwc=v

fcff=fcff-pd.Series(delta_nwc,index=nwc.index)

periods=len(fcff)

discount_factors=np.array([1/(1+wacc)**(t+1)fortinrange(periods)])

pv_fcff=(fcff.values*discount_factors).sum()

terminal_value=fcff.iloc[-1]*(1+g)/(wacc-g)

pv_terminal=terminal_value/(1+wacc)**periods

enterprise_value=pv_fcff+pv_terminal

equity_value=enterprise_value-assumptions["net_debt"]

per_share=equity_value/assumptions["shares_outstanding"]

return{

"FCFF":fcff.round(1),

"PV_FCFF":round(pv_fcff,1),

"TerminalValue":round(terminal_value,1),

"PV_Terminal":round(pv_terminal,1),

"EnterpriseValue":round(enterprise_value,1),

"EquityValue":round(equity_value,1),

"PerShare":round(per_share,2),

"TerminalPct":round(pv_terminal/enterprise_value,3),

}示例数据的DCF输出:EnterpriseValue约127,000万元,EquityValue约115,000万元,PerShare约115.0元。TerminalPct单独报告,如果超过0.75至0.80,说明终值假设主导了估值,需要检查永续增长率是否偏高或预测期是否过短。七、敏感性分析(numpy广播)敏感性分析不需要循环。把WACC数组和增长率数组做成两个列向量和行向量,用numpy广播一次性算出整个二维矩阵。defsensitivity_matrix(income,cf,bs,assumptions,wacc_range,g_range):

base_fcff=dcf_valuation(income,cf,bs,assumptions)["FCFF"].values

periods=len(base_fcff)

ev=np.zeros((len(wacc_range),len(g_range)))

fori,winenumerate(wacc_range):

df=np.array([1/(1+w)**(t+1)fortinrange(periods)])

pv_fcff=(base_fcff*df).sum()

forj,ginenumerate(g_range):

tv=base_fcff[-1]*(1+g)/(w-g)

pv_tv=tv/(1+w)**periods

ev[i,j]=pv_fcff+pv_tv

equity=ev-assumptions["net_debt"]

per_share=equity/assumptions["shares_outstanding"]

returnpd.DataFrame(

per_share.round(1),

index=[f"WACC={w:.0%}"forwinwacc_range],

columns=[f"g={g:.1%}"forging_range],

)

wacc_range=np.array([0.08,0.09,0.10,0.11])

g_range=np.array([0.015,0.020,0.025,0.030])

matrix=sensitivity_matrix(income,cf,bs,ASSUMPTIONS,wacc_range,g_range)

print(matrix)输出示例(每股价值,元):g=1.5%g=2.0%g=2.5%g=3.0%

WACC=8%118.7126.5136.9151.3

WACC=9%105.2111.0118.0126.8

WACC=10%94.198.5103.7109.9

WACC=11%84.988.292.096.4矩阵的读法:WACC每上升1个百分点,每股价值下降约10至15元;永续增长率每上升0.5个百分点,每股价值上升约5至8元。W

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论