☰
MATLAB 复现 HANK 模型:从 EGM 到脉冲响应的完整指南
2026/10/18 18:52:11 网站建设 项目流程

简介:这份资源面向宏观经济研究者、经济学研究生及具备一定MATLAB基础的量化分析人员,提供异构代理新凯恩斯(HANK)模型在MATLAB环境下的完整复制方案,帮助解决模型设定、动态方程求解与参数校准等实现难题。压缩包共321个文件,约22.32MB,以m脚本、f90与f等Fortran源码、txt说明、out输出结果、mat数据文件为主,辅以makefile、sh脚本、pdf文档及少量cu、cpp等计算代码,覆盖模型构建、数值求解与结果输出各环节。已有49人学习下载。读者可据此获得可运行的模型代码框架、参数校准与模拟分析思路,并借助Fortran与MATLAB混合编程结构理解非线性动态方程的迭代求解流程,适合用于经济波动、宏观政策评估与财富分配等方向的扩展研究。

1. 从一行标题到可运行模型:HANK 复制到底难在哪

如果你在 MATLAB 里搜过 HANK 模型的复现代码,大概率会撞见一个尴尬现实:论文附录写得像天书,作者主页的代码包要么缺文件、要么版本对不上,自己从头写又不知道从哪下手。HANK(Heterogeneous Agent New Keynesian,异质性代理人新凯恩斯模型)把家庭异质性与名义摩擦塞进同一个一般均衡框架,理论上很漂亮,但落到数值求解上,它同时要求你处理离散状态分布、非线性期望、以及跨期预算约束下的市场出清。这三件事叠在一起,MATLAB 里一个维度没对齐,结果就是解不出来或者解出来是错的。

这篇笔记面向的是已经懂一点 DSGE、想在 MATLAB 里把 HANK 跑起来的从业者。我会按“先立住理论骨架、再动手复现、最后排坑”的顺序,把复制一个标准 HANK 模型需要的关键步骤、参数含义和常见翻车点讲清楚。不依赖任何特定代码包,你照着思路可以自己搭一套最小可运行版本。核心目标只有一个:让你在本地 MATLAB 里得到一个能收敛、能出脉冲响应、能改参数做比较静态的 HANK 求解器。

2. 先立住骨架:HANK 的四个核心模块与 MATLAB 映射

2.1 家庭侧:离散状态空间与 EGM 反推

HANK 和 RANK(代表性代理人新凯恩斯)最大的区别在家庭侧。RANK 里家庭是同质的,消费由欧拉方程直接决定;HANK 里每个家庭有自己的资产持有量 (a) 和生产率状态 (s),消费函数 (c(a,s)) 是一个定义在二维网格上的函数。复制 HANK 的第一步,就是把家庭问题写成贝尔曼方程,然后用内生网格法(EGM)反推消费策略。

标准做法是:给定价格序列(利率 (r)、工资 (w)、转移支付 (T)),家庭在预算约束下最大化效用。离散化资产网格 (a \in {a_1,...,a_N}),生产率用有限状态马尔可夫链近似,转移矩阵记为 (\Pi)。EGM 的核心步骤是:先猜一个消费策略 (c(a,s)),算出对应的资产下一期持有量 (a'),再通过欧拉方程反推当期消费,迭代到收敛。

在 MATLAB 里,这一步对应一个三维数组操作:维度分别是资产网格、生产率状态、时间。常见做法是用ndgrid生成状态组合,用interp1做线性插值反推。下面是一个最小化的 EGM 单步迭代骨架:

% 参数设定 N_a = 100; % 资产网格点数 a_min = 0; a_max = 50; % 资产上下界 a_grid = linspace(a_min, a_max, N_a)'; N_s = 3; % 生产率状态数 beta = 0.96; % 贴现因子 sigma = 2; % 相对风险厌恶系数 r = 0.02; % 利率 w = 1.0; % 工资 % 生产率状态与转移矩阵(示例) s_grid = [0.5; 1.0; 1.5]; Pi = [0.9 0.08 0.02; 0.05 0.9 0.05; 0.02 0.08 0.9]; % 初始化消费策略(猜一个) c_guess = repmat(w * s_grid', N_a, 1); % N_a x N_s % EGM 单步迭代 for iter = 1:1000 % 当期资产持有 = 当期资源 - 当期消费 a_now = repmat(a_grid, 1, N_s) + repmat(w * s_grid', N_a, 1) ... - c_guess + r * repmat(a_grid, 1, N_s); % 下一期资产(预算约束) a_next = (1 + r) * repmat(a_grid, 1, N_s) + w * repmat(s_grid', N_a, 1) ... - c_guess; % 对每个生产率状态,用 interp1 反推消费 c_new = zeros(N_a, N_s); for i_s = 1:N_s % 欧拉方程右边:beta*(1+r)*E[u'(c_next)] c_next = interp1(a_grid, c_guess(:, i_s), a_next(:, i_s), 'linear', 'extrap'); rhs = beta * (1 + r) * c_next.^(-sigma); % 反推当期消费 c_new(:, i_s) = rhs.^(-1/sigma); end % 检查收敛 if max(abs(c_new(:) - c_guess(:))) < 1e-6 break; end c_guess = c_new; end

这段代码的逻辑是:给定价格和转移矩阵,家庭通过欧拉方程在资产网格上迭代消费策略。参数说明:N_a决定资产离散化精度,太小会导致插值误差大,太大会拖慢速度,一般 100 到 500 之间;a_max要足够大,保证富裕家庭的借贷约束不绑定;sigma影响跨期替代弹性,HANK 里通常取 1 到 3。注意这里省略了借贷约束 (a' \geq \underline{a}) 的处理,实际复制时必须加max(a_next, a_min)并在插值时做截断,否则会出现负资产导致解发散。

2.2 生产侧与名义摩擦:卡尔沃定价的 MATLAB 实现

家庭侧给出消费和劳动供给后,生产侧要决定价格和工资。HANK 通常采用卡尔沃定价:每期有 (1-\theta) 比例的企业能调整价格,(\theta) 是不调整概率。这导致新凯恩斯菲利普斯曲线,在 MATLAB 里表现为一个关于通胀 (\pi_t) 和边际成本 (mc_t) 的差分方程。

复制时常见做法是把生产侧写成线性化形式,因为 HANK 的求解通常围绕稳态做局部近似。菲利普斯曲线:

[ \pi_t = \beta E_t \pi_{t+1} + \kappa , mc_t ]

其中 (\kappa = (1-\theta)(1-\beta\theta)/\theta)。在 MATLAB 里,这对应一个线性方程,可以用矩阵求逆或迭代求解。关键是边际成本 (mc_t) 要由家庭侧的总消费和劳动供给聚合得到,这一步是 HANK 和 RANK 的分岔点:RANK 里总消费由欧拉方程直接给出,HANK 里总消费是所有家庭消费策略在分布上的积分。

% 菲利普斯曲线参数 theta = 0.75; % 不调整概率 kappa = (1-theta)*(1-beta*theta)/theta; % 给定边际成本路径,求解通胀 T = 100; % 时间期数 mc = 0.01 * ones(T, 1); % 边际成本冲击(示例) pi = zeros(T, 1); % 后向求解(终端条件 pi_T = 0) for t = T-1:-1:1 pi(t) = beta * pi(t+1) + kappa * mc(t); end

这段代码的逻辑是:给定边际成本路径,用后向迭代求解通胀路径。参数说明:theta越大,价格粘性越强,通胀对边际成本的反应越弱;kappa是斜率,直接决定货币政策的效果。注意这里用的是线性化形式,实际复制时如果要做全局解,需要把菲利普斯曲线写成非线性形式并用牛顿法求解。

2.3 市场出清与分布演化:从个体策略到总量

HANK 最容易被忽略的一步是分布演化。家庭策略 (c(a,s)) 给出个体行为,但总量消费 (C_t = \int c_t(a,s) , d\mu_t(a,s)),其中 (\mu_t) 是资产和生产率状态的联合分布。这个分布在每期按家庭的资产选择更新:(\mu_{t+1}(a',s') = \sum_{a,s} \mu_t(a,s) \Pi(s'|s) \mathbf{1}{a' = g_t(a,s)})。

在 MATLAB 里,这对应一个矩阵乘法:把分布拉成向量,转移矩阵是稀疏矩阵,每期更新一次。常见做法是用sparse构造转移矩阵,用*做乘法。下面是一个最小化的分布更新骨架:

% 分布初始化(稳态分布,示例) mu = ones(N_a, N_s) / (N_a * N_s); % 资产策略(由 EGM 得到) a_policy = a_next; % N_a x N_s % 构造转移矩阵(稀疏) N_state = N_a * N_s; rows = zeros(N_state * N_s, 1); cols = zeros(N_state * N_s, 1); vals = zeros(N_state * N_s, 1); idx = 0; for i_a = 1:N_a for i_s = 1:N_s for i_s_next = 1:N_s % 找到 a_policy(i_a, i_s) 在资产网格上的位置 [~, i_a_next] = min(abs(a_grid - a_policy(i_a, i_s))); idx = idx + 1; rows(idx) = (i_a_next - 1) * N_s + i_s_next; cols(idx) = (i_a - 1) * N_s + i_s; vals(idx) = Pi(i_s, i_s_next); end end end T_mat = sparse(rows(1:idx), cols(1:idx), vals(1:idx), N_state, N_state); % 分布更新 mu_vec = mu(:); mu_next = T_mat' * mu_vec; mu_next = reshape(mu_next, N_a, N_s);

这段代码的逻辑是:把分布拉成向量,构造转移矩阵,每期做一次乘法。参数说明:N_state是状态总数,等于资产网格点数乘以生产率状态数;转移矩阵的稀疏性来自家庭只移动到相邻资产网格点。注意这里用的是最近邻插值,实际复制时应该用线性插值把资产策略分配到相邻两个网格点,否则分布会过度集中,导致总量波动被放大。

2.4 求解器结构:外层价格迭代与内层家庭迭代

把上面三个模块拼起来,HANK 的求解器是一个嵌套循环:外层猜价格序列(利率、工资、通胀),内层解家庭问题得到消费策略,然后聚合得到总量,检查市场出清条件,更新价格,直到收敛。常见做法是用不动点迭代,外层用fsolve或手动阻尼更新。

% 外层价格迭代(简化版) max_iter = 200; tol = 1e-4; r_seq = 0.02 * ones(T, 1); % 初始猜测 w_seq = 1.0 * ones(T, 1); for iter = 1:max_iter % 内层:给定价格,解家庭问题 c_policy = solve_household(r_seq, w_seq, ...); % 聚合 C = aggregate_consumption(c_policy, mu); L = aggregate_labor(c_policy, mu); % 检查市场出清 excess = C - (1 - theta) * L; % 示例:商品市场出清 if max(abs(excess)) < tol break; end % 更新价格(阻尼) r_seq = r_seq - 0.1 * excess; end

这段代码的逻辑是:外层迭代价格,内层解家庭,聚合后检查出清。参数说明:max_iter是最大迭代次数,tol是收敛容差,阻尼系数 0.1 是为了防止震荡。注意这里省略了货币政策规则(泰勒规则),实际复制时必须加上,否则价格序列没有锚,迭代会发散。

3. 动手复现:从零搭一个最小 HANK 求解器

3.1 网格设计与离散化参数怎么选

资产网格的设计直接决定求解精度和速度。常见做法是用linspace均匀网格,但 HANK 里家庭分布通常集中在低资产区域,均匀网格在低资产处分辨率不够。更好的选择是使用非均匀网格,比如在低资产处加密。MATLAB 里可以用exp变换生成:

N_a = 200; a_min = 0; a_max = 100; % 非均匀网格:低资产处加密 u = linspace(0, 1, N_a)'; a_grid = a_min + (a_max - a_min) * (u.^2);

参数说明:N_a取 200 到 500 之间,太少会导致分布演化误差大,太多会拖慢内层迭代。a_max要足够大,保证最富家庭的借贷约束不绑定,一般取稳态财富的 5 到 10 倍。生产率状态数N_s通常取 3 到 7,用 Tauchen 方法或 Rouwenhorst 方法离散化 AR(1) 过程。

3.2 EGM 迭代的收敛判断与加速技巧

EGM 迭代的收敛速度取决于贴现因子和网格精度。常见做法是用while循环加最大迭代次数,收敛判据用消费策略的最大变化。加速技巧包括:用上一次迭代的消费策略作为初值、用安德森加速(Anderson acceleration)或阻尼更新。

% EGM 迭代(带阻尼) damp = 0.5; for iter = 1:max_iter c_new = egm_step(c_guess, ...); diff = max(abs(c_new(:) - c_guess(:))); if diff < tol break; end c_guess = damp * c_new + (1 - damp) * c_guess; end

参数说明:damp取 0.3 到 0.7 之间,太小收敛慢,太大可能震荡。tol取 1e-6 到 1e-8,取决于你需要多高的精度。注意如果迭代不收敛,先检查欧拉方程右边的期望计算是否正确,尤其是转移矩阵的维度是否对齐。

3.3 分布演化的稀疏矩阵实现与稳态求解

稳态分布是 HANK 求解的起点。常见做法是反复迭代转移矩阵直到分布收敛,或者直接求转移矩阵的左特征向量(特征值 1 对应的特征向量)。MATLAB 里可以用eigs求稀疏矩阵的特征向量:

% 求稳态分布 [V, D] = eigs(T_mat', 1, 'lm'); mu_ss = V / sum(V); mu_ss = reshape(mu_ss, N_a, N_s);

参数说明:eigs的第二个参数 1 表示求一个特征值,'lm'表示最大模。注意转移矩阵的转置是因为分布更新是左乘转移矩阵的转置。如果eigs不收敛,可以改用幂迭代法,反复乘转移矩阵直到分布变化小于容差。

3.4 脉冲响应计算:从稳态出发的线性化路径

脉冲响应是 HANK 复制的主要输出。常见做法是从稳态出发,给一个冲击(比如利率下降 25 个基点),然后沿着转移路径求解。由于 HANK 是非线性的,严格来说需要全局解,但实践中常用线性化近似:在稳态附近做一阶泰勒展开,用线性理性预期模型求解。

% 脉冲响应(线性化近似) shock = zeros(T, 1); shock(1) = -0.0025; % 利率冲击 r_path = r_ss + shock; % 沿着路径求解家庭问题 for t = 1:T c_policy_t = solve_household(r_path(t), w_ss, ...); C(t) = aggregate_consumption(c_policy_t, mu_ss); end % 计算偏离稳态的百分比 C_irf = 100 * (C - C_ss) / C_ss;

参数说明:T取 40 到 100 期,取决于冲击的持久性。shock的大小通常取 25 个基点,对应货币政策的标准冲击。注意这里用的是稳态分布,实际复制时分布会随冲击演化,需要每期更新分布,否则会低估异质性带来的放大效应。

4. 避坑与排查:HANK 复制中最容易翻车的五个地方

4.1 资产网格上界太小导致解发散

现象:EGM 迭代不收敛,消费策略在富裕家庭处出现负值或爆炸。原因:资产网格上界a_max太小,富裕家庭的借贷约束绑定,欧拉方程右边无法正确计算。解决:把a_max调到稳态财富的 10 倍以上,或者在插值时做线性外推并检查外推是否合理。

4.2 转移矩阵维度不对齐导致分布爆炸

现象:分布演化后总概率不等于 1,或者分布集中在少数网格点。原因:转移矩阵构造时资产策略的插值索引错误,或者生产率状态的转移矩阵没有归一化。解决:构造转移矩阵后检查每列和是否为 1,用full(sum(T_mat, 1))验证;资产策略插值时用线性插值分配到相邻两个网格点,而不是最近邻。

4.3 菲利普斯曲线符号错误导致通胀反向

现象:脉冲响应中通胀对边际成本的反应方向相反。原因:菲利普斯曲线的斜率kappa符号错误,或者边际成本的定义与理论不一致。解决:检查kappa的公式,确保(1-theta)*(1-beta*theta)/theta为正;检查边际成本是实际边际成本还是对数偏离,两者差一个稳态值。

4.4 外层价格迭代震荡不收敛

现象:外层迭代中价格序列来回震荡,不收敛。原因:阻尼系数太大,或者市场出清条件写错。解决:把阻尼系数降到 0.05 到 0.1,检查出清条件是否遗漏了某个市场(比如债券市场出清);用fsolve替代手动迭代,让 MATLAB 自动处理雅可比矩阵。

4.5 脉冲响应中分布更新遗漏导致放大效应消失

现象:脉冲响应中消费对利率的反应和 RANK 几乎一样,异质性没有体现。原因:计算脉冲响应时用了稳态分布,没有每期更新分布。解决:在每期求解家庭问题后,用当期的资产策略更新分布,再聚合下一期的总量;检查分布更新的转移矩阵是否随价格变化而更新。

5. 进阶技巧:用线性化方法加速 HANK 求解

5.1 一阶扰动法与状态空间表示

HANK 的全局解很慢,实践中常用一阶扰动法。核心思想是把家庭策略和分布演化在稳态附近线性化,得到一个线性状态空间模型,然后用gensys或dynare求解。MATLAB 里可以手动构造状态空间:

% 线性化状态空间(简化) % 状态:资产分布偏离、价格偏离 % 控制:消费、劳动、通胀 A = [1, 0; 0, beta]; % 状态转移矩阵(示例) B = [0; kappa]; % 控制影响矩阵 % 用 gensys 求解 [G1, ~, ~, ~, ~, ~] = gensys(A, B, ...);

参数说明:A和B的维度取决于状态数和控制数,需要从家庭策略和分布演化的雅可比矩阵得到。注意线性化只在稳态附近有效,冲击太大时误差会很大。

5.2 用稀疏矩阵和并行计算加速

HANK 的瓶颈在分布演化和 EGM 迭代。常见加速技巧:用sparse存储转移矩阵,用parfor并行化 EGM 中不同生产率状态的迭代,用mex编译关键循环。下面是一个parfor的示例:

% 并行化 EGM 中不同生产率状态的迭代 c_new = zeros(N_a, N_s); parfor i_s = 1:N_s c_next = interp1(a_grid, c_guess(:, i_s), a_next(:, i_s), 'linear', 'extrap'); rhs = beta * (1 + r) * c_next.^(-sigma); c_new(:, i_s) = rhs.^(-1/sigma); end

参数说明:parfor需要 Parallel Computing Toolbox,启动并行池有开销,适合N_s较大的情况。注意parfor里不能有依赖顺序的操作,每个i_s的迭代必须独立。

5.3 验证方法:用 RANK 极限和稳态检查

复制 HANK 后,验证结果是否合理很重要。常见做法:把异质性关掉(让生产率状态只有一个,或者让资产网格只有一个点),HANK 应该退化为 RANK,脉冲响应应该和标准 RANK 模型一致。另外检查稳态:总消费加总投资是否等于总产出,总劳动是否等于 1 减去闲暇,分布的总概率是否为 1。

% 验证:HANK 退化为 RANK N_s = 1; % 关闭异质性 s_grid = 1.0; Pi = 1.0; % 重新求解,比较脉冲响应

参数说明:退化测试是检查求解器正确性的最直接方法。如果退化后结果和 RANK 不一致,说明家庭侧或生产侧有符号错误。稳态检查用sum(mu_ss(:))验证分布归一化,用C_ss + I_ss - Y_ss验证市场出清。

我自己的习惯是:每次改完参数或网格,先跑退化测试,再跑稳态检查,最后才看脉冲响应。这个顺序能帮你把大部分翻车挡在早期。希望帮到你。

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

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

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

立即咨询