# Load packages
 
# Core
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.2
## ✔ ggplot2   3.5.2     ✔ tibble    3.3.0
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.1.0     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidyquant)
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo 
## ── Attaching core tidyquant packages ─────────────────────── tidyquant 1.0.11 ──
## ✔ PerformanceAnalytics 2.0.8      ✔ TTR                  0.24.4
## ✔ quantmod             0.4.28     ✔ xts                  0.14.1── Conflicts ────────────────────────────────────────── tidyquant_conflicts() ──
## ✖ zoo::as.Date()                 masks base::as.Date()
## ✖ zoo::as.Date.numeric()         masks base::as.Date.numeric()
## ✖ dplyr::filter()                masks stats::filter()
## ✖ xts::first()                   masks dplyr::first()
## ✖ dplyr::lag()                   masks stats::lag()
## ✖ xts::last()                    masks dplyr::last()
## ✖ PerformanceAnalytics::legend() masks graphics::legend()
## ✖ quantmod::summary()            masks base::summary()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(readr)
 
# Time series
library(lubridate)
library(tibbletime)
## 
## Attaching package: 'tibbletime'
## 
## The following object is masked from 'package:tidyquant':
## 
##     FANG
## 
## The following object is masked from 'package:stats':
## 
##     filter
# modeling
library(broom)

Goal

Examine how each asset contributes to portfolio standard deviation. This is to ensure that our risk is not concentrated in any one asset.

five stocks: “SPY”, “EFA”, “IJS”, “EEM”, “AGG” from 2012-12-31 to 2017-12-31

1 Import stock prices

symbols <- c("SPY", "EFA", "IJS", "EEM", "AGG")
 
prices <- tq_get(x    = symbols,
                 get  = "stock.prices",   
                 from = "2012-12-31",
                 to   = "2017-12-31")

2 Convert prices to returns

asset_returns_tbl <- prices %>%
   
    group_by(symbol) %>%
   
    tq_transmute(select     = adjusted,
                 mutate_fun = periodReturn,
                 period     = "monthly",
                 type       = "log") %>%
   
    slice(-1) %>%
   
    ungroup() %>%
   
    set_names(c("asset", "date", "returns"))

3 Component Contribution Step-by-Step

Refresh your memory on covariance with this video. Click this link Refresh your memory on matrix multiplication. Click this link

# Transform data into wide form
asset_returns_wide_tbl <- asset_returns_tbl %>%
 
    pivot_wider(names_from = asset, values_from = returns) %>%
 
    column_to_rownames(var = "date")
 
asset_returns_wide_tbl
##                      AGG           EEM           EFA          IJS           SPY
## 2013-01-31 -0.0062310457 -0.0029351463  0.0366064100  0.052132546  4.992329e-02
## 2013-02-28  0.0058914525 -0.0231052317 -0.0129694742  0.016175779  1.267773e-02
## 2013-03-28  0.0009848214 -0.0102349968  0.0129694742  0.040258197  3.726801e-02
## 2013-04-30  0.0096390312  0.0120844287  0.0489675324  0.001222230  1.903041e-02
## 2013-05-31 -0.0202136539 -0.0494832365 -0.0306555956  0.041976501  2.333560e-02
## 2013-06-28 -0.0157790397 -0.0547278361 -0.0271443442 -0.001402804 -1.343466e-02
## 2013-07-31  0.0026884008  0.0131593482  0.0518603060  0.063541236  5.038593e-02
## 2013-08-30 -0.0082985786 -0.0257056421 -0.0197463333 -0.034743386 -3.045122e-02
## 2013-09-30  0.0111446788  0.0695884725  0.0753386957  0.063873442  3.115603e-02
## 2013-10-31  0.0082914918  0.0408613118  0.0320814903  0.034234260  4.526629e-02
## 2013-11-29 -0.0025098286 -0.0025938430  0.0054498453  0.041661079  2.920721e-02
## 2013-12-31 -0.0055835536 -0.0040742395  0.0215279258  0.012892132  2.559605e-02
## 2014-01-31  0.0152918846 -0.0903226652 -0.0534132683 -0.035775228 -3.588445e-02
## 2014-02-28  0.0037569150  0.0332206105  0.0595050030  0.045257347  4.451024e-02
## 2014-03-31 -0.0014820057  0.0380216985 -0.0046024932  0.013315634  8.261100e-03
## 2014-04-30  0.0081840460  0.0077727328  0.0165292640 -0.023184430  6.927655e-03
## 2014-05-30  0.0117211727  0.0290911922  0.0158284448  0.006205040  2.294107e-02
## 2014-06-30 -0.0005756182  0.0237339422  0.0091654929  0.037719053  2.043455e-02
## 2014-07-31 -0.0025121221  0.0135553537 -0.0263798113 -0.052009905 -1.352841e-02
## 2014-08-29  0.0114309487  0.0279045056  0.0018005551  0.043657957  3.870468e-02
## 2014-09-30 -0.0061674243 -0.0808564550 -0.0395986151 -0.061260494 -1.389227e-02
## 2014-10-31  0.0105844785  0.0140964574 -0.0026548517  0.068874901  2.327789e-02
## 2014-11-28  0.0065492958 -0.0155409752  0.0006252476  0.004773888  2.710122e-02
## 2014-12-31  0.0014740779 -0.0404424139 -0.0407466475  0.025295754 -2.539736e-03
## 2015-01-30  0.0203159766 -0.0068955122  0.0062265356 -0.054628047 -3.007710e-02
## 2015-02-27 -0.0089887997  0.0431360190  0.0614505830  0.056914767  5.468205e-02
## 2015-03-31  0.0037406417 -0.0150860615 -0.0143887121  0.010156465 -1.583014e-02
## 2015-04-30 -0.0032334442  0.0662812245  0.0358164204 -0.018417877  9.785609e-03
## 2015-05-29 -0.0043832917 -0.0419112674  0.0019524781  0.007510015  1.277408e-02
## 2015-06-30 -0.0108256289 -0.0297465285 -0.0316786893  0.004171131 -2.052115e-02
## 2015-07-31  0.0085845606 -0.0651781464  0.0201144184 -0.027375352  2.233793e-02
## 2015-08-31 -0.0033631090 -0.0925124034 -0.0771523474 -0.047268277 -6.288654e-02
## 2015-09-30  0.0080809270 -0.0318248882 -0.0451948496 -0.038464672 -2.584735e-02
## 2015-10-30  0.0006852829  0.0618083473  0.0640260786  0.063589765  8.163534e-02
## 2015-11-30 -0.0038979023 -0.0255603330 -0.0075559791  0.024415115  3.647987e-03
## 2015-12-31 -0.0019189286 -0.0389473501 -0.0235951381 -0.052156928 -1.743350e-02
## 2016-01-29  0.0123300847 -0.0516365505 -0.0567577056 -0.060307072 -5.106870e-02
## 2016-02-29  0.0088317389 -0.0082116920 -0.0339138310  0.020605390 -8.259655e-04
## 2016-03-31  0.0087081490  0.1218789684  0.0637457506  0.089910462  6.510024e-02
## 2016-04-29  0.0025469287  0.0040795146  0.0219748413  0.021044242  3.933169e-03
## 2016-05-31  0.0001353220 -0.0376285978 -0.0008559120  0.004397103  1.686868e-02
## 2016-06-30  0.0191658779  0.0445823535 -0.0244916138  0.008292095  3.469675e-03
## 2016-07-29  0.0054302887  0.0524420342  0.0390004125  0.049348287  3.582216e-02
## 2016-08-31 -0.0021563460  0.0087986629  0.0053266743  0.011261133  1.196729e-03
## 2016-09-30  0.0005163148  0.0248728547  0.0132791635  0.008614736  5.794373e-05
## 2016-10-31 -0.0082058670 -0.0083120763 -0.0224034948 -0.038134742 -1.748914e-02
## 2016-11-30 -0.0259892240 -0.0451618807 -0.0179747460  0.125246070  3.617624e-02
## 2016-12-30  0.0025379634 -0.0025300489  0.0267031596  0.031492126  2.006906e-02
## 2017-01-31  0.0021263418  0.0644312942  0.0323817418 -0.012143956  1.773633e-02
## 2017-02-28  0.0064373565  0.0172578991  0.0118364314  0.013428709  3.853926e-02
## 2017-03-31 -0.0005527710  0.0361890944  0.0318056710 -0.006532682  1.249196e-03
## 2017-04-28  0.0090296250  0.0168664077  0.0239523293  0.005107513  9.877302e-03
## 2017-05-31  0.0068472518  0.0280598995  0.0348102158 -0.022862866  1.401427e-02
## 2017-06-30 -0.0001826277  0.0092236606  0.0029556298  0.029151834  6.354434e-03
## 2017-07-31  0.0033343818  0.0565946395  0.0261881019  0.007481467  2.034608e-02
## 2017-08-31  0.0093693372  0.0232436118 -0.0004484313 -0.027564590  2.913287e-03
## 2017-09-29 -0.0057325031 -0.0004461574  0.0233428849  0.082321917  1.994914e-02
## 2017-10-31  0.0009780204  0.0322785999  0.0166536521  0.005916098  2.329075e-02
## 2017-11-30 -0.0014838784 -0.0038969189  0.0068700998  0.036913032  3.010802e-02
## 2017-12-29  0.0047400524  0.0369254159  0.0133983425 -0.003731078  1.205501e-02
# Covariance of asset returns
covariance_matrix <- cov(asset_returns_wide_tbl)
 
covariance_matrix
##               AGG          EEM          EFA           IJS           SPY
## AGG  7.398462e-05 0.0001042070 4.178323e-05 -0.0000781197 -9.032465e-06
## EEM  1.042070e-04 0.0017547072 1.039016e-03  0.0006437726  6.795422e-04
## EFA  4.178323e-05 0.0010390161 1.064237e-03  0.0006490294  6.975411e-04
## IJS -7.811970e-05 0.0006437726 6.490294e-04  0.0015654498  8.290255e-04
## SPY -9.032465e-06 0.0006795422 6.975411e-04  0.0008290255  7.408302e-04
# Standard deviation of portfolio
# Summarizes how much each asset's returns vary with those of other assets within the portfolio into a single number
w <- c(0.25, 0.25, 0.2, 0.2, 0.1)
 
sd_portfolio <- sqrt(t(w) %*% covariance_matrix %*% w)
sd_portfolio
##            [,1]
## [1,] 0.02347489
# Component contribution
# Similar to the formula for sd_portfolio
# Mathematical trick to summarize the same, sd_portfolio, by asset instead of a single number
component_contribution <- (t(w) %*% covariance_matrix * w) / sd_portfolio[1,1]
component_contribution
##               AGG         EEM         EFA        IJS        SPY
## [1,] 0.0003874072 0.009257129 0.005815637 0.00568447 0.00233025
rowSums(component_contribution)
## [1] 0.02347489
# Component contribution in percentage
component_percentages <- (component_contribution / sd_portfolio[1,1]) %>%
    round(3) %>%
    as_tibble()
 
component_percentages
## # A tibble: 1 × 5
##     AGG   EEM   EFA   IJS   SPY
##   <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.017 0.394 0.248 0.242 0.099
component_percentages %>%
 
    as_tibble() %>%
    gather(key = "asset", value = "contribution")
## # A tibble: 5 × 2
##   asset contribution
##   <chr>        <dbl>
## 1 AGG          0.017
## 2 EEM          0.394
## 3 EFA          0.248
## 4 IJS          0.242
## 5 SPY          0.099

4 Component Contribution with a Custom Function

# Transform data into wide form
asset_returns_wide_tbl <- asset_returns_tbl %>%
 
    pivot_wider(names_from = asset, values_from = returns) %>%
 
    column_to_rownames(var = "date")
 
# Custom function
calculate_component_contribution <- function(asset_returns_wide_tbl, w) {
 
    # Covariance of asset returns
    covariance_matrix <- cov(asset_returns_wide_tbl)
   
    # Standard deviation of portfolio
    sd_portfolio <- sqrt(t(w) %*% covariance_matrix %*% w)
 
    # Component contribution
    component_contribution <- (t(w) %*% covariance_matrix * w) / sd_portfolio[1,1]
 
    # Component contribution in percentage
    component_percentages <- (component_contribution / sd_portfolio[1,1]) %>%
        round(3) %>%
        as_tibble()
   
    return(component_percentages)
 
}
 
asset_returns_wide_tbl %>% calculate_component_contribution(w = c(0.25,0.25,0.2,0.2,0.1))
## # A tibble: 1 × 5
##     AGG   EEM   EFA   IJS   SPY
##   <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.017 0.394 0.248 0.242 0.099

5 Visualizing Component Contribution

# Figure 10.1 Contribution to Standard Deviation ----
asset_returns_wide_tbl %>%
 
    calculate_component_contribution(w = c(0.25,0.25,0.2,0.2,0.1)) %>%
    gather(key = "asset", value = "contribution") %>%
 
    ggplot(aes(asset, contribution)) +
    geom_col(fill = "cornflowerblue") +
   
    theme(plot.title = element_text(hjust = 0.5)) +
    scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
   
    labs(title = "Percent Contribution to Portfolio Standard Deviation",
         y = "Percent Contribution to Risk",
         x = NULL)

# Figure 10.2 Weight versus Contribution ----
asset_returns_wide_tbl %>%
 
    calculate_component_contribution(w = c(0.25,0.25,0.2,0.2,0.1)) %>%
    gather(key = "asset", value = "contribution") %>%
    add_column(weights = c(0.25,0.25,0.2,0.2,0.1)) %>%
    pivot_longer(cols = c(contribution, weights), names_to = "type", values_to = "value") %>%
 
    ggplot(aes(asset, value, fill = type)) +
    geom_col(position = "dodge") +
   
    theme(plot.title = element_text(hjust = 0.5)) +
    scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
    theme_tq() +
    scale_fill_tq() +
 
    labs(title = "Percent Contribution to Volatility",
         y = "percent",
         x = "asset")

6 Rolling Component Contribution