ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python模拟一场注定崩溃的经济系统:从信贷扩张到债务通缩

用Python模拟一场注定崩溃的经济系统:从信贷扩张到债务通缩 如果我告诉你有一个不到 300 行的 Python 脚本能让一个虚拟经济体从繁荣、过热、滞涨走到全面崩盘你信不信更“反直觉”的是这个崩溃并不是程序 bug而是设计者故意埋进去的核心规则。这篇文章就来拆解这样一个项目设计一个从一开始就注定崩溃的经济系统。我们会用代理建模Agent-Based ModelingABM的方式在一个沙盒环境里塞入居民、信贷、资产泡沫、债务清算和信心传导机制然后观察它如何自己把自己玩崩。适合人群有三类一是想用 Python 练习复杂系统模拟的开发者二是对经济学模型、量化风控感兴趣的后端工程师三是想理解“信贷扩张—资产泡沫—债务通缩”这个链条的程序员。读完你会得到一份完整可运行的模拟代码以及一套分析崩溃路径的方法论。1. 为什么要设计一个“注定崩溃”的经济系统1.1 这不是制造恐慌而是研究脆弱性大多数经济模拟项目目标都是“维持增长”“避免崩溃”。而这个项目的设计目标反过来了——我们希望崩溃必然发生并且发生在模型内部逻辑驱动下而不是靠外部随机事件硬砸出来。这种“反向设计”的价值在于当你把崩溃当作系统的一个正常输出你反而能更清楚地看到哪些参数、哪些规则是导致不稳定的根源。比如杠杆率过高、资产价格只涨不跌的预期、债务约束失效、流动性枯竭这些因素在真实世界里往往被各种噪音掩盖但在一个干净的模拟环境里会被放大得非常明显。1.2 和普通经济模拟的区别普通经济模拟通常追求“长期均衡”比如产量围绕某个稳态波动价格回归价值政策干预有效果系统存在负反馈。而“注定崩溃”的模拟不一样它要构造一个正反馈占主导的系统价格上涨 → 借贷增加 → 买入增加 → 价格继续上涨价格下跌 → 抵押物缩水 → 强制卖出 → 价格继续下跌。正反馈系统一旦启动就很容易走向极端。我们要做的就是让这个“走向极端”的过程可复现、可观察、可分析。1.3 现实世界中的“崩溃配方”真实的金融危机虽然复杂但核心配方其实是高度相似的信贷扩张让一部分人加杠杆购买资产资产价格上升反过来提高抵押物价值允许更多借贷价格短期不跌让市场参与者默认“价格永远涨”有一天新增信贷跟不上价格增长价格滞涨信心松动部分人抛售价格下跌杠杆者被强制清算导致更大抛压违约率上升银行收紧信贷流动性消失消费和投资同时收缩经济全面下行。下面我们要做的模拟就是把上面这 8 步浓缩成 200 行左右的 Python 代码。2. 崩溃引擎四个核心机制在写代码之前先把“崩溃怎么被设计出来”这件事拆成四个可实现的机制。每个机制对应模拟里的一个具体函数。2.1 用信贷放大购买力如果所有代理只能用存量现金买资产那么资产价格最多是现金的重新分配很难形成泡沫。所以我们要加入一个“银行”允许代理以当前资产价值为抵押去借钱。杠杆的存在让代理的购买力远高于自身收入这是泡沫的燃料。2.2 资产价格自我强化资产价格不取决于“内在价值”而取决于本期净买盘的大小买盘 卖盘价格上涨买盘 卖盘价格下跌。因为借贷会带来买盘价格上涨又会提高抵押物价值从而带来更多借贷所以价格和买盘之间形成了正反馈。2.3 债务约束与强制卖出单纯借钱不能保证崩溃必须有一个“清算规则”。我们规定每个代理的负债不能超过贷款比例上限 × 资产市值一旦超标必须按“超出部分”卖出资产还债。这条规则平时不触发但一旦价格下跌资产市值缩水很多代理会同时触发强制卖出形成踩踏。2.4 信心指数与流动性螺旋前面三个机制是“硬规则”第四个是“软规则”。我们定义信心指数它受两个因素影响资产价格增长率当期违约人数。信心高时代理更愿意借钱信心低时即使有额度也不愿意借。信心下降会导致买盘消失于是价格涨不动信心进一步下降这就形成了流动性螺旋。这四个机制拼起来就是一个完整的“内生崩溃机器”。3. 环境准备与数据结构3.1 开发环境本文代码使用 Python 3.9 及以上版本即可不需要复杂依赖。核心逻辑只用标准库可视化部分依赖matplotlib。如果你只是想看文字结果不装matplotlib也能跑。# 建议创建虚拟环境 python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate # 安装可视化依赖可选 pip install matplotlib注意版本不需要追求最新matplotlib 3.5均能运行本文代码。3.2 数据结构说明整个模拟有两大类对象Agent经济中的个体也就是“人”属性包括现金、资产数量、负债、工资、消费倾向、生存状态。Economy整个虚拟经济系统属性包括代理列表、资产价格、利率、贷款比例上限、信心指数、历史数据。我们不需要单独设计“银行”类而是把放贷行为写成Economy的方法这样代码更紧凑也方便观察整个信贷池的变化。4. 从零实现一个三主体经济模拟4.1 完整代码我把完整代码放在一个文件里命名为crash_economy.py。你直接复制保存运行后就能得到完整的崩溃过程输出。# file: crash_economy.py import random import math from dataclasses import dataclass dataclass class Agent: 一个经济代理可以持有现金、资产也可以负债。 pid: int cash: float asset_qty: float debt: float salary: float 40.0 consume_propensity: float 0.85 alive: bool True def net_worth(self, price: float) - float: return self.cash self.asset_qty * price - self.debt class Economy: def __init__( self, n_agents: int 200, init_cash: float 100.0, init_asset: float 10.0, price: float 10.0, interest_rate: float 0.08, max_loan_ratio: float 0.8, seed: int 42, ): random.seed(seed) self.n_agents n_agents self.agents [ Agent( pidi, cashinit_cash, asset_qtyinit_asset, debt0.0, salaryrandom.uniform(30, 50), ) for i in range(n_agents) ] self.price price self.interest_rate interest_rate self.max_loan_ratio max_loan_ratio self.confidence 1.0 self.time 0 self.history [] self.default_count 0 def alive_agents(self): return [a for a in self.agents if a.alive] def distribute_salary(self): 每个存活代理领取工资相当于经济体注入基础货币。 for a in self.agents: if a.alive: a.cash a.salary def consume(self) - float: 消费现金减少货币退出流通同时形成需求热度。 total_demand 0.0 for a in self.agents: if a.alive: spend min(a.cash * a.consume_propensity, 50.0) a.cash - spend total_demand spend return total_demand def borrow_and_invest(self) - float: 乐观时借钱买资产。 这是泡沫阶段的主要买盘来源。 buy_orders 0.0 for a in self.agents: if not a.alive: continue asset_value a.asset_qty * self.price existing_debt_ratio a.debt / asset_value if asset_value 0 else 0 if self.confidence 0.7 and existing_debt_ratio self.max_loan_ratio: # 可借额度 资产价值 * 上限 - 已有负债 borrow asset_value * self.max_loan_ratio - a.debt borrow min(borrow, a.salary * 5) a.debt borrow a.cash borrow # 将一部分借款用于购买资产 invest borrow * random.uniform(0.5, 1.0) a.cash - invest a.asset_qty invest / self.price buy_orders invest return buy_orders def forced_liquidation(self) - float: 负债超过上限时必须卖出资产。 这是崩溃阶段的主要卖盘来源。 sell_orders 0.0 for a in self.agents: if not a.alive: continue asset_value a.asset_qty * self.price if asset_value 0 and a.debt / asset_value self.max_loan_ratio: # 超过上限的部分需要卖掉对应价值的资产 excess a.debt / self.price - a.asset_qty * self.max_loan_ratio sell_asset max(0.0, excess) sell_asset min(sell_asset, a.asset_qty) a.asset_qty - sell_asset a.cash sell_asset * self.price sell_orders sell_asset * self.price return sell_orders def update_price(self, buy_orders: float, sell_orders: float): 价格由净买盘和信心共同推动。 alive_num max(1, len(self.alive_agents())) net_flow buy_orders - sell_orders flow_effect 0.05 * math.tanh(net_flow / (alive_num * 10)) confidence_effect 0.02 * (self.confidence - 0.5) self.price * 1.0 flow_effect confidence_effect self.price max(0.5, self.price) def pay_interest(self): 支付利息付不起时债务会继续累积形成被动杠杆。 for a in self.agents: if a.alive: interest a.debt * self.interest_rate / 12.0 if a.cash interest: a.cash - interest else: a.debt interest * 0.5 def default_check(self) - int: 当现金 资产市值 小于 负债的一半时代理违约退场。 违约会直接造成银行坏账压缩市场流动性。 defaults 0 for a in self.agents: if not a.alive: continue asset_value a.asset_qty * self.price if a.cash asset_value a.debt * 0.5: a.alive False defaults 1 self.default_count defaults return defaults def update_confidence(self, default_rate: float): 信心由价格增长率和违约率共同决定。 价格涨得快 → 信心高 → 加杠杆 价格滞涨或下跌 → 信心快速回落 → 买盘消失。 if self.history: last_price self.history[-1][price] price_growth self.price / last_price - 1 else: price_growth 0.05 target 0.6 price_growth * 8.0 - default_rate * 6.0 target max(0.05, min(1.0, target)) # 信心下降很快恢复很慢 if target self.confidence: self.confidence target else: self.confidence (target - self.confidence) * 0.1 def step(self): self.time 1 self.distribute_salary() demand self.consume() buy self.borrow_and_invest() sell self.forced_liquidation() self.update_price(buy, sell demand * 0.1) self.pay_interest() defaults self.default_check() alive self.alive_agents() default_rate defaults / max(1, len(alive)) self.update_confidence(default_rate) total_net_worth sum(a.net_worth(self.price) for a in alive) total_debt sum(a.debt for a in alive) total_cash sum(a.cash for a in alive) self.history.append( { time: self.time, price: self.price, confidence: self.confidence, defaults: defaults, alive: len(alive), total_net_worth: total_net_worth, total_debt: total_debt, demand: demand, } ) def run(self, steps: int 240): for _ in range(steps): self.step() if len(self.alive_agents()) 5: print( f[提前终止] 第 {self.time} 期存活代理只剩 f{len(self.alive_agents())} 个 ) break return self.history if __name__ __main__: eco Economy(seed42) history eco.run(steps240) print(time,price,confidence,defaults,alive,total_debt,total_net_worth) for h in history: print( f{h[time]},{h[price]:.2f},{h[confidence]:.3f}, f{h[defaults]},{h[alive]},{h[total_debt]:.2f}, f{h[total_net_worth]:.2f} ) # 可选的可视化输出 try: import matplotlib matplotlib.use(Agg) import matplotlib.pyplot as plt xs [h[time] for h in history] fig, axes plt.subplots(2, 1, figsize(10, 8)) axes[0].plot(xs, [h[price] for h in history], labelAsset Price) axes[0].set_ylabel(Price) axes[0].legend() axes[1].plot(xs, [h[confidence] for h in history], labelConfidence, colorred) axes[1].set_ylabel(Confidence) axes[1].set_xlabel(Time) axes[1].legend() plt.tight_layout() plt.savefig(economy_crash.png, dpi120) print(图表已保存: economy_crash.png) except ImportError: print(未安装 matplotlib跳过图表输出)4.2 代码逻辑逐段拆解先看Agent这个数据类。它承担了最小粒度的状态管理现金、资产、负债、工资、消费倾向、是否存活。net_worth()方法把资产按照当前市场价折算成净值这是后面判断破产的主要依据。然后是Economy类。初始化时我们生成 200 个代理每个代理初始有 100 元现金、10 单位资产、0 负债。资产初始价格是 10贷款比例上限是 0.8年利率 8%。每一期的执行顺序是固定的这也是模拟的关键distribute_salary()发工资注入流动性consume()消费货币退出流通borrow_and_invest()信心高时借钱买资产forced_liquidation()负债超标时强制卖资产update_price()根据净买盘和信心更新价格pay_interest()付利息default_check()判断违约update_confidence()更新信心。顺序为什么重要因为“先注入流动性再决定消费和借贷”保证了每一轮的状态更新都基于一致的时间点。如果顺序乱掉比如先强平再发工资会出现跨期套利破坏模拟逻辑。borrow_and_invest里的可借额度公式是可借额度 资产市值 × 贷款比例上限 - 已有负债这个公式很关键它让“资产涨价”和“借钱能力”绑定在一起。价格越高可借额度越大可借额度越大买盘越强买盘越强价格越高。这就是正反馈的核心循环。forced_liquidation里的“超额部分”计算需要卖出的资产数量 负债 / 当前价格 - 持有资产数量 × 贷款比例上限这里要注意负债是货币单位资产是数量单位必须除以价格换算成数量。这是新手最容易写错的地方后面会专门讲。update_price没有让价格等于某种“均衡值”而是用“净买盘”驱动价格变化。tanh函数让价格对买盘的反应有一个饱和效应避免单期暴涨暴跌太离谱。update_confidence是崩溃的“扳机”。价格增长率为正时信心高大家继续借钱价格一旦滞涨信心目标值立刻下降信心下降后又会让价格缺乏买盘支撑于是进入下跌螺旋。5. 运行结果与崩溃过程分析5.1 运行方式在终端执行python crash_economy.py如果安装了 matplotlib会生成一张包含资产价格和信心指数两条曲线的economy_crash.png图如果没有安装也能从终端输出的 CSV 样式数据里看到变化趋势。5.2 典型阶段特征由于随机种子固定为seed42理论上每次运行结果完全一致。但如果你修改了代理数量、贷款比例上限等参数具体期数会变化。总体来看你会看到以下四个阶段阶段表现核心驱动繁荣期价格快速上涨总债务和总净值同步上升违约数为 0信贷扩张 买盘正反馈滞涨期价格涨幅收窄信心开始小幅下滑部分代理借款空间耗尽可借额度见顶抛售期价格转跌强制卖出出现信心快速下降抵押物缩水 强平崩盘期价格深度下跌违约集中爆发大量代理“死亡”债务通缩 违约传染这不是一个“可能”出现的路径而是规则设计下必然出现的路径。原因很简单膨胀依赖持续新增杠杆而杠杆不可能无限增长因为代理的资产有限、工资有限。一旦新增借款放缓买盘跟不上供给价格滞涨信心模型会让滞涨演变成下跌下跌又触发强平和违约。5.3 崩溃为什么会发生我们可以在代码层面把崩溃归因到三个“不平衡”债务累积速度 收入增长能力。代理的工资是 30 到 50但单次借款可以到工资的 5 倍。初期这种“超前购买”能拉高资产价格但债务本身不会消失它会通过利息持续消耗现金。资产价格与实体购买力脱钩。资产价格由买盘驱动而买盘主要来自借款。当地产商和家庭的现金流都拿去还利息时实体购买力下降资产价格失去支撑。强制清算没有下限。强平规则要求“超过上限就卖”但在价格下跌时卖出越多价格越跌价格越跌负债率越高于是进入“越卖越要卖”的循环。这是典型的流动性螺旋。6. 常见报错与坑点我在调这个模型时遇到过几个问题列出来供你排查。问题现象常见原因解决思路价格突然变成负数update_price中没有做最小值保护设置self.price max(0.5, self.price)ZeroDivisionError某代理asset_qty 0时仍参与计算在除法前判断if asset_value 0模拟跑很久始终不崩溃贷款比例上限过低杠杆不足把max_loan_ratio从 0.5 提高到 0.8 或 1.0模拟在几十期就全部死亡借款上限工资倍数过高、违约阈值过松降低salary * 5或提高违约判断阈值多次运行结果不同代码没有固定随机种子在__init__中调用random.seed(seed)无法导入 matplotlib可视化依赖未安装执行pip install matplotlib不需要可视化可直接注释“强制卖出算出来是负数”超额资产计算里缺了max(0.0, ...)对excess做非负保护另外有一个非常隐蔽的坑不要在Agent里直接引用全局价格。许多初学者会把资产净值写成一个依赖全局变量的属性这会让代码在批量模拟时很难调试。正确的做法是把价格作为参数传入就像net_worth(self, price)一样。7. 这类模拟的工程化建议这个项目看起来只是一个小游戏但如果想把它扩展到团队项目、量化研究或毕业论文以下几点值得注意。7.1 随机种子与可复现性模拟类项目最重要的工程要求就是可复现。必须在入口处固定random.seed最好在配置类中显式记录种子输出结果时把参数和种子一起保存。否则你根本无法判断结果变化是“参数调整导致的”还是“随机波动导致的”。7.2 参数配置与敏感性分析把可变参数集中放到构造函数的参数列表里而不是散落在函数内部。比如本文的interest_rate、max_loan_ratio、salary倍数都可以提取成一个Config数据类。然后可以试着跑一组参数扫描for ratio in [0.4, 0.6, 0.8, 1.0]: eco Economy(max_loan_ratioratio, seed42) hist eco.run(steps240) # 记录崩溃期、最大回撤、存活代理数你会发现杠杆上限越高繁荣期越疯狂崩盘期越惨烈。这是一个非常直观的敏感性分析结果。7.3 数据记录不要只在控制台打印。建议把每一期的历史数据实时写入 CSV 文件方便后续画图、做统计分析。import csv with open(result.csv, w, newline) as f: writer csv.DictWriter(f, fieldnameshistory[0].keys()) writer.writeheader() writer.writerows(history)这样做的好处是即使程序崩溃前期的模拟数据也不会丢。7.4 安全边界与正确解读最后必须强调一点这是一个教学模拟不是经济预测工具。它的意义在于展示“杠杆 正反馈 强制清算”这三者组合会产生怎样的系统脆弱性而不是断言真实经济一定按这个路径发展。在生产环境或金融相关工作中需要引入更复杂的制度约束、监管规则和随机冲击并且必须在测试环境充分验证。不要用这个小 demo 的结论直接指导真实投资或政策建议。8. 总结与下一步扩展这个项目最核心的收获不是“我写出了崩溃”而是“我能精确地指出崩溃在哪里被触发”。你掌握了四个关键机制信贷购买力放大、资产价格正反馈、负债强平规则、信心传导。用一个不到 300 行的脚本就能完整复现“繁荣—滞涨—抛售—崩盘”的链条。如果你想让这个模拟更深入可以从这几个方向扩展加入中央银行观察货币政策在泡沫期是否有效能否避免崩溃加入银行资产负债约束银行也会破产而不是无限放贷加入异质性代理让一部分代理是保守型一部分是投机型观察两者比例对崩溃时间的影响加入外部冲击比如工资随机下降、资产收益波动观察系统在不同冲击下的表现改成实时可视化用matplotlib.animation把价格和信心画成动态曲线效果会很直观。如果你也想让这个虚拟世界“晚一点崩溃”可以试着调低max_loan_ratio或者提高信心恢复速度。动手改一次参数比读十遍理论都管用。
返回列表