Fluctuations

NOTE: The present notebook is coded in R. It relies heavily on the tidyverse ecosystem of packages. We load the tidyverse below as a prerequisite for the rest of the notebook - along with a few other libraries.

\(\rightarrow\) Don’t forget that code flows sequentially. A random chunk may not work if the previous have have not been executed.

library(tidyverse)   # Package for data wrangling
library(readxl)      # Package to import MS Excel files
library(latex2exp)   # Package for LaTeX expressions
library(quantmod)    # Package for stock data extraction
library(highcharter) # Package for reactive plots
library(WDI)         # Package for World Bank data
library(ggsci)       # Package for color palettes

The content of the notebook is heavily inspired from the book Advanced Macro-economics - An Easy Guide.

Context

Until now, we have mostly been working on equilibrium models that are supposed to characterize long-term trends (e.g., growth). But, “in practice”, all economic indicators fluctuate, and in some cases, sharp variations occurs, for instance during crises.

So studying the short-term evolution of the economy is also useful (some could suggest it is even more useful!).

Let’s illustrate this rapidly with some data.

wb_raw = WDI(indicator = c("gdp_per_capita" = "NY.GDP.PCAP.KD",
                           "population" = "SP.POP.TOTL",
                           "gdp" = "NY.GDP.MKTP.CD",
                           "consumption" = "NE.CON.TOTL.CD",
                           "gdp_growth" = "NY.GDP.MKTP.KD.ZG",
                           "unemployment" = "SL.UEM.TOTL.ZS"), 
             #country = c('FR','US'), # here you can specify some countries
             extra = TRUE,
             start = 1960, 
             end = 2025) |>
  mutate(across(everything(), as.vector)) |>
  select(-status, -lending, -iso2c, -iso3c)
wb_data <- wb_raw |> filter(country %in% c("France", "United States")) |> arrange(country, year)
wb_eu <- wb_raw |> 
  filter(country %in% c("Spain", "Italy", "Poland", "Slovenia", "Venezuela, RB")) |> 
  arrange(country, year)

Let us depict the growth rate of the world’s GDP.

wb_raw |>
  filter(country == "World") |>
  ggplot(aes(x = year, y = gdp_growth)) + geom_hline(yintercept = 0, linetype = 2, color = "gray") + 
  geom_hline(yintercept = 3.46, linetype = 3, color = "red", alpha = 0.5) +  # Sample average
  geom_line(linewidth = 1.2) +
  ggtitle("GDP growth rate (%) - World") + 
  theme_classic() + 
  theme(axis.title = element_blank()) 

Growth does oscillate a lot (around 2.5%).

Same for unemployment… in the US.
Worldwide, things are much smoother (aggregation effect, perhaps!).

wb_raw |>
  filter(country == c("World", "United States", "China"), year > 1990) |>
  ggplot(aes(x = year, y = unemployment, color = country)) + 
  geom_line(linewidth = 1.2) +
  theme_classic() + 
  ggtitle("Unemployment rate (%)") + 
  theme(axis.title = element_blank(), 
        legend.title = element_blank(),
        legend.position = "bottom") 

Real business cycle (RBC) models

We extend the Ramsey model in two directions:
  • Following Time to Build and Aggregate Fluctuations, we assume that the representative agent can choose between working and leisure time. Working provides labor \(l_t\), whereas leisure is denoted \(h_t\). Because time is limited (it is impossible to increase \(l_t\) and \(h_t\) indefinitely), we introduce the (normalizing) constraint \[l_t+h_t=1.\] Here for instance, one means “one day awake”. So let’s say 16 hours: this could mean 8+8, meaning \(l_t=h_t=0.5\).
  • We assume that, in the production function \(y_t = z_tk_t^\alpha l_t^{1-\alpha}\), the productivity factor is subject to shocks which will generate fluctuations: \[z_t = \phi z_{t-1}+e_t, \quad |\phi|<1,\] \(e_t\) representing iid shocks.

We moreover posit that the agent owns the capital \(k_t\) and rents it to the firm at rate \(r_t\), so that the budget constraint reads: \[k_{t+1} = l_tw_t + (1+r_t)k_t-c_t,\]

i.e., future available capital will be the proceeds of investment in prior capital (net of depreciation) plus income from working (\(w_t\) is the wage) minus consumption.

The utility of the agent is \[U=\sum_{t=0}^\infty (1+\rho)^{-t}\left((1-\psi)u(c_t) + \psi v(h_t) \right),\] where \(\psi\) determines the relative importance of work versus leisure (and \(\rho\) the time preferences).

The trade-off between leisure and work is obtained by seeing that \[c_t = w_t(1-h_t) + (1+r_t)k_t-c_t-k_{t+1},\] and plugging this in the utility and differentiating w.r.t. \(h_t\) leads to \[w_t(1-\psi)u'(c_t)=\psi v'(h_t),\] so that if we assume log-utility, \[w_t(1-\psi)h_t=\psi c_t.\]

We recall the Euler equation: \[u'(c_t) = \frac{1+r_{t}}{1+\rho} \mathbb{E}_t[u'(c_{t+1})],\] which, under logarithmic utility (again) gives

\[c_t^{-1} = \frac{1+r_{t}}{1+\rho} \mathbb{E}_t[c_{t+1}^{-1}],\]

As the model is anyway impossible to solve numerically, we can instead try to exploit it via simulations. We can thus imagine an auto-regressive process for consumption as well: \[c_{t+1}=\frac{1+r_{t}}{1+\rho}c_t + \sigma \epsilon_{t+1},\]

though, as we will see in the data, in this case the autoregressive parameter should be larger than one (consumption grows steadily). One corollary is that we cannot estimate an AR(1) process for this. Time is relatively finite, there will be no “explosion”.

Digressions via ILO data

Data (working hours)

We exploit amazing data from the ILO on working hours.

The data is highly granular sector-wise and by age but we focus on the economy as a whole and not on particular industries or age groups (for simplicity).

url <- "https://rplumber.ilo.org/data/indicator/?id=HOW_TEMP_SEX_AGE_ECO_NB_A&lang=en&type=label&format=.csv&channel=ilostat&title=mean-weekly-hours-actually-worked-per-employed-person-by-sex-age-and-economic-activity-annual"
ilo_hours <- read_csv(url) |>
  filter(classif2.label == "Economic activity (Broad sector): Total",
         classif1.label == "Age (Aggregate bands): Total") |>
  select(ref_area.label, sex.label, time, obs_value) 
colnames(ilo_hours) <- c("country", "gender", "year", "value")

Values for gender: Total, Male, Female, Other.

ilo_hours |> 
  filter(gender != "Other", country == "United States of America") |>
  ggplot(aes(x = year, y = value, color = gender)) + geom_line(linewidth = 1.2) + 
  theme_classic() + 
  ggtitle("Weekly hours worked in the US") + 
  theme(legend.position = c(0.9, 0.98),
        axis.title = element_blank(),
        title = element_text(face = "bold"),
        legend.text = element_text(size = 10),
        legend.title = element_blank())

Data (wages)

Average hourly earnings of employees (local currency).

url <- "https://rplumber.ilo.org/data/indicator/?id=SDG_0851_SEX_OCU_NB_A&lang=en&type=label&format=.csv&channel=ilostat&title=sdg-indicator-851-average-hourly-earnings-of-employees-by-sex-local-currency-annual"
ilo_wages <- read_csv(url) |>
  select(ref_area.label, sex.label, classif1.label, time, obs_value)
colnames(ilo_wages) <- c("country", "gender", "occupation", "year", "value")

We can directly see tne gender gap.

ilo_wages |>
  filter(country %in% c("United States of America", "France"), 
         occupation == "Occupation (Skill level): Total") |>
  ggplot(aes(x = year, y = value, color = gender, linetype = country)) + 
  geom_line(linewidth = 1.0) +
  theme_minimal() + ggtitle("Avg. hourly earning($)") + 
  theme(axis.title = element_blank(),
        legend.title = element_blank(),
        title = element_text(face = "bold"),
        legend.spacing.y = unit(-1, "pt"),
        legend.background = element_rect(fill = "white", color = "white"),
        legend.position = c(0.27, 0.75))

Calibration

Thanks to World Bank data, we can try to estimate some of these quantities.
For instance, for the volatility of consumption… or its shocks.

wb_data |> 
   filter(country == "United States") |>
  ggplot(aes(x = year, y = consumption/10^9)) + geom_line() + 
  theme(axis.title.x = element_blank())

wb_data |>
   filter(country == "United States") |>
   mutate(c_growth = consumption/dplyr::lag(consumption) - 1) |>
   summarise(m = mean(c_growth, na.rm = T),
             s = sd(c_growth, na.rm = T))
m s
0.0655962 0.0275734

So consumption grows at a rate of 6.5% per year with a 2.8% standard deviation for its growth.

Given this other dataset of the World Bank, we can also estimate that the total productivity factor (TPF) grows at a rate of 0.6% per year, with a variance of 1%.

Simulations

Below, we assume fixed \(r_t\) for simplicity (which drives consumption and capital dynamics), as well as exogenous wages \(w_t=e^{r_wt}\) following deterministic inflation. We start by setting the parameters and initializing the variables. Note that we impose \((1+r)/(1+\rho)\approx 1.06\).

rho <- 0.01       # Discounting factor
sigma_c <- 0.03   # Vol of consumption innovations
sigma_A <- 0.005  # Vol of TPF schocks
psi <- 0.5        # Relative work/leisure preference
phi <- 0.003      # Growth rate of TPF
r <- 0.07         # Interest rate
r_w <- 0.06       # Growth rate of wages

n <- 100          # Number of periods
z <- 1            # Initialization of productivity
c <- 1            # Initialization of consumption
w <- 5            # Initialization of wages
k <- 6            # Initialization of capital 
h <- 0.5          # Initialization of leisure

Next, we launch the loop and plot the resulting time-series.

set.seed(42)
for(t in 1:(n-1)){ 
  w[t+1] <- (1+r_w) * w[t]                                # Deterministic wages
  z[t+1] <- (1+phi + sigma_A * rnorm(1)) * z[t]             # Productivity shock
  c[t+1] <- ((1+r)/(1+rho) + sigma_c * rnorm(1)) * c[t]   # Consumption with shock
  h[t+1] <- psi/(1-psi)*c[t+1]/w[t+1]                     # Leisure from log-utility
  k[t+1] <- (1-h[t])*w[t] + (1+r)*k[t] - c[t]             # Motion of capital
}
data.frame(t = 1:n, wage = w, productivity = z, consumption = c, leisure = h, capital = k) |>
  pivot_longer(-t, names_to = "variable", values_to = "value") |>
  ggplot(aes(x = t, y = value)) + geom_line() + 
  facet_wrap(vars(variable), scales = "free") +
  theme(axis.title.y = element_blank())

This can seem a bit simplistic, but some matches a few patterns in the data.
For productivity, the trend is exactly that from the data (makes sense), but for the relative importance of leisure, it’s hard to tell.
Maybe the model could serve as a tool to determine reasonable values for \(l\)!

Keynesian models

Honest disclaimer

This subsection was partly generated by generative AI.

Introduction to IS-LM

1️⃣ The IS Curve — Goods Market Equilibrium

The is \[Y = C(Y - T) + I(i) + G + NX. \]

The interpretation is as follows. Total output (Y) equals total planned spending (aggregate demand):

  • \(C(Y - T)\): consumption by households (depends positively on disposable income, minus taxes);
  • \(I(i)\): investment by firms (depends negatively on the interest rate);
  • \(G\): government spending (exogenous);
  • \(NX\): net exports (exports minus imports).

This is the GDP expenditure approach/decomposition which we have briefly mentioned earlier. Mechanically,

  • When the interest rate (\(i\)) falls → investment (\(I\)) rises → total demand increases → output (\(Y\)) rises.
  • When the interest rate rises → investment falls → demand and output fall.

Hence, the IS curve shows an inverse relationship between (\(i\)) and (\(Y\)): it is downward sloping in the (\(Y\), \(i\)) plane.

Why “IS”?

It stands for Investment = Saving: every point on the curve represents equilibrium in the goods market.


2️⃣ The LM Curve — Money Market Equilibrium

The LM Equation is

\[\frac{M}{P} = L(i, Y)\]

Equilibrium in the money market occurs when:

  • \(M/P\): real money supply (nominal supply set by the central bank, adjusted for prices to take inflation into account; \(P\) is a proxy for the aggregate price level);

  • \(L(i, Y)\): money demand (desired amount of purchasing power that agents wish to hold in liquid form), which is a function that:

    • increases with \(Y\) (more income → more transactions),
    • decreases with \(i\) (higher interest rates → less willingness to hold money).

The implications:

  • When income (\(Y\)) rises → people want more money for transactions.
    Since the real money supply \(M/P\) is fixed, the interest rate (\(i\)) must rise to restore equilibrium.
  • Therefore, the LM curve shows a positive relationship between \(Y\) and \(i\):
    it is upward sloping in the \((Y, i)\) plane.
Why “LM”?

It stands for Liquidity preference = Money supply, i.e., points where the money market is in equilibrium.


3️⃣ The IS–LM Intersection — General Equilibrium

At the intersection of IS and LM, we obtain: \((Y^*, i^*)\):

  • \(Y^*\): equilibrium output (goods market equilibrium)
  • \(i^*\): equilibrium interest rate (money market equilibrium)

Interpretation

At this point, both markets clear:

  • The goods market is in equilibrium (aggregate demand = output).
  • The money market is in equilibrium (money supply = money demand).

🧭 A few policy implications

  • Fiscal policy for the government (↑\(G\) or ↓\(T\)) → shifts IS to the right → higher \(Y\), higher \(i\).
  • Monetary policy for the central bank (↑\(M\)) → shifts LM to the right → higher \(Y\), lower \(i\).

Thus, policymakers can influence short-run output and interest rates through fiscal and monetary actions.
Clear examples of this with the FED that micro-tunes interest rates to keep the economy in balance (not heating and out of recession). But with dual objectives on inflation and unemployment

Code

We use linear forms for simplicity:

  • IS: \(i = a - b \times Y\)
  • LM: \(i = c + d \times Y\)

First, the parameters and the generation of data (code on the right).

# Parameters
a <- 12      # IS intercept
b <- 0.8     # IS slope
c <- 2       # LM intercept
d <- 0.3     # LM slope

# Range of Y (output)
Y_seq <- seq(0, 20, length.out = 200)

# IS and LM curves
data <- data.frame(
  Y = Y_seq,
  IS = a - b * Y_seq,
  LM = c + d * Y_seq
)

# Compute equilibrium (intersection point) --------------------------------
# a - bY = c + dY  =>  Y* = (a - c)/(b + d)
Y_eq <- (a - c) / (b + d)
i_eq <- a - b * Y_eq

Then, the plot.

ggplot(data, aes(x = Y)) +
  # Curves
  geom_line(aes(y = IS, color = "IS Curve"), size = 1.2, linetype = "solid") +
  geom_line(aes(y = LM, color = "LM Curve"), size = 1.2, linetype = "solid") +
  # Equilibrium point
  geom_point(aes(x = Y_eq, y = i_eq), size = 4, color = "black") +
  geom_vline(xintercept = Y_eq, linetype = "dashed", color = "gray40") +
  geom_hline(yintercept = i_eq, linetype = "dashed", color = "gray40") +
  # Labels for equilibrium
  annotate("text", x = Y_eq + 0.6, y = i_eq + 0.5, label = "E*", fontface = "bold") +
  annotate("text", x = Y_eq + 0.5, y = -0.5, label = expression(Y^"*"), parse = TRUE) +
  annotate("text", x = -0.8, y = i_eq - 0.3, label = expression(i^"*"), parse = TRUE) +
  # Axes labels
  labs(x = expression("Output (Y)"), y = expression("Interest rate (i)"), color = "") +
  # Colors & theme
  scale_color_manual(values = c("IS Curve" = "#E74C3C", "LM Curve" = "#3498DB")) +
  theme_minimal(base_size = 14) +
  theme(
    legend.position = c(0.85, 0.85),
    legend.background = element_rect(fill = alpha("white", 0.7), color = NA),
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "gray85")
  ) 

What happens when a policy change occurs?

# Original curves:
a  <- 12    # IS intercept
b  <- 0.8   # IS slope
c  <- 2     # LM intercept
d  <- 0.3   # LM slope
# Policy-shifted curves:
a_shift <- 14   # Fiscal expansion -> IS shifts right (higher intercept)
c_shift <- 1    # Monetary expansion -> LM shifts down (lower intercept)
Y_seq <- seq(0, 20, length.out = 200)
# --- 2. Compute curves -----------------------------------------------------
data <- data.frame(
  Y = Y_seq,
  IS_orig = a - b * Y_seq,
  IS_new  = a_shift - b * Y_seq,
  LM_orig = c + d * Y_seq,
  LM_new  = c_shift + d * Y_seq
)
# --- 3. Compute equilibria -------------------------------------------------
# Original equilibrium
Y_eq_orig <- (a - c) / (b + d)
i_eq_orig <- a - b * Y_eq_orig
# New equilibrium (after both shifts)
Y_eq_new <- (a_shift - c_shift) / (b + d)
i_eq_new <- a_shift - b * Y_eq_new
# --- 4. Build plot ---------------------------------------------------------
ggplot(data, aes(x = Y)) +
  # Shaded regions to highlight movement
  annotate("rect", xmin = Y_eq_orig, xmax = Y_eq_new,
           ymin = 0, ymax = i_eq_new, alpha = 0.05, fill = "gray50") +
  # IS curves
  geom_line(aes(y = IS_orig, color = "IS1"), size = 1.2) +
  geom_line(aes(y = IS_new, color = "IS2"), size = 1.2, linetype = "solid") +
  # LM curves
  geom_line(aes(y = LM_orig, color = "LM1"), size = 1.2) +
  geom_line(aes(y = LM_new, color = "LM2"), size = 1.2, linetype = "solid") +
  # Equilibria
  geom_point(aes(x = Y_eq_orig, y = i_eq_orig), size = 3, color = "black") +
  geom_point(aes(x = Y_eq_new, y = i_eq_new), size = 3, color = "black") +
  # Dashed lines for equilibrium coordinates
  geom_vline(xintercept = c(Y_eq_orig, Y_eq_new), linetype = "dashed", color = "gray50") +
  geom_hline(yintercept = c(i_eq_orig, i_eq_new), linetype = "dashed", color = "gray50") +
  # Labels for equilibria
  annotate("text", x = Y_eq_orig - 0.5, y = i_eq_orig - 0.5, label = "E1", fontface = "bold") +
  annotate("text", x = Y_eq_new + 0.6, y = i_eq_new + 0.3, label = "E2", fontface = "bold") +
  # Arrows showing movement
  annotate("segment",
           x = Y_eq_orig , xend = Y_eq_new ,
           y = i_eq_orig , yend = i_eq_new ,
           color = "black", arrow = arrow(length = unit(0.25, "cm"))) +
  labs(x = expression("Output, " ~ Y), y = expression("Interest rate, " ~ i), color = "Curves") +
  scale_color_manual(values = c(
    "IS1" = "#E74C3C",   # red
    "IS2" = "#FF9999",   # lighter red
    "LM1" = "#3498DB",   # blue
    "LM2" = "#91C9F7"    # lighter blue
  )) +
  theme_minimal(base_size = 14) +
  theme(
    legend.position = c(0.8, 0.85),
    legend.background = element_rect(fill = alpha("white", 0.7), color = NA),
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "gray85")
  ) +
  # Annotations for policy
  annotate("text", x = 6.5, y = 13, label = "Fiscal expansion → IS shifts right", color = "#E74C3C", size = 3.8) +
  annotate("text", x = 5.5, y = 0.5, label = "Monetary expansion → LM shifts down", color = "#3498DB", size = 3.8)

  • red shift: government spending increases so output increases too. But the LM curve implies a higher \(i\) (where dark red and light blue lines intersect).
  • blue shift: the central bank increases money supply (e.g. it buys Treasury bills from banks or investors with newly created money). This increases demand (via clearing) and output, but reduces \(i\).

When both shifts occur at the same time, the new equilibrium is favorable to growth, but mixed for interest rates (opposite effects).


The Taylor rule

The Taylor Rule is a simple yet powerful guideline for how a central bank (like the Federal Reserve or the European Central Bank) should set its short-term nominal interest rate in response to changes in inflation and economic activity.

It was introduced by economist John B. Taylor (in 1992) as a way to describe and formalize the behavior of monetary policy under an inflation-targeting framework.

The equation

\[i_t = r^* + \pi_t + \phi_{\pi} (\pi_t - \pi^*) + \phi_{y} (y_t - \bar{y}_t)\]

Where:

  • \(i_t\): nominal policy interest rate (e.g., the federal funds rate) \(\rightarrow\) the instrument
  • \(r^*\): equilibrium real interest rate (neutral rate consistent with full employment)
  • \(\pi_t\): current inflation rate
  • \(\pi^*\): target inflation rate
  • \(y_t - \bar{y}_t\): output gap (actual output minus potential output)
  • \(\phi_{\pi}, \phi_{y}\): policy response coefficients to inflation and output, respectively

We clearly see the dual objective of the FED here: balancing inflation & growth (and implicitly, tackling unemployment, too).

Economic Intuition

  • When inflation is above target (\(\pi_t > \pi^*\)), the rule recommends raising the policy rate.
  • When output is below potential (\(y_t < \bar{y}_t\)), it suggests lowering the policy rate to stimulate the economy.
  • The Taylor Rule thus balances the central bank’s dual mandate:
    • price stability (control inflation), and
    • economic stability (support output and employment).

Example (Typical Parameterization)

A commonly used version is:

\[i_t = r^* + \pi_t + 0.5 \times (\pi_t - \pi^*) + 0.5 \times (y_t - \bar{y}_t)\]

This means the central bank raises the nominal rate by 0.5 percentage points for each 1-point rise in inflation above target and by 0.5 points for each 1-point rise in the output gap.

Let’s take values that reflect historical patterns (2010-2025). For instance, \(r^*=2\%\), \(\pi= 2.5\%\), \(\pi^*=2\%\), \(\bar{y}=1.5\%\), \(y=1\%\). Then, \[i_t = 2\% + 2.5\% + 0.5 \times (0.5\%) + 0.5 \times (-0.5\%)= 4.5\%\]

and this is not too far from the FED rate as of late 2025.

Relation to IS–LM and Modern Policy

In the IS–LM framework, the Taylor Rule replaces the exogenous LM curve with an interest-rate rule that depends on inflation and output.
This leads to the New Keynesian IS–MP model, where monetary policy (MP curve) is explicitly governed by a central bank rule instead of a fixed money supply.

Key Takeaway

The Taylor Rule provides a systematic, transparent, and predictable way to conduct monetary policy.
It adjusts interest rates to stabilize both inflation and output around their desired levels.

A more complete model

Notations

We work in discrete time, with periods indexed by \(t = 0, 1, 2, ...\), moreover:

  • \(x_t\) : output gap (deviation of output from its natural level \(y^n_t\) if there were no price rigidity - see below)
  • \(\pi_t\) : inflation rate
  • \(i_t\) : nominal interest rate set by the central bank (on bonds)
  • \(r_t^n\) : natural (real) rate of interest
  • \(\mathbb{E}_t\) : expectations operator conditional on information available at time \(t\)
  • \(\beta\) : subjective discount factor (\(\beta\in(0,1)\))
  • \(\sigma\) : intertemporal elasticity of substitution (inverse of the coefficient of relative risk aversion)
  • \(\kappa\) : slope of the New Keynesian Phillips curve
  • \(\phi_{\pi}\), \(\phi_x\) : policy rule coefficients (response of interest rate to inflation and output gap)
  • \(\varepsilon_t\) : monetary policy shock

Assumptions

  • Households maximize expected lifetime utility \[\mathbb{E}_t \left[ ∑_{s=0}^{∞} β^s [ u(C_{t+s}) - v(L_{t+s}) ] \right]\] subject to the intertemporal budget constraint: \[P_t C_t + B_t = W_t L_t + (1 + i_{t-1}) B_{t-1} + \Pi_t,\] (l.h.s. is expenses/investment and r.h.s is income), where

    • \(P_t\): price level
    • \(C_t\): consumption
    • \(B_t\): nominal bond holdings
    • \(W_t\): nominal wage
    • \(L_t\): labor supply
    • \(i_{t-1}\): nominal interest rate on previous period’s bonds
    • \(\Pi_t\): profits from firms.
  • Firms operate under monopolistic competition and face Calvo price rigidity: In each period, only a fraction (\(1 - θ\)) of firms can reset prices optimally; the rest keep their previous price.

  • The monetary authority sets the nominal interest rate using a Taylor-type rule: \[i_t = r_t^n + \phi_{\pi} \pi_t + \phi_x x_t + \varepsilon_t\]

  • Market clearing implies that aggregate demand equals aggregate supply in equilibrium: the output gap (i.e., actual minus potential output) is equal to the consumption gap \(x_t = y_t - y_t^n = c_t-c^n_t\).

Core equations

First, the Dynamic IS Curve. Households choose consumption to satisfy a standard Euler equation:

\[ 1= β \mathbb{E}_t \left[ \frac{C_t}{C_{t+1}} \frac{1 + i_t}{1 + \pi_{t+1}} \right],\] with, upon equilibrium, \(\beta^{-1}=\mathbb{E}[(1+i_t)]\).

Assuming output and consumption move together (small open economy approximation or closed economy with flexible labor supply):

\[x_t ≈ c_t - c_t^n.\]

Hence, linearizing around the steady state with \(\frac{C_t}{C_{t+1}}= 1+\frac{C_t}{C_{t+1}} - 1\approx 1+ \log\left(\frac{C_t}{C_{t+1}}\right)=1+c_t-c_{t+1}\), and defining output gap \(x_t\) leads to:

\[x_t = \mathbb{E}_t [x_{t+1}] - (1 / \sigma) ( i_t - \mathbb{E}_t [\pi_{t+1}] - r_t^n ), \tag{1}\] with \(\sigma\) being the IES in all generality, so with log-utility it is equal to one.

Interpretation: The output gap today depends positively on expected future output and negatively on the real interest rate (\(i_t - E_t [\pi_{t+1}]\)). If monetary policy raises the real rate, consumption and output fall.

Next, the New Keynesian Phillips Curve (NKPC):

\[\pi_t = \beta \mathbb{E}_t [\pi_{t+1}] + \kappa x_t \tag{2}\]

Firms set prices based on expected future inflation and current marginal costs (proxied by \(x_t\)). The parameter \(\kappa\) measures how sensitive inflation is to real activity. If output is above potential (x_t > 0), inflation tends to rise.

Finally, the monetary policy rule (à la Taylor, but around the steady state):

\[i_t = \phi_{\pi} \pi_t + \phi_x x_t + \varepsilon_t \tag{3}\]

As seen before, the central bank adjusts the nominal interest rate in response to deviations of inflation and output from targets. Typically, \(\phi_{\pi} > 1\) ensures that monetary policy is stabilizing (the Taylor principle).


Solution

Combining the three equations Equation 1 - Equation 3, we get for \(\sigma=1\) a linear system in the pair (\(x_t,\pi_t\)):

\[\begin{align} x_t &= E_t x_{t+1} - ( \phi_{\pi} \pi_t + \phi_x x_t + \varepsilon_t - E_t \pi_{t+1} - r_t^n ) \\ \pi_t &= \beta E_t [\pi_{t+1}] + \kappa x_t \end{align}\]

This can be written more compactly as the following system:

\[A y_t = B E_t [ y_{t+1} ] + C s_t, \]

where \(y_t = (x_t, \pi_t)'\) and \(s_t = (r_t^n, \varepsilon_t)'\).

with

\[A = \begin{pmatrix} 1 + \phi_x & \phi_\pi \\ -\kappa & 1 \end{pmatrix}, \qquad B = \begin{pmatrix} 1 & 1 \\[8pt] 0 & \beta \end{pmatrix}, \qquad C = \begin{pmatrix} 1 & -1 \\[8pt] 0 & 0 \end{pmatrix}.\]

The equilibrium paths of \(x_t\) and \(\pi_t\) depend on expectations of future conditions and policy parameters.

Under rational expectations (\(\mathbb{E}_t[y_{t+1}]=y_t\)) and suitable parameter restrictions (notably \(\phi_{\pi} > 1\)), the system has a unique stable solution where inflation and output return to steady state after shocks.

# Parameters
beta <- 0.99
kappa <- 0.1
phi_pi <- 1.5
phi_x <- 0.5
TT <- 40
set.seed(42)
eps <- rnorm(TT, 0, 0.005)     # monetary shocks
r_n <- rep(0, TT)
#r_n[5] <- -0.01               # temporary fall in natural rate (demand shock)
# Initialization
x <- pi <- i <- rep(0, TT)
# Matrix coefficients
A <- matrix(c(1 + phi_x, phi_pi, -kappa, 1), nrow = 2, byrow = TRUE)
B <- matrix(c(1, 1, 0, beta), nrow = 2, byrow = TRUE)
C <- matrix(c(1, -1, 0, 0), nrow = 2, byrow = TRUE)

Below we assume simple expectations such that the agent is agnostic to change and thus predicts a flat evolution of the system. Moreover, we alter the system so that \(y_{t+1}=A^{-1}(By_t+Cs_t)\) in order to be able to move the system forward, even if it is not exactly the formulation of the model. This is purely a matter of convenience.

# Solve forward using expectations (perfect foresight)
for(t in 1:(TT-1)){
  y_t <- c(x[t], pi[t])           # current state vector y_t
  E_next <- y_t                   # naive expectations for next period E_t[y_{t+1}] = y_t 
  s_t <- c(r_n[t], eps[t])        # exogenous shocks at t
  rhs <- B %*% E_next + C %*% s_t # right-hand side: B E_t[y_{t+1}] + C s_t
  y_next <- solve(A, rhs)         # solve for y_t (A y_t = rhs) 
  x[t+1] <- y_next[1]
  pi[t+1] <- y_next[2]
  # policy rate by Taylor rule (observed at t)
  i[t] <- r_n[t] + phi_pi * pi[t] + phi_x * x[t] + eps[t]
}

data.frame(t = 1:(TT-1), output_gap = x[1:(TT-1)], inflation = pi[1:(TT-1)], rate = i[1:(TT-1)]) |>
  pivot_longer(-t, names_to = "variable", values_to = "value") |>
  ggplot(aes(x = t, y = value)) + geom_line() +
  ggtitle("NK model simulation with naive expectations") +
  facet_grid(rows = vars(variable)) +
  theme_minimal() +
  theme(axis.title = element_blank(), title = element_text(face = "bold"))