| Title: | Regime-Switching Dynamic Correlation Models |
|---|---|
| Description: | Estimation, forecasting, simulation, and portfolio construction for regime-switching models with exogenous variables as in Pelletier (2006) <doi:10.1016/j.jeconom.2005.01.013>. |
| Authors: | David Ardia [aut, cre, cph, fnd] (ORCID: <https://orcid.org/0000-0003-2823-782X>), Benjamin Seguin [aut], Roosevelt Ymele Nguemo [aut] (ORCID: <https://orcid.org/0009-0003-9399-6256>) |
| Maintainer: | David Ardia <[email protected]> |
| License: | GPL-3 |
| Version: | 1.5-0 |
| Built: | 2026-07-02 20:40:12 UTC |
| Source: | https://github.com/ardiad/rsdc |
broom-style augmentation: returns the estimation residuals together with the smoothed regime probabilities and the most likely (Viterbi) regime path.
## S3 method for class 'rsdc_fit' augment(x, ...)## S3 method for class 'rsdc_fit' augment(x, ...)
x |
An |
... |
Unused; for generic compatibility. |
A data frame with one row per observation: the residual columns
(.resid1, ...), smoothed regime probabilities
(.smoothed_p1, ...) and the Viterbi state .state.
y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) head(generics::augment(fit))y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) head(generics::augment(fit))
Plots the smoothed regime probabilities over the sample as stacked areas. Requires the suggested ggplot2 package.
## S3 method for class 'rsdc_fit' autoplot(object, ...)## S3 method for class 'rsdc_fit' autoplot(object, ...)
object |
An |
... |
Unused; for generic compatibility. |
A ggplot object.
if (requireNamespace("ggplot2", quietly = TRUE)) { y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) ggplot2::autoplot(fit) }if (requireNamespace("ggplot2", quietly = TRUE)) { y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) ggplot2::autoplot(fit) }
broom-style one-row model summary.
## S3 method for class 'rsdc_fit' glance(x, ...)## S3 method for class 'rsdc_fit' glance(x, ...)
x |
An |
... |
Unused; for generic compatibility. |
A one-row data frame with method, n_regimes,
logLik, AIC, BIC, df and nobs.
y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) generics::glance(fit)y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) generics::glance(fit)
Daily returns for a green and a brown portfolios constructed following the equal-weighted 10-90 percentile approach.
data(greenbrown)data(greenbrown)
A data frame with 2266 rows and three columns:
Dates ranging from 2014-01-02 to 2022-12-30.
Numeric returns for the green portfolio.
Numeric returns for the brown portfolio.
Built from the source workbook in data-raw/green-brown-ptf.xlsx
(not shipped on CRAN); see data-raw/greenbrown.R.
data("greenbrown") str(greenbrown) head(greenbrown)data("greenbrown") str(greenbrown) head(greenbrown)
Daily Media Climate Change Concerns (MCCC) index used as the exogenous
covariate driving the time-varying transition probabilities (TVTP) in the
empirical illustration. Only the headline Aggregate series is
retained. The index is published at the monthly frequency; here it is
forward-filled across the calendar days of each month and aligned to the
greenbrown trading calendar, so that mccc is row-for-row
mergeable with greenbrown on DATE.
data(mccc)data(mccc)
A data frame with 2266 rows and two columns:
Dates ranging from 2014-01-02 to 2022-12-30, matching
greenbrown.
Numeric Media Climate Change Concerns (Aggregate) index value
for the month containing DATE. Provided in raw (unstandardized)
form; standardize over your estimation window before use as a covariate.
Monthly Aggregate series in
inst/extdata/mccc-monthly.csv, extracted from the Media Climate
Change Concerns workbook of (Ardia et al. 2023).
Ardia D, Bluteau K, Boudt K, Inghelbrecht K (2023). “Climate Change Concerns and the Performance of Green vs. Brown Stocks.” Management Science, 69(12), 7607–7632. doi:10.1287/mnsc.2022.4636.
data("mccc") str(mccc) head(mccc) # Row-aligned with greenbrown; build a TVTP covariate matrix (intercept + MCCC) data("greenbrown") stopifnot(identical(mccc$DATE, greenbrown$DATE)) X <- cbind(intercept = 1, MCCC = as.numeric(scale(mccc$mccc)))data("mccc") str(mccc) head(mccc) # Row-aligned with greenbrown; build a TVTP covariate matrix (intercept + MCCC) data("greenbrown") stopifnot(identical(mccc$DATE, greenbrown$DATE)) X <- cbind(intercept = 1, MCCC = as.numeric(scale(mccc$mccc)))
Unified front-end that dispatches to one of three estimators:
f_optim() — TVTP specification (method = "tvtp").
f_optim_noX() — fixed transition matrix (method = "noX").
f_optim_const() — constant correlation, single regime (method = "const").
## S3 method for class 'rsdc_fit' print(x, digits = 4, ...) ## S3 method for class 'rsdc_fit' coef(object, ...) ## S3 method for class 'rsdc_fit' nobs(object, ...) ## S3 method for class 'rsdc_fit' logLik(object, ...) ## S3 method for class 'rsdc_fit' vcov(object, type = c("hessian", "opg", "sandwich", "bootstrap"), ...) ## S3 method for class 'rsdc_fit' confint( object, parm, level = 0.95, type = c("hessian", "opg", "sandwich", "bootstrap"), ... ) ## S3 method for class 'rsdc_fit' summary(object, ...) ## S3 method for class 'rsdc_fit' predict( object, residuals, sigma_matrix, value_cols, X = NULL, out_of_sample = FALSE, ... ) ## S3 method for class 'rsdc_fit' simulate(object, nsim = 1, seed = NULL, X = NULL, n = NULL, ...) ## S3 method for class 'rsdc_fit' plot(x, which = c("smoothed", "filtered"), ...) rsdc_estimate( method = c("tvtp", "noX", "const"), residuals, N = 2, X = NULL, out_of_sample = FALSE, control = list() )## S3 method for class 'rsdc_fit' print(x, digits = 4, ...) ## S3 method for class 'rsdc_fit' coef(object, ...) ## S3 method for class 'rsdc_fit' nobs(object, ...) ## S3 method for class 'rsdc_fit' logLik(object, ...) ## S3 method for class 'rsdc_fit' vcov(object, type = c("hessian", "opg", "sandwich", "bootstrap"), ...) ## S3 method for class 'rsdc_fit' confint( object, parm, level = 0.95, type = c("hessian", "opg", "sandwich", "bootstrap"), ... ) ## S3 method for class 'rsdc_fit' summary(object, ...) ## S3 method for class 'rsdc_fit' predict( object, residuals, sigma_matrix, value_cols, X = NULL, out_of_sample = FALSE, ... ) ## S3 method for class 'rsdc_fit' simulate(object, nsim = 1, seed = NULL, X = NULL, n = NULL, ...) ## S3 method for class 'rsdc_fit' plot(x, which = c("smoothed", "filtered"), ...) rsdc_estimate( method = c("tvtp", "noX", "const"), residuals, N = 2, X = NULL, out_of_sample = FALSE, control = list() )
x |
An object of class |
digits |
Number of significant digits for printing. |
... |
Further arguments passed to methods (currently unused). |
object |
An object of class |
type |
Covariance estimator: one of |
parm |
Vector of parameter names/indices (default: all). |
level |
Confidence level. |
residuals |
Numeric matrix |
sigma_matrix |
Numeric matrix of conditional standard deviations. |
value_cols |
Columns of |
X |
Numeric matrix |
out_of_sample |
Logical. If |
nsim |
Unused (kept for generic compatibility; one path is returned). |
seed |
Optional RNG seed. |
n |
Series length for |
which |
Either |
method |
Character. One of |
N |
Integer. Number of regimes. Ignored when |
control |
Optional list forwarded to the backends and optimizers:
|
Method selection: match.arg() validates method.
Inputs: "tvtp" requires non-NULL X; N is ignored for "const".
Split: If out_of_sample = TRUE, the first 70% is used for fitting.
An object of class "rsdc_fit": a list with components
transition_matrixEstimated transition matrix ( for "const";
for "tvtp", evaluated at the in-sample covariate means).
correlationsRegime lower-triangular correlations (rows ordered by ascending mean).
covariancesArray of regime correlation matrices.
log_likelihoodIn-sample log-likelihood; full-sample when out_of_sample = FALSE.
log_likelihood_oosOOS log-likelihood on held-out 30 percent, or NULL.
betaTVTP coefficients (only for "tvtp"; NULL otherwise).
par, coefficients
Named vector of estimated parameters in objective order.
vcovObserved-information variance-covariance at the MLE, or NULL if
the Hessian is singular/unavailable (e.g. at a bound).
seNamed standard errors (sqrt(diag(vcov))), or NULL.
convergenceoptim convergence code (0 = converged).
npar, nobs
Number of free parameters and observations used in estimation.
method, N, K, p, call
Fit metadata.
Standard methods are provided: print, summary,
coef, logLik (so AIC/BIC work),
nobs, vcov, confint, predict, and simulate.
print(rsdc_fit): Compact printer for a fitted model.
coef(rsdc_fit): Coefficient vector (named, in objective order).
nobs(rsdc_fit): Number of observations used in estimation.
logLik(rsdc_fit): Log-likelihood (carries df and nobs so
stats::AIC/BIC work out of the box).
vcov(rsdc_fit): Variance-covariance matrix of the estimates. The
numerical estimators are type = "hessian" (default, inverse
observed information), "opg" (outer product of gradients) and
"sandwich" (QML/robust ); the
simulation-based estimator is "bootstrap", which calls
rsdc_bootstrap (pass B and seed through
...). The bootstrap is recomputed on each call.
confint(rsdc_fit): Wald confidence intervals from the chosen covariance
(numerical or "bootstrap"). For bootstrap percentile intervals
instead of Wald, use rsdc_bootstrap directly.
summary(rsdc_fit): Summary with a coefficient table (estimate, SE, z, p).
predict(rsdc_fit): Forecast from a fitted model (wraps
rsdc_forecast); supply the residuals, conditional volatilities,
and (for "tvtp") the covariate matrix X.
simulate(rsdc_fit): Simulate from a fitted model. For "tvtp" supply
a covariate matrix X (its row count sets the length); for "noX"
the fixed transition matrix is used; for "const" a single regime is drawn.
plot(rsdc_fit): Plot the smoothed regime probabilities (one panel per
regime) for a fitted "noX"/"tvtp" model. which = "filtered"
plots filtered probabilities instead.
Mullen K, Ardia D, Gil D, Windover D, Ulrich J (2011).
“DEoptim: An R Package for Global Optimization by Differential Evolution.”
Journal of Statistical Software, 40(6), 1–26.
doi:10.18637/jss.v040.i06.
Hamilton JD (1989).
“A New Approach to the Economic Analysis of Nonstationary Time Series and the Business Cycle.”
Econometrica, 57(2), 357–384.
doi:10.2307/1912559.
Pelletier D (2006). “Regime switching for dynamic correlations.” Journal of Econometrics, 131(1-2), 445–473. doi:10.1016/j.jeconom.2005.01.013.
rsdc_hamilton and rsdc_likelihood.
y <- scale(matrix(rnorm(100 * 3), 100, 3)) fit <- rsdc_estimate("noX", residuals = y, N = 2) print(fit) summary(fit) c(AIC = AIC(fit), BIC = BIC(fit)) X <- cbind(1, scale(seq_len(nrow(y)))) fit_tvtp <- rsdc_estimate("tvtp", residuals = y, N = 2, X = X) coef(fit_tvtp)y <- scale(matrix(rnorm(100 * 3), 100, 3)) fit <- rsdc_estimate("noX", residuals = y, N = 2) print(fit) summary(fit) c(AIC = AIC(fit), BIC = BIC(fit)) X <- cbind(1, scale(seq_len(nrow(y)))) fit_tvtp <- rsdc_estimate("tvtp", residuals = y, N = 2, X = X) coef(fit_tvtp)
The RSDC package provides a comprehensive framework for modeling, estimating, and forecasting correlation structures in multivariate time series under regime-switching dynamics. It supports both fixed transition probabilities and time-varying transition probabilities (TVTP) driven by exogenous variables.
The methodology is particularly suited to empirical asset pricing and portfolio management applications, enabling users to incorporate macroeconomic, financial, or climate-related predictors into the regime dynamics. The package integrates the full workflow — from model estimation to covariance matrix reconstruction and portfolio optimization — in a single, reproducible pipeline.
Model estimation and filtering:
rsdc_hamilton (Hamilton filter),
rsdc_likelihood (likelihood computation),
rsdc_estimate (parameter estimation).
Correlation and covariance forecasting:
rsdc_forecast (in-sample / 70-30 path),
rsdc_forecast_ahead (multi-step-ahead),
rsdc_corr_bands (parameter-uncertainty bands),
rsdc_viterbi (most likely regime path).
Portfolio construction:
rsdc_minvar (minimum-variance portfolios),
rsdc_maxdiv (maximum-diversification portfolios).
Simulation:
rsdc_simulate (simulate TVTP regime-switching series).
Fitted-model methods: rsdc_estimate() returns an
object of class "rsdc_fit" with print, summary,
coef, logLik (so AIC/BIC work), nobs,
vcov, confint, predict, and simulate methods.
David Ardia, Benjamin Seguin and Roosevelt Ymele Nguemo. David Ardia is the maintainer (also copyright holder and funder).
Engle RF (2002).
“Dynamic conditional correlation: A simple class of multivariate generalized autoregressive conditional heteroskedasticity models.”
Journal of Business & Economic Statistics, 20(3), 339–350.
doi:10.1198/073500102288618487.
Hamilton JD (1989).
“A New Approach to the Economic Analysis of Nonstationary Time Series and the Business Cycle.”
Econometrica, 57(2), 357–384.
doi:10.2307/1912559.
Pelletier D (2006). “Regime switching for dynamic correlations.” Journal of Econometrics, 131(1-2), 445–473. doi:10.1016/j.jeconom.2005.01.013.
Simulates B data sets from the fitted data-generating process, re-estimates
each one — warm-started at the original MLE so the global search is skipped —
and returns the bootstrap covariance, standard errors and percentile intervals of
the parameters. This complements the analytic (Hessian/OPG/sandwich) covariances
from vcov.rsdc_fit and, being simulation-based, respects the parameter
bounds (, transition probabilities in ).
rsdc_bootstrap(object, B = 199, X = NULL, seed = NULL, level = 0.95)rsdc_bootstrap(object, B = 199, X = NULL, seed = NULL, level = 0.95)
object |
An object of class |
B |
Integer number of bootstrap replications (default 199). |
X |
Covariate matrix for |
seed |
Optional integer seed for reproducibility. |
level |
Confidence level for the percentile intervals (default 0.95). |
For method = "tvtp" the data are simulated with rsdc_simulate
from the fitted beta and covariate matrix X; for "noX" from the
fitted (fixed) transition matrix; for "const" as i.i.d. draws from the single
regime. Each replicate is re-estimated with the same method/N and
control = list(start = coef(object), compute_se = FALSE), which warm-starts the
local optimizer at the original estimates (fast, deterministic given a seed).
Replicates whose re-estimation fails are dropped.
A list with replicates ( npar matrix of successful
re-estimates), vcov, se, ci (percentile intervals) and the
effective number of replicates B.
vcov.rsdc_fit, rsdc_simulate, rsdc_estimate
set.seed(1) X <- cbind(1, as.numeric(scale(seq_len(300)))) y <- scale(matrix(rnorm(300 * 2), 300, 2)) fit <- rsdc_estimate("tvtp", residuals = y, N = 2, X = X) bs <- rsdc_bootstrap(fit, B = 50, seed = 1) bs$se bs$ciset.seed(1) X <- cbind(1, as.numeric(scale(seq_len(300)))) y <- scale(matrix(rnorm(300 * 2), 300, 2)) fit <- rsdc_estimate("tvtp", residuals = y, N = 2, X = X) bs <- rsdc_bootstrap(fit, B = 50, seed = 1) bs$se bs$ci
Propagates parameter uncertainty into the (smoothed-probability-weighted)
predicted correlation path of a fitted "rsdc_fit" model. Parameter
vectors are drawn from the asymptotic sampling distribution
(using the
covariance returned by vcov.rsdc_fit); for each draw the Hamilton
filter/smoother is rerun and the predicted correlation path recomputed. The
pointwise quantiles of these paths form the band.
rsdc_corr_bands( object, residuals = NULL, X = NULL, B = 500L, level = 0.95, seed = NULL )rsdc_corr_bands( object, residuals = NULL, X = NULL, B = 500L, level = 0.95, seed = NULL )
object |
An |
residuals |
Optional |
X |
Optional |
B |
Integer. Number of parameter draws (default |
level |
Confidence level for the pointwise band (default |
seed |
Optional integer seed for reproducibility. |
This reflects estimation (parameter) uncertainty in the correlation parameters and transition dynamics; it does not add the irreducible regime-classification uncertainty already summarized by the smoothed probabilities.
Draws are taken from the unconstrained Gaussian approximation; draws
that yield an invalid model (e.g. , a non-positive-definite
correlation matrix, or an invalid transition matrix) are dropped with a
warning and the band is computed from the remaining draws. When the estimate
sits close to a parameter bound this truncates the sampling distribution, so
the reported band can be somewhat too narrow there; the attribute
B_used reports how many draws survived.
A list with one element per correlation pair
( of them), each a matrix with columns
fit, lower, upper; plus attributes level and
B_used (draws that yielded a valid path).
y <- scale(matrix(rnorm(250 * 2), 250, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) bands <- rsdc_corr_bands(fit, B = 100) head(bands[[1]])y <- scale(matrix(rnorm(250 * 2), 250, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) bands <- rsdc_corr_bands(fit, B = 100) head(bands[[1]])
Generates per-period correlation and covariance matrices from a fitted model:
"const" (constant correlation), "noX" (fixed transition matrix), or
"tvtp" (time-varying transition probabilities).
rsdc_forecast( method = c("tvtp", "noX", "const"), N, residuals, X = NULL, final_params, sigma_matrix, value_cols, out_of_sample = FALSE, control = list() )rsdc_forecast( method = c("tvtp", "noX", "const"), N, residuals, X = NULL, final_params, sigma_matrix, value_cols, out_of_sample = FALSE, control = list() )
method |
Character. One of |
N |
Integer. Number of regimes (ignored for |
residuals |
Numeric matrix |
X |
Optional numeric matrix |
final_params |
List with fitted parameters (e.g., from |
sigma_matrix |
Numeric matrix |
value_cols |
Character/integer vector of columns in |
out_of_sample |
Logical. If |
control |
Optional list; supports |
Forecast horizon: If out_of_sample = TRUE, filter on the first threshold
fraction and forecast on the remainder.
Correlation paths:
"const" — constant correlation from final_params$correlations[1,], repeated across time.
"noX"/"tvtp" — regime-probability weighted average of regime
correlations. Out-of-sample (out_of_sample = TRUE) the weights are the
filtered probabilities , which use no
future information beyond time (the OOS window is initialized from
the in-sample terminal filtered state). Note these condition on the
contemporaneous , so they are a real-time nowcast, not a
strictly pre-observation one-step-ahead forecast; for the latter use
rsdc_forecast_ahead. The full-sample case uses smoothed
probabilities . The returned
smoothed_probs field always contains the smoothed probabilities.
Covariance build: Reconstruct from the pairwise vector (columns ordered by
combn(K, 2)), set , and
.
BIC: Parameter count is
N * ncol(X) + N * K * (K - 1) / 2 for "tvtp" with ,
N * (N-1) * ncol(X) + N * K * (K - 1) / 2 for "tvtp" with ,
N * (N - 1) + N * K * (K - 1) / 2 for "noX",
and K * (K - 1) / 2 for "const".
smoothed_probs smoothed probabilities ("noX"/"tvtp" only).
sigma_matrix slice aligned to the forecast horizon.
cov_matricesList of covariance matrices .
predicted_correlations pairwise correlations in combn(K, 2) order.
BICInformation criterion .
When out_of_sample = TRUE and final_params$log_likelihood_oos is non-NULL,
uses the OOS log-likelihood and OOS sample size; otherwise uses the IS values.
Caveat: with out_of_sample = TRUE this is an out-of-sample predictive
score (held-out log-likelihood penalized by the IS parameter count), not a
conventional in-sample BIC; use it only to compare models fit on the same split, and
do not interpret it as a textbook BIC. The backends (rsdc_estimate)
hardcode a 70/30 split, so the OOS log-likelihood is only fully consistent with
rsdc_forecast when the default threshold = 0.7 is used.
y residual matrix aligned to the forecast horizon.
rsdc_hamilton, rsdc_estimate,
rsdc_minvar, rsdc_maxdiv
set.seed(123) T <- 60; K <- 3; N <- 2 y <- scale(matrix(rnorm(T*K), T, K)) vols <- matrix(0.2 + 0.05*abs(sin(seq_len(T)/7)), T, K) rho <- rbind(c(0.10, 0.05, 0.00), c(0.60, 0.40, 0.30)) Pfix <- matrix(c(0.9, 0.1, 0.2, 0.8), 2, 2, byrow = TRUE) rsdc_forecast("noX", N, y, NULL, list(correlations = rho, transition_matrix = Pfix, log_likelihood = -200), vols, 1:K)set.seed(123) T <- 60; K <- 3; N <- 2 y <- scale(matrix(rnorm(T*K), T, K)) vols <- matrix(0.2 + 0.05*abs(sin(seq_len(T)/7)), T, K) rho <- rbind(c(0.10, 0.05, 0.00), c(0.60, 0.40, 0.30)) Pfix <- matrix(c(0.9, 0.1, 0.2, 0.8), 2, 2, byrow = TRUE) rsdc_forecast("noX", N, y, NULL, list(correlations = rho, transition_matrix = Pfix, log_likelihood = -200), vols, 1:K)
Produces genuine -step-ahead forecasts of the regime distribution and
the implied correlation matrix from the end of the estimation sample. Starting
from the terminal filtered regime probabilities , the regime
distribution is propagated through the Markov chain,
, and the forecast correlations are the
regime-probability-weighted averages of the regime correlation matrices,
.
rsdc_forecast_ahead( object, horizon = 10L, residuals = NULL, X = NULL, X_future = NULL )rsdc_forecast_ahead( object, horizon = 10L, residuals = NULL, X = NULL, X_future = NULL )
object |
An |
horizon |
Integer forecast horizon |
residuals |
Optional |
X |
Optional in-sample covariate matrix ( |
X_future |
Optional |
For "noX"/"const" the transition matrix is constant, so
. For "tvtp" each step uses a covariate row:
supply future covariates in X_future (an matrix); if it
is NULL the last in-sample covariate row is held constant and a message
is emitted.
A list with:
Integer vector 1:h.
matrix of forecast regime probabilities.
matrix () of
forecast correlations, one column per asset pair.
rsdc_forecast for the in-sample / 70-30 path.
y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) rsdc_forecast_ahead(fit, horizon = 5)y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) rsdc_forecast_ahead(fit, horizon = 5)
Runs the Hamilton (1989) filter for a multivariate regime-switching correlation model.
Supports either a fixed (time-invariant) transition matrix or time-varying transition
probabilities (TVTP) built from exogenous covariates X: a logistic link for
or a softmax link for .
Returns filtered/smoothed regime probabilities and the log-likelihood.
rsdc_hamilton( y, X = NULL, beta = NULL, rho_matrix, K, N, P = NULL, xi_init = NULL, engine = c("cpp", "r") )rsdc_hamilton( y, X = NULL, beta = NULL, rho_matrix, K, N, P = NULL, xi_init = NULL, engine = c("cpp", "r") )
y |
Numeric matrix |
X |
Optional numeric matrix |
beta |
Optional numeric matrix of TVTP coefficients.
For |
rho_matrix |
Numeric matrix |
K |
Integer. Number of observed series (columns of |
N |
Integer. Number of regimes. |
P |
Optional |
xi_init |
Optional numeric vector of length |
engine |
Character; |
Correlation rebuild: For regime , a correlation matrix is reconstructed
from rho_matrix[m, ] (lower-triangular fill + symmetrization). Non-PD proposals are penalized.
Transition dynamics:
Fixed P: If X or beta is missing, a constant is used
(user-provided via P; otherwise uniform rows).
TVTP: With X and beta:
for , and
();
for , a softmax over free logit vectors per row with
the -th logit fixed at 0 (reference category); log-sum-exp stabilized.
Numerical safeguards: A small ridge is added before inversion; if filtering
degenerates at a time step, log_likelihood = -Inf is returned.
A list with:
matrix of filtered probabilities
.
matrix of smoothed probabilities
.
Scalar log-likelihood of the model given y.
For , TVTP uses a logistic link on the diagonal; off-diagonals follow by
complement. For , a full softmax is used with free logit
vectors per row; all entries are independently determined.
X-timing: uses X[t, ] (contemporaneous) to form , consistent with
rsdc_simulate.
Hamilton JD (1989). “A New Approach to the Economic Analysis of Nonstationary Time Series and the Business Cycle.” Econometrica, 57(2), 357–384. doi:10.2307/1912559.
rsdc_likelihood and rsdc_estimate.
set.seed(1) T <- 50; K <- 3; N <- 2 y <- scale(matrix(rnorm(T * K), T, K), center = TRUE, scale = TRUE) # Example rho: two regimes with different average correlations rho <- rbind(c(0.10, 0.05, 0.00), c(0.60, 0.40, 0.30)) # lower-tri order for K=3 # Fixed-P filtering Pfix <- matrix(c(0.9, 0.1, 0.2, 0.8), nrow = 2, byrow = TRUE) out_fix <- rsdc_hamilton(y = y, X = NULL, beta = NULL, rho_matrix = rho, K = K, N = N, P = Pfix) str(out_fix$filtered_probs) # TVTP filtering (include an intercept yourself) X <- cbind(1, scale(seq_len(T))) beta <- rbind(c(1.2, 0.0), c(0.8, -0.1)) out_tvtp <- rsdc_hamilton(y = y, X = X, beta = beta, rho_matrix = rho, K = K, N = N) out_tvtp$log_likelihoodset.seed(1) T <- 50; K <- 3; N <- 2 y <- scale(matrix(rnorm(T * K), T, K), center = TRUE, scale = TRUE) # Example rho: two regimes with different average correlations rho <- rbind(c(0.10, 0.05, 0.00), c(0.60, 0.40, 0.30)) # lower-tri order for K=3 # Fixed-P filtering Pfix <- matrix(c(0.9, 0.1, 0.2, 0.8), nrow = 2, byrow = TRUE) out_fix <- rsdc_hamilton(y = y, X = NULL, beta = NULL, rho_matrix = rho, K = K, N = N, P = Pfix) str(out_fix$filtered_probs) # TVTP filtering (include an intercept yourself) X <- cbind(1, scale(seq_len(T))) beta <- rbind(c(1.2, 0.0), c(0.8, -0.1)) out_tvtp <- rsdc_hamilton(y = y, X = X, beta = beta, rho_matrix = rho, K = K, N = N) out_tvtp$log_likelihood
Computes the negative log-likelihood for a multivariate correlation-only regime-switching model, with either a fixed (time-invariant) transition matrix or time-varying transition probabilities (TVTP) driven by exogenous covariates. Likelihood evaluation uses the Hamilton (1989) filter.
rsdc_likelihood(params, y, exog = NULL, K, N)rsdc_likelihood(params, y, exog = NULL, K, N)
params |
Numeric vector of model parameters packed as:
|
y |
Numeric matrix |
exog |
Optional numeric matrix |
K |
Integer. Number of observed series (columns of |
N |
Integer. Number of regimes. |
Transition dynamics:
Fixed P (no exog): params begins with transition
parameters. For , the implementation maps them to
.
TVTP: with exog, for ,
and
();
for , a softmax over free logit vectors per row
with the -th logit fixed at 0 (reference category).
Correlation build: per regime, the lower-triangular vector is filled into
a symmetric correlation matrix. Non-positive-definite proposals or
are penalized via a large objective value.
Evaluation: delegates to rsdc_hamilton; if the filter returns
log_likelihood = -Inf, a large penalty is returned.
Numeric scalar: the negative log-likelihood to be minimized by an optimizer.
The function is written for use inside optimizers; it performs inexpensive validation and returns large penalties for invalid parameterizations instead of stopping with errors.
rsdc_hamilton (filter),
optim, and DEoptim
# Small toy example (N = 2, K = 3), fixed P (no exog) set.seed(1) T <- 40; K <- 3; N <- 2 y <- scale(matrix(rnorm(T * K), T, K), center = TRUE, scale = TRUE) # Pack parameters: trans (p11, p22), then rho by regime (lower-tri order) p11 <- 0.9; p22 <- 0.8 rho1 <- c(0.10, 0.05, 0.00) # (2,1), (3,1), (3,2) rho2 <- c(0.60, 0.40, 0.30) params <- c(p11, p22, rho1, rho2) nll <- rsdc_likelihood(params, y = y, exog = NULL, K = K, N = N) nll # TVTP example: add X and beta (pack beta row-wise, then rho) X <- cbind(1, scale(seq_len(T))) beta <- rbind(c(1.2, 0.0), c(0.8, -0.1)) params_tvtp <- c(as.vector(t(beta)), rho1, rho2) nll_tvtp <- rsdc_likelihood(params_tvtp, y = y, exog = X, K = K, N = N) nll_tvtp# Small toy example (N = 2, K = 3), fixed P (no exog) set.seed(1) T <- 40; K <- 3; N <- 2 y <- scale(matrix(rnorm(T * K), T, K), center = TRUE, scale = TRUE) # Pack parameters: trans (p11, p22), then rho by regime (lower-tri order) p11 <- 0.9; p22 <- 0.8 rho1 <- c(0.10, 0.05, 0.00) # (2,1), (3,1), (3,2) rho2 <- c(0.60, 0.40, 0.30) params <- c(p11, p22, rho1, rho2) nll <- rsdc_likelihood(params, y = y, exog = NULL, K = K, N = N) nll # TVTP example: add X and beta (pack beta row-wise, then rho) X <- cbind(1, scale(seq_len(T))) beta <- rbind(c(1.2, 0.0), c(0.8, -0.1)) params_tvtp <- c(as.vector(t(beta)), rho1, rho2) nll_tvtp <- rsdc_likelihood(params_tvtp, y = y, exog = X, K = K, N = N) nll_tvtp
Computes rolling maximum-diversification (MaxDiv) portfolio weights from a sequence of per-period covariance matrices implied by forecasted volatilities and correlations. Falls back to equal weights if the nonlinear solver fails.
rsdc_maxdiv( sigma_matrix, value_cols, predicted_corr, y, long_only = TRUE, lag = FALSE )rsdc_maxdiv( sigma_matrix, value_cols, predicted_corr, y, long_only = TRUE, lag = FALSE )
sigma_matrix |
Numeric matrix |
value_cols |
Character/integer vector naming columns in |
predicted_corr |
Numeric matrix |
y |
Numeric matrix |
long_only |
Logical. If |
lag |
Logical. If |
Covariance build: For each , reconstruct from the
pairwise vector; set and
.
Objective (MaxDiv): maximize
subject to and bounds on . Implemented by minimizing
the negative ratio.
Solver: Rsolnp::solnp with equality and
bounds by long_only; on error, weights default to .
weights matrix of weights.
returnsVector of realized portfolio returns sum(y[t,] * weights[t,]).
diversification_ratiosVector of realized diversification ratios.
mean_diversificationAverage diversification ratio.
KNumber of assets.
assetsAsset names.
volatilityStandard deviation of realized portfolio returns.
# Toy example with K = 3 if (requireNamespace("Rsolnp", quietly = TRUE)) { T <- 50; K <- 3 set.seed(42) vols <- matrix(0.2 + 0.05*abs(sin(seq_len(T)/7)), T, K) colnames(vols) <- paste0("A", 1:K) # simple, stationary correlations (order: (2,1), (3,1), (3,2)) pred_corr <- cbind(rep(0.20, T), rep(0.10, T), rep(0.05, T)) rets <- matrix(rnorm(T*K, sd = 0.01), T, K); colnames(rets) <- colnames(vols) mx <- rsdc_maxdiv(sigma_matrix = vols, value_cols = colnames(vols), predicted_corr = pred_corr, y = rets, long_only = TRUE) head(mx$weights) mx$mean_diversification }# Toy example with K = 3 if (requireNamespace("Rsolnp", quietly = TRUE)) { T <- 50; K <- 3 set.seed(42) vols <- matrix(0.2 + 0.05*abs(sin(seq_len(T)/7)), T, K) colnames(vols) <- paste0("A", 1:K) # simple, stationary correlations (order: (2,1), (3,1), (3,2)) pred_corr <- cbind(rep(0.20, T), rep(0.10, T), rep(0.05, T)) rets <- matrix(rnorm(T*K, sd = 0.01), T, K); colnames(rets) <- colnames(vols) mx <- rsdc_maxdiv(sigma_matrix = vols, value_cols = colnames(vols), predicted_corr = pred_corr, y = rets, long_only = TRUE) head(mx$weights) mx$mean_diversification }
Computes rolling minimum-variance (MV) portfolio weights from a sequence of per-period covariance matrices implied by forecasted volatilities and pairwise correlations. Supports long-only or unconstrained MV. If the QP solver fails at a time step, the routine falls back to equal weights.
rsdc_minvar( sigma_matrix, value_cols, predicted_corr, y, long_only = TRUE, lag = FALSE )rsdc_minvar( sigma_matrix, value_cols, predicted_corr, y, long_only = TRUE, lag = FALSE )
sigma_matrix |
Numeric matrix |
value_cols |
Character or integer vector giving the columns in |
predicted_corr |
Numeric matrix |
y |
Numeric matrix |
long_only |
Logical. If |
lag |
Logical. If |
Covariance build: For each , a correlation matrix
is reconstructed from predicted\_corr[t, ] (columns in combn(K, 2) order)
by placing each pairwise correlation in the corresponding off-diagonal entries of a
identity matrix.
Let
and .
Optimization: Minimize subject to
and, if long_only,
(solved with quadprog::solve.QP).
Failure handling: If the QP fails at time \(t\), weights default to equal allocation \(w_i = 1/K\).
An object of class "minvar_portfolio":
matrix of MV weights (one row per time).
List of length with the per-period covariance matrices.
Realized standard deviation of portfolio returns (same units as y).
The input y matrix (coerced to ).
Number of assets.
rsdc_maxdiv (maximum diversification),
solve.QP
# Toy example with K = 3 (requires the suggested 'quadprog' package) if (requireNamespace("quadprog", quietly = TRUE)) { T <- 50; K <- 3 set.seed(42) vols <- matrix(0.2 + 0.05*abs(sin(seq_len(T)/7)), T, K) colnames(vols) <- paste0("A", 1:K) # simple, stationary correlations pred_corr <- cbind(rep(0.20, T), rep(0.10, T), rep(0.05, T)) # order: (2,1), (3,1), (3,2) rets <- matrix(rnorm(T*K, sd = 0.01), T, K); colnames(rets) <- colnames(vols) mv <- rsdc_minvar(sigma_matrix = vols, value_cols = colnames(vols), predicted_corr= pred_corr, y = rets, long_only = TRUE) head(mv$weights) mv$volatility }# Toy example with K = 3 (requires the suggested 'quadprog' package) if (requireNamespace("quadprog", quietly = TRUE)) { T <- 50; K <- 3 set.seed(42) vols <- matrix(0.2 + 0.05*abs(sin(seq_len(T)/7)), T, K) colnames(vols) <- paste0("A", 1:K) # simple, stationary correlations pred_corr <- cbind(rep(0.20, T), rep(0.10, T), rep(0.05, T)) # order: (2,1), (3,1), (3,2) rets <- matrix(rnorm(T*K, sd = 0.01), T, K); colnames(rets) <- colnames(vols) mv <- rsdc_minvar(sigma_matrix = vols, value_cols = colnames(vols), predicted_corr= pred_corr, y = rets, long_only = TRUE) head(mv$weights) mv$volatility }
Simulates a multivariate time series from a regime-switching model with
time-varying transition probabilities (TVTP) driven by covariates X.
Transition probabilities are generated via a multinomial logistic (softmax) link;
observations are drawn from regime-specific Gaussian distributions.
rsdc_simulate(n, X, beta, mu, sigma, N, seed = NULL)rsdc_simulate(n, X, beta, mu, sigma, N, seed = NULL)
n |
Integer. Number of time steps to simulate; must be at least 2. |
X |
Numeric matrix |
beta |
Numeric matrix |
mu |
Numeric matrix |
sigma |
Numeric array |
N |
Integer. Number of regimes. |
seed |
Optional integer. If supplied, sets the RNG seed for reproducibility. |
Initial state and first draw: The initial regime is drawn from
with and built from
via the same logistic/softmax link, matching the Hamilton-filter t=1
prior in rsdc_hamilton; the first observation is drawn
from .
TVTP transition: For and ,
; for
, a softmax is used with free logit vectors per
row and the -th logit fixed at zero (reference category).
Both use log-sum-exp stabilization.
Sampling: Given , draw from the categorical
distribution with probabilities and
.
First-slice NA: transition_matrices[,,1] is always NA.
At there is no predecessor state, so no transition matrix is computed.
The assignment loop begins at . Users iterating over the returned array
should skip the first slice or use transition_matrices[,,2:n].
A list with:
Integer vector of length n; the simulated regime index at each time.
Numeric matrix ; the simulated observations.
Array ; the transition matrix
used at each time step (with undefined by construction; see Details).
Requires mvtnorm for multivariate normal sampling (called as mvtnorm::rmvnorm).
X-timing: uses X[t, ] (contemporaneous) to form , consistent with
rsdc_hamilton.
rsdc_hamilton (filter/evaluation),
rsdc_estimate (estimators),
rsdc_forecast (forecasting)
set.seed(123) n <- 200; K <- 3; N <- 2; p <- 2 X <- cbind(1, scale(seq_len(n))) beta <- matrix(0, nrow = N, ncol = (N - 1) * p) beta[1, ] <- c(1.2, 0.0) beta[2, ] <- c(1.0, -0.1) mu <- rbind(c(0, 0, 0), c(0, 0, 0)) rho <- rbind(c(0.10, 0.05, 0.00), c(0.60, 0.40, 0.30)) Sig <- array(0, dim = c(K, K, N)) for (m in 1:N) { R <- diag(K); R[lower.tri(R)] <- rho[m, ]; R[upper.tri(R)] <- t(R)[upper.tri(R)] Sig[, , m] <- R } sim <- rsdc_simulate(n = n, X = X, beta = beta, mu = mu, sigma = Sig, N = N, seed = 99)set.seed(123) n <- 200; K <- 3; N <- 2; p <- 2 X <- cbind(1, scale(seq_len(n))) beta <- matrix(0, nrow = N, ncol = (N - 1) * p) beta[1, ] <- c(1.2, 0.0) beta[2, ] <- c(1.0, -0.1) mu <- rbind(c(0, 0, 0), c(0, 0, 0)) rho <- rbind(c(0.10, 0.05, 0.00), c(0.60, 0.40, 0.30)) Sig <- array(0, dim = c(K, K, N)) for (m in 1:N) { R <- diag(K); R[lower.tri(R)] <- rho[m, ]; R[upper.tri(R)] <- t(R)[upper.tri(R)] Sig[, , m] <- R } sim <- rsdc_simulate(n = n, X = X, beta = beta, mu = mu, sigma = Sig, N = N, seed = 99)
Returns the single most likely sequence of regimes
under a fitted "rsdc_fit" model, via the Viterbi
algorithm. This is the maximum a posteriori joint state path and
complements the marginal filtered/smoothed regime probabilities from
rsdc_forecast: the smoothed probabilities are decoded pointwise,
whereas Viterbi enforces a globally coherent transition path.
rsdc_viterbi(object, residuals = NULL, X = NULL)rsdc_viterbi(object, residuals = NULL, X = NULL)
object |
An |
residuals |
Optional |
X |
Optional |
An integer vector of length giving the most likely regime at
each time, in the model's ascending-correlation regime labeling
(1 = lowest-correlation state).
rsdc_forecast for marginal regime probabilities.
y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) table(rsdc_viterbi(fit))y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) table(rsdc_viterbi(fit))
broom-style tidier returning a one-row-per-parameter coefficient table.
## S3 method for class 'rsdc_fit' tidy(x, ...)## S3 method for class 'rsdc_fit' tidy(x, ...)
x |
An |
... |
Unused; for generic compatibility. |
A data frame with columns term, estimate,
std.error, statistic and p.value.
y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) generics::tidy(fit)y <- scale(matrix(rnorm(200 * 2), 200, 2)) fit <- rsdc_estimate("noX", residuals = y, N = 2) generics::tidy(fit)