This vignette walks through the package API: estimating the three
model variants, using the standard S3 methods on a fitted object,
comparing models with AIC/BIC, forecasting,
and simulating. To keep the build fast we use a small simulated data set
and a reduced optimizer budget via control; in real use,
keep the defaults.
"rsdc_fit"set.seed(1)
n <- 300L
X <- cbind(intercept = 1, z = as.numeric(scale(seq_len(n)))) # include an intercept
beta <- rbind(c(1.5, -0.6), # regime 1 (logistic stay-prob coefficients)
c(1.0, 0.2)) # regime 2
rho <- rbind(0.2, 0.8) # K = 2 -> one correlation per regime
Sig <- array(c(matrix(c(1, 0.2, 0.2, 1), 2),
matrix(c(1, 0.8, 0.8, 1), 2)), dim = c(2, 2, 2))
sim <- rsdc_simulate(n = n, X = X, beta = beta, mu = matrix(0, 2, 2),
sigma = Sig, N = 2, seed = 42)
y <- scale(sim$observations) # treated as unit-variance standardized residuals
ctrl <- list(itermax = 30, NP = 40, seed = 1) # small budget for the vignette
fit <- rsdc_estimate("tvtp", residuals = y, N = 2, X = X, control = ctrl)
class(fit)
#> [1] "rsdc_fit"
fit
#> RSDC fit: method = "tvtp", N = 2 regime(s), K = 2 series, T = 300
#> Call: rsdc_estimate(method = "tvtp", residuals = y, N = 2, X = X, control = ctrl)
#> logLik = -827.126 npar = 6 AIC = 1666.25 BIC = 1688.47
#>
#> Regime correlations (rows = regimes, ascending mean):
#> [,1]
#> [1,] 0.2793
#> [2,] 0.8696
#>
#> Transition matrix (evaluated at the in-sample covariate means):
#> [,1] [,2]
#> [1,] 1e+00 0.0000
#> [2,] 2e-04 0.9998
#>
#> Optimiser convergence code: 0 (0 = converged)AIC() / BIC() work directly because
logLik() carries df and nobs:
logLik(fit)
#> 'log Lik.' -827.1 (df=6)
c(AIC = AIC(fit), BIC = BIC(fit), nobs = nobs(fit))
#> AIC BIC nobs
#> 1666 1688 300
coef(fit)
#> b[g1,x1] b[g1,x2] b[g2,x1] b[g2,x2] rho[g1,12] rho[g2,12]
#> 10.0000 -4.7439 8.6512 4.7218 0.2793 0.8696summary() reports a coefficient table with standard
errors (from the observed information at the MLE), and
confint() gives Wald intervals:
summary(fit)
#> RSDC fit summary: method = "tvtp", N = 2, K = 2, T = 300
#> logLik = -827.126 npar = 6 AIC = 1666.25 BIC = 1688.47
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> b[g1,x1] 10.00000 5.82624 1.716 0.0861 .
#> b[g1,x2] -4.74391 4.36730 -1.086 0.2774
#> b[g2,x1] 8.65120 NA NA NA
#> b[g2,x2] 4.72180 NA NA NA
#> rho[g1,12] 0.27931 0.05337 5.234 1.66e-07 ***
#> rho[g2,12] 0.86956 0.04686 18.556 < 2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Transition probabilities at the in-sample covariate means (delta-method SE):
#> entry estimate se
#> 1 p[1,1] 0.9999546 0.0002645
#> 2 p[1,2] 0.0000454 0.0002645
#> 3 p[2,1] 0.0001749 NA
#> 4 p[2,2] 0.9998251 NA
#>
#> Regime diagnostics at the in-sample covariate means (stay prob., expected duration, ergodic prob.):
#> regime stay_prob exp_duration ergodic_prob
#> 1 1 1.0000 22027 0.7939
#> 2 2 0.9998 5718 0.2061
head(confint(fit))
#> 2.5% 97.5%
#> b[g1,x1] -1.4192 21.4192
#> b[g1,x2] -13.3037 3.8158
#> b[g2,x1] NA NA
#> b[g2,x2] NA NA
#> rho[g1,12] 0.1747 0.3839
#> rho[g2,12] 0.7777 0.9614fit_const <- rsdc_estimate("const", residuals = y, control = ctrl)
fit_noX <- rsdc_estimate("noX", residuals = y, N = 2, control = ctrl)
data.frame(
model = c("const", "noX(N=2)", "tvtp(N=2)"),
AIC = c(AIC(fit_const), AIC(fit_noX), AIC(fit)),
BIC = c(BIC(fit_const), BIC(fit_noX), BIC(fit))
)
#> model AIC BIC
#> 1 const 1671 1675
#> 2 noX(N=2) 1667 1682
#> 3 tvtp(N=2) 1666 1688predict() wraps rsdc_forecast(); supply the
conditional volatilities:
sigma_mat <- matrix(1, n, 2, dimnames = list(NULL, c("a", "b"))) # unit vols here
fc <- predict(fit, residuals = y, sigma_matrix = sigma_mat,
value_cols = c("a", "b"), X = X)
str(fc$predicted_correlations)
#> num [1:300, 1] 0.673 0.555 0.396 0.281 0.279 ...simulate() draws a new path from the fitted model (here,
TVTP given X):
For a look-ahead-free forecast, set
out_of_sample = TRUE: the filter is run on the first 70% of
the sample, its terminal state initializes the held-out run, and the
out-of-sample correlations are weighted by filtered
(not smoothed) probabilities so no future information leaks in.
The following tools were added in version 1.5-0.
Multi-start estimation.
control$n_starts repeats the global + local search from
several seeds and keeps the highest-likelihood fit, storing each start’s
log-likelihood. Because DEoptim is a stochastic
global search whose result depends on the seed, this is primarily a
stability/reproducibility diagnostic (do the seeds agree?) rather than a
replacement for the global search:
fit_ms <- rsdc_estimate("tvtp", residuals = y, N = 2, X = X,
control = c(ctrl, list(n_starts = 3)))
fit_ms$start_logliks # one log-likelihood per start
#> [1] -827.1 -827.3 -827.7Uncertainty bands on the predicted correlation.
rsdc_corr_bands() draws parameter vectors from the fitted
sampling distribution, re-runs the filter for each, and returns
pointwise quantile bands (one matrix per asset pair, with columns
fit, lower, upper):
bands <- rsdc_corr_bands(fit, B = 100, seed = 1)
head(bands[[1]])
#> fit lower upper
#> [1,] 0.6728 0.3116 0.8494
#> [2,] 0.5545 0.2683 0.7877
#> [3,] 0.3955 0.2320 0.6589
#> [4,] 0.2807 0.1745 0.7845
#> [5,] 0.2793 0.1736 0.3736
#> [6,] 0.2793 0.1738 0.8336Most likely regime path (Viterbi).
rsdc_viterbi() returns the single most likely
joint regime sequence, complementing the marginal smoothed
probabilities:
Multi-step-ahead forecasts.
rsdc_forecast_ahead() propagates the terminal filtered
state through the Markov chain to forecast the regime distribution and
the implied correlations h steps ahead (pass
X_future for the time-varying case; here the last covariate
row is held constant):
fa <- rsdc_forecast_ahead(fit, horizon = 5)
fa$predicted_correlations
#> [,1]
#> [1,] 0.8695
#> [2,] 0.8695
#> [3,] 0.8695
#> [4,] 0.8695
#> [5,] 0.8695Tidy output and plotting. rsdc_fit
objects work with the generics tidy() /
glance() / augment() and a
autoplot() method:
generics::tidy(fit)
#> term estimate std.error statistic p.value
#> 1 b[g1,x1] 10.0000 5.82624 1.716 8.609e-02
#> 2 b[g1,x2] -4.7439 4.36730 -1.086 2.774e-01
#> 3 b[g2,x1] 8.6512 NA NA NA
#> 4 b[g2,x2] 4.7218 NA NA NA
#> 5 rho[g1,12] 0.2793 0.05337 5.234 1.661e-07
#> 6 rho[g2,12] 0.8696 0.04686 18.556 7.267e-77
generics::glance(fit)
#> method n_regimes logLik AIC BIC df nobs
#> 1 tvtp 2 -827.1 1666 1688 6 300N >= 4 is supported for "noX" and
"tvtp":
Because the data above were simulated with known regime correlations
(rho = c(0.2, 0.8)), we can check that the estimator
recovers them — the fitted tvtp(N = 2) correlations should
be close to the truth:
rbind(true = c(0.2, 0.8),
estimated = sort(as.numeric(fit$correlations)))
#> [,1] [,2]
#> true 0.2000 0.8000
#> estimated 0.2793 0.8696A full Monte-Carlo study (five model variants, \(M = 100\) replications, \(T\) up to \(2000\), \(K\) up to \(5\), reporting bias/RMSE and the value of
the multi-start protocol) ships with the package in
inst/simulation/run_mc_recovery_study.R and can be run from
the package root with
source(system.file("simulation", "run_mc_recovery_study.R", package = "RSDC")).