# Time-varying parameters vs. rolling windows

In this tutorial, I compare the coefficients estimated using a rolling window regression to those estimated using a Bayesian time-varying parameter model. For a primer on how to fit rolling window regressions using `tidyfit`, please see here. This articles follows directly from the linked primer, but focuses on only one industry.

    library(dplyr); library(tidyr); library(purrr) # Data wrangling
    library(ggplot2); library(stringr) # Plotting
    library(lubridate)   # Date calculations
    library(tidyfit)     # Model fitting

## Load the data

    data <- Factor_Industry_Returns
    data <- data |>
      mutate(Date = ym(Date)) |>         # Parse dates
      mutate(Return = Return - RF) |>    # Excess return
      select(-RF)
    data
    #> # A tibble: 7,080 × 8
    #>    Date       Industry Return `Mkt-RF`   SMB   HML   RMW   CMA
    #>    <date>     <chr>     <dbl>    <dbl> <dbl> <dbl> <dbl> <dbl>
    #>  1 1963-07-01 NoDur     -0.76    -0.39 -0.44 -0.89  0.68 -1.23
    #>  2 1963-08-01 NoDur      4.64     5.07 -0.75  1.68  0.36 -0.34
    #>  3 1963-09-01 NoDur     -1.96    -1.57 -0.55  0.08 -0.71  0.29
    #>  4 1963-10-01 NoDur      2.36     2.53 -1.37 -0.14  2.8  -2.02
    #>  5 1963-11-01 NoDur     -1.4     -0.85 -0.89  1.81 -0.51  2.31
    #>  6 1963-12-01 NoDur      2.52     1.83 -2.07 -0.08  0.03 -0.04
    #>  7 1964-01-01 NoDur      0.49     2.24  0.11  1.47  0.17  1.51
    #>  8 1964-02-01 NoDur      1.61     1.54  0.3   2.74 -0.05  0.9
    #>  9 1964-03-01 NoDur      2.77     1.41  1.36  3.36 -2.21  3.19
    #> 10 1964-04-01 NoDur     -0.77     0.1  -1.59 -0.58 -1.27 -1.04
    #> # ℹ 7,070 more rows

We will examine only the HiTec industry to avoid unnecessary complexity:

    data <- data |>
      filter(Industry == "HiTec") |>
      select(-Industry)

## Model fitting

We begin by fitting a rolling window OLS regression with adjusted standard errors. In this example, we cannot simply pass both the TVP and the OLS models to the same `regress` function because the rolling window regression requires a `.force_cv = TRUE` argument, while the TVP model does not need to be fitted on rolling slices. The below code is adapted directly from the previous article on rolling window regressions:

    mod_rolling <- data |>
      regress(Return ~ CMA + HML + `Mkt-RF` + RMW + SMB,
              m("lm", vcov. = "HAC"),
              .cv = "sliding_index", .cv_args = list(lookback = years(5), step = 6, index = "Date"),
              .force_cv = TRUE, .return_slices = TRUE)

The TVP model is fitted next. `tidyfit` uses the `shrinkTVP` package to fit TVP models. The package is extremely powerful and provides a Bayesian implementation with a specialized hierarchical prior that automatically shrinks some of the parameters towards constant processes based on the data. While I do not discuss this in more detail, have a look here for more information.

    mod_tvp <- data |>
      regress(Return ~ .,
              m("tvp", sv = TRUE, niter = 1000, index_col = "Date"))

The `sv = TRUE` adds stochastic volatility, while the `index_col` argument is a convenience feature added by `tidyfit` to allow the index column (i.e. the dates) to be passed through to the estimates.

Next we combine the two model frames:

    mod_frame <- bind_rows(mod_rolling, mod_tvp)

The coefficients for all models can be obtained in the usual manner:

    beta <- coef(mod_frame)

Now, however, we require some wrangling to adapt the output of the two methods. First we unnest the detailed model information. This contains upper and lower credible intervals for the TVP model. For the OLS model we generate the confidence interval, as before. Finally, we combine the `slice_id` and `index` columns into a joint `date` column:

    beta <- beta |>
      unnest(model_info) |>
      mutate(lower = ifelse(is.na(lower), estimate - 2*std.error, lower)) |>
      mutate(upper = ifelse(is.na(upper), estimate + 2*std.error, upper)) |>
      mutate(date = coalesce(as.Date(index), as.Date(slice_id)))

## Plotting the results

Plotting the outcome is relatively simple with `ggplot2`:

    beta |>
      ggplot(aes(date, estimate)) +
      facet_wrap("term", scales = "free", ncol = 2) +
      geom_ribbon(aes(ymax = upper, ymin = lower, fill = model), alpha = 0.25) +
      geom_line(aes(color = model)) +
      theme_bw(8)

plot of chunk unnamed-chunk-10

We can make a two interesting observations from these results:

1. The variation seen in the rolling regression for some of the parameters — specifically the risk-adjusted alpha (intercept) and the market beta — is not supported by the TVP model, which shrinks these to almost constant parameters.
2. The TVP estimates are markedly smoother and importantly remove the window effects seen in the rolling regression (i.e. the rolling regression adjusts too late).

The TVP model seems to offer a much better tool analyze the factor betas in this case.