Load packages

# Core
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.1     ✔ stringr   1.5.2
## ✔ ggplot2   4.0.0     ✔ 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(timetk)
## 
## Attaching package: 'timetk'
## 
## The following object is masked from 'package:tidyquant':
## 
##     FANG
library(PerformanceAnalytics)
library(e1071)
## 
## Attaching package: 'e1071'
## 
## The following objects are masked from 'package:PerformanceAnalytics':
## 
##     kurtosis, skewness
## 
## The following object is masked from 'package:ggplot2':
## 
##     element

Goal

Visualize and compare skewness of your portfolio and its assets.


1. Import Stock Prices

symbols <- c("NVDA", "AAPL", "AMD", "GOOG", "INTC")

prices <- tq_get(x = symbols, 
                 get = "stock.prices",
                 from = "2012-12-31",
                 to   = "2017-12-31") 
head(prices)
## # A tibble: 6 × 8
##   symbol date        open  high   low close    volume adjusted
##   <chr>  <date>     <dbl> <dbl> <dbl> <dbl>     <dbl>    <dbl>
## 1 NVDA   2012-12-31 0.301 0.308 0.301 0.306 326460000    0.283
## 2 NVDA   2013-01-02 0.314 0.318 0.313 0.318 478836000    0.293
## 3 NVDA   2013-01-03 0.318 0.322 0.315 0.318 298888000    0.294
## 4 NVDA   2013-01-04 0.319 0.330 0.318 0.329 524968000    0.303
## 5 NVDA   2013-01-07 0.329 0.329 0.317 0.319 610732000    0.295
## 6 NVDA   2013-01-08 0.320 0.321 0.310 0.312 466424000    0.288

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")) 

head(asset_returns_tbl)
## # A tibble: 6 × 3
##   asset date         returns
##   <chr> <date>         <dbl>
## 1 AAPL  2013-01-31 -0.156   
## 2 AAPL  2013-02-28 -0.0256  
## 3 AAPL  2013-03-28  0.00285 
## 4 AAPL  2013-04-30  0.000271
## 5 AAPL  2013-05-31  0.0222  
## 6 AAPL  2013-06-28 -0.126

3. Assign Weights to Each Asset

symbols <- asset_returns_tbl %>% distinct(asset) %>% pull()
weights <- c(0.25, 0.25, 0.20, 0.20, 0.10)
w_tbl <- tibble(symbols, weights)
w_tbl
## # A tibble: 5 × 2
##   symbols weights
##   <chr>     <dbl>
## 1 AAPL       0.25
## 2 AMD        0.25
## 3 GOOG       0.2 
## 4 INTC       0.2 
## 5 NVDA       0.1

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")

head(portfolio_returns_tbl)
## # A tibble: 6 × 2
##   date        returns
##   <date>        <dbl>
## 1 2013-01-31 -0.00164
## 2 2013-02-28 -0.00108
## 3 2013-03-28  0.0152 
## 4 2013-04-30  0.0583 
## 5 2013-05-31  0.114  
## 6 2013-06-28 -0.0279

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.338

6. Plot: Skewness Comparison

asset_skewness_tbl <- asset_returns_tbl %>%
    group_by(asset) %>%
    summarise(skew = skewness(returns)) %>%
    ungroup() %>%
    add_row(tibble(asset = "Portfolio",
                   skew  = skewness(portfolio_returns_tbl$returns)))

asset_skewness_tbl
## # A tibble: 6 × 2
##   asset        skew
##   <chr>       <dbl>
## 1 AAPL      -0.541 
## 2 AMD        0.285 
## 3 GOOG       0.765 
## 4 INTC      -0.0313
## 5 NVDA       0.876 
## 6 Portfolio  0.330
# Plot skewness comparison
asset_skewness_tbl %>%
    ggplot(aes(x = asset, y = skew, color = asset)) + 
    geom_point(size = 3) + 
    geom_text(aes(label = asset), vjust = -1.2, size = 3.5) +
    labs(y = "Skewness", title = "Skewness Comparison: Assets vs Portfolio") +
    theme_minimal()


7. Rolling 24-Month Skewness

rolling_skew_tbl <- portfolio_returns_tbl %>%
    tq_mutate(select     = returns, 
              mutate_fun = rollapply, 
              width      = 24,
              FUN        = skewness,
              col_rename = "Skew") %>%
    select(-returns) %>%
    na.omit()

# Plot rolling skewness
rolling_skew_tbl %>%
    ggplot(aes(x = date, y = Skew)) + 
    geom_line(color = "cornflowerblue") +
    geom_hline(yintercept = 0, linetype = "dotted", size = 1.2) +
    scale_y_continuous(limits = c(-1, 1), breaks = seq(-1, 1, 0.2)) +
    theme_minimal() +
    theme(plot.title = element_text(hjust = 0.5)) +
    labs(y = "Skewness", x = NULL, title = "Rolling 24-Month Skewness") +
    annotate(geom = "text", 
             x = as.Date("2016-07-01"), y = 0.8, 
             color = "red", size = 4,
             label = "Positive skewness about half the time; overall skewness near neutral.")
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.


Interpretation

  • Skewness measures the asymmetry of return distributions.
  • A positive skew indicates more frequent small losses and occasional large gains.
  • A negative skew suggests more frequent small gains and occasional large losses.
  • Comparing assets, NVDA and GOOG show the highest positive skew, implying potential for large upside surprises.
  • The portfolio skewness (0.338) suggests it is more likely to experience extreme positive returns than negative ones.