Markov-switching VAR¶

Vector autoregressions, Part 11: discrete regimes, discovered by the data¶

Part 5's time-varying-parameter VAR let the economy's dynamics drift smoothly. But some changes are not gradual — they are switches: the economy flips between a calm state and a turbulent one, between active and passive policy, between expansion and recession. The Markov-switching VAR (Hamilton 1989; Krolzig 1997) models exactly that: the VAR's parameters jump between a small number of regimes, governed by a latent Markov chain that the data infer. Where Part 10 imposed the Great-Moderation break to identify shocks, the MS-VAR is handed no dates at all — it discovers the regimes, and (as we'll see) rediscovers 1984 on its own. Built from scratch with a Gibbs sampler whose centrepiece is the forward-filter-backward-sample of the hidden regime path.

The model and the sampler¶

An MSIH-VAR — switching intercept and covariance, constant autoregressive dynamics: $$y_t = c_{S_t} + B_1 y_{t-1} + \dots + B_p y_{t-p} + \varepsilon_t,\qquad \varepsilon_t\sim\mathcal N(0,\Sigma_{S_t}),$$ where $S_t\in\{1,\dots,K\}$ is a latent Markov chain with transition matrix $P$ ($P_{jk}=\Pr(S_t=k\mid S_{t-1}=j)$). Each regime has its own mean level $c_k$ and shock covariance $\Sigma_k$; $P$'s diagonal governs how persistent the regimes are.

Gibbs sampler (from scratch). Three blocks cycle:

  1. The regime path $S_{1:T}$ — forward-filter-backward-sample. Run the Hamilton filter forward: predict $\Pr(S_t\mid y_{1:t-1})$ through $P$, then update with each regime's VAR likelihood $\mathcal N(y_t;c_k+B x_t,\Sigma_k)$. Then sample backwards: draw $S_T$ from its filtered distribution and $S_t\mid S_{t+1}$ down to $t=1$. This is the discrete-state analogue of the Carter-Kohn smoother.
  2. The parameters — given $S$, each regime's intercept and covariance are conjugate Normal / inverse-Wishart draws; the constant lag matrix $B$ is a GLS draw across regimes.
  3. The transition matrix $P$ — each row is a Dirichlet draw from the counted regime transitions.

We fix the label-switching by ordering regimes by volatility (regime 0 = calm, regime 1 = turbulent). Data: the same monetary system as Parts 4/10 (bvar_sv_data.csv: GDP growth, inflation, the funds rate), $K=2$ regimes, $p=2$.

In [1]:
import numpy as np, pandas as pd, matplotlib.pyplot as plt
import msvar as MS

d = pd.read_csv("bvar_sv_data.csv", index_col=0, parse_dates=True)
lab = ["GDP growth", "inflation", "funds rate"]; Y = d[["gdp_growth", "inflation", "tbill"]].values
dates = d.index; p = 2
fit = MS.gibbs_msvar(Y, p, K=2, ndraw=4000, burn=2500, seed=1)     # Gibbs with FFBS of the regime path
pturb = fit["S"].mean(0)                                           # smoothed P(turbulent regime | data)
tdates = dates[p:]; Pm = fit["P"].mean(0)
sd = np.sqrt(fit["Sigma"].mean(0)[:, [0, 1, 2], [0, 1, 2]])
print("regime shock volatilities (gdp/infl/tbill):  calm", np.round(sd[0], 1), " turbulent", np.round(sd[1], 1))
print("P(turbulent) pre-1984 %.2f  post-1984 %.2f  (the Great Moderation, found endogenously)"
      % (pturb[tdates < pd.Timestamp("1984-01-01")].mean(), pturb[tdates >= pd.Timestamp("1984-01-01")].mean()))
print("persistence: calm %.2f (avg %.0fq)  turbulent %.2f (avg %.0fq)" % (Pm[0,0], 1/(1-Pm[0,0]), Pm[1,1], 1/(1-Pm[1,1])))

# NBER recession quarters over the sample (peak -> trough)
recessions = [("1969-12", "1970-11"), ("1973-11", "1975-03"), ("1980-01", "1980-07"),
              ("1981-07", "1982-11"), ("1990-07", "1991-03"), ("2001-03", "2001-11"), ("2007-12", "2009-06")]
inrec = np.zeros(len(tdates), bool)
for a, b in recessions:
    inrec |= (tdates >= pd.Timestamp(a)) & (tdates <= pd.Timestamp(b))
print("P(turbulent) in NBER recessions %.2f vs expansions %.2f" % (pturb[inrec].mean(), pturb[~inrec].mean()))

# figure: smoothed probability of the turbulent regime, NBER recessions shaded  ->  msvar_1.png
fig, ax = plt.subplots(figsize=(14, 4.5))
ax.plot(tdates, pturb, color="firebrick", lw=1.6)
ax.fill_between(tdates, pturb, color="firebrick", alpha=.35)
for a, b in recessions:
    ax.axvspan(pd.Timestamp(a), pd.Timestamp(b), color="gray", alpha=.2)
ax.axvline(pd.Timestamp("1984-01-01"), color="navy", ls="--", lw=1.2)
ax.text(pd.Timestamp("1984-06-01"), 0.97, "Great Moderation begins (found endogenously)",
        color="navy", fontsize=9, va="top")
ax.set_ylim(0, 1.05); ax.set_ylabel("P(turbulent regime | data)"); ax.grid(alpha=.25)
ax.set_title("Markov-switching VAR: smoothed probability of the high-volatility regime (shaded = NBER recessions).\n"
             "The model discovers the Great Moderation and the turbulent episodes on its own - no dates imposed")
fig.tight_layout(); plt.show()
regime shock volatilities (gdp/infl/tbill):  calm [2.4 0.8 0.4]  turbulent [5.1 1.5 1.8]
P(turbulent) pre-1984 0.40  post-1984 0.03  (the Great Moderation, found endogenously)
persistence: calm 0.96 (avg 26q)  turbulent 0.83 (avg 6q)
P(turbulent) in NBER recessions 0.57 vs expansions 0.13
No description has been provided for this image

The regimes the data found¶

  • The Great Moderation, rediscovered without a date. The turbulent (high-volatility) regime is active 40% of the time before 1984 but only 3% after — the model draws the Great-Moderation line on its own, from the data, precisely where Part 10 had to assume it. The calm regime, once entered around the mid-1980s, essentially never leaves (bar 2008).
  • Turbulent = volatile and recessionary. The turbulent regime carries 2–4× the shock volatility of the calm one in all three variables (GDP-growth std 5.1 vs 2.4), and its smoothed probability spikes in the NBER recessions (average probability 0.57 in recessions vs 0.13 in expansions) as well as across the high-inflation 1970s. It is the postwar economy's "stress" state.
  • Persistence differs sharply. The transition matrix implies the calm regime lasts ~26 quarters on average and the turbulent one ~6 — long stable stretches punctuated by shorter bouts of turbulence, the hallmark of Markov-switching dynamics.
In [2]:
print("transition matrix (posterior mean):\n", np.round(Pm, 2))
print("long-run regime shares:", np.round(np.linalg.matrix_power(Pm, 500)[0], 2))

# figure: regime-specific shock volatilities (bars) + transition matrix / durations / long-run shares  ->  msvar_2.png
lr = np.linalg.matrix_power(Pm, 500)[0]
fig, (axb, axt) = plt.subplots(1, 2, figsize=(14, 4.2), gridspec_kw={"width_ratios": [1.3, 1]})
x = np.arange(len(lab)); w = 0.4
axb.bar(x - w / 2, sd[0], w, color="steelblue", label="calm regime")
axb.bar(x + w / 2, sd[1], w, color="firebrick", label="turbulent regime")
axb.set_xticks(x); axb.set_xticklabels(lab)
axb.set_ylabel("shock std. dev."); axb.set_title("Shock volatility by regime")
axb.legend()
txt = ("Transition matrix (posterior mean):\n"
       "          to calm   to turbulent\n"
       "calm       %.2f        %.2f\n"
       "turbulent  %.2f        %.2f\n\n"
       "expected duration:  calm %.0f q,  turbulent %.0f q\n"
       "long-run share:     calm %.0f%%,  turbulent %.0f%%\n\n"
       "the turbulent regime is %.1fx more volatile (GDP)\n"
       "and concentrated pre-1984 + in recessions"
       % (Pm[0, 0], Pm[0, 1], Pm[1, 0], Pm[1, 1],
          1 / (1 - Pm[0, 0]), 1 / (1 - Pm[1, 1]),
          100 * lr[0], 100 * lr[1], sd[1, 0] / sd[0, 0]))
axt.axis("off")
axt.text(0.0, 0.95, txt, va="top", ha="left", family="monospace", fontsize=11, transform=axt.transAxes)
fig.suptitle("Two endogenous macro regimes: a persistent calm state and a volatile one")
fig.tight_layout(); plt.show()
transition matrix (posterior mean):
 [[0.96 0.04]
 [0.17 0.83]]
long-run regime shares: [0.82 0.18]
No description has been provided for this image

Results — switches, not drift¶

  • A different picture of change from Part 5. The TVP-VAR let every coefficient wander continuously; the MS-VAR says the important variation is a jump between two states. Both are true descriptions of a changing economy, but they answer different questions: how much has the world drifted? versus which regime are we in right now? The MS-VAR delivers a probabilistic chronology — a dated, regime-by-regime narrative of postwar macro — that a smooth-drift model cannot.
  • Endogenous structural breaks. Because the regimes are inferred, the MS-VAR is a break detector: it locates the Great Moderation and the turbulent episodes without prior information, complementing the imposed-split identification of Part 10 and the continuous drift of Part 5. Together the three notebooks span the ways a VAR can be non-constant: smooth drift, imposed regimes, and data-discovered regimes.
  • The building block. The forward-filter-backward-sample of a hidden discrete state is the same machinery behind regime-switching term-structure, business-cycle dating, and Markov-switching volatility models — here assembled from scratch for a VAR. The R cross-check recovers the same regime chronology with a Markov-switching model on GDP growth.

References¶

  • Hamilton, J. D. (1989). A new approach to the economic analysis of nonstationary time series and the business cycle. Econometrica 57, 357–384.
  • Krolzig, H.-M. (1997). Markov-Switching Vector Autoregressions. Springer.
  • Kim, C.-J. & Nelson, C. R. (1999). State-Space Models with Regime Switching. MIT Press.
  • Sims, C. A. & Zha, T. (2006). Were there regime switches in US monetary policy? American Economic Review 96, 54–81.

Data: bvar_sv_data.csv — GDP growth, inflation, funds rate, quarterly 1959–2019 (FRED-QD). Next: msvar_R.ipynb — the R cross-check (MSwM).