R Codes and Results

#==============================
#    Portfolio Backtesting
#==============================

#--------------------
#    Prepare data
#--------------------

# Clear workspace: 
rm(list = ls())


# Collect price data from Yahoo: 
symbols <- c("FB", "AMZN", "NFLX", "GOOG", "^IXIC") # Note that ^IXIC is NASDAQ Composite.  
tidyquant::tq_get(x = symbols, from = "2016-01-01", to = "2021-05-30") -> data_raw

# Convert to wide form: 

library(tidyverse)

data_raw %>% 
  select(date, symbol, adjusted) %>% 
  pivot_wider(names_from = symbol, values_from = adjusted) -> data_wide_price


date_ymd <- data_wide_price$date # Date time series. 

market_data <- data_wide_price$`^IXIC` # Assume that IXIC is market indicator. 

stocks_data <- data_wide_price %>% select(symbols[1:4]) # Historical price data for FB, AMZN NFLX and GOOG. 

n <- ncol(stocks_data) # Number of stocks. 

# Convert to xts object: 

library(xts)

xts(stocks_data, order.by = date_ymd) -> stocks_data_xts
xts(market_data, order.by = date_ymd) -> market_data_xts

# Prepare data for backtesting: 
data_for_backtesting <- list(list(adjusted = stocks_data_xts, index = market_data_xts))

#----------------------------------------------------------------------------
#  Compare two portfolios by Portfolio Backtesting Process (Naive approach)
#----------------------------------------------------------------------------

# Equal-weighted portfolio: 

portfolio1 <- function(...) {
  
  equal_weights <- rep(1 / n, n)
  
  return(equal_weights)
}

# Portfolio contains 40% FB, 10% AMZN, 25% NFLX, 25% GOOG: 

portfolio2 <- function(...) {
  
  weights <- c(0.4, 0.1, 0.25, 0.25)
  
  return(weights)
  
}

# Back-testing process for comparing: 

portfolios <- list("Portfolio1" = portfolio1, "Portfolio2" = portfolio2)

library(portfolioBacktest)

bt <- portfolioBacktest(portfolio_funs = portfolios, 
                        data = data_for_backtesting, 
                        benchmark = c("index"), 
                        T_rolling_window = 252*4, 
                        show_progress_bar = TRUE)

res_sum <- backtestSummary(bt)

res_sum$performance_summary[1:9, 1:2] %>% 
  data.frame() %>% 
  mutate(Metric = row.names(.)) %>% 
  mutate_if(is.numeric, function(x) {round(x, 3)}) %>% 
  select(Metric, everything()) -> df_compare

rownames(df_compare) <- NULL

library(kableExtra) # For presenting table. 

# Cpmpare performance: 

df_compare %>% 
  kbl(caption = "Table 1: Portfolio Performance", escape = TRUE) %>%
  kable_classic(full_width = FALSE, html_font = "Cambria")
Table 1: Portfolio Performance
Metric Portfolio1 Portfolio2
Sharpe ratio 1.360 1.270
max drawdown 0.267 0.283
annual return 0.460 0.446
annual volatility 0.338 0.352
Sterling ratio 1.725 1.576
Omega ratio 1.252 1.240
ROT (bps) 1931.357 1947.215
VaR (0.95) 0.034 0.034
CVaR (0.95) 0.047 0.050
# Cumulative weath: 

library(lubridate)

bt$Portfolio1$data1$wealth %>% 
  as.data.frame() %>% 
  mutate(Date = row.names(.)) %>% 
  mutate(Date = ymd(Date)) %>% 
  rename(Weath = `portfolio wealth`) %>% 
  mutate(Approach = "Portfolio1") -> df1

bt$Portfolio2$data1$wealth %>% 
  as.data.frame() %>% 
  mutate(Date = row.names(.)) %>% 
  mutate(Date = ymd(Date)) %>% 
  rename(Weath = `portfolio wealth`) %>% 
  mutate(Approach = "Portfolio2") -> df2

bt$index$data1$wealth %>% 
  as.data.frame() %>% 
  mutate(Date = row.names(.)) %>% 
  mutate(Date = ymd(Date)) %>% 
  rename(Weath = V1) %>% 
  mutate(Approach = "Portfolio3") -> df3

bind_rows(df1, df2, df3) -> df

df %>% 
  mutate(Weath = round(Weath, 3)) %>% 
  ggplot(aes(Date, Weath, color = Approach)) + 
  geom_line() + 
  theme(legend.title = element_blank()) + 
  labs(title = "Compare Daily-Portfolio Cumulative Return") -> p

# https://rstudio.github.io/dygraphs/
library(dygraphs)

df %>% 
  mutate(Weath = round(100*Weath, 2)) %>% 
  pivot_wider(names_from = Approach, values_from = Weath) -> df_wider

xts(df_wider %>% select(2:4), order.by = df_wider$Date) -> df_wider_xts  

df_wider_xts %>% 
  dygraph(main = "Daily Portfolio Weath") %>%
  dyAxis(name = "y", 
         # label = "Weath ($)", 
         axisLabelFormatter = 'function(d){return Math.round(d) + "$"}') %>% 
  dyRangeSelector(dateWindow = c("2020-01-03", "2020-12-31"))
LS0tDQp0aXRsZTogJ1F1YW50aXRhdGl2ZSBGaW5hbmNlOiBDb21wYXJlIFBvcnRmb2xpbyBQZXJmb3JtYW5jZSB1c2luZyBCYWNrdGVzdGluZycNCmF1dGhvcjogJ0F1dGhvcjogTmd1eWVuIENoaSBEdW5nJw0Kc3VidGl0bGU6ICJSIEZpbmFuY2UgU2VyaWVzIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgaGlnaGxpZ2h0OiB6ZW5idXJuDQogICAgIyBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiAiZmxhdGx5Ig0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZSA9IFRSVUUpDQoNCmBgYA0KDQojIFIgQ29kZXMgYW5kIFJlc3VsdHMNCg0KYGBge3J9DQoNCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgICAgUG9ydGZvbGlvIEJhY2t0ZXN0aW5nDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCiMtLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyAgICBQcmVwYXJlIGRhdGENCiMtLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIENsZWFyIHdvcmtzcGFjZTogDQpybShsaXN0ID0gbHMoKSkNCg0KDQojIENvbGxlY3QgcHJpY2UgZGF0YSBmcm9tIFlhaG9vOiANCnN5bWJvbHMgPC0gYygiRkIiLCAiQU1aTiIsICJORkxYIiwgIkdPT0ciLCAiXklYSUMiKSAjIE5vdGUgdGhhdCBeSVhJQyBpcyBOQVNEQVEgQ29tcG9zaXRlLiAgDQp0aWR5cXVhbnQ6OnRxX2dldCh4ID0gc3ltYm9scywgZnJvbSA9ICIyMDE2LTAxLTAxIiwgdG8gPSAiMjAyMS0wNS0zMCIpIC0+IGRhdGFfcmF3DQoNCiMgQ29udmVydCB0byB3aWRlIGZvcm06IA0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KZGF0YV9yYXcgJT4lIA0KICBzZWxlY3QoZGF0ZSwgc3ltYm9sLCBhZGp1c3RlZCkgJT4lIA0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gc3ltYm9sLCB2YWx1ZXNfZnJvbSA9IGFkanVzdGVkKSAtPiBkYXRhX3dpZGVfcHJpY2UNCg0KDQpkYXRlX3ltZCA8LSBkYXRhX3dpZGVfcHJpY2UkZGF0ZSAjIERhdGUgdGltZSBzZXJpZXMuIA0KDQptYXJrZXRfZGF0YSA8LSBkYXRhX3dpZGVfcHJpY2UkYF5JWElDYCAjIEFzc3VtZSB0aGF0IElYSUMgaXMgbWFya2V0IGluZGljYXRvci4gDQoNCnN0b2Nrc19kYXRhIDwtIGRhdGFfd2lkZV9wcmljZSAlPiUgc2VsZWN0KHN5bWJvbHNbMTo0XSkgIyBIaXN0b3JpY2FsIHByaWNlIGRhdGEgZm9yIEZCLCBBTVpOIE5GTFggYW5kIEdPT0cuIA0KDQpuIDwtIG5jb2woc3RvY2tzX2RhdGEpICMgTnVtYmVyIG9mIHN0b2Nrcy4gDQoNCiMgQ29udmVydCB0byB4dHMgb2JqZWN0OiANCg0KbGlicmFyeSh4dHMpDQoNCnh0cyhzdG9ja3NfZGF0YSwgb3JkZXIuYnkgPSBkYXRlX3ltZCkgLT4gc3RvY2tzX2RhdGFfeHRzDQp4dHMobWFya2V0X2RhdGEsIG9yZGVyLmJ5ID0gZGF0ZV95bWQpIC0+IG1hcmtldF9kYXRhX3h0cw0KDQojIFByZXBhcmUgZGF0YSBmb3IgYmFja3Rlc3Rpbmc6IA0KZGF0YV9mb3JfYmFja3Rlc3RpbmcgPC0gbGlzdChsaXN0KGFkanVzdGVkID0gc3RvY2tzX2RhdGFfeHRzLCBpbmRleCA9IG1hcmtldF9kYXRhX3h0cykpDQoNCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojICBDb21wYXJlIHR3byBwb3J0Zm9saW9zIGJ5IFBvcnRmb2xpbyBCYWNrdGVzdGluZyBQcm9jZXNzIChOYWl2ZSBhcHByb2FjaCkNCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgRXF1YWwtd2VpZ2h0ZWQgcG9ydGZvbGlvOiANCg0KcG9ydGZvbGlvMSA8LSBmdW5jdGlvbiguLi4pIHsNCiAgDQogIGVxdWFsX3dlaWdodHMgPC0gcmVwKDEgLyBuLCBuKQ0KICANCiAgcmV0dXJuKGVxdWFsX3dlaWdodHMpDQp9DQoNCiMgUG9ydGZvbGlvIGNvbnRhaW5zIDQwJSBGQiwgMTAlIEFNWk4sIDI1JSBORkxYLCAyNSUgR09PRzogDQoNCnBvcnRmb2xpbzIgPC0gZnVuY3Rpb24oLi4uKSB7DQogIA0KICB3ZWlnaHRzIDwtIGMoMC40LCAwLjEsIDAuMjUsIDAuMjUpDQogIA0KICByZXR1cm4od2VpZ2h0cykNCiAgDQp9DQoNCiMgQmFjay10ZXN0aW5nIHByb2Nlc3MgZm9yIGNvbXBhcmluZzogDQoNCnBvcnRmb2xpb3MgPC0gbGlzdCgiUG9ydGZvbGlvMSIgPSBwb3J0Zm9saW8xLCAiUG9ydGZvbGlvMiIgPSBwb3J0Zm9saW8yKQ0KDQpsaWJyYXJ5KHBvcnRmb2xpb0JhY2t0ZXN0KQ0KDQpidCA8LSBwb3J0Zm9saW9CYWNrdGVzdChwb3J0Zm9saW9fZnVucyA9IHBvcnRmb2xpb3MsIA0KICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfZm9yX2JhY2t0ZXN0aW5nLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGJlbmNobWFyayA9IGMoImluZGV4IiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgVF9yb2xsaW5nX3dpbmRvdyA9IDI1Mio0LCANCiAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfcHJvZ3Jlc3NfYmFyID0gVFJVRSkNCg0KcmVzX3N1bSA8LSBiYWNrdGVzdFN1bW1hcnkoYnQpDQoNCnJlc19zdW0kcGVyZm9ybWFuY2Vfc3VtbWFyeVsxOjksIDE6Ml0gJT4lIA0KICBkYXRhLmZyYW1lKCkgJT4lIA0KICBtdXRhdGUoTWV0cmljID0gcm93Lm5hbWVzKC4pKSAlPiUgDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSB7cm91bmQoeCwgMyl9KSAlPiUgDQogIHNlbGVjdChNZXRyaWMsIGV2ZXJ5dGhpbmcoKSkgLT4gZGZfY29tcGFyZQ0KDQpyb3duYW1lcyhkZl9jb21wYXJlKSA8LSBOVUxMDQoNCmxpYnJhcnkoa2FibGVFeHRyYSkgIyBGb3IgcHJlc2VudGluZyB0YWJsZS4gDQoNCiMgQ3BtcGFyZSBwZXJmb3JtYW5jZTogDQoNCmRmX2NvbXBhcmUgJT4lIA0KICBrYmwoY2FwdGlvbiA9ICJUYWJsZSAxOiBQb3J0Zm9saW8gUGVyZm9ybWFuY2UiLCBlc2NhcGUgPSBUUlVFKSAlPiUNCiAga2FibGVfY2xhc3NpYyhmdWxsX3dpZHRoID0gRkFMU0UsIGh0bWxfZm9udCA9ICJDYW1icmlhIikNCg0KIyBDdW11bGF0aXZlIHdlYXRoOiANCg0KbGlicmFyeShsdWJyaWRhdGUpDQoNCmJ0JFBvcnRmb2xpbzEkZGF0YTEkd2VhbHRoICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgbXV0YXRlKERhdGUgPSByb3cubmFtZXMoLikpICU+JSANCiAgbXV0YXRlKERhdGUgPSB5bWQoRGF0ZSkpICU+JSANCiAgcmVuYW1lKFdlYXRoID0gYHBvcnRmb2xpbyB3ZWFsdGhgKSAlPiUgDQogIG11dGF0ZShBcHByb2FjaCA9ICJQb3J0Zm9saW8xIikgLT4gZGYxDQoNCmJ0JFBvcnRmb2xpbzIkZGF0YTEkd2VhbHRoICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgbXV0YXRlKERhdGUgPSByb3cubmFtZXMoLikpICU+JSANCiAgbXV0YXRlKERhdGUgPSB5bWQoRGF0ZSkpICU+JSANCiAgcmVuYW1lKFdlYXRoID0gYHBvcnRmb2xpbyB3ZWFsdGhgKSAlPiUgDQogIG11dGF0ZShBcHByb2FjaCA9ICJQb3J0Zm9saW8yIikgLT4gZGYyDQoNCmJ0JGluZGV4JGRhdGExJHdlYWx0aCAlPiUgDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogIG11dGF0ZShEYXRlID0gcm93Lm5hbWVzKC4pKSAlPiUgDQogIG11dGF0ZShEYXRlID0geW1kKERhdGUpKSAlPiUgDQogIHJlbmFtZShXZWF0aCA9IFYxKSAlPiUgDQogIG11dGF0ZShBcHByb2FjaCA9ICJQb3J0Zm9saW8zIikgLT4gZGYzDQoNCmJpbmRfcm93cyhkZjEsIGRmMiwgZGYzKSAtPiBkZg0KDQpkZiAlPiUgDQogIG11dGF0ZShXZWF0aCA9IHJvdW5kKFdlYXRoLCAzKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKERhdGUsIFdlYXRoLCBjb2xvciA9IEFwcHJvYWNoKSkgKyANCiAgZ2VvbV9saW5lKCkgKyANCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIA0KICBsYWJzKHRpdGxlID0gIkNvbXBhcmUgRGFpbHktUG9ydGZvbGlvIEN1bXVsYXRpdmUgUmV0dXJuIikgLT4gcA0KDQojIGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vZHlncmFwaHMvDQpsaWJyYXJ5KGR5Z3JhcGhzKQ0KDQpkZiAlPiUgDQogIG11dGF0ZShXZWF0aCA9IHJvdW5kKDEwMCpXZWF0aCwgMikpICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IEFwcHJvYWNoLCB2YWx1ZXNfZnJvbSA9IFdlYXRoKSAtPiBkZl93aWRlcg0KDQp4dHMoZGZfd2lkZXIgJT4lIHNlbGVjdCgyOjQpLCBvcmRlci5ieSA9IGRmX3dpZGVyJERhdGUpIC0+IGRmX3dpZGVyX3h0cyAgDQoNCmRmX3dpZGVyX3h0cyAlPiUgDQogIGR5Z3JhcGgobWFpbiA9ICJEYWlseSBQb3J0Zm9saW8gV2VhdGgiKSAlPiUNCiAgZHlBeGlzKG5hbWUgPSAieSIsIA0KICAgICAgICAgIyBsYWJlbCA9ICJXZWF0aCAoJCkiLCANCiAgICAgICAgIGF4aXNMYWJlbEZvcm1hdHRlciA9ICdmdW5jdGlvbihkKXtyZXR1cm4gTWF0aC5yb3VuZChkKSArICIkIn0nKSAlPiUgDQogIGR5UmFuZ2VTZWxlY3RvcihkYXRlV2luZG93ID0gYygiMjAyMC0wMS0wMyIsICIyMDIwLTEyLTMxIikpDQpgYGANCg0KDQoNCiMgUmVmZXJlbmNlcw0KDQoxLiBbRmluYW5jaWFsIFJpc2sgRm9yZWNhc3RpbmcsIENoYXB0ZXIgOF0oaHR0cHM6Ly93d3cuZmluYW5jaWFscmlza2ZvcmVjYXN0aW5nLmNvbS9jb2RlL1JNQVRMQUI4Lmh0bWwpDQoyLiBbTWFjaGluZSBMZWFybmluZyBmb3IgRmFjdG9yIEludmVzdGluZzogUiBWZXJzaW9uLCBDaGFwdGVyIDEyXShodHRwczovL3d3dy5hbWF6b24uY29tL01hY2hpbmUtTGVhcm5pbmctRmFjdG9yLUludmVzdGluZy1NYXRoZW1hdGljcy9kcC8wMzY3NTQ1ODYxL3JlZj1zcl8xXzE/ZGNoaWxkPTEma2V5d29yZHM9TWFjaGluZStMZWFybmluZytmb3IrRmFjdG9yK0ludmVzdGluZyZxaWQ9MTYxNDY4MDA5MCZzcj04LTEpDQozLiBbTnVtZXJpY2FsIE1ldGhvZHMgYW5kIE9wdGltaXphdGlvbiBpbiBGaW5hbmNlLCBDaGFwdGVyIDE1XShodHRwczovL3d3dy5hbWF6b24uY29tL051bWVyaWNhbC1NZXRob2RzLU9wdGltaXphdGlvbi1GaW5hbmNlLU1hbmZyZWQtZWJvb2svZHAvQjA3V0c2MTRSVy9yZWY9c3JfMV8xNz9kY2hpbGQ9MSZrZXl3b3Jkcz1wb3J0Zm9saW8rYmFja3Rlc3RpbmcmcWlkPTE2MTQ2ODAzMzQmc3I9OC0xNykgDQo0LiBbQmFja3Rlc3RpbmcgT3B0aW1hbCBQb3J0Zm9saW9zIGJhc2VkIG9uIEZvcmVjYXN0aW5nIE1vZGVsc10oaHR0cHM6Ly93d3cuYW1hem9uLmNvbS9CYWNrdGVzdGluZy1PcHRpbWFsLVBvcnRmb2xpb3MtRm9yZWNhc3RpbmctTW9kZWxzL2RwLzM2Mzk0OTE0NTkvcmVmPXNyXzFfMjM/ZGNoaWxkPTEma2V5d29yZHM9cG9ydGZvbGlvK2JhY2t0ZXN0aW5nJnFpZD0xNjE0NjgwMzM0JnNyPTgtMjMpDQo1LiBbVGhlIEVzc2VudGlhbCBHdWlkZSB0byBCYWNrdGVzdGluZyBUcmFkaW5nIFN0cmF0ZWdpZXNdKGh0dHBzOi8vd3d3LmFtYXpvbi5jb20vRXNzZW50aWFsLUd1aWRlLUJhY2t0ZXN0aW5nLVRyYWRpbmctU3RyYXRlZ2llcy1lYm9vay9kcC9CMDhMNjQzV1BTL3JlZj1wZF9zYnNfND9wZF9yZF93PTZ2NVNOJnBmX3JkX3A9NWUwZjdmOGQtZjMyMS00YTNlLWJkYWMtMzE0MmZjZDg0OGQ3JnBmX3JkX3I9Wkg1OEg5TktTWDVFMkc4TkdFVzEmcGRfcmRfcj0yMDNmNmRhNy04YWNhLTQ4NmItOTdmNi1hMzc0YTRkODYzYjcmcGRfcmRfd2c9Q1NVZXAmcGRfcmRfaT1CMDhMNjQzV1BTJnBzYz0xKQ0KDQo=