KMV模型与违约距离:新能源上市公司信用风险度量实战
2026/9/17 15:25:06 网站建设 项目流程

简介:这份资源为《基于KMV模型的我国中部地区新能源上市企业信用风险度量及分析》学术论文PDF,面向金融风险管理、企业财务与产业经济方向的研究者、高校师生及从业者,聚焦新能源上市企业信用风险评估这一细分议题。全文以KMV模型为主线,先梳理信用风险的定义与分类,再介绍该模型的基本原理,随后以湖北、湖南、江西、安徽、河南、山西六省若干新能源上市企业为样本展开实证测度,测算其信用风险水平并讨论影响因素,最终提出将风险管理纳入业务流程、健全财务信息披露、推动风险评估量化等建议。资源包仅含1个PDF文件,体积约375KB,为期刊论文全文,摘要、关键词、中图分类号、基金项目及作者简介等信息完整,便于检索与引用。目前已有256人学习下载,适合作为新能源信用风险课题的文献参考与论文写作范本。

1. 从18家新能源上市公司的违约距离说起

2016年一季度末,中部六省在沪深两市挂牌的新能源上市企业一共18家。把这批公司的月末股价和资产负债表放到一起算一件事——违约距离DD——结果没有一家超过2,最紧的康欣新材只有0.877,对应的理论预期违约率接近19%。这个数字放在一个被政策连续加码的行业里是有点反直觉的:市场给新能源的估值一直在抬,但用市场化的量化口径量出来,这批企业的信用风险反而普遍偏高。

《基于KMV模型的我国中部地区新能源上市企业信用风险度量及分析》这份材料做的就是把Merton的期权定价框架套到这批企业身上:把股权看成以企业资产为标的的看涨期权,用股权价值波动率反解资产市场价值和资产价值波动率,再拿资产价值和违约点之间的距离除以资产波动率,得到违约距离和预期违约率EDF。选KMV而不是CreditMetrics或者Z值评分,理由很实际——输入只要股价和财务报表,能按月滚动更新,计算结果对真实信用状况的变化跟得上。

适合谁看:做信用评分卡和评级系统落地的、要把论文里的公式变成能跑脚本的、以及需要理解违约距离这个指标边界在哪里的从业者。

2. KMV模型的期权定价内核与方程组反解

2.1 股权为什么被当成看涨期权

Merton框架的核心假设只有一条:企业资产价值V低于负债D时,股东会选择不履约。

债务到期那天有两种情况。V大于D,股东相当于执行了一个以资产为标的、以负债为执行价的看涨期权,拿到V减D的剩余价值;V小于D,股东放弃履约,损失以已投入的股权价值为上限,不会倒贴。所以股权价值E天然等于一份欧式看涨期权的价格,可以直接套Black-Scholes-Merton公式。KMV在这个基础上进一步假设:违约风险由三个量决定——资产市场价值、资产市场价值波动率、负债价值。

模型的输入和输出可以列成一张对照表,这张表决定了后面所有代码的字段设计。

符号含义本案例取法
E股权市场价值总股本 × 期末收盘价
D负债规模流动负债 + 非流动负债
V资产市场价值待求解的未知数
σE股权价值年化波动率月末收盘价对数收益率年化
σV资产价值年化波动率待求解的未知数
T债务期限一般取 1 年
r无风险利率商业银行一年期定期存款利率
SD / LD短期 / 长期负债流动负债 / 非流动负债
DP预期违约点SD + 0.5 × LD

表里前两个未知数是关键:E、σE、D、T、r这五个量全部可观测,方程只有两个,刚好把V和σV反解出来。这也是KMV相对其他结构化模型最讨喜的地方——不需要历史违约数据库就能起步。

2.2 两个方程与迭代求解的代码实现

方程组本身不复杂。第一个是BSM定价公式,第二个来自伊藤引理——它描述股权波动率和资产波动率之间的传递关系,本质上是杠杆效应的数学表达。

import numpy as np from scipy.stats import norm from scipy.optimize import fsolve def solve_kmv(E, sigma_E, D, r, T=1.0, DP=None): """ E : 股权市场价值(元) sigma_E : 股权价值年化波动率(小数,例如 0.671) D : 进入期权定价方程的负债规模(元),一般取总负债 r : 无风险利率(小数) T : 债务期限(年) DP : 预期违约点,缺省时退化为 D """ if DP is None: DP = D def equations(x): V, sigma_V = x # BSM 中间变量 d1 = (np.log(V / D) + (r + 0.5 * sigma_V ** 2) * T) / (sigma_V * np.sqrt(T)) d2 = d1 - sigma_V * np.sqrt(T) # 方程一:看涨期权定价 E = V*N(d1) - D*e^{-rT}*N(d2) f1 = V * norm.cdf(d1) - D * np.exp(-r * T) * norm.cdf(d2) - E # 方程二:波动率传递关系 sigma_E = (V/E) * N(d1) * sigma_V f2 = (V / E) * norm.cdf(d1) * sigma_V - sigma_E return [f1, f2] V0 = E + D # 初值一:资产约等于股权加负债 sV0 = sigma_E * E / (E + D) # 初值二:按权益占比折算波动率 V, sigma_V = fsolve(equations, [V0, sV0], factor=0.1) DD = (V - DP) / (V * sigma_V) # 违约距离 EDF = norm.cdf(-DD) # 理论预期违约率 return dict(V=V, sigma_V=sigma_V, DD=DD, EDF=EDF)

fsolve 内部走的是MINPACK的hybrd混合算法,对初值敏感。V0取E加D、sV0按权益占比折算是行业里比较稳的起点;factor=0.1把首次试探步长压小,能明显减少在企业负债率极高时发散的情况。两个方程两个未知数,理论上解唯一,但数值上仍要加一道校验:解出来的V必须大于E,σV必须小于σE。如果出现V小于E,基本可以断定σE输入有问题,先回去查波动率那一段。

注意:f2 也可以写成 σV = σE × E / (V × N(d1)),两种写法数学等价,但迭代过程中前者不会出现除以N(d1)导致的除零,数值上更稳。

2.3 违约点、违约距离与EDF的换算

违约点DP不是总负债。KMV的经验做法是流动负债全额计入,长期负债只计一半,理由是流动负债到期即需偿付,而长期负债的到期日分散在若干年之后,短期内形成的偿付压力没有账面数字那么大。

拿到DP之后,违约距离定义为资产期望值减去违约点,再除以资产价值乘以资产波动率。这个量的经济含义很直白:资产价值每波动一个标准差,距离违约点还剩几个标准差。DD等于2,意思是资产要向下走两个标准差才碰到违约点;DD等于0.877,意思是不到一个标准差的距离。

EDF理论上由标准正态分布函数给出,直接取负DD的累积概率。这一步是所有分歧的来源——它假设资产收益服从正态分布,而真实市场的尾部明显更厚,所以理论EDF系统性低估极端违约事件。

2.4 参数取值与三种常见误用

无风险利率r的取法会直接影响结果。论文用的是商业银行一年期定期存款利率,这是国内文献的主流选择;换成一年期国债收益率或SHIBOR,反解出的V和σV会有差异,因为r同时出现在d1的漂移项和贴现因子e^(-rT)里。但DD的排序通常很稳定,做同业比较时不必在这个参数上过度纠结。

债务期限T一般固定取1年。取0.5年会让σV乘上更大的系数,DD整体下移,横截面排序基本不变。

三种反复出现的误用值得单独点出来。第一种是把σE当成σV直接代入,跳过了第二个方程——因为V大于E但N(d1)小于1,资产波动率一定小于股权波动率,直接替换会高估σV、低估DD。第二种是用总负债D算违约距离,而不是用SD加0.5倍LD,这会把DD系统性抬高一截,风险看起来比实际小。第三种是波动率用日频收益率算完直接乘√252,但模型其余部分是按月频数据构建的,频次不一致会让股权价值E的时间口径和波动率口径对不上。

3. 股权价值波动率的数据管道与停牌处理

3.1 对数收益率与年化的推导

KMV假设股价服从对数正态分布,所以收益率必须用对数形式定义:第i个月末价格除以第i减1个月末价格,再取自然对数。用简单收益率会破坏可加性,月度收益率加总不等于区间收益率,年化就无从谈起。

月度波动率按样本标准差公式计算,分母取n减1做无偏修正。年化这一步看着简单,实际有个前提常被忽略:对数收益率需要近似独立同分布,方差才具有可加性,σ²年等于12倍的σ²月,开方之后年化系数就是√12。如果用的是周频数据,换成√52。

3.2 从收盘价到年化波动率的实现

import numpy as np import pandas as pd def annualized_vol(close, freq=12): """ close : 按月末排列的复权收盘价,pd.Series,索引为日期 freq : 一年包含的期数,月频 12,周频 52 返回 : 对数收益率序列、周期波动率、年化波动率 """ s = pd.Series(close).astype(float).dropna() u = np.log(s / s.shift(1)).dropna() # 对数收益率 n = len(u) u_bar = u.mean() sigma_period = np.sqrt(((u - u_bar) ** 2).sum() / (n - 1)) sigma_annual = sigma_period * np.sqrt(freq) return u, sigma_period, sigma_annual # 逐只股票计算 vol_table = {} for name, px in price_dict.items(): _, _, sv = annualized_vol(px, freq=12) vol_table[name] = sv

代码里两个细节值得说。s.shift(1)生成滞后一期价格,相除取对数得到对数收益率;(n - 1)是无偏修正,样本量小于30时这两者差别很可观。样本区间取2015年3月末到2016年3月末,实际只落到12个收益率观测值,波动率估计的标准误不小,这一点后面解读结果时要记在心上。

提示:价格必须用复权价。新能源板块在这段区间内分红送转不少,用不复权价会把除权当跌幅算进波动率,σE会被虚高。

3.3 停牌样本的识别与三种补救方案

18家样本里有4家在这个区间内停过牌:泰豪科技、江特电机、湘电股份、四创电子。停牌对模型的影响机制很明确:停牌期间没有成交,价格被冻结在停牌前一个交易日的水平,收益率序列里填进去一串零,样本标准差被系统性压小。

压小σE之后,方程二会解出一个偏小的σV,而DD等于资产价值减违约点再除以资产价值乘σV,分母变小,DD被抬高——模型算出来这批公司反而更安全。这正是论文里那句"停牌提升了企业的信用风险"背后的口径问题:不是说停牌本身让DD变小,而是停牌让模型算不准,波动率失真之后的读数不可信。

企业停牌情形对σE的影响处理建议
泰豪科技区间内阶段性停牌零收益拉低样本标准差剔除停牌段重算
江特电机同上同上改用GARCH条件波动率
湘电股份同上同上单独标注,不参与排序
四创电子同上同上单独标注,不参与排序

实操上我一般按三步走。第一步最省事,直接从收益率序列里剔除停牌区间,用停牌前后连续交易日重算,缺点是样本量进一步缩水。第二步用GARCH拟合条件方差,给近期波动更大权重,对停牌前后的波动聚集刻画更好;arch包里几行就能跑。

from arch import arch_model import numpy as np # u 为对数收益率序列(小数形式) am = arch_model(u * 100, vol='Garch', p=1, q=1, dist='t') res = am.fit(disp='off') sigma_t = res.conditional_volatility / 100 # 还原成小数 sigma_next = np.sqrt(res.forecast(horizon=1).variance.iloc[-1, 0]) / 100

乘以100是arch包的数值稳定性惯例,dist='t'针对新能源股票收益的厚尾特征。第三步是兜底:如果停牌时间过长,历史区间内根本没几个有效交易日,就别硬算了,把这几个样本拎出来单独说明,不和其他样本放在一张表里排序。

3.4 股权价值E的口径选择

E的取法看着最没争议,实际上坑不少。总股本乘期末收盘价是最常用的口径,但要先确认三件事:限售股和非流通股是否按流通股价格计价,A加H两地上市的按哪个市场价格折算,区间内是否有增发或回购导致股本变动。

还有一个容易忽略的点:论文取的是2015年二季度到2016年一季度的股权总价值均值。用区间均值会把波动率抹平——市值本身在波动,取均值等于人为做了一次平滑,和后面用月末序列算出来的σE口径对不上。我个人的做法是E取评估时点当期的期末市值,保持和波动率的时间口径一致,宁可数据糙一点,也别让两个模块的输入互相打架。

4. 中部六省18家企业的实证计算与结果解读

4.1 违约点数据的整理

先把每家企业2016年3月末的资产负债数据拉出来,按SD加0.5倍LD算出违约点。这一步用Excel或者pandas都行,关键是列名统一,后面批量求解时直接按列名取。

企业股权价值 E(元)流动负债 SD(元)长期负债 LD(元)违约点 DP(元)
泰豪科技6,847,157,5003,483,810,000618,443,0003,793,031,500
赣能股份6,335,607,5001,364,780,0001,420,680,0002,075,120,000
江特电机5,498,770,0004,026,450,000251,123,0004,152,011,500
赣锋锂业2,556,457,500772,713,000259,524,000902,475,000
易成新能5,546,712,5001,615,820,000704,796,0001,968,218,000
漳泽电力32,807,400,00011,966,800,00014,217,400,00019,075,500,000
阳光电源6,407,750,0004,585,530,00057,047,9004,614,053,950
美菱电器9,951,372,5005,609,440,000802,272,0006,010,576,000
国轩高科6,200,447,5003,083,890,0001,170,950,0003,669,365,000

漳泽电力和美菱电器的量级明显大于其他样本,原因是股本规模和企业性质差异。这会直接影响后面DD的横截面比较,但DD本身是标准化过的比值,量级差异不会污染排序。

4.2 批量求解的代码组织

单次调用和批量调用之间只差一个循环,但字段口径必须分清楚:进期权方程的是总负债SD加LD,进违约距离的是违约点DP。这两者混用是新手最容易犯的错。

import pandas as pd r = 0.015 # 一年期定期存款利率,按样本期实际值替换 T = 1.0 rows = [] for _, row in df.iterrows(): total_debt = row['SD'] + row['LD'] # 期权方程用的负债 dp = row['SD'] + 0.5 * row['LD'] # 违约距离用的违约点 res = solve_kmv(E=row['E'], sigma_E=row['sigma_E'], D=total_debt, r=r, T=T, DP=dp) rows.append({'name': row['name'], **res}) out = pd.DataFrame(rows).sort_values('DD') # 按DD升序,风险高的排前面 print(out[['name', 'sigma_V', 'DD', 'EDF']].round(4))

按DD升序排序是为了让最危险的样本落在表格最上面,做报告时直接截前几行就能讲。求解过程中如果出现某个样本不收敛,不要直接丢掉——先看它的σE是不是异常小或者异常大,多数情况下问题出在波动率输入而不是求解器本身。

4.3 结果表与三条解读

把18家企业的股权波动率、资产波动率、违约距离和理论违约率整理到一起:

企业σE 年化σV 资产DDEDF(%)
康欣新材1.1020.8630.87719.04
湖北能源0.9650.7451.01415.53
江特电机0.9090.5451.03215.11
赣锋锂业0.9320.7021.04914.70
四创电子0.8960.5671.06214.41
光电股份0.8960.6301.08413.93
湘电股份0.8560.4971.10413.48
易成新能0.8170.6101.20711.37
天茂集团0.7340.2971.25910.41
阳光电源0.7640.4561.26610.28
中粮生化0.7650.5011.27810.07
赣能股份0.7230.5481.3728.51
漳泽电力0.6930.4441.4177.82
泰豪科技0.6710.4371.4687.11
国轩高科0.6300.4001.5645.89
*ST山煤0.6020.3441.6305.15
宏发股份0.6160.4721.6145.33
美菱电器0.5380.3381.8373.31

第一条结论是资产市场价值普遍高于股权价值。这不是巧合——反解出的V等于股东权益加上负债的市场化估值,只要负债是正的且模型收敛,V必然大于E。真正有信息量的是V相对于E加账面负债的偏离幅度,偏离越大说明资产的市场估值越高于账面,增值空间越大。

第二条是违约距离和资产波动率大致成反比。看表里σV最大的一批,康欣新材0.863对应DD 0.877,湖北能源0.745对应DD 1.014,基本都是波动率上去了DD就下来。但反过来不成立:美菱电器的σE是所有样本里最低的0.538,DD却最高,看起来最安全——而股价稳定本身不能直接推出信用风险低,可能是流动性差、成交清淡造成的低波动,这时候低波动率是一种噪音,不是一种优点。

第三条是所有样本的DD都小于2,康欣新材甚至不到1。结合同期其他行业上市公司的测算结果,中部地区新能源上市企业的整体信用风险偏高,经营状况不够稳定。这个结论在DD这个相对指标上是站得住的。

4.4 结论的适用边界

DD小于2不等于会违约。KMV的经验阈值本身是相对的,取决于同业分布和市场基准,把2当成硬门槛去套所有行业是误读。

EDF等于N(-DD)给出的是理论值,不是经验值。真实的经验违约率需要在海量历史违约样本上建立DD到违约频率的映射函数,国内缺少公开的违约数据库,理论EDF只能当排序指标用,读成绝对概率会出偏差。

样本量也要摆出来:18家企业、12个月、4家有停牌。参数误差和样本误差叠加之后,数值本身的可信区间相当宽,排序比具体数字可靠得多。这份材料里最有价值的部分其实是方法和参数口径,不是那几张具体的表。

5. 违约点系数调优与EDF经验映射的落地技巧

5.1 违约点系数的敏感性扫描

违约点写成SD加0.5倍LD只是KMV的经验默认值。国内有文献用穷举法搜索系数组合,得到过(1.8, 1.2)这样的结果,判据是模型既能把风险企业识别出来,又不至于把正常企业误判成高风险。

好消息是扫描这个系数非常便宜:资产价值和资产波动率已经由方程组解出,改变DP只会让DD整体平移,不需要重新迭代。

import numpy as np import pandas as pd def dd_under_weights(df, V, sigma_V, alpha, beta): """ df : 含 SD / LD 列的样本表 V : 已解出的资产市场价值 sigma_V : 已解出的资产波动率 alpha : 流动负债在违约点中的权重 beta : 长期负债在违约点中的权重 """ DP = alpha * df['SD'] + beta * df['LD'] return (V - DP) / (V * sigma_V) rows = [] for a in (1.0, 1.5, 1.8): for b in (0.2, 0.5, 1.0, 1.2): DD = dd_under_weights(df, V_series, sigmaV_series, a, b) rows.append({'alpha': a, 'beta': b, 'DD_mean': round(DD.mean(), 3), 'DD_min': round(DD.min(), 3), 'DD_max': round(DD.max(), 3)}) print(pd.DataFrame(rows))

扫描结果主要看两件事:DD均值的移动方向,以及横截面排序是否稳定。如果换个系数组合排序就大乱,说明样本里企业的负债结构差异太大,这时候该做的不是继续调系数,而是按负债结构分组,组内单独定系数。判据方面,实操里比"识别准确又不过度误判"更具体的做法是:拿DD做打分,用事后是否出现ST或退市作为标签,算ROC曲线下的面积,哪个系数组合的AUC高就用哪个。

5.2 从理论EDF到经验EDF的分桶映射

理论EDF在样本量小的时候直接读绝对概率会失真,常见做法是在历史样本上把DD分桶,统计每桶的实际违约频率,再对桶边界做单调插值。

import numpy as np def empirical_edf(dd_history, default_flag, bins=8): """ dd_history : 历史样本的违约距离数组 default_flag : 对应的是否违约标签(1/0) 返回 : 分桶边界与每桶的经验违约率 """ edges = np.quantile(dd_history, np.linspace(0, 1, bins + 1)) edges[0], edges[-1] = -np.inf, np.inf idx = np.digitize(dd_history, edges[1:-1]) rates = [default_flag[idx == k].mean() if (idx == k).any() else np.nan for k in range(bins)] return edges, np.array(rates)

用分位数而不是等宽切分,是为了保证每桶样本量大致均衡。没有真实违约样本时,可以用ST或退市作为代理标签,国内文献里这种做法很常见,但必须在报告里写明代理变量的定义和它的局限——被ST的原因可能是财务造假或者连续亏损,跟信用违约不是一回事。

分桶数bins别拍脑袋定。样本量在500以下时取5到8桶,落到某一桶里的样本少于30个就把相邻桶合并,否则单桶违约率的估计误差会比DD本身的误差还大,最后算出来的经验EDF曲线在尾部会剧烈抖动,看着像有信息量,实际全是噪音。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询