I'm trying to simulate a fairly standard RBC model. The equations used in the code are all correct based on the chosen specification, so my question relates more to the implementation side.
Specifically, I want to use the technology shock generated in z_hat:
# Generate the autoregressive process for the technology shock:
for t in range(1, T):
# Each period the shock decays by the factor rho_z, mimicking an AR(1) process.
z_hat[t] = rho_z * z_hat[t - 1]
to influence the other model equations recursively. The responses of the model variables are captured in the for loop that follows.
The issue is this: when I run the code as it is, the impulse response functions (IRFs) appear to be flipped. I only get the expected graph if I reverse the order of the calculated IRFs for each variable.
Has anyone encountered a similar issue or can spot what might be going wrong in how the IRFs are being generated or stored? I attached the code which created the IRFs and the full code.
Thanks in advance!
Important part of the code:
# Generate the autoregressive process for the technology shock:
for t in range(1, T):
# Each period the shock decays by the factor rho_z, mimicking an AR(1) process.
z_hat[t] = rho_z * z_hat[t - 1]
# Initialize arrays to store the log-deviations from steady state for various variables.
# The deviations represent percentage deviations from the benchmark steady state.
c_hat = np.zeros(T+1) # Consumption log-deviation, extra period for terminal condition.
i_hat = np.zeros(T) # Investment log-deviation.
k_hat = np.zeros(T+1) # Capital log-deviation, extra period due to accumulation.
y_hat = np.zeros(T) # Output log-deviation.
r_hat = np.zeros(T) # Interest rate log-deviation.
w_hat = np.zeros(T) # Wage log-deviation.
l_hat = np.zeros(T) # Labor log-deviation.
# Terminal condition is assumed for consumption: c_T = 0 (i.e., no deviation at the final period).
# Set initial conditions: assume no deviation in capital and consumption at time t = 0.
k_hat[0] = 0
c_hat[0] = 0
# Loop over each time period to simulate the dynamic responses (Impulse Response Functions - IRFs)
for t in range(T):
# 1. Factor Prices from Marginal Products:
# The wage (w_hat) is determined as the sum of the technology shock and contributions from
# capital and labor deviations scaled by the capital share.
w_hat[t] = z_hat[t] + alpha * k_hat[t] - alpha * l_hat[t]
# The rental rate (r_hat) is calculated using the marginal product conditions;
# it is affected by the deviation of capital and labor.
r_hat[t] = (alpha - 1) * k_hat[t] + z_hat[t] + (1 - alpha) * l_hat[t]
# 2. Labor Supply from the Intratemporal Condition:
# The log-deviation of labor (l_hat) is updated based on the difference between wages and consumption,
# scaled by the ratio ((1-L_ss)/L_ss), which captures the elasticity in this specification.
l_hat[t] = (w_hat[t] - c_hat[t]) * ((1 - L_ss) / L_ss)
# 3. Output Computation:
# Output is determined by combining the contributions of capital and labor deviations weighted by alpha and
# (1-alpha), respectively, then adding the technology shock.
# This update is performed after updating the labor deviation.
y_hat[t] = alpha * k_hat[t] + (1 - alpha) * l_hat[t] + z_hat[t]
# 4. Investment from the Resource Constraint:
# Investment log-deviation is calculated using the resource constraint where output minus the scaled consumption
# is divided by the share of investment in output.
i_hat[t] = (y_hat[t] - phi_c * c_hat[t]) / phi_i
# 5. Capital Accumulation:
# The next period's capital log-deviation is determined by the current capital after depreciation and the
# new investment weighted by delta, representing the adjustment process.
k_hat[t + 1] = (1 - delta) * k_hat[t] + delta * i_hat[t]
# 6. Euler Equation (Consumption Dynamics):
# According to the Euler equation in a log-linearized setting, the change in the consumption deviation
# equals the product of the steady state return factor (beta*q_ss) and the rental rate deviation.
c_hat[t + 1] = c_hat[t] + (beta * q_ss) * r_hat[t]
Full code:
###############################################################################
# Loading Required Packages
###############################################################################
# Import the numpy package for efficient numerical computing and array manipulation.
import numpy as np
# Import sympy for symbolic mathematics (e.g., symbolic algebra, calculus).
import sympy as sp
# Import matplotlib's pyplot for plotting and creating visualizations.
import matplotlib.pyplot as plt
# Set the seed for NumPy's random number generator to ensure that any random processes produce
# the same output every time the code is run (this aids in reproducibility).
np.random.seed(42) # optional, for reproducibility
# Import the math module for accessing standard mathematical functions.
import math
###############################################################################
# End of package loading
###############################################################################
###############################################################################
###############################################################################
# 1% Shock in Technology: Set up the DSGE model environment and steady state.
###############################################################################
###############################################################################
# Starting parameter values for our DSGE model:
# beta: Discount factor, reflecting how future payoffs are weighted relative to present ones.
beta = 0.96
# delta: Depreciation rate of capital per period.
delta = 0.1
# p: Capital's share of output (alternatively represented as alpha in many models).
p = 0.36
# Lss: Steady-state (or initial) labor input; here, representing a small fraction for the population.
Lss = 0.01 # Population starting value
# z: Technology level; initially set to a small value (here equivalent to a 1% initial shock).
z = 0.01
# Define functions to compute steady state values using the model's equations.
# These functions calculate key economic quantities as functions of the parameters and z.
def K_fun(beta, delta, p, Lss, z):
"""
Computes the steady state level of Capital (K) using the model's formula.
The expression is derived from equating marginal products and other steady state conditions.
"""
global K_expr
# The expression involves the technology term z, labor Lss and other parameters.
K_expr = (p * Lss**(1 - p) * z / (1/beta - 1 + delta))**(1/(1 - p))
return K_expr
def q_fun(beta, delta, p, Lss, z):
"""
Computes the steady state marginal product of capital (q) which is related
to the return on capital.
"""
global q_expr
# The function inverts part of the K_fun expression and rescales it by z, p and Lss.
q_expr = z * p * (p * Lss**(1 - p) * z / (1/beta - 1 + delta))**(-1) * Lss**(1 - p)
return q_expr
def w_fun(beta, delta, p, Lss, z):
"""
Computes the steady state wage (w) as given by the marginal product of labor.
"""
global w_expr
# Note the use of the exponent p/(1-p) in the wage formula, capturing the relative power
# of capital’s effect adjusted by the labor share.
w_expr = z * (1 - p) * Lss**(-p) * (p * Lss**(1 - p) * z / (1/beta - 1 + delta))**(p/(1 - p))
return w_expr
def Y_fun(beta, delta, p, Lss, z):
"""
Computes the steady state level of Output (Y) using the production function.
This function assumes a Cobb-Douglas production function depending on K and L.
"""
global Y_expr
# K_expr is used here, and it is assumed to be computed before using K_fun.
Y_expr = z * (K_expr)**p * Lss**(1 - p)
return Y_expr
def I_fun(beta, delta, p, Lss, z):
"""
Computes the level of Investment (I) in the steady state.
Investment is proportional to the capital stock through the depreciation rate.
"""
global I_expr
I_expr = delta * K_expr
return I_expr
def C_fun(beta, delta, p, Lss, z):
"""
Computes steady state Consumption (C) as the residual of output after investment.
"""
global C_expr
C_expr = Y_expr - I_expr
return C_expr
def L_fun(beta, delta, p, Lss, z):
"""
Provides the value of steady state labor. Here it is set to L_ss (previously defined),
implying fixed labor input in the model.
"""
global L_expr
L_expr = Lss
return L_expr
# ---------------------------
# 2. Define the steady state equations (as expressions) in terms of z
# ---------------------------
# Compute the steady state values using the defined functions.
# These are the benchmark values around which log-linearized deviations are computed.
K_val = K_fun(beta, delta, p, Lss, z)
q_val = q_fun(beta, delta, p, Lss, z)
w_val = w_fun(beta, delta, p, Lss, z)
Y_val = Y_fun(beta, delta, p, Lss, z)
I_val = I_fun(beta, delta, p, Lss, z)
C_val = C_fun(beta, delta, p, Lss, z)
L_val = L_fun(beta, delta, p, Lss, z)
# The above assignments are immediately repeated to store the steady state as "_ss" variables,
# which might be used later to make the notation clearer that these are steady-state benchmarks.
K_ss = K_fun(beta, delta, p, Lss, z)
q_ss = q_fun(beta, delta, p, Lss, z)
w_ss = w_fun(beta, delta, p, Lss, z)
Y_ss = Y_fun(beta, delta, p, Lss, z)
I_ss = I_fun(beta, delta, p, Lss, z)
C_ss = C_fun(beta, delta, p, Lss, z)
L_ss = L_fun(beta, delta, p, Lss, z)
# Print the steady state values to verify the baseline of the model. These are calculated at z=1.
print("Steady State Values (at z=1):")
print("K_ss =", K_val)
print("q_ss =", q_val)
print("w_ss =", w_val)
print("Y_ss =", Y_val)
print("I_ss =", I_val)
print("C_ss =", C_val)
print("L_ss =", L_val)
# Define additional parameters for the simulation:
alpha = p # alpha is set equal to capital share, which in some literature uses alpha.
T = 20 # T defines the total number of time periods for the IRF simulation.
rho_z = 0.95 # Autoregressive parameter for the technology shock process.
# From the steady state, compute additional model parameters:
# r_ss is the steady state real interest rate derived from the Euler equation.
r_ss = 1 / beta - 1 + delta
# phi_c and phi_i are ratios of consumption and investment to output in the steady state,
# and are used to scale the IRF responses.
phi_c = C_ss / Y_ss
phi_i = I_ss / Y_ss
# Initialize the technology shock process:
# z_hat is an array representing the shock process over T periods.
z_hat = np.zeros(T)
z_hat[0] = 1 # At time 0, we introduce a 1% technology shock.
# Generate the autoregressive process for the technology shock:
for t in range(1, T):
# Each period the shock decays by the factor rho_z, mimicking an AR(1) process.
z_hat[t] = rho_z * z_hat[t - 1]
# Initialize arrays to store the log-deviations from steady state for various variables.
# The deviations represent percentage deviations from the benchmark steady state.
c_hat = np.zeros(T+1) # Consumption log-deviation, extra period for terminal condition.
i_hat = np.zeros(T) # Investment log-deviation.
k_hat = np.zeros(T+1) # Capital log-deviation, extra period due to accumulation.
y_hat = np.zeros(T) # Output log-deviation.
r_hat = np.zeros(T) # Interest rate log-deviation.
w_hat = np.zeros(T) # Wage log-deviation.
l_hat = np.zeros(T) # Labor log-deviation.
# Terminal condition is assumed for consumption: c_T = 0 (i.e., no deviation at the final period).
# Set initial conditions: assume no deviation in capital and consumption at time t = 0.
k_hat[0] = 0
c_hat[0] = 0
# Loop over each time period to simulate the dynamic responses (Impulse Response Functions - IRFs)
for t in range(T):
# 1. Factor Prices from Marginal Products:
# The wage (w_hat) is determined as the sum of the technology shock and contributions from
# capital and labor deviations scaled by the capital share.
w_hat[t] = z_hat[t] + alpha * k_hat[t] - alpha * l_hat[t]
# The rental rate (r_hat) is calculated using the marginal product conditions;
# it is affected by the deviation of capital and labor.
r_hat[t] = (alpha - 1) * k_hat[t] + z_hat[t] + (1 - alpha) * l_hat[t]
# 2. Labor Supply from the Intratemporal Condition:
# The log-deviation of labor (l_hat) is updated based on the difference between wages and consumption,
# scaled by the ratio ((1-L_ss)/L_ss), which captures the elasticity in this specification.
l_hat[t] = (w_hat[t] - c_hat[t]) * ((1 - L_ss) / L_ss)
# 3. Output Computation:
# Output is determined by combining the contributions of capital and labor deviations weighted by alpha and
# (1-alpha), respectively, then adding the technology shock.
# This update is performed after updating the labor deviation.
y_hat[t] = alpha * k_hat[t] + (1 - alpha) * l_hat[t] + z_hat[t]
# 4. Investment from the Resource Constraint:
# Investment log-deviation is calculated using the resource constraint where output minus the scaled consumption
# is divided by the share of investment in output.
i_hat[t] = (y_hat[t] - phi_c * c_hat[t]) / phi_i
# 5. Capital Accumulation:
# The next period's capital log-deviation is determined by the current capital after depreciation and the
# new investment weighted by delta, representing the adjustment process.
k_hat[t + 1] = (1 - delta) * k_hat[t] + delta * i_hat[t]
# 6. Euler Equation (Consumption Dynamics):
# According to the Euler equation in a log-linearized setting, the change in the consumption deviation
# equals the product of the steady state return factor (beta*q_ss) and the rental rate deviation.
c_hat[t + 1] = c_hat[t] + (beta * q_ss) * r_hat[t]
# The consumption and capital paths are reversed to align the time axis for plotting,
# recognizing that c(t+1) and k(t+1) are plotted rather than c(t) and k(t).
c_hat_aligned = np.insert(c_hat[::-1][:-1], 0, 0.0) # Insert a zero at the beginning after reversing.
k_hat_aligned = np.insert(k_hat[::-1][:-1], 0, 0.0) # Similarly for capital.
# Prepare the variables for plotting by converting log-deviations into percentage deviations.
# The dictionary "variables" maps variable names to their corresponding reversed IRF paths.
variables = {
'Output (Y)': y_hat[::-1],
'Capital (K)': k_hat_aligned,
'Investment (I)': i_hat[::-1],
'Consumption (C)': c_hat_aligned,
'Wage (w)': w_hat[::-1],
'Interest Rate (r)': r_hat[::-1],
'Labor (L)': l_hat[::-1],
}
# Set up the plot size for the IRFs.
plt.figure(figsize=(12, 6))
# Plot each variable's deviation over time.
for name, var in variables.items():
# Create a time array that matches the length of the variable series.
time_var = np.arange(len(var))
plt.plot(time_var, var, label=name)
# Also plot the technology shock (z_hat) for reference.
plt.plot(np.arange(len(z_hat)), z_hat, label='Technology (z)', linewidth=1, color='black')
# Add a horizontal reference line at zero to clearly see deviations from the steady state.
plt.axhline(0, color='gray', linestyle='--')
# Add plot title and axis labels.
plt.title("IRFs from Log-Linearized DSGE Model (Shock at t = 0)")
plt.xlabel("Time")
plt.ylabel("% Deviation from Steady State")
# Add a legend to distinguish between different variables.
plt.legend()
# Display grid lines for improved readability.
plt.grid(True)
# Adjust the layout to ensure nothing is clipped.
plt.tight_layout()
# Render the final plot.
plt.show()