R Codes and Results

#=====================================================
#   Reference: https://www.pfaffikus.de/books/jwex2/
#=====================================================

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

# Import data: 

library(tidyverse)
read_csv("C:/Users/Admin/Documents/CafeF.HSX.Upto27.01.2021.csv") -> raw_data

# Select and rename some columns: 

raw_data %>% 
  select(1, 2, 6) -> raw_data_mini

names(raw_data_mini) <- c("symbol", "date_ymd", "close")

# Select some symbols: 

symbols_vn30 <- readxl::read_excel("C:/Users/Admin/Documents/data.xlsx", sheet = 1) %>% pull(2)

# Filter data belongs to VN30

library(lubridate)

my_date <- "2016-12-28"

raw_data_mini %>% 
  mutate(date_ymd = ymd(date_ymd)) %>%  
  filter(symbol %in% symbols_vn30, date_ymd >= ymd(my_date)) -> df_return_long

# Stocks that >= 1021 observations: 

df_return_long %>% 
  group_by(symbol) %>% 
  count(sort = TRUE) %>% 
  ungroup() %>% 
  filter(n >= 1021) %>% 
  pull(symbol) -> my_symbols

# Convert to wide form and claculate return: 

df_return_long %>% 
  filter(symbol %in% my_symbols) %>% 
  pivot_wider(names_from = symbol, values_from = close) %>% 
  slice(order(date_ymd)) %>% 
  mutate_if(is.numeric, function(x) {x / lag(x, n = 1L) - 1}) %>% 
  na.omit() -> data_wide_return

# Function calculates portfolio return: 

library(fPortfolio)

window_size <- 700

portfolio_return <- function(k_initial) {
  
  data_wide_return %>% slice((1 + k_initial):(window_size + k_initial)) -> train_data

  data_wide_return %>% slice(window_size + k_initial + 1) -> test_data

  # Prepare data for back-testing and portfolio optimization: 
  
  date_ymd <- train_data$date_ymd

  stocks_data <- train_data %>% select(-date_ymd)

  stocks_data %>% 
    timeSeries::timeSeries(charvec = as.character(date_ymd)) %>% 
    na.omit() -> stock_return 
  
  # Portfolio 1: 

  pspec <- portfolioSpec()

  gmv <-  pspec

  gmvpf <- minvariancePortfolio(data = stock_return, spec = pspec, constraints = "LongOnly")
  
  wGMV <- getWeights(gmvpf)

  test_data %>% 
    select(-date_ymd) %>% 
    as.vector() -> asset_returns

  p_return_minVar <- sum(asset_returns*wGMV) 
  
  # Portfolio 2: 
  
  cvar <- pspec

  setType(cvar) <- "CVaR"

  setAlpha(cvar) <- 0.1

  setSolver(cvar) <- "solveRglpk.CVAR"

  cvarpf <- minriskPortfolio(data = stock_return, spec = cvar, constraints = "LongOnly")
  
  wCVAR <- getWeights(cvarpf)
  
  p_return_minRisk <- sum(asset_returns*wCVAR) 

  test_data %>% 
    mutate(p_return_minVar = p_return_minVar, 
           p_return_minRisk = p_return_minRisk) -> port_return
  
  return(port_return)
  
}

# Use the function: 

n_days <- nrow(data_wide_return)

n_future_days <- n_days - window_size - 1 

lapply(0:n_future_days, portfolio_return) -> port_return_list

do.call("bind_rows", port_return_list) -> port_return_rolling

initial_investment <- 100

k <- nrow(port_return_rolling)

calculate_port_return_t <- function(t) {
  
  port_return_rolling$p_return_minVar[1:t] + 1 -> r_minVar
  
  port_return_rolling$p_return_minRisk[1:t] + 1 -> r_minRisk
  
  prod(r_minVar) -> r_minVar
  
  value_minVar <- r_minVar*initial_investment
  
  prod(r_minRisk) -> r_minRisk
  
  value_minRisk <- r_minRisk*initial_investment
  
  port_return_rolling %>% 
    slice(t) %>% 
    select(date_ymd, contains("return")) %>% 
    mutate(value_minRisk = value_minRisk, value_minVar = value_minVar) -> df_final
  
  return(df_final)
  
}

lapply(1:k, calculate_port_return_t) -> return_port_list

do.call("bind_rows", return_port_list) -> port_values


port_values %>% 
  select(date_ymd, contains("value")) %>% 
  pivot_longer(cols = c("value_minRisk", "value_minVar"), names_to = "type", values_to = "value") -> df_long


df_long %>% 
  ggplot(aes(date_ymd, value, color = type)) + 
  geom_line() + 
  theme(legend.title = element_blank()) + 
  labs(title = "Backtesting for comparing Daily-Value of Portfolio", x = NULL, y = "Investment Value") -> p

plotly::ggplotly(p)
LS0tDQp0aXRsZTogIlF1YW50aXRhdGl2ZSBGaW5hbmNlOiBDb21wYXJlIEludmVzdG1lbnQgU3RyYXRlZ2llcyBVc2luZyBCYWNrdGVzdGluZyINCmF1dGhvcjogJ0F1dGhvcjogTmd1eWVuIENoaSBEdW5nJw0Kc3VidGl0bGU6IFIgRmluYW5jZSBTZXJpZXMNCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBoaWdobGlnaHQ6IHplbmJ1cm4NCiAgICB0aGVtZTogZmxhdGx5DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICB3b3JkX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZSA9IFRSVUUpDQoNCmBgYA0KDQohW10oQzpcXFVzZXJzXFxBZG1pblxcRG9jdW1lbnRzXFxiYWNrdGVzdC5wbmcpDQoNCiMgUiBDb2RlcyBhbmQgUmVzdWx0cw0KDQoNCmBgYHtyfQ0KDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgICBSZWZlcmVuY2U6IGh0dHBzOi8vd3d3LnBmYWZmaWt1cy5kZS9ib29rcy9qd2V4Mi8NCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQojIENsZWFyIHdvcmtzcGFjZTogDQpybShsaXN0ID0gbHMoKSkNCg0KIyBJbXBvcnQgZGF0YTogDQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KcmVhZF9jc3YoIkM6L1VzZXJzL0FkbWluL0RvY3VtZW50cy9DYWZlRi5IU1guVXB0bzI3LjAxLjIwMjEuY3N2IikgLT4gcmF3X2RhdGENCg0KIyBTZWxlY3QgYW5kIHJlbmFtZSBzb21lIGNvbHVtbnM6IA0KDQpyYXdfZGF0YSAlPiUgDQogIHNlbGVjdCgxLCAyLCA2KSAtPiByYXdfZGF0YV9taW5pDQoNCm5hbWVzKHJhd19kYXRhX21pbmkpIDwtIGMoInN5bWJvbCIsICJkYXRlX3ltZCIsICJjbG9zZSIpDQoNCiMgU2VsZWN0IHNvbWUgc3ltYm9sczogDQoNCnN5bWJvbHNfdm4zMCA8LSByZWFkeGw6OnJlYWRfZXhjZWwoIkM6L1VzZXJzL0FkbWluL0RvY3VtZW50cy9kYXRhLnhsc3giLCBzaGVldCA9IDEpICU+JSBwdWxsKDIpDQoNCiMgRmlsdGVyIGRhdGEgYmVsb25ncyB0byBWTjMwDQoNCmxpYnJhcnkobHVicmlkYXRlKQ0KDQpteV9kYXRlIDwtICIyMDE2LTEyLTI4Ig0KDQpyYXdfZGF0YV9taW5pICU+JSANCiAgbXV0YXRlKGRhdGVfeW1kID0geW1kKGRhdGVfeW1kKSkgJT4lICANCiAgZmlsdGVyKHN5bWJvbCAlaW4lIHN5bWJvbHNfdm4zMCwgZGF0ZV95bWQgPj0geW1kKG15X2RhdGUpKSAtPiBkZl9yZXR1cm5fbG9uZw0KDQojIFN0b2NrcyB0aGF0ID49IDEwMjEgb2JzZXJ2YXRpb25zOiANCg0KZGZfcmV0dXJuX2xvbmcgJT4lIA0KICBncm91cF9ieShzeW1ib2wpICU+JSANCiAgY291bnQoc29ydCA9IFRSVUUpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgZmlsdGVyKG4gPj0gMTAyMSkgJT4lIA0KICBwdWxsKHN5bWJvbCkgLT4gbXlfc3ltYm9scw0KDQojIENvbnZlcnQgdG8gd2lkZSBmb3JtIGFuZCBjbGFjdWxhdGUgcmV0dXJuOiANCg0KZGZfcmV0dXJuX2xvbmcgJT4lIA0KICBmaWx0ZXIoc3ltYm9sICVpbiUgbXlfc3ltYm9scykgJT4lIA0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gc3ltYm9sLCB2YWx1ZXNfZnJvbSA9IGNsb3NlKSAlPiUgDQogIHNsaWNlKG9yZGVyKGRhdGVfeW1kKSkgJT4lIA0KICBtdXRhdGVfaWYoaXMubnVtZXJpYywgZnVuY3Rpb24oeCkge3ggLyBsYWcoeCwgbiA9IDFMKSAtIDF9KSAlPiUgDQogIG5hLm9taXQoKSAtPiBkYXRhX3dpZGVfcmV0dXJuDQoNCiMgRnVuY3Rpb24gY2FsY3VsYXRlcyBwb3J0Zm9saW8gcmV0dXJuOiANCg0KbGlicmFyeShmUG9ydGZvbGlvKQ0KDQp3aW5kb3dfc2l6ZSA8LSA3MDANCg0KcG9ydGZvbGlvX3JldHVybiA8LSBmdW5jdGlvbihrX2luaXRpYWwpIHsNCiAgDQogIGRhdGFfd2lkZV9yZXR1cm4gJT4lIHNsaWNlKCgxICsga19pbml0aWFsKTood2luZG93X3NpemUgKyBrX2luaXRpYWwpKSAtPiB0cmFpbl9kYXRhDQoNCiAgZGF0YV93aWRlX3JldHVybiAlPiUgc2xpY2Uod2luZG93X3NpemUgKyBrX2luaXRpYWwgKyAxKSAtPiB0ZXN0X2RhdGENCg0KICAjIFByZXBhcmUgZGF0YSBmb3IgYmFjay10ZXN0aW5nIGFuZCBwb3J0Zm9saW8gb3B0aW1pemF0aW9uOiANCiAgDQogIGRhdGVfeW1kIDwtIHRyYWluX2RhdGEkZGF0ZV95bWQNCg0KICBzdG9ja3NfZGF0YSA8LSB0cmFpbl9kYXRhICU+JSBzZWxlY3QoLWRhdGVfeW1kKQ0KDQogIHN0b2Nrc19kYXRhICU+JSANCiAgICB0aW1lU2VyaWVzOjp0aW1lU2VyaWVzKGNoYXJ2ZWMgPSBhcy5jaGFyYWN0ZXIoZGF0ZV95bWQpKSAlPiUgDQogICAgbmEub21pdCgpIC0+IHN0b2NrX3JldHVybiANCiAgDQogICMgUG9ydGZvbGlvIDE6IA0KDQogIHBzcGVjIDwtIHBvcnRmb2xpb1NwZWMoKQ0KDQogIGdtdiA8LSAgcHNwZWMNCg0KICBnbXZwZiA8LSBtaW52YXJpYW5jZVBvcnRmb2xpbyhkYXRhID0gc3RvY2tfcmV0dXJuLCBzcGVjID0gcHNwZWMsIGNvbnN0cmFpbnRzID0gIkxvbmdPbmx5IikNCiAgDQogIHdHTVYgPC0gZ2V0V2VpZ2h0cyhnbXZwZikNCg0KICB0ZXN0X2RhdGEgJT4lIA0KICAgIHNlbGVjdCgtZGF0ZV95bWQpICU+JSANCiAgICBhcy52ZWN0b3IoKSAtPiBhc3NldF9yZXR1cm5zDQoNCiAgcF9yZXR1cm5fbWluVmFyIDwtIHN1bShhc3NldF9yZXR1cm5zKndHTVYpIA0KICANCiAgIyBQb3J0Zm9saW8gMjogDQogIA0KICBjdmFyIDwtIHBzcGVjDQoNCiAgc2V0VHlwZShjdmFyKSA8LSAiQ1ZhUiINCg0KICBzZXRBbHBoYShjdmFyKSA8LSAwLjENCg0KICBzZXRTb2x2ZXIoY3ZhcikgPC0gInNvbHZlUmdscGsuQ1ZBUiINCg0KICBjdmFycGYgPC0gbWlucmlza1BvcnRmb2xpbyhkYXRhID0gc3RvY2tfcmV0dXJuLCBzcGVjID0gY3ZhciwgY29uc3RyYWludHMgPSAiTG9uZ09ubHkiKQ0KICANCiAgd0NWQVIgPC0gZ2V0V2VpZ2h0cyhjdmFycGYpDQogIA0KICBwX3JldHVybl9taW5SaXNrIDwtIHN1bShhc3NldF9yZXR1cm5zKndDVkFSKSANCg0KICB0ZXN0X2RhdGEgJT4lIA0KICAgIG11dGF0ZShwX3JldHVybl9taW5WYXIgPSBwX3JldHVybl9taW5WYXIsIA0KICAgICAgICAgICBwX3JldHVybl9taW5SaXNrID0gcF9yZXR1cm5fbWluUmlzaykgLT4gcG9ydF9yZXR1cm4NCiAgDQogIHJldHVybihwb3J0X3JldHVybikNCiAgDQp9DQoNCiMgVXNlIHRoZSBmdW5jdGlvbjogDQoNCm5fZGF5cyA8LSBucm93KGRhdGFfd2lkZV9yZXR1cm4pDQoNCm5fZnV0dXJlX2RheXMgPC0gbl9kYXlzIC0gd2luZG93X3NpemUgLSAxIA0KDQpsYXBwbHkoMDpuX2Z1dHVyZV9kYXlzLCBwb3J0Zm9saW9fcmV0dXJuKSAtPiBwb3J0X3JldHVybl9saXN0DQoNCmRvLmNhbGwoImJpbmRfcm93cyIsIHBvcnRfcmV0dXJuX2xpc3QpIC0+IHBvcnRfcmV0dXJuX3JvbGxpbmcNCg0KaW5pdGlhbF9pbnZlc3RtZW50IDwtIDEwMA0KDQprIDwtIG5yb3cocG9ydF9yZXR1cm5fcm9sbGluZykNCg0KY2FsY3VsYXRlX3BvcnRfcmV0dXJuX3QgPC0gZnVuY3Rpb24odCkgew0KICANCiAgcG9ydF9yZXR1cm5fcm9sbGluZyRwX3JldHVybl9taW5WYXJbMTp0XSArIDEgLT4gcl9taW5WYXINCiAgDQogIHBvcnRfcmV0dXJuX3JvbGxpbmckcF9yZXR1cm5fbWluUmlza1sxOnRdICsgMSAtPiByX21pblJpc2sNCiAgDQogIHByb2Qocl9taW5WYXIpIC0+IHJfbWluVmFyDQogIA0KICB2YWx1ZV9taW5WYXIgPC0gcl9taW5WYXIqaW5pdGlhbF9pbnZlc3RtZW50DQogIA0KICBwcm9kKHJfbWluUmlzaykgLT4gcl9taW5SaXNrDQogIA0KICB2YWx1ZV9taW5SaXNrIDwtIHJfbWluUmlzayppbml0aWFsX2ludmVzdG1lbnQNCiAgDQogIHBvcnRfcmV0dXJuX3JvbGxpbmcgJT4lIA0KICAgIHNsaWNlKHQpICU+JSANCiAgICBzZWxlY3QoZGF0ZV95bWQsIGNvbnRhaW5zKCJyZXR1cm4iKSkgJT4lIA0KICAgIG11dGF0ZSh2YWx1ZV9taW5SaXNrID0gdmFsdWVfbWluUmlzaywgdmFsdWVfbWluVmFyID0gdmFsdWVfbWluVmFyKSAtPiBkZl9maW5hbA0KICANCiAgcmV0dXJuKGRmX2ZpbmFsKQ0KICANCn0NCg0KbGFwcGx5KDE6aywgY2FsY3VsYXRlX3BvcnRfcmV0dXJuX3QpIC0+IHJldHVybl9wb3J0X2xpc3QNCg0KZG8uY2FsbCgiYmluZF9yb3dzIiwgcmV0dXJuX3BvcnRfbGlzdCkgLT4gcG9ydF92YWx1ZXMNCg0KDQpwb3J0X3ZhbHVlcyAlPiUgDQogIHNlbGVjdChkYXRlX3ltZCwgY29udGFpbnMoInZhbHVlIikpICU+JSANCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKCJ2YWx1ZV9taW5SaXNrIiwgInZhbHVlX21pblZhciIpLCBuYW1lc190byA9ICJ0eXBlIiwgdmFsdWVzX3RvID0gInZhbHVlIikgLT4gZGZfbG9uZw0KDQoNCmRmX2xvbmcgJT4lIA0KICBnZ3Bsb3QoYWVzKGRhdGVfeW1kLCB2YWx1ZSwgY29sb3IgPSB0eXBlKSkgKyANCiAgZ2VvbV9saW5lKCkgKyANCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIA0KICBsYWJzKHRpdGxlID0gIkJhY2t0ZXN0aW5nIGZvciBjb21wYXJpbmcgRGFpbHktVmFsdWUgb2YgUG9ydGZvbGlvIiwgeCA9IE5VTEwsIHkgPSAiSW52ZXN0bWVudCBWYWx1ZSIpIC0+IHANCg0KcGxvdGx5OjpnZ3Bsb3RseShwKQ0KDQoNCmBgYA0KDQo=