简介面向金融领域研究者、监管机构及金融网络分析学者这份资源提供基于DebtRank模型的多层银行网络风险传染复现资料可解决系统性风险传染评估与政策模拟问题。资源包仅含1个PDF文件大小约1002KB以中国银行业为背景完整覆盖三层网络构建、冲击模拟与代码逐段解释。作者设计银行间借贷、交叉持股和共同资产持有三条风险传染渠道模拟个体、行业及宏观经济冲击识别四大国有商业银行为系统重要性机构并指出制造业、交通运输业贷款和个人信贷需重点关注。已有152人学习适合从事金融风险监测、政策模拟或学术复现的读者。内容还扩展了非线性叠加效应表达、资产价格反馈机制和系统重要性识别算法可帮助读者深入掌握多层网络建模与DebtRank应用并基于Python自行搭建评估系统。1. 金融系统性风险传染模型从单层网络到三层网络的认知升级一个银行即使从未在银行间市场拆借过一分钱也可能被同行的坏账拖垮——传染路径藏在共同资产持有里而单层网络模型看不到这条路径。这篇基于中国银行业实证的论文核心贡献是把传统的单层银行间借贷网络扩展成三层结构银行间借贷、交叉持股、共同资产持有并把经典的DebtRank算法改造成三层非线性叠加版本。配套的Python代码包完整实现了从网络构建、DebtRank传染计算、三类冲击模拟到系统重要性银行识别的全流程每段代码都带中文注释和参数说明。对金融监管研究员、做压力测试的风控从业者以及研究复杂网络应用的人来说这套代码是直接能跑、能改、能接真实数据的底座。2. 单层网络的盲区为什么一定要引入多层结构2.1 直接连接与间接连接两种网络构建思路的分野在系统性风险研究里网络构建方式大致分成两条技术路线。第一条是基于真实连接的网络构建直接观测银行间的业务和交易关系比如利用银行间市场交易数据画出拆借关系图。这条路的数据可得性高结果直观但只能反映部分风险传染渠道——它天然忽略了银行之间通过其他业务形成的隐性关联。第二条是基于间接连接的网络构建不直接看业务关系而是通过股价波动传导、股价相关性、投资业务相似度这些间接信号来推断银行间的关联强度。这条路能捕捉到市场心理和协同行为但网络构建方法更复杂且推断出来的边带有较强假设。这篇论文走的是一条折中路线既不放弃直接连接的信息又把间接连接纳入统一框架。它把中国银行业的真实关联拆成三个可观测的维度——银行间借贷、交叉持股、共同资产持有。这三个维度恰好覆盖了金融网络中最重要的三种相互作用债权债务关系带来的直接传染、股权纽带带来的资产端风险传递、以及共同持仓带来的间接传染当一个银行抛售资产导致价格下跌持有相同资产的银行跟着受损。单看任何一层都不完整三层叠在一起才逼近真实系统。2.2 三个邻接矩阵的定义与数据来源模型的核心数据结构是三个n_banks × n_banks的邻接矩阵对应代码里的interbank_network、cross_holding_network、common_asset_network。第一个矩阵是银行间借贷网络interbank_network[i][j]表示银行i对银行j的拆借敞口通常是按银行间市场成交数据聚合出来的方向性很强所以代码里用有向图处理。第二个矩阵是交叉持股网络cross_holding_network[i][j]表示银行i持有银行j的股权比例来自上市公司股东数据库Wind或CSMAR都能拿到同样是有向的。第三个矩阵是共同资产持有网络common_asset_network[i][j]代表银行i和银行j在投资组合中的重叠程度由银行资产负债表中的投资持仓数据计算出来本质是无向的所以代码里把它强制构造成对称矩阵并对角线置零。这三个矩阵的构建口径直接影响后面的传染计算结果也是实际应用时最花时间的一步。我一般会建议先做归一化再入模型否则借贷敞口的绝对值可能上亿和持股比例0到1之间的小数放在一起算impact时量纲差异会直接把后两个渠道淹没掉。2.3 非线性叠加三种传染渠道为什么不能简单相加把DebtRank扩展到三层最核心的数学改动是三种传染渠道的合成方式。代码里用的公式是total_impact 1 - (1 - interbank_impact) × (1 - holding_impact) × (1 - asset_impact)。这不等于三个impact简单相加而是一种如果三个独立风险事件同时发生至少发生一个的概率式的合成逻辑。这样做的好处体现在两个地方。第一三个渠道的影响天然存在重叠——同一家银行的损失可能通过借贷渠道传过来又通过共同资产渠道再传一次简单相加会重复计算。第二极端情况下三个渠道同时作用时简单相加可能超过1导致损失比例失真而乘积形式的公式天然把total_impact压在0到1区间内。这样的非线性叠加设计从数学上规避了传染总效应超过100%的尴尬局面。3. DebtRank 扩展到三层网络状态机、冲击向量与损失积累3.1 DebtRank 的基本机制h 与 s 状态向量怎么配合DebtRank的原始思想是用两个向量描述每家银行在传染过程中的状态h是累计损失比例0到1之间s是状态标记0未受影响1受影响但未违约2已违约。这两个向量是配套走的h记录损失数字s记录银行处于哪个阶段后面每一轮迭代都要参照上一轮的状态来决定当前轮怎么算。h np.zeros((self.contagion_steps 1, self.n_banks)) # 银行损失比例 s np.zeros((self.contagion_steps 1, self.n_banks)) # 银行状态 # 初始冲击 h[0] initial_shock s[0] (h[0] 0).astype(int)这段代码对应传染开始前的初始化。h的第一行存的就是初始冲击向量s的第一行通过向量化比较得到——哪家银行初始损失大于0状态就标记为1。注意这里有个隐含设定初次冲击直接打到银行头上时银行先处于受影响状态只有当累计损失在后续传染中超过违约阈值状态才会变成2。初始冲击本身不直接判定违约这是DebtRank与普通阈值模型的一个显著区别。3.2 三个 impact 的逐项计算与分母选择进入传染主循环后每轮迭代对每家未违约银行计算三种渠道的影响。关键代码是这样一段# 1. 银行间借贷影响 interbank_impact np.sum(self.interbank_network[:, i] * h[t-1]) / self.liabilities[i] # 2. 交叉持股影响 holding_impact np.sum(self.cross_holding_network[:, i] * h[t-1]) / self.equity[i] # 3. 共同资产影响 asset_impact np.sum(self.common_asset_network[i, :] * h[t-1]) / self.assets[i]三个等式的结构完全相同都是别人受的损失 × 与我的关联强度求和后再除以一个规模分母。差别在分母的选择借贷渠道除以银行i的总负债因为拆借敞口最终是靠负债端来吸收的持股渠道除以银行i的资本因为股权价值下跌直接影响的是资本金共同资产渠道除以总资产因为资产价格的波动直接打在资产的账面价值上。这个分母选择不是拍脑袋它对应的是损失在资产负债表中实际冲击的科目。如果换成统一的分母三个渠道的传染强度对比就失去意义了。3.3 非线性叠加与状态更新为什么损失比例不会下降三个impact算出来后进入合成与更新阶段# 综合影响(非线性叠加) total_impact 1 - (1 - interbank_impact) * (1 - holding_impact) * (1 - asset_impact) h[t, i] min(h[t-1, i] total_impact * (1 - h[t-1, i]), 1) # 更新银行状态 if h[t, i] self.default_threshold: s[t, i] 2 # 违约 elif h[t, i] 0: s[t, i] 1 # 受影响 else: s[t, i] 0h的更新公式值得细看h[t] h[t-1] total_impact × (1 - h[t-1])。这个形式保证了h单调递增且不会超过1——因为每次加上的增量都乘了(1 - h[t-1])h越接近1能继续增加的空间越小。这意味着模型里不存在银行先受损后恢复的场景损失只积累不清偿这是DebtRank作为系统性风险度量工具的保守性设计。状态更新则完全由h与default_threshold的比较驱动阈值默认0.05即资本损失超过5%就认为违约这篇论文的设定是合理的。4. 三类冲击模拟与系统重要性银行识别代码实现与参数说明4.1 个体银行冲击与行业冲击的差异冲击向量的构造逻辑冲击模拟是整个模型最有实用价值的部分。simulate_shocks函数用shock_type参数区分三类冲击本质区别在初始冲击向量initial_shock的构造方式上if shock_type individual: initial_shock[shock_target] 0.5 # 假设50%的初始冲击 elif shock_type industry: for i in range(self.n_banks): if i in shock_target: initial_shock[i] 0.3 # 30%的初始冲击 elif shock_type macro: initial_shock np.full(self.n_banks, 0.2) # 20%的初始冲击个体银行冲击是把全部冲击集中到一家银行身上冲击强度0.5意味着这家银行初始就损失一半资本模拟单一银行出险的极端情形。行业冲击则需要传入一个银行索引列表凡是该行业涉及的银行都施加0.3的初始损失对应的是某个行业的贷款集中违约场景。宏观经济冲击最简单所有银行一视同仁承受0.2的损失模拟利率突变、宏观政策收紧这类全局性风险。这里0.5、0.3、0.2的数值是论文作者根据中国银行业实际数据校出来的实际使用就该按压力测试的重度来调整。4.2 系统重要性银行识别资产加权损失与阈值判定识别系统重要性银行用的是一种逐个击破策略。对每一家银行单独施加以1.0为强度的完全违约冲击然后把算出来的全系统损失按资产规模加权求和最后归一化systemic_importance np.zeros(self.n_banks) for i in range(self.n_banks): initial_shock np.zeros(self.n_banks) initial_shock[i] 1.0 # 假设该银行完全违约 losses self.calculate_debtrank(initial_shock) systemic_importance[i] np.sum(losses * self.assets) # 以资产加权的总损失 systemic_importance / np.sum(self.assets) return np.where(systemic_importance threshold)[0].tolist()这里有个值得注意的细节np.sum(losses * self.assets)做的是逐银行乘积再求和用的是银行资产规模去放大损失比例资产越大的银行即便自身损失比例不高对系统的绝对冲击也越大。阈值threshold默认0.3含义是该银行违约导致全系统损失的资产加权比例超过30%。在实际监管压力测试中这个阈值往往要降下来配合监管容忍度而且这轮跑完得到的排名稳定性取决于网络矩阵的质量——矩阵如果噪声大排名会漂移。4.3 网络可视化三层网络分开画看传染路径更清晰代码的最后一部分用networkx画三层网络结构图分三个子图并排展示银行间借贷用有向图交叉持股用有向图共同资产持有用无向图。因为三个网络密度差异很大借贷网络往往稀疏而共同资产网络可能接近全连通合在一起画会变成一坨线分开画反而容易看出哪一层是稠密的传染通道。实际应用中我习惯再加一步——用节点大小映射银行的资产规模用边的粗细映射敞口强度这样一眼就能看出哪些大银行处在多个网络的枢纽位置。5. 避坑指南复现多层网络模型最容易翻车的五个地方5.1 三个矩阵量纲差异导致占比失真现象跑出来的losses结果中某个渠道的影响恒定为0或小到可以忽略但数据本身明明有相关性。原因interbank_network的数值可能是几十亿级别的敞口而cross_holding_network里是0.01这种持股比例两个矩阵直接套进impact公式时数值大的矩阵会天然主导结果。代码里除以的是liabilities、equity、assets这些不同量级的分母但矩阵本身的量级差异依然会扭曲三个渠道的贡献对比。解决在build_networks之前先对三个矩阵做标准化。常见的做法是分别按行归一化使每行的和等于1或者按矩阵最大的元素缩放把三个矩阵压到同一个量级区间。我一般会先把矩阵转成百分比口径再进模型。5.2 违约阈值 default_threshold 的敏感性判断失误现象把default_threshold从0.05改到0.1传染结果变化剧烈某些银行从违约直接变回受影响。原因这个阈值不是理论推导出来的是作者依据巴塞尔协议对资本充足率的最低要求设定的经验值。不同国家的监管标准不同改阈值本身合法但系统性风险模型的结论对阈值敏感是正常现象改完阈值后必须重新校准其他参数否则结果不可比。解决每次调整阈值后把传染结果与基准场景做对比确认变化幅度是否在可接受范围内。如果阈值微调就导致结论反转说明模型本身处在临界状态要回头检查网络矩阵的质量而不是继续调阈值。5.3 h 值在传染中途被错误重置现象银行明明已经受了冲击下一轮损失比例却变小了传染过程出现回血。原因这是把多维数组和循环变量的生命周期搞混了。如果某轮循环里直接给h[t, i]赋值而不是累加或者复用了上一轮的数组没有深拷贝h的历史会被覆盖掉。代码里h是一个(contagion_steps1, n_banks)的二维数组行代表轮次列代表银行一旦用错索引维度就会出现时间维度的数据串位。解决跑完传染后把h的每一行打印出来逐轮检查。h[t]里所有元素必须大于等于h[t-1]的对应元素如果有任意一处递减检查更新公式是否被改成直接赋值了。这个检查我每次跑模型都会做是判断传染逻辑是否正确的第一道防线。5.4 共同资产矩阵对称性被破坏现象asset_impact的计算结果与理论不对称——银行A受到B的传染影响和B受到A的影响数值上差很多但代码逻辑上它们应该相等。原因共同资产持有关系天然是对称的银行A和银行B共同持有某个资产A抛售影响BB抛售同样影响A。但实际数据来源比如投资组合重叠度计算很容易因为数据口径不一致产生非对称矩阵。代码里已经有asset_matrix (asset_matrix asset_matrix.T) / 2这一步来强制对称但如果你自己加载真实数据时直接赋值给模型绕过了这个对称化步骤就会踩坑。解决在build_networks之前对共同资产矩阵先做一次对称化并顺手把对角线元素置零。另外检查矩阵对角线上是否残留了大量非零值——对角线非零意味着银行自己传染自己会让模拟结果虚高。5.5 传染步数 contagion_steps 与收敛性的拿捏现象把contagion_steps从10改成50最终损失比例几乎没变化改成5结果却明显偏低。原因DebtRank的传染过程本质上会在有限步内收敛——当所有银行状态都变成2或不再新增受影响银行时再多的步数也只是重复计算。10步在大多数场景下已经够用但如果网络规模变大真实数据可能有数十家银行传染链条变长10步可能不足以让风险传遍全网结果就偏低了。解决每跑一个新的数据集先做一个步数敏感性测试——分别用5、10、20、50跑一遍看最终损失的差异。差异小于0.01%时选较小的步数以节省计算时间差异大说明网络结构里的间接路径较长要适当调大步数上限。这个测试在我调真实数据时每次必做步数的选择直接影响压力测试结论的可靠性。6. 进阶广义均值叠加、资产价格反馈与蒙特卡洛识别基础的DebtRank三层模型跑通之后论文配套代码里还有三层进阶扩展。第一层是改进非线性叠加的数学表达用广义均值函数替代固定的乘积公式p 0.5 # 通过实证数据校准 non_impacts 1 - np.array(impacts) combined np.power(np.mean(np.power(non_impacts, p)), 1/p) return 1 - combinedp是调节渠道间交互强度的关键参数。p趋近负无穷时结果接近最小效应趋近正无穷时接近最大效应p0.5表示三种传染渠道处于中等交互状态。这个参数理论上需要通过历史危机数据校准我在实际使用中会先跑一组p从0.1到1.0的网格搜索观察系统重要性银行的排名稳定性选择排名变化最小的区间。第二层是资产价格反馈机制模拟多家银行同时抛售共同资产时的火灾抛售效应。代码用指数衰减函数描述价格下跌与抛售压力之间的关系fire_sale_pressure越大资产价格下跌比例越接近1但边际效果递减。这一层扩展对捕捉2015年股灾和2022年债券波动这类极端场景特别有用基础模型里资产价格是被动受冲击的加上反馈后资产价格变成内生变量传染过程会出现加速效应。第三层是蒙特卡洛模拟与边际贡献拆解。随机生成上千个冲击场景逐个计算每加一家银行受影响时对总损失的边际贡献把所有场景的结果平均后给出系统重要性排名。这个做法比单点冲击更稳健——单点冲击只能回答某一家银行单独出事的后果蒙特卡洛能覆盖多家银行同时出事的组合情形。我在评估一家银行的系统重要性时如果条件允许都会以蒙特卡洛结果为主单点冲击结果作交叉验证。这套代码从输入数据到监管结论的链路比我预想的短——拿着银行间市场成交数据、股东数据库和资产负债表就能跑通全流程得到的结果能直接支撑三个决策哪些银行要纳入附加监管、哪些行业的贷款需要设定集中度上限、宏观经济冲击的容忍度应该定在什么压力水平。从那以后我再评估金融网络风险都会强制走一遍基础DebtRank、敏感性测试、蒙特卡洛验证这三级流程。希望这个复现思路也能帮你少走一些弯路。本文还有配套的精品资源点击获取