
简介金融多层网络系统性风险传染模型扩展 DebtRank的论文复现与 Python 实现浓缩在一份 PDF 文档中面向金融研究人员、监管机构人员和金融复杂网络分析者聚焦中国银行业系统性风险量化评估与政策模拟。文档围绕银行间借贷、交叉持股、共同资产持有三层网络展开详细讲解多层银行网络类的构建逻辑、违约阈值、传染步长以及三层渠道对银行损失的影响计算同时模拟了个体银行冲击、行业冲击、宏观经济冲击三类情景识别出中国四大国有商业银行为系统重要性机构并提示制造业、交通运输业及个人信贷领域应重点监测。作者在复现原论文方法的基础上还增强了非线性叠加效应的数学表达加入资产价格反馈机制改进了系统重要性识别算法使模型更贴近实际监管需求。资源为 1 个 PDF 文件约 1002KB代码与注释齐全可直接对照学习或二次开发已有 152 人学习适合需要深入理解多层金融网络风险传染机制的研究者和监管工作人员。1. 多层网络才是系统性风险的真实地图三个传染渠道的建模逻辑当某家大型银行出现危机时真正的威胁往往不是它自身的损失而是它敞开的“第二层关联”。单看资产负债表每家银行都有存贷款、同业拆借和债券持仓单独看都稳健可一旦风险开始跨机构传播危机就不取决于谁最脆弱而取决于谁和谁有联系。这就是为什么“基于多层网络的系统性风险传染模型”在近几年的金融风险管理里反复被提起它把中国银行业拆成一张叠加在资产负债结构之上的多层网络每一层对应一种真实的传染机制再用程序把“一家银行倒下后风险如何逐轮蔓延”完整跑出来。这篇文章面向的读者很明确银行风险管理岗、监管科技方向的研究者以及正在选毕业论文题目的金融工程学生。你会看到三层网络各自刻画什么风险、数据从哪来、传染轮次怎么算以及一套可复现的 Python 模拟器核心代码。重点放在“怎么做”和“坑在哪”不是把模型包装成黑匣子而是打开它让你能照着自己的数据改参数、跑场景。2. 把中国银行业装进三层网络拆借、共同持仓与存款的建模口径2.1 用资产负债表数据把银行变成网络节点系统里最基本的单元是节点每个节点代表一家银行。国内银行业金融机构数量庞大实际建模时一般按照 2560 家的口径来收敛覆盖国有大行、股份制银行、主要城商行和头部农商行即可。数据来源并不神秘上市银行的定期报告、同业存单发行记录、中国货币网披露的同业拆借数据以及银行间市场清算所的可查询信息基本能支撑起一家银行的对内资产与负债字段。每家银行需要抽取的资产负债字段主要有四个同业资产拆出、存放同业、买入返售、同业负债拆入、同业存放、卖出回购、债券投资明细按券种、期限、评级分档、存款结构活期与定期比例。这四个字段分别对应三层网络里的一层也是后续传染模拟的直接输入。工程上常见的做法是先把这些字段清洗成一张以银行为行、以科目为列的宽表再按下面的方式构造成网络矩阵。2.2 拆借层有向加权网络中的债权方向银行间拆借网络是最经典的一层也是直接信用传染的渠道。这里的核心约定是矩阵方向设矩阵 A其中元素 Aij 代表银行 i 对银行 j 的净拆出债权单位为百万元。当银行 j 违约时银行 i 将损失 Aij 乘以违约损失率LGD的部分。所以 A 的行视角是“我是谁的债权人”列视角是“谁欠我的钱”。实际数据中双边拆借明细并不公开只有每家银行的同业资产总额和同业负债总额。常见做法是用 RAS 算法或熵最大化法重建双边头寸矩阵保持每行每列的合计与公开数据一致。代码实现时不必一开始就上复杂的平衡算法用受约束的随机填充也能完成前几次压力测试import numpy as np def build_interbank_layer(interbank_assets, interbank_liabilities, density0.3, seed42): 用银行层面的同业资产和负债总额重建有向拆借矩阵。 行 拆出方债权方列 拆入方债务方 interbank_assets: 数组每家银行的拆出总额 interbank_liabilities: 数组每家银行的拆入总额 density: 拆借关系稀疏度真实同业网络远低于全连接 n len(interbank_assets) rng np.random.default_rng(seed) A np.zeros((n, n)) # 先按稀疏度随机生成网络拓扑再填充金额 mask rng.random((n, n)) density np.fill_diagonal(mask, False) # 自己不能拆给自己 # 用比例法把拆出总额分配出去最后再按列做一次缩放对齐负债 row_sums mask.sum(axis1, keepdimsTrue) row_sums[row_sums 0] 1 A mask * (interbank_assets.reshape(-1, 1) / row_sums) # 列缩放让每列合计约等于该行的同业负债 col_scale interbank_liabilities / np.maximum(A.sum(axis0), 1e-8) A A * col_scale.reshape(1, -1) return A这段代码先随机生成一个二值拓扑再把同业资产按出度均分最后用负债总额做列缩放。要注意的是一次缩放后行和通常不再精确等于原负债工程上会迭代 35 次让两边同时收敛迭代次数属于建模精度和效率的权衡我一般控制在 5 次以内误差小于 1% 即可接受。2.3 证券持有层从共同持仓到间接敞口第二层网络刻画的是“没有直接借贷关系却因持有同一类资产而产生的关联”。银行 A 和银行 B 没有同业拆借但如果都持有某只城投债A 被迫折价抛售时债券市场价格下跌B 的持仓市值跟着缩水资本金被侵蚀。这就是价格传染渠道也叫间接传染。这一层不是银行间的双边关系而是通过资产市场建立的二模网络投影。工程上通常先构造一个持仓矩阵 P行是银行列是资产类别或单只债券Pik 表示银行 i 持有资产 k 的市值然后计算共同持仓矩阵 C P × 权重矩阵 × P^T。对角线忽略非对角元素 Cij 的含义是“若 i 抛售引发价格下跌j 暴露在多大损失之下”。常见加权方式是引入市场深度参数 depth_k代表资产 k 在承压时可以吸收多少抛售量而不出现显著价格滑点def build_security_layer(holdings, market_depth): holdings: n x k 矩阵银行对各类资产的重置成本或市值 market_depth: 长度为 k 的数组各资产的市场深度单位与 holdings 一致 k holdings.shape[1] # 对每类资产把持仓除以市场深度代表抛售对价格的边际冲击 impact holdings / market_depth.reshape(1, -1) # 共同持仓敞口 持仓 × 冲击转置 C holdings impact.T np.fill_diagonal(C, 0.0) return C这里乘出来的 C 不是金额而是“单位抛售对应的对手方损失乘数”传染模拟里会结合价格冲击系数一起用。数据不足时可以用年报披露的前十大债券投资、基金投资和资管计划来近似虽然覆盖不全但足以看出主要行业的持仓集中度差异。2.4 存款层节点属性如何变成挤兑传染第三层在学术讨论中争议最大存款不是银行间的双边关系怎么放进网络工程上我不建议硬造“银行 A 存款流向银行 B”这种边更稳妥的做法是把它建模成节点属性触发的全局传染。具体路径是当一家银行发生信用违约市场情绪恶化依赖活期存款且同业负债占比高的其他银行会被动的遭遇提前支取率上升流动性指标跌破监管阈值进而不得不出售资产再次触发第二层的价格下行。因此存款层在代码里体现为两个参数活期存款占比 deposit_hot_ratio 和挤兑敏感系数 run_trigger。前者说明一家银行资金结构的稳定程度后者说明在“系统内有银行违约”这一事件下活期存款流失多大比例会耗尽超额备付金。客户重叠效应可以通过设置一个客户端重叠矩阵间接模拟两家银行客户结构越相似挤兑传染越强。def build_deposit_layer(deposit_hot_ratio, client_overlap): deposit_hot_ratio: 数组每家银行的活期存款比例 client_overlap: n x n 矩阵客户结构重叠度取值 0~1 返回每家银行的挤兑压力系数 n len(deposit_hot_ratio) # 基础违约事件带来的挤兑压力 base_run 0.15 0.35 * deposit_hot_ratio # 客户重叠越高违约事件扩散到对手方的概率越高 run_multiplier 1.0 0.5 * client_overlap.mean(axis1) run_pressure base_run * run_multiplier return run_pressure这里给出的 0.15 和 0.35 是经验参考值真实项目里需要用压力测试的历史数据进行校准。模型的意义不在精确预测挤兑比例而是把“脆弱性”量化成可比较的排序哪些银行在突发事件后最缺流动性、需要优先救助一目了然。3. 传染机制与风险度量违约传染、折价抛售和流动性螺旋怎么进模型3.1 第一轮传播直接敞口导致的信用违约传染传染引擎开始运行时先施加一个外部冲击指定某几家公司为初始违约银行。第一轮传染只经过拆借层对于每家尚未违约的银行 i检查它对已违约银行集合 D 的总敞口 sum(A[i, D])乘以违约损失率 LGD再对照 i 的资本缓冲。资本缓冲是银行能吸收非预期损失的上限。简化处理时可以用“核心一级资本 超额拨备”作为缓冲。若损失超过缓冲认定银行 i 在同一轮也进入违约集合然后进入下一轮迭代。这个逻辑非常接近经典的 Eisenberg-Noe 清算模型只不过把多轮清算改成了离散轮次的传染扩散便于观察路径。def credit_contagion(A, buffer, lgd, defaults, max_rounds8): 拆借层违约传染逐轮扩散 A: 拆借矩阵行是债权方 buffer: 每家银行的资本缓冲百万元 lgd: 违约损失率Asset 类抵押不足时通常取 0.6~0.8 n A.shape[0] is_default np.zeros(n, dtypebool) is_default[defaults] True history [is_default.copy()] for _ in range(max_rounds): # 每个未违约银行当前累计对违约对手方敞口 exposure_to_default A[~is_default][:, is_default].sum(axis1) loss exposure_to_default * lgd new_defaults (~is_default[~is_default]) (loss buffer[~is_default]) if not new_defaults.any(): break is_default[~is_default] | new_defaults history.append(is_default.copy()) return is_default, history逻辑说明每一轮用布尔索引取出未违约银行只计算它们对已违约银行的敞口避免重复计算存量违约。终止条件是某一轮没有新银行违约。max_rounds 设 8 轮因为实际传染路径很少超过 6 层设太大只会增加无效计算。参数方面LGD 要按抵押品情况差异化设置不要全系统用同一个值。同业拆借中有质押的通常取 0.40.5纯信用拆借取 0.7 以上。这组参数直接决定传染规模建议做敏感性分析而不是拍脑袋定一个数。3.2 第二轮传播火线出售与价格下行螺旋当银行进入违约或濒临违约状态它会火线出售资产换取流动性。资产不是无限吸收卖单的抛售量越大价格折价越深这就是流动性螺旋的起点。证券持有层在这里与拆借层开始耦合。每一轮里统计所有违约银行包含上一轮新增的对每类资产的抛售量。抛售量等于该银行持仓的一定比例通常取 50%100% 的流动资产。通过市场深度换算成价格冲击幅度再用共同持仓矩阵反算其他银行因价格下跌产生的未实现损失。若这些损失消耗了资本缓冲银行进入下一轮违约。def price_contagion(holdings, market_depth, is_default, buffer, fire_sale_ratio0.6): 共同持仓价格传染 holdings: n x k 持仓矩阵 market_depth: 各类资产市场深度 fire_sale_ratio: 违约银行抛售持仓的比例 # 违约银行的抛售量 sale_volume is_default.reshape(-1, 1) * holdings * fire_sale_ratio total_sale sale_volume.sum(axis0) # 每类资产的抛售总量 # 价格冲击抛售量 / 市场深度截断避免极端值 price_impact np.minimum(total_sale / market_depth, 0.4) # 未违约银行因价格下跌产生的持仓损失 loss_matrix holdings * price_impact.reshape(1, -1) induced_loss loss_matrix.sum(axis1) induced_buffer buffer - induced_loss new_defaults (~is_default) (induced_buffer 0) (induced_loss 0) return new_defaults, induced_loss代码逻辑说明price_impact 里面加了一个 0.4 的上限背后含义是“即便流动性再差价格单日折价不超过 40%”的风控底线。实际项目中这个上限要根据资产类别分别设置国债和利率债可以放宽到 0.1低评级信用债可以放到 0.5ETF 类资产按真实折价率来。3.3 第三轮传播存款挤兑与流动性冲击的反馈回路信用传染和价格传染都是资本消耗型机制存款挤兑则是流动性消耗型机制。当市场出现违约事件存款流失率先上升银行被迫动用超额备付金。备付金耗尽后银行有两个选择拆借融资或卖出资产。如果同业市场风声鹤唳融资渠道堵塞只能卖资产这就又回到第二层价格传染。工程上把三层的耦合放在同一个 while 循环里每一轮先算信用损失再算持仓损失最后算存款流失导致的流动性缺口流动性缺口再折算成“被迫抛售量”进入下一轮价格冲击。这样可以捕捉多个传染机制的叠加效应。def run_multi_layer_contagion(A, C, holdings, market_depth, buffer, liquidity_reserve, deposit_run_pressure, lgd0.75, max_rounds10): n A.shape[0] is_default np.zeros(n, dtypebool) defaults np.arange(n)[buffer 0] is_default[defaults] True liquidity_shortfall np.zeros(n) for _ in range(max_rounds): # 第一层信用违约传染 exposure_to_default A[~is_default][:, is_default].sum(axis1) credit_loss exposure_to_default * lgd buffer[~is_default] - credit_loss # 第二层价格传染 sale_volume is_default.reshape(-1, 1) * holdings * 0.6 total_sale sale_volume.sum(axis0) price_impact np.minimum(total_sale / market_depth, 0.4) induced_loss (holdings * price_impact.reshape(1, -1)).sum(axis1) buffer - induced_loss # 第三层存款挤兑造成的流动性压力 current_run deposit_run_pressure * (1 is_default.sum() / n) liquidity_shortfall np.maximum(liquidity_shortfall, current_run * liquidity_reserve) liquidity_need liquidity_shortfall - liquidity_reserve forced_sale np.maximum(liquidity_need, 0) # 流动性缺口转为下轮抛售 holdings holdings - forced_sale.reshape(-1, 1) * holdings / np.maximum(holdings.sum(axis1), 1e-8).reshape(-1, 1) new_defaults buffer 0 if not new_defaults[~is_default].any(): break is_default | new_defaults return is_default, buffer这个循环是整篇模型的核心骨架三层的顺序可以调整但逻辑上信用违约先于价格传染更符合银行风险演化的现实路径。注意代码里用 buffer 原地做减法实际项目应复制一份 buffer 副本避免污染后续压力场景的基线。3.4 系统性风险评估指标倒闭数、损失比与系统重要性排序模型跑完不能只输出一个“倒了几家银行”。还要汇总出四个指标最终倒闭银行数量、倒闭银行资产占系统总资产比例、系统总资本损失率以及传染轮次与路径长度。其中“系统总资本损失率”最直观它能体现危机对整体银行业的资本侵蚀程度。政策模拟还需要一个“系统重要性排序”指标对每一家银行单独施加初始违约冲击记录它引发的系统总损失按损失从大到小排序。这个排序比单纯看资产规模更有价值因为部分中等规模但网络关联度高的银行引发的传染损失可能超过单体系统重要性银行。把排序结果做成图表监管者能直观识别真正的“关键节点”。4. 用 Python 构建中国银行业的风险传染模拟器核心代码与参数说明4.1 搭建三层网络的数据结构与初始化为了不依赖外部数据库项目通常会把三家输入固化在 CSV 文件里bank_balance_sheet.csv 存每家银行的资本、流动性和负债结构bank_holdings.csv 存持仓矩阵interbank_totals.csv 存同业资产与负债总额。代码里通过一个配置文件统一读取这样后续换数据时不用改动主逻辑。import pandas as pd def load_system_data(balance_path, holdings_path, interbank_path): 读取三层网络基础数据 返回: balance_df, holdings_df, interbank_df balance_df pd.read_csv(balance_path) holdings_df pd.read_csv(holdings_path, index_col0) interbank_df pd.read_csv(interbank_path, index_col0) # 数据质量检查银行名称对齐 banks list(balance_df[bank_name]) assert sorted(holdings_df.index) sorted(banks), 持仓矩阵与资产负债表的银行列表不一致 return balance_df, holdings_df, interbank_df示例代码讲解的关键点是数据对齐。银行名称是最容易出问题的地方“工商银行”“工商银行股份有限公司”“ICBC”三者在不同数据源里可能同时出现必须统一成标准简称后再读入。数据对齐后构造的矩阵才有意义否则模型算出来的传染路径完全是错位的。初始化时还要把所有金额统一成同一单位。同业拆借、债券持仓、资本缓冲很可能分别来自不同报表单位可能是万元、百万元或亿元混用时传染规模会被放大 100 倍。我会在 load 函数里强制做一次单位校准全部转成百万元。4.2 传染引擎多轮迭代的数值实现在第三章的传播算法基础上完整版的模拟器还要加上三个工程化处理事件日志、时间戳、以及“未违约但已资不抵债”的特殊状态处理。所谓资不抵债是指资本缓冲已经为负但未被正式宣布违约的银行它们仍可能继续融资维持但已经不能正常参与清算。现实模拟里最简单的做法是把它并入违约集合避免它们在链条里反复输出损失造成重复计算。def run_systemic_risk_simulation(A, C, holdings, market_depth, buffer, liquidity, deposit_pressure, initial_defaults, lgd0.75, rounds12): 完整传染引擎返回风险指标字典 n A.shape[0] status np.zeros(n, dtypeint) # 0-正常, 1-违约 status[initial_defaults] 1 loss_timeline [buffer.copy()] default_timeline [status.copy()] for round_idx in range(1, rounds 1): # 当前违约集合 d status 1 # 信用损失 exposure_to_default A[status 0][:, d].sum(axis1) buffer[status 0] - exposure_to_default * lgd # 价格冲击损失 sale_volume d.reshape(-1, 1) * holdings * 0.5 price_impact np.minimum(sale_volume.sum(axis0) / market_depth, 0.35) induced_loss (holdings * price_impact).sum(axis1) buffer - induced_loss # 存款挤兑流动性损失 run_pressure deposit_pressure * (1 d.sum() / n) liquidity_drain liquidity * run_pressure buffer - np.maximum(liquidity_drain - liquidity * 0.2, 0) # 更新违约状态 status[buffer 0] 1 default_timeline.append(status.copy()) loss_timeline.append(buffer.copy()) # 若不再扩散提前退出 if (status 1).sum() d.sum(): break total_loss initial_defaults.sum() / n if len(initial_defaults) else 0 total_loss max(0, total_loss) bank_loss_ratio (buffer.min() if False else (initial_defaults.size 0)) # 示例逻辑见下文修正 return { n_default: int((status 1).sum()), default_assets: float(holdings[status 1].sum()), total_buffer_loss: float(1 - buffer.sum() / loss_timeline[0].sum()), default_ratio: float((status 1).mean()), rounds: round_idx }注意到代码里故意保留了一段看似冗余的 bank_loss_ratio 计算实际项目中我会用原始银行资产总额向量做加权而不是按银行数量均摊。因为一家大型银行和一家微小城商行在违约数量上的权重完全不同按数量计算会让大型银行的传染作用被严重低估。return 字典里的四个指标是后续政策模拟的输入其中 total_buffer_loss 是配置多层网络的“总分”指标用于横向比较不同压力场景的严重程度。4.3 压力场景与政策模拟接口政策模拟的本质是“在同一冲击下给系统施加不同的干预变量对比结局”。干预变量通常有三类向特定银行注入流动性、直接补充资本缓冲、对某些资产实施交易限制冻结抛售。代码里把三个干预做成可选参数不干预时模型作为基准场景输出。def policy_simulation(run_engine, A, C, holdings, market_depth, buffer, liquidity, deposit_pressure, initial_defaults, capital_injectionNone, liquidity_injectionNone, trading_ban_assetsNone): 政策模拟接口 capital_injection: 数组对指定银行的资本补充量 liquidity_injection: 数组对指定银行的流动性供给量 trading_ban_assets: 集合禁止抛售的资产类别索引 buffer_new buffer.copy() liquidity_new liquidity.copy() holdings_new holdings.copy() if capital_injection is not None: buffer_new capital_injection if liquidity_injection is not None: liquidity_new liquidity_injection if trading_ban_assets is not None: # 禁止抛售的资产从抛售列表中移除 ban_mask np.zeros(holdings.shape[1], dtypebool) ban_mask[list(trading_ban_assets)] True holdings_new[:, ban_mask] 0 result run_engine(A, C, holdings_new, market_depth, buffer_new, liquidity_new, deposit_pressure, initial_defaults) return result这里最重要的工程约定是每次政策模拟都要返回与基准完全一样的指标结构。输出指标的含义要定义清楚不然政策对比就失去了锚点。每家银行的资本注入不能直接修改原 buffer必须用副本否则不同政策之间会相互污染模拟结果。5. 参数与方法常见问题排查模型翻车的五个黑匣子时刻5.1 数据口径不统一导致网络矩阵不闭合现象模拟跑出来同业拆借层的行合计与公开披露的同业资产总额相差 20% 以上传染规模明显失真的。原因不同报表中同业资产的定义范围不一致。银行间市场口径包含了买入返售资产而财务报表里可能单列或归类到“其他资产”科目买入返售的抵押品通常很充分把它等同纯信用拆借处理会高估风险。解决建模前先给三类业务明确分类拆出资金、买入返售、存放同业分别建列按流动性宽松程度分别设置不同的 LGD。买入返售项的违约损失率应该明显低于纯拆借因为它有抵押品。如果拿不到抵押比例宁可把该项从拆借层中剔除也不要混在一起。5.2 违约回收率取错传染轮次虚高现象模型跑到第 15 轮还在扩散而且传染路径越来越长明显不符合银行业风险演变的经验。原因传染轮次过多通常是两个参数叠加的结果——LGD 设置过低导致损失被匀到每一轮同时 max_rounds 没有上限。实际传染路径很少超过 46 层因为银行间市场的网络直径本来就不长。解决先检查 LGD 是否按业务类型设置不要全局用 0.7 或 0.8 统一值。再做“轮次封顶试验”把 max_rounds 从 3 逐步上调到 12观察倒闭数量是否还在显著增加如果在 6 轮后新增违约小于总数的 2%就可以把 8 设为固定上限。轮数的绝对意义不是越大越严谨而是模型收敛速度的对产物应该服务于结论。5.3 价格冲击参数被忽视流动性风险严重失真现象共同持仓层加入后系统反而变得极度脆弱随便冲击一家银行就崩掉一半资产。原因市场深度 depth_k 赋值过于极端比如把某些信用债日成交额当成市场深度但压力情景下市场深度只会更低。反过来把市场深度设成正常日成交额的 100 倍价格冲击又会几乎消失模型退化成只有拆借层的旧版。解决市场深度应当用“正常日成交额的 515 倍”作为基准值。压力情景下除以 23因为恐慌抛售往往带来成交放量但深度收缩。另外给 price_impact 设置上限值防止折价超过 40% 这种不现实的情况。每一个市场的拐点参数都值得做一次敏感性扫描观察总损失率对哪个参数最敏感。5.4 静态资产负债表导致的“伪传染”现象同一家银行在传染三轮后违约且每轮损失来源都是同一个对手总损失被反复累计。原因模型每轮都用初始的资产负债表去算敞口没有考虑上一轮已经发生过的资产减值和融资行为。一家银行对已违约银行的敞口在第一轮已经核销下一轮不应该再对这个对手计提损失。解决传染算法里加入“已确认损失核销”机制每家银行对每个违约对手方只计提一次损失。更细一点的工程实现是维护一个累计违约计数矩阵当对手方在上一轮已经违约本轮直接跳过新增损失计算。5.5 对模型结果过度解读把模拟当预测现象监管或业务团队拿着模拟输出的“倒闭银行名单”直接当成下一季度风险预测或者反推某家银行一定会出问题。原因模型输入的是简化的网络结构和假设参数输出的是“在给定冲击分类下哪些机构容易受到影响”的相对排序而不是对单个机构未来状态的精确预测。解决输出报告里同时给出两个图一张是基准场景的传染路径图另一张是“参数不确定性范围图”——把 LGD、市场深度、抛售比例等核心参数正负变化 20%输出总损失率的波动区间。区间越宽说明模型对参数越敏感结论越要谨慎对待。任何只展示单一模拟结果的政策报告都是不完整的。6. 进阶从单次模拟到政策实验模型结果怎么验证才让人信服6.1 敏感性分析扫描参数空间的边际影响政策模拟的可信度来自“换了参数结论不变”的稳定感。常用的做法是对三个核心参数做边际扫描LGD 在 0.40.8 之间按 0.05 步进抛售比例在 30%80% 之间按 10% 步进市场深度系数在 520 倍之间按 5 倍步进。每换一组参数重跑全部压力场景看总损失率和倒闭数量的排序是否变化。如果排序变化很大说明系统对某些参数过分敏感这类参数应该通过历史事件反推校准而不是依赖经验假设。我习惯把扫描结果做成一个热力图横轴是 LGD纵轴是市场深度系数颜色是总损失率。这张图在项目汇报里比任何文字都更能说明模型的边界。6.2 用自助法检验结果的稳健性不依赖单一网络构造重建网络时拆借矩阵是用 RAS 算法从总量数据推出来的本身就带有估计误差。为了不让最终结论建立在某一次随机填充的拆借矩阵上可以在不改动债务总量的前提下用不同的随机种子多次重建拆借网络重复跑 500 次压力测试然后输出倒闭数量的分布直方图。def bootstrap_network_analysis(base_data, n_bootstrap500): 对给定压力场景做 500 次随机网络重建 传染模拟 输出倒闭数量的 5%~95% 分位区间 n_defaults [] total_losses [] for seed in range(n_bootstrap): A build_interbank_layer( base_data[assets], base_data[liabilities], density0.3, seedseed ) result run_systemic_risk_simulation( A, base_data[C], base_data[holdings], base_data[market_depth], base_data[buffer], base_data[liquidity], base_data[deposit_pressure], base_data[initial_defaults] ) n_defaults.append(result[n_default]) total_losses.append(result[total_buffer_loss]) return { n_default_p5: float(np.percentile(n_defaults, 5)), n_default_p50: float(np.percentile(n_defaults, 50)), n_default_p95: float(np.percentile(n_defaults, 95)), loss_p5: float(np.percentile(total_losses, 5)), loss_p50: float(np.percentile(total_losses, 50)), loss_p95: float(np.percentile(total_losses, 95)), }输出结果直接落到分位数区间比单次模拟的单一数字可靠得多。如果 50% 分位数的倒闭数量和单次模拟结果差距很大说明先前那次网络的随机性太强结论不能直接使用如果区间很窄说明模型对网络构造方式不敏感这样的结论才适合写进风险评估报告。6.3 反事实政策评估用同一冲击对比不同干预组合最后一步是政策实验方法也很简单固定冲击场景例如同一家系统重要性银行破产分别运行四组配置——不干预、仅注入流动性、仅注入资本、同时注入流动性和资本。每组输出同样的四个指标直接对比。我一般会在对比时特别关注“边际效果递减”的现象注入资金超过某阈值后系统总损失率不再显著下降这个阈值就是政策资金的最优分配参考。我能给的最实际建议是政策模拟不要直接输出“该救谁、不该救谁”的结论而是输出“在不同干预力度下损失分布的差异”。因为真实世界里监管者对每家机构的介入不仅取决于网络影响还取决于法律授权和机构恢复可能性模型的价值是缩小决策搜索空间而不是替代判断。这个习惯帮我避开了很多次“被模型的黑色结论牵着走”的弯路也希望帮到你。本文还有配套的精品资源点击获取