# Load packages

# Core
library(tidyverse)
library(tidyquant)

Goal

Visualize and compare skewness of your portfolio and its assets.

1 Import stock prices

symbols <- c("Asker.st", "Atco-B.st", "Axfo.st", "Bahn-b.st", "BRK-B", "Cers", "LLY", "Embrac-b.st", "Indu-c.st", "Inve-b.st", "Inwi.st", "Novo-b.co", "NVDA", "Yubico.st")

prices <- tq_get(x    = symbols, 
                 get  = "stock.prices", 
                 from = "2020-04-01",
                 to   = "2025-06-01")
prices
## # A tibble: 16,672 × 8
##    symbol   date        open  high   low close   volume adjusted
##    <chr>    <date>     <dbl> <dbl> <dbl> <dbl>    <dbl>    <dbl>
##  1 Asker.st 2025-03-27  83    87.2  80.2  83.7 16441271     83.7
##  2 Asker.st 2025-03-28  83    84.0  81.7  82    1262083     82  
##  3 Asker.st 2025-03-31  81.3  81.9  80.1  80.5   626988     80.5
##  4 Asker.st 2025-04-01  80.8  82.2  80.6  81.9   356628     81.9
##  5 Asker.st 2025-04-02  81.9  82.1  80.9  82.1   576561     82.1
##  6 Asker.st 2025-04-03  81    81.8  80.1  80.7   235131     80.7
##  7 Asker.st 2025-04-04  80.5  81.2  77.3  78.6   780928     78.6
##  8 Asker.st 2025-04-07  74.8  80.1  71.4  77.8   377461     77.8
##  9 Asker.st 2025-04-08  79.2  79.9  75    77     371563     77  
## 10 Asker.st 2025-04-09  76.1  78.7  72.8  74.2  1171607     74.2
## # ℹ 16,662 more rows

2 Convert prices to returns (monthly)

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 Assign a weight to each asset

symbols <- asset_returns_tbl %>% distinct(asset) %>% pull() 
symbols
##  [1] "Asker.st"    "Atco-B.st"   "Axfo.st"     "BRK-B"       "Bahn-b.st"  
##  [6] "Cers"        "Embrac-b.st" "Indu-c.st"   "Inve-b.st"   "Inwi.st"    
## [11] "LLY"         "NVDA"        "Novo-b.co"   "Yubico.st"
weights <- c(0.0314, 0.0133, 0.0136, 0.0589, 0.0112, 0.0068, 0.0201, 0.1858, 0.2298, 0.0584, 0.0892, 0.2504, 0.0168, 0.0143)
weights
##  [1] 0.0314 0.0133 0.0136 0.0589 0.0112 0.0068 0.0201 0.1858 0.2298 0.0584
## [11] 0.0892 0.2504 0.0168 0.0143
w_tbl <- tibble(symbols, weights)
w_tbl
## # A tibble: 14 × 2
##    symbols     weights
##    <chr>         <dbl>
##  1 Asker.st     0.0314
##  2 Atco-B.st    0.0133
##  3 Axfo.st      0.0136
##  4 BRK-B        0.0589
##  5 Bahn-b.st    0.0112
##  6 Cers         0.0068
##  7 Embrac-b.st  0.0201
##  8 Indu-c.st    0.186 
##  9 Inve-b.st    0.230 
## 10 Inwi.st      0.0584
## 11 LLY          0.0892
## 12 NVDA         0.250 
## 13 Novo-b.co    0.0168
## 14 Yubico.st    0.0143

4 Build a portfolio

portfolio_returns_tbl <- asset_returns_tbl %>%
    
    tq_portfolio(assets_col   = asset, 
                 returns_col  = returns, 
                 weights      = w_tbl, 
                 rebalance_on = "months",
                 col_rename = "returns")

portfolio_returns_tbl
## # A tibble: 68 × 2
##    date       returns
##    <date>       <dbl>
##  1 2020-05-29  0.0285
##  2 2020-06-30  0.0121
##  3 2020-07-31  0.0243
##  4 2020-08-31  0.0373
##  5 2020-09-30  0.0430
##  6 2020-10-30 -0.0622
##  7 2020-11-30  0.0746
##  8 2020-12-30  0.0297
##  9 2020-12-31  0.0128
## 10 2021-01-29  0.0187
## # ℹ 58 more rows

5 Compute Skewness

portfolio_skew_tidyquant_builtin_percent <- portfolio_returns_tbl %>%
    
    tq_performance(Ra = returns, 
                   performance_fun = table.Stats) %>%
    select(Skewness)

portfolio_skew_tidyquant_builtin_percent
## # A tibble: 1 × 1
##   Skewness
##      <dbl>
## 1   -0.964

6 Plot: Skewness Comparison

# Data transformation: calculate skewness 
asset_skewness_tbl <- asset_returns_tbl %>% 
    
    group_by(asset) %>% 
    summarise(skew = skewness(returns)) %>%  
    ungroup() %>% 
    
    # Add portfolio skewness 
    add_row(tibble(asset = "Portfolio", 
                   skew = skewness(portfolio_returns_tbl$returns)))

asset_skewness_tbl 
## # A tibble: 15 × 2
##    asset          skew
##    <chr>         <dbl>
##  1 Asker.st     0     
##  2 Atco-B.st    0.152 
##  3 Axfo.st      0.0381
##  4 BRK-B       -0.262 
##  5 Bahn-b.st    0.158 
##  6 Cers        -0.125 
##  7 Embrac-b.st -1.36  
##  8 Indu-c.st   -0.0815
##  9 Inve-b.st   -0.492 
## 10 Inwi.st     -0.297 
## 11 LLY         -0.170 
## 12 NVDA        -0.458 
## 13 Novo-b.co   -1.50  
## 14 Yubico.st    0.854 
## 15 Portfolio   -0.964
# Plot Skewness
    asset_skewness_tbl %>% 
        
        ggplot(aes(x = asset, y = skew, color = asset)) + 
        geom_point() + 
        
        ggrepel::geom_text_repel(aes(label = asset), 
                                 data = asset_skewness_tbl %>% 
                                     filter(asset == "Portfolio")) + 
        theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 6))

    labs(y = "Skewness")
## $y
## [1] "Skewness"
## 
## attr(,"class")
## [1] "labels"

Is any asset in your portfolio more likely to return extreme positive returns than your portfolio collectively? Discuss in terms of skewness. You may also refer to the distribution of returns you plotted in Code along 4.

Yes, the best example in this graph is Yubico. Yubico has a moderate skewness of close to 1, this means that the returns are asymmetrical with mostly small negative returns and the occasional extreme positive return. This could indicate that Yubico is an opportunistic asset but with higher risk. The portfolio shows a more negative skewness which indicates small positive returns with the occasional sharp loss. Furthermore, this makes sense since the portfolio shows very stable returns and smaller risk, the occasional sharp downfalls are likely macro related.