Example 1: BRISMA Risk Model Pipeline
This walkthrough demonstrates the complete BRISMA risk modeling workflow in 17 steps.
Source: brisma/R/examples/example1.R
Outputs: Q_emp (empirical), Q_shrink (shrinkage-adjusted), Q_garch (forecasted) covariance matrices
Phase 1: Data Loading & Preparation
Load all data sheets from the Excel file: client portfolio, risk model, index data, and FX rates. Filter to working days only (Monday-Friday).
data <- load_data() tbl_client_portfolio <- data$tbl_client_portfolio tbl_risk_model <- data$tbl_risk_model tbl_idx_data <- data$tbl_idx_data tbl_fx_data <- data$tbl_fx_data # load_data() reads 4 sheets: # - tbl_client_portfolio: Portfolio holdings and weights # - tbl_risk_model: Factor definitions and mappings # - idx_data: Index time series (filtered to working days) # - fx_data: FX spot rates (filtered to working days)
Build ID-to-currency and ID-to-name lookup tables for consistent referencing across portfolio and risk model data.
mappings <- create_mapping_tables(tbl_client_portfolio, tbl_risk_model) tbl_map_id_currency <- mappings$tbl_map_id_currency tbl_map_id_name <- mappings$tbl_map_id_name # ID to currency mapping combines portfolio and risk model currencies # ID to name mapping uses factor_name from risk model, falls back to name
Define the base currency (EUR) and identify the cash index for excess return calculations.
to_currency <- "EUR"
cli::cat_bullet(glue::glue("Basiswahrung: {to_currency}"), bullet_col = "red")
cash_id <- tbl_client_portfolio |>
dplyr::filter(name == glue::glue("Cash {to_currency}")) |>
dplyr::pull(id)
# Output: "Cash Index: Cash EUR"
Convert all indices to base currency EUR and calculate excess returns over the cash index. Also convert Trade-Weighted Index (TWI) data.
tbl_idx_data_base <- convert_to_excess_returns( tbl_idx_data, tbl_fx_data, tbl_client_portfolio, tbl_risk_model, tbl_map_id_currency, to_currency, cash_id ) tbl_twi_data_base <- convert_twi_to_base( tbl_idx_data, tbl_fx_data, tbl_risk_model, to_currency ) # Excess returns: value_excess = value_base / value_cash # TWI indices inverted and converted to EUR
Combine time series, determine date ranges (10-year lookback), and filter portfolio/risk model IDs based on data availability.
ts_prep <- prepare_time_series( tbl_idx_data_base, tbl_twi_data_base, tbl_client_portfolio, tbl_risk_model, lookback_years = 10 ) start_date <- ts_prep$start_date end_date <- ts_prep$end_date id_port <- ts_prep$id_port # Portfolio IDs id_rm <- ts_prep$id_rm # Risk model IDs id <- ts_prep$id # All IDs # Output: "Gesamtzeitraum der Daten: 2014-01-03 bis 2024-01-03"
Phase 2: Covariance Estimation
Build the date x ID index matrix, compute log returns, and calculate 22-day rolling returns for covariance estimation.
returns <- create_index_matrix_and_returns( ts_prep$tbl_ts_data, start_date, end_date, id, id_rm, period_length = 22L ) idx_date_id <- returns$idx_date_id # Index matrix ret_date_id <- returns$ret_date_id # Daily log returns ret22_date_id <- returns$ret22_date_id # 22-day rolling returns annualization_factor <- returns$annualization_factor # ~11.86 # Working days per year: 365.2425 / 7 * 5 = 260.89 # Annualization factor: 260.89 / 22 = 11.86
Core algorithm: Iteratively estimate covariance with GARCH-based time-decay weights. Uses eigenvalue decomposition to create orthogonal risk factors.
cov_est <- estimate_iterative_covariance(ret22_date_id, id, id_rm, period_length) Q_emp <- cov_est$Q_emp # Empirical covariance matrix Q_rm <- cov_est$Q_rm # Scaled risk model covariance Q_rm_comp <- cov_est$Q_rm_comp # Component covariance weight <- cov_est$weight # Time-decay weights idx_rm_comp <- cov_est$idx_rm_comp # Component indices # Algorithm: # 1. Initialize linear time weights # 2. Compute weighted covariance Q_emp # 3. Scale risk model, eigenvalue decomposition # 4. Fit GARCH(1,1) to first component # 5. Update weights: weight = time^(1-s) * garch^s # 6. Repeat until convergence
Visualize the converged time-decay weights and the first 5 principal risk factors.
plot_weights(weight) plot_risk_factors(idx_rm_comp, n = 5) # Weights: Higher in recent periods, lower in older periods # Risk factors: Orthogonal components explaining portfolio variance
Display annualized volatility for each portfolio asset from the empirical covariance matrix.
plot_volatility_barplot( Q_emp[id_port, id_port], tbl_map_id_name, annualization_factor, "Annualized Volatility of 22-Day Returns" ) # Volatility = sqrt(diag(Q_emp) * annualization_factor)
Phase 3: Factor Model & GARCH
Estimate the factor model with beta decomposition. Select components explaining 95% of variance and compute shrinkage-adjusted covariance.
factor_model <- estimate_factor_model(Q_rm_comp, Q_emp, cov_est$ei, id_port,
variance_threshold = 0.95)
n_comp <- factor_model$n_comp # Number of components
id_comp <- factor_model$id_comp # Component IDs
beta_id_port_id_comp.fit <- factor_model$beta_id_port_id_comp.fit # Betas
Q_shrink <- factor_model$Q_shrink # Shrinkage covariance
# Q_shrink = Q_fit + Q_res
# Q_fit = beta' * Q_comp * beta (systematic risk)
# Q_res = diag(Q_emp - Q_fit) (idiosyncratic risk)
# Output: "Anzahl relevanter Risk Model Faktoren: 7"
Fit GARCH(1,1) models to residuals and components for forward-looking volatility forecasts.
garch_results <- fit_garch_models( idx_rm_comp, beta_id_port_id_comp.resid, id_port, id_comp, prediction_horizon, workday_per_year ) sd_resid <- garch_results$sd_resid # Residual volatilities sd_comp <- garch_results$sd_comp # Component volatilities # For each component/residual: # 1. Fit GARCH(1,1) specification # 2. Forecast h=prediction_horizon steps ahead # 3. Annualize: sd * sqrt(workday_per_year)
Visualize GARCH-forecasted volatilities for each risk model component.
barplot( sd_comp / sqrt(annualization_factor), main = "Annualized Volatility of Risk Model Components" ) abline(h = 0.01) # Reference line at 1% volatility
Construct the forward-looking GARCH covariance matrix using forecasted component and residual volatilities.
q_shrink <- calculate_garch_covariance( beta_id_port_id_comp.fit, sd_comp, sd_resid, id_port ) # Q_garch = beta' * diag(sd_comp^2) * beta + diag(sd_resid^2) # This is the forward-looking covariance matrix
Display GARCH-forecasted annualized volatility for each portfolio asset.
plot_volatility_barplot( q_shrink, tbl_map_id_name, 1, # Already annualized "Annualized Volatility of Portfolio IDs from Estimated Covariance Matrix" )
Phase 4: Final Output
Extract the three final covariance matrices for portfolio assets: empirical, shrinkage-adjusted, and GARCH-forecasted.
Q_port_emp <- Q_emp[id_port, id_port] # Empirical (historical) Q_port_shrink <- Q_shrink[id_port, id_port] # Shrinkage (factor model) Q_port_garch <- q_shrink[id_port, id_port] # GARCH (forward-looking) # All three matrices are: # - Symmetric # - Positive semi-definite # - Portfolio-sized (n_assets x n_assets)
Compare volatilities across the three estimation methods using scatter plots.
plot_volatility_comparisons(Q_port_emp, Q_port_shrink, Q_port_garch, annualization_factor) # Creates two scatter plots: # 1. Empirical vs Shrinkage volatility # 2. Empirical vs GARCH volatility # Red line = 45-degree reference (perfect agreement)
Output the correlation matrices (subset) for validation and comparison.
print_final_correlations(Q_port_emp, Q_port_shrink, Q_port_garch) # Prints 3x3 correlation matrices for assets 6-8 # Example output: # [,1] [,2] [,3] # [1,] 1.00 0.42 0.35 # [2,] 0.42 1.00 0.28 # [3,] 0.35 0.28 1.00
Summary: Three Covariance Matrices
Q_port_emp: Historical empirical covariance from weighted returns
Q_port_shrink: Shrinkage-adjusted using factor model decomposition
Q_port_garch: Forward-looking GARCH(1,1) forecasted covariance