Fits Bayesian nonlinear Ornstein-Uhlenbeck models with cubic drift,
stochastic volatility, and Student-t innovations. The package implements
hierarchical priors for sector-specific parameters and supports parallel
MCMC sampling via 'Stan'. Model comparison is performed using Pareto
Smoothed Importance Sampling Leave-One-Out (PSIS-LOO) cross-validation
following Vehtari, Gelman, and Gabry (2017)
Bayesian Nonlinear Ornstein-Uhlenbeck Models with Stochastic Volatility
bayesianOU fits a Bayesian nonlinear Ornstein-Uhlenbeck (OU) / nonlinear
error-correction model with cubic drift, stochastic volatility (SV), and
Student-t innovations. Sector-specific parameters use a non-centered
hierarchical (partial-pooling) parameterization, and sampling is done with
Stan (reduce_sum for within-chain parallelism). Model comparison uses
PSIS-LOO (Vehtari, Gelman, Gabry 2017).
# install.packages("remotes")
remotes::install_github("IsadoreNabi/bayesianOU")
# Stan backend (cmdstanr recommended):
install.packages("cmdstanr", repos = c("https://mc-stan.org/r-packages/", getOption("repos")))
cmdstanr::install_cmdstan()
# Or rstan:
install.packages("rstan")
library(bayesianOU)
Y <- as.matrix(your_prices_data) # market prices by sector
X <- as.matrix(your_production_prices_data) # prices of production
TMG <- your_tmg_series # aggregate profit rate
COM <- as.matrix(your_com_data) # organic composition of capital
K <- as.matrix(your_capital_data) # total capital by sector
results <- fit_ou_nonlinear_tmg(
results_robust = list(),
Y = Y, X = X, TMG = TMG, COM = COM, CAPITAL_TOTAL = K,
fit_window = "train", # honest out-of-sample evaluation
chains = 4, iter = 4000, warmup = 2000,
verbose = TRUE
)
validate_ou_fit(results) # MCMC + LOO (incl. Pareto-k) + OOS
kappa_stability_evidence(results) # dynamic mean-reversion evidence
plot_beta_tmg(results)
plot_drift_curves(results)
For each sector s, on training-standardized series, the one-step (Euler,
dt = 1) increment is
dY[t,s] = kappa_s * (theta_s - Y[t-1,s] + a3_s * (Y[t-1,s] - theta_s)^3) # cubic OU drift
+ (beta0_s + beta1 * zTMG[t]) * X[t-1,s] # TMG-modulated pass-through
+ gamma * COM_std[t-1,s] # organic composition in mean
+ eps[t,s], eps ~ Student-t(nu, 0, sigma[t,s])
log sigma^2[t,s] = h[t,s], h ~ stationary AR(1) (alpha_s, rho_s, sigma_eta_s)
kappa_s mean-reversion speed, theta_s equilibrium level, a3_s < 0 cubic
term. The implied long-run equilibrium is Y* = theta_s + (beta/kappa_s) X,
i.e. a nonlinear error-correction toward a linear function of the prices of
production.theta_s, kappa_s, a3_s, beta0_s are built hierarchically as
intercept + slope * COM_s + sd * z, z ~ N(0,1) (non-centered).nu > 2 (finite variance). sigma[t,s] is the Student-t scale; the
conditional standard deviation is sigma * sqrt(nu/(nu-2)).Training/test and "out-of-sample". A Bayesian fit on all data is perfectly
valid for inference and for PSIS-LOO (which approximates leave-one-out
predictive performance from the full-data posterior — this is exactly what
rstanarm does, and it is not leakage). Leakage only arises if one labels a
block "out-of-sample" while it still entered the likelihood. This package keeps
the two designs coherent via fit_window:
fit_window = "train" (default): the likelihood and log_lik are summed
only over the training window 2:T_train; the test block is genuinely
held out, so evaluate_oos is a real out-of-sample evaluation.fit_window = "full": fit on all observations (full-information). PSIS-LOO is
then valid over all points, but evaluate_oos becomes in-sample.Priors are configurable and neutral by default. The key hypothesis prior is
neutral: beta1 ~ Normal(0, 0.5) (no sign baked in). The Student-t df uses a
weakly-informative nu_tilde ~ Gamma(2, 0.1) (prior mean nu ~ 22), and SV
persistence rho_s ~ Normal(0.7, 0.2). Override any of these through the
priors argument and run a sensitivity analysis.
Leakage in preprocessing is avoided by default. use_train_loadings = TRUE
computes the common-factor loadings from the training window only and then
projects the full series, so the orthogonalized TMG regressor does not see the
future.
Stability assumption. a3_s < 0 is enforced, so mean reversion strengthens
with the deviation. This is an assumption (global stabilization), not an
estimated result; it precludes detecting locally expansive / self-amplifying
regimes.
Half-life. With the Euler dt = 1 discretization, the discrete persistence of
the linear part is 1 - kappa; interpret half-lives as
log(0.5)/log(1 - kappa), not log(2)/kappa (these coincide only as
kappa -> 0).
PSIS-LOO caveat. The model has one latent SV state per observation, so plain
PSIS-LOO tends to be optimistic and to produce high Pareto-k. validate_ou_fit
surfaces a Pareto-k summary and warns; for genuine forecasting assessment prefer
leave-future-out (LFO-CV).
validate_ou_fit).tests/testthat/test-recovery.R).fit_window = "train") and PSIS-LOO
comparison against baselines.@software{bayesianOU,
author = {Gómez Julián, José Mauricio},
title = {bayesianOU: Bayesian Nonlinear Ornstein-Uhlenbeck Models},
year = {2024},
note = {ORCID: 0009-0000-2412-3150},
url = {https://github.com/IsadoreNabi/bayesianOU}
}
MIT License