Viettel Global Investment JSC was founded in 2007 by Viettel Group, a leading digital service provider based in Vietnam with over 50% market share in the telecom industry. This project will estimate and evaluate the risk and return associated with its stock (VGI) from 2019 - 2021 through 2 measures: CAPM Beta and Sharpe Ratio.
Time-series data source: Yahoo Finance, Investing.com

I. CAPM BETA ANALYSIS

Beta of stock is a widely used measure of its risk and volatility. Moreover, this measure captures the sensitivity of a security to market fluctuations. A beta greater than 1.0 suggests that the stock is more volatile than the broader market, and a beta less than 1.0 indicates a stock with lower volatility.

Estimation Method: Ordinary Least Squares (OLS) regression

\(𝑅i,t =𝛼+𝛽i𝑅m,t +𝜀\)

𝑅i,t is the return on stock i, at time t. 𝛽! is Beta of stock i

𝑅m,t is return on index m, at time t.

𝛼 is the intercept and 𝜀 is the error term

Vaneck Vectors Vietnam ETF (VNM)’s returns are used as a benchmark for market performance. By regressing VGI’s rate of return (RoR) on VNM’s RoR, we will get the associated beta coefficient as a measure of volatility.

1. First, load the packaged requires to wrangle/visualize data

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──
## ✓ ggplot2 3.3.5     ✓ purrr   0.3.4
## ✓ tibble  3.1.5     ✓ stringr 1.4.0
## ✓ tidyr   1.1.4     ✓ forcats 0.5.1
## ✓ readr   2.1.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(ggplot2)
library(cowplot)
library(plotly)
## 
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout
library(xts)
## Loading required package: zoo
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
## 
## Attaching package: 'xts'
## The following objects are masked from 'package:dplyr':
## 
##     first, last
library(dygraphs)

2. Load stock price datasets

#VGI Stock Price
vgi <- read.csv("~/Documents/Data Science /R/projects/vgi.csv")

#VNM Index
vnm <- read.csv("~/Documents/Data Science /R/projects/VNM.csv")

3. Merge datasets into one to run regression / plot data

#inner join, foreign key Date
merged_dt <- inner_join(vgi, vnm, by = "Date")
dim(merged_dt)
## [1] 832  11
#rename variables
revised_names <- c("Date", "VGI_Close", "VGI_Open", "VGI_High", "VGI_Low", "VGI_Vol",
                   "VNM_Open", "VNM_High", "VNM_Low", "VNM_Close", "VNM_Vol")
colnames(merged_dt) <- revised_names
head(merged_dt)
##         Date VGI_Close VGI_Open VGI_High VGI_Low VGI_Vol VNM_Open VNM_High
## 1 2018-09-26     23900    23900    23900   23800 214.20K    16.93    16.93
## 2 2018-09-27     27400    27400    27400   27400 388.10K    16.80    16.89
## 3 2018-09-28     28200    31500    31500   27400   2.11M    16.84    16.90
## 4 2018-10-01     26800    29500    29600   25800 831.10K    16.80    16.81
## 5 2018-10-02     26000    27100    27300   25900 596.27K    16.55    16.72
## 6 2018-10-03     23500    26200    26200   23000 705.23K    16.74    16.75
##   VNM_Low VNM_Close VNM_Vol
## 1   16.73  16.37579  281900
## 2   16.79  16.40505  401300
## 3   16.73  16.39529  257300
## 4   16.55  16.17097  378400
## 5   16.53  16.16121  190000
## 6   16.47  16.08319   86900
#change VNM unit price to VND
merged_dt$VNM_Close <- merged_dt$VNM_Close*22840

4. We are now ready to calculate the rate of returns of VGI and VNM ETF

#Calculate VGI RoR
n = nrow(merged_dt)
merged_dt$VGI_RoR[2:n] <- ((merged_dt$VGI_Close[2:n] - merged_dt$VGI_Close[1:(n-1)])/merged_dt$VGI_Close[1:(n-1)] * 100)
merged_dt$VGI_RoR[1] <- 0

#Calculate VNM RoR
merged_dt$VNM_RoR[2:n] <- ((merged_dt$VNM_Close[2:n] - merged_dt$VNM_Close[1:(n-1)])/merged_dt$VNM_Close[1:(n-1)] * 100)
merged_dt$VNM_RoR[1] <- 0

#double check
head(merged_dt)
##         Date VGI_Close VGI_Open VGI_High VGI_Low VGI_Vol VNM_Open VNM_High
## 1 2018-09-26     23900    23900    23900   23800 214.20K    16.93    16.93
## 2 2018-09-27     27400    27400    27400   27400 388.10K    16.80    16.89
## 3 2018-09-28     28200    31500    31500   27400   2.11M    16.84    16.90
## 4 2018-10-01     26800    29500    29600   25800 831.10K    16.80    16.81
## 5 2018-10-02     26000    27100    27300   25900 596.27K    16.55    16.72
## 6 2018-10-03     23500    26200    26200   23000 705.23K    16.74    16.75
##   VNM_Low VNM_Close VNM_Vol   VGI_RoR     VNM_RoR
## 1   16.73  374023.0  281900  0.000000  0.00000000
## 2   16.79  374691.3  401300 14.644351  0.17867842
## 3   16.73  374468.5  257300  2.919708 -0.05946950
## 4   16.55  369344.9  378400 -4.964539 -1.36821595
## 5   16.53  369122.2  190000 -2.985075 -0.06031797
## 6   16.47  367340.1   86900 -9.615385 -0.48278548

5. To better visualize the rate of returns, we will compare their distributions through the histograms below:

#VGI returns histogram
hist_vgi <- ggplot(merged_dt, aes(VGI_RoR)) +
  geom_histogram(fill = "brown", color = "black") +
  labs(title = "Viettel Global (VGI)", subtitle = "3-year period", x = "Daily returns (%)", y = "Count (days)") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))
ggplotly(hist_vgi)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#Market returns histogram
hist_3yr <- ggplot(merged_dt, aes(VNM_RoR)) +
  geom_histogram(fill = "orange", color = "black") +
  labs(title = "Market (VNM)", subtitle = "3-year period", x = "Daily returns (%) ", y = "Count (days)") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif")) 
ggplotly(hist_3yr)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#Market returns vs VGI returns, side by side, 3 year period
plot_grid(hist_3yr, hist_vgi)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

6. We will now run a simple linear regression model, the beta coefficient of which will be the VGI’s stock beta for the 3-year period from late 2018 until Feb 2022.

vgi_vnm <- lm(merged_dt$VGI_RoR ~ merged_dt$VNM_RoR)
summary(vgi_vnm)
## 
## Call:
## lm(formula = merged_dt$VGI_RoR ~ merged_dt$VNM_RoR)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -10.7246  -1.5231  -0.3399   1.1527  14.9364 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        0.06058    0.10337   0.586    0.558    
## merged_dt$VNM_RoR  0.73995    0.06867  10.776   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 2.981 on 830 degrees of freedom
## Multiple R-squared:  0.1227, Adjusted R-squared:  0.1217 
## F-statistic: 116.1 on 1 and 830 DF,  p-value: < 2.2e-16

From the model report, VGI’s beta is 0.73995, indicating a relatively low risk for the past 3 years.

plot1 <- ggplot(merged_dt, aes(VNM_RoR, VGI_RoR)) +
  geom_point(position = "jitter", alpha = 0.5, color = "tomato3") +
  labs(title = "RoR: VGI vs VNM", subtitle = "3-year beta: 0.74", x = "VNM daily returns (%)", y = "VGI daily returns (%)") +
  geom_smooth(method = "lm", se = FALSE, col = "black") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))
ggplotly(plot1)
## `geom_smooth()` using formula 'y ~ x'

7. Period Breakdown Analysis

We will now estimate the beta for Viettel Global Investment (VGI) in 3 consecutive time periods: 2019, 2020, 2021.

a. 2019 period

#filter for 2019 data
dt_2019 <- filter(merged_dt, grepl("2019", Date, ignore.case = TRUE))
head(dt_2019)
##         Date VGI_Close VGI_Open VGI_High VGI_Low VGI_Vol VNM_Open VNM_High
## 1 2019-01-02     13200    13200    14000   13100  60.56K    14.69    15.11
## 2 2019-01-03     13000    13300    13400   12800  74.90K    14.76    14.83
## 3 2019-01-04     12600    12700    13000   12600  60.45K    14.64    14.92
## 4 2019-01-07     13000    12700    13400   12400  65.70K    14.94    15.09
## 5 2019-01-08     13100    13000    13500   13000  42.26K    15.02    15.05
## 6 2019-01-09     13300    13000    13500   13000  84.64K    15.04    15.21
##   VNM_Low VNM_Close VNM_Vol    VGI_RoR    VNM_RoR
## 1   14.65  334654.9  267900 -1.4925373  0.3366916
## 2   14.40  325221.7  203300 -1.5151515 -2.8187959
## 3   14.61  334879.6  187600 -3.0769231  2.9696198
## 4   14.84  337799.4  264400  3.1746032  0.8719015
## 5   14.81  336002.6  120900  0.7692308 -0.5319201
## 6   15.02  340270.0  149100  1.5267176  1.2700575

RoR Distribution:

#VGI returns histogram: 2019
hist_vgi_19 <- ggplot(dt_2019, aes(VGI_RoR)) +
  geom_histogram(fill = "brown", color = "black") +
  labs(title = "Viettel Global (VGI)", subtitle = "2019", x = "Daily returns (%)", y = "Frequency") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))
ggplotly(hist_vgi_19)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#Market returns histogram: 2019
hist_vnm_19 <- ggplot(dt_2019, aes(VNM_RoR)) +
  geom_histogram(fill = "orange", color = "black") +
  labs(title = "Market (VNM)", subtitle = "2019", x = "Daily returns (%) ", y = "Frequency") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))
ggplotly(hist_vnm_19)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#Market returns vs VGI returns, side by side, 2019
plot_grid(hist_vnm_19, hist_vgi_19)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Regression + Beta:

#2019 beta
beta_19 <- lm(dt_2019$VGI_RoR ~ dt_2019$VNM_RoR)
summary(beta_19)
## 
## Call:
## lm(formula = dt_2019$VGI_RoR ~ dt_2019$VNM_RoR)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -11.2914  -1.7881  -0.3519   1.2842  14.9333 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)
## (Intercept)      0.30345    0.21480   1.413    0.159
## dt_2019$VNM_RoR  0.07332    0.21346   0.344    0.732
## 
## Residual standard error: 3.346 on 241 degrees of freedom
## Multiple R-squared:  0.0004894,  Adjusted R-squared:  -0.003658 
## F-statistic: 0.118 on 1 and 241 DF,  p-value: 0.7315

From the model report, VGI’s beta is 0.07332, signifying very low sensitivity to market overall performance. R-squared is low at -0.003658, indicating variation of the response variable VGI is not at all explained by the model. The statistically insignificant p-value (0.7315 > 0.05) further proves that there is little to no association between VGI stock price and overall market performance. In other words, in 2019, VGI stock price is not at all influenced by market sentiment.

Visualize:

plot_2019 <- ggplot(dt_2019, aes(VNM_RoR, VGI_RoR)) +
  geom_point(position = "jitter", alpha = 0.5, color = "coral1") +
  labs(title = "RoR: VGI vs VNM", subtitle = "2019 beta: 0.07", x = "VNM daily returns (%)", y = "VGI daily returns (%)") +
  geom_smooth(method = "lm", se = FALSE, col = "black") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))
ggplotly(plot_2019)
## `geom_smooth()` using formula 'y ~ x'

The almost horizontal regression line indicates very low correlation between VGI’s stock price and ETF. 2019 was a year of low risk for Viettel Global Investment JSC.

b. 2020 period

#filter for 2020 data
dt_2020 <- filter(merged_dt, grepl("2020", Date, ignore.case = TRUE))
head(dt_2020)
##         Date VGI_Close VGI_Open VGI_High VGI_Low VGI_Vol VNM_Open VNM_High
## 1 2020-01-02     25300    25000    25300   24700 152.89K    16.00    16.27
## 2 2020-01-03     25000    25400    25400   24800 130.71K    16.20    16.20
## 3 2020-01-06     24600    24900    25000   24400 164.57K    16.00    16.07
## 4 2020-01-07     24500    24700    24700   24300 124.00K    16.04    16.09
## 5 2020-01-08     23200    24200    24300   23000 505.00K    15.85    15.92
## 6 2020-01-09     23200    23400    23700   22900 157.15K    15.86    15.92
##   VNM_Low VNM_Close VNM_Vol    VGI_RoR     VNM_RoR
## 1   15.99  368002.5  615400  2.4291498  1.68857039
## 2   16.00  364155.0  402400 -1.1857708 -1.04551249
## 3   15.90  363023.4  157600 -1.6000000 -0.31075521
## 4   16.01  362344.4  108500 -0.4065041 -0.18703686
## 5   15.77  358949.5  193300 -5.3061224 -0.93691782
## 6   15.80  358723.2  220100  0.0000000 -0.06305108

RoR Distribution:

#VGI returns histogram: 2020
hist_vgi_20 <- ggplot(dt_2020, aes(VGI_RoR)) +
  geom_histogram(fill = "brown", color = "black") +
  labs(title = "Viettel Global (VGI)", subtitle = "2020", x = "Daily returns (%)", y = "Frequency") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))

#Market returns histogram: 2020
hist_vnm_20 <- ggplot(dt_2020, aes(VNM_RoR)) +
  geom_histogram(fill = "orange", color = "black") +
  labs(title = "Market (VNM)", subtitle = "2020", x = "Daily returns (%) ", y = "Frequency") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))

#Market returns vs VGI returns, side by side, 2020
plot_grid(hist_vnm_20, hist_vgi_20)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Regression + Beta:

beta_20 <- lm(dt_2020$VGI_RoR ~ dt_2020$VNM_RoR)
summary(beta_20)
## 
## Call:
## lm(formula = dt_2020$VGI_RoR ~ dt_2020$VNM_RoR)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -8.4312 -1.3459 -0.1398  1.0471 12.7268 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      0.12605    0.17249   0.731    0.466    
## dt_2020$VNM_RoR  0.83247    0.08177  10.180   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 2.693 on 242 degrees of freedom
## Multiple R-squared:  0.2998, Adjusted R-squared:  0.297 
## F-statistic: 103.6 on 1 and 242 DF,  p-value: < 2.2e-16

From the model report, VGI’s beta in 2020 is 0.83247, indicating higher and more prominent risk from the year before. However, as beta here is < 1, it is still considered relatively safe compared to other stocks in the market (with beta > 1). This was due mostly to the onset of the COVID-19 pandemic leading to national lock-down and business closure.

Visualize:

plot_2020 <- ggplot(dt_2020, aes(VNM_RoR, VGI_RoR)) +
  geom_point(position = "jitter", alpha = 0.5, color = "brown3") +
  labs(title = "RoR: VGI vs VNM", subtitle = "2020 beta: 0.83", x = "VNM daily returns (%)", y = "VGI daily returns (%)") +
  geom_smooth(method = "lm", se = FALSE, col = "black") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))
ggplotly(plot_2020)
## `geom_smooth()` using formula 'y ~ x'

c. 2021 period

#filter for 2021 data
dt_2021 <- filter(merged_dt, grepl("2021", Date, ignore.case = TRUE))
head(dt_2021)
##         Date VGI_Close VGI_Open VGI_High VGI_Low VGI_Vol VNM_Open VNM_High
## 1 2021-01-04     34600    33300    35000   33300   1.23M    17.60    17.79
## 2 2021-01-05     34400    34600    34700   34000 578.13K    17.75    17.99
## 3 2021-01-06     34800    34300    35800   34300   1.07M    17.96    18.05
## 4 2021-01-07     35000    34800    35100   34300   1.04M    18.07    18.22
## 5 2021-01-08     38000    35000    38300   34900   3.36M    18.25    18.46
## 6 2021-01-11     41700    38000    41800   38000   1.44M    18.48    18.58
##   VNM_Low VNM_Close VNM_Vol    VGI_RoR   VNM_RoR
## 1   17.53  398854.2  580500  3.2835821 0.3430505
## 2   17.73  408172.2  502900 -0.5780347 2.3361778
## 3   17.83  409081.3  641100  1.1627907 0.2227248
## 4   18.01  413853.9  813100  0.5747126 1.1666633
## 5   18.21  418626.5  499900  8.5714286 1.1532147
## 6   18.42  420671.9  410600  9.7368421 0.4886010

RoR Distribution:

#VGI returns histogram: 2020
hist_vgi_21 <- ggplot(dt_2021, aes(VGI_RoR)) +
  geom_histogram(fill = "brown", color = "black") +
  labs(title = "Viettel Global (VGI)", subtitle = "2021", x = "Daily returns (%)", y = "Frequency") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))

#Market returns histogram: 2020
hist_vnm_21 <- ggplot(dt_2021, aes(VNM_RoR)) +
  geom_histogram(fill = "orange", color = "black") +
  labs(title = "Market (VNM)", subtitle = "2021", x = "Daily returns (%) ", y = "Frequency") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))

#Market returns vs VGI returns, side by side, 2020
plot_grid(hist_vnm_21, hist_vgi_21)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Regression + Beta:

#2021 beta
beta_21 <- lm(dt_2021$VGI_RoR ~ dt_2021$VNM_RoR)
summary(beta_21)
## 
## Call:
## lm(formula = dt_2021$VGI_RoR ~ dt_2021$VNM_RoR)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -8.5115 -1.4673 -0.3321  1.2286 11.6596 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)     -0.05591    0.17103  -0.327    0.744    
## dt_2021$VNM_RoR  0.98458    0.14545   6.769 9.78e-11 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 2.658 on 241 degrees of freedom
## Multiple R-squared:  0.1598, Adjusted R-squared:  0.1563 
## F-statistic: 45.82 on 1 and 241 DF,  p-value: 9.775e-11
#==> 2021 beta = 0.98458

In 2021, VGI beta went very close to 1 at 0.98, indicating an increase in volatility influenced by market performance. This roughly tells that as the market price move by 1 unit, VGI stock price moves by 0.98 unit.

Visualize:

plot_2021 <- ggplot(dt_2021, aes(VNM_RoR, VGI_RoR)) +
  geom_point(position = "jitter", alpha = 0.5, color = "brown1") +
  labs(title = "RoR: VGI vs VNM", subtitle = "2021 beta: 0.98", x = "VNM daily returns (%)", y = "VGI daily returns (%)") +
  geom_smooth(method = "lm", se = FALSE, col = "black") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5), plot.subtitle = element_text(hjust = 0.5), text = element_text(family = "serif"))
ggplotly(plot_2021)
## `geom_smooth()` using formula 'y ~ x'

d. Overall Comparison

plot_grid(plot1, plot_2019, plot_2020, plot_2021)
## `geom_smooth()` using formula 'y ~ x'
## `geom_smooth()` using formula 'y ~ x'
## `geom_smooth()` using formula 'y ~ x'
## `geom_smooth()` using formula 'y ~ x'

It can be observed that beta of Viettel Global Investment (VGI) increases as time progresses. As events with significant impact make their marks on the global world, Vietnamese economy is also undergoing substantial changes, which are likely impacting VGI’s stock price in an escalating manner.


II. SHARPE RATIO: VGI (Viettel Global) vs SGT (Saigon Telecommunication & Technologies Corp)

Sharpe ratio is one of the most popular risk-reward measures in finance. This ratio tells how much more return for a given unit of risk a stock would generate relative to just putting money into a low-cost index fund. For this measure, we will execute a peer comparison between VGI (Viettel Global) and SGT (Saigon Telecommunication & Technologies Corp).

Annualized Sharpe Ratio = Average Return of Difference / Standard Deviation of Return Difference * Annual Factor

Based on the above formula, we will calculate annualized Sharpe Ratio for the stocks of two telecom companies Viettel and SGT, using Vaneck Vietnam ETF Fund (VNM) as a banchmark.

1. Load dataset + convert into xts objects to plot time series

#Load SGT dataset
sgt <- read.csv("~/Documents/Data Science /R/projects/SGT Historical Data-2.csv")

#Convert vgi into an xts object
vgi_ts <- xts(vgi[,2], order.by=as.POSIXct(vgi$Date))

#Convert sgt into an xts object
sgt_ts <- xts(sgt[,2], order.by=as.POSIXct(sgt$Date))

#Convert vnm into an xts object
vnm_ts <- xts(vnm[,5], order.by=as.POSIXct(vnm$Date))

2. Merge and filter datasets

merged_dt <- inner_join(vgi, vnm, by = "Date")
#merged data 
vgi_sgt_vnm <- inner_join(merged_dt, sgt, by = "Date")
revised_names <- c("Date", "VGI_Close", "VGI_Open", "VGI_High", "VGI_Low", "VGI_Vol",
                   "VNM_Open", "VNM_High", "VNM_Low", "VNM_Close", "VNM_Vol", "SGT_Close", "SGT_Open", "SGT_High", "SGT_Low", "SGT_Vol")
colnames(vgi_sgt_vnm) <- revised_names
vgi_sgt_vnm$VNM_Close <- vgi_sgt_vnm$VNM_Close*22840

#select only closing prices
concise_dt <- vgi_sgt_vnm %>% 
  select(Date, VGI_Close, SGT_Close, VNM_Close)

#convert to xts for plotting
concise_dt <- xts(concise_dt[,2:4], order.by=as.POSIXct(concise_dt$Date))
head(concise_dt)
##            VGI_Close SGT_Close VNM_Close
## 2018-09-26     23900      5230  374023.0
## 2018-09-27     27400      5490  374691.3
## 2018-09-28     28200      5400  374468.5
## 2018-10-01     26800      5030  369344.9
## 2018-10-02     26000      4750  369122.2
## 2018-10-03     23500      4700  367340.1

3. Price movement observation

Plot daily price movement for VGI and SGT:

#Individual plots
vgi_plot <- dygraph(vgi_ts, main = "VGI Stock Price", ylab = "VND") %>% 
  dyOptions(fillGraph = TRUE, drawGrid = FALSE, colors = "brown") 
vgi_plot
sgt_plot <- dygraph(sgt_ts, main = "SGT Stock Price", ylab = "VND") %>% 
  dyOptions(fillGraph = TRUE, drawGrid = FALSE, colors = "steelblue") 
sgt_plot
#Together in a plot
agg_plot <- dygraph(concise_dt[, 1:2], main = "VGI vs SGT", ylab = "VND") %>% 
  dySeries("VGI_Close", label = "VGI", color = "brown", fillGraph = "brown") %>%
  dySeries("SGT_Close", label = "SGT", color = "steelblue", fillGraph = "steelblue") %>%
  dyOptions(drawGrid = FALSE, axisLabelFontSize = 11, fillAlpha = 0.1)
agg_plot

Plot benchmark price movement:

vnm_plot <- dygraph(concise_dt[,3], main = "VNM Stock Index", ylab = "VND") %>% 
  dyOptions(drawGrid = FALSE, colors = "orange", axisLabelFontSize = 11, fillAlpha = 0.1, fillGraph = TRUE) 
vnm_plot

4. Daily Stock Returns (RoR)

Calculation:

#Calculate VGI RoR
n = nrow(vgi_sgt_vnm)
vgi_sgt_vnm$VGI_RoR[2:n] <- ((vgi_sgt_vnm$`VGI_Close`[2:n] - vgi_sgt_vnm$`VGI_Close`[1:(n-1)])/vgi_sgt_vnm$`VGI_Close`[1:(n-1)] * 100)
vgi_sgt_vnm$VGI_RoR[1] <- 0

#Calculate SGT RoR
vgi_sgt_vnm$SGT_RoR[2:n] <- ((vgi_sgt_vnm$`SGT_Close`[2:n] - vgi_sgt_vnm$`SGT_Close`[1:(n-1)])/vgi_sgt_vnm$`SGT_Close`[1:(n-1)] * 100)
vgi_sgt_vnm$SGT_RoR[1] <- 0

#Calculate VNM RoR
vgi_sgt_vnm$VNM_RoR[2:n] <- ((vgi_sgt_vnm$`VNM_Close`[2:n] - vgi_sgt_vnm$`VNM_Close`[1:(n-1)])/vgi_sgt_vnm$`VNM_Close`[1:(n-1)] * 100)
vgi_sgt_vnm$VNM_RoR[1] <- 0

ror_dt <- xts(vgi_sgt_vnm[,17:19], order.by=as.POSIXct(vgi_sgt_vnm$Date))
head(ror_dt)
##              VGI_RoR   SGT_RoR     VNM_RoR
## 2018-09-26  0.000000  0.000000  0.00000000
## 2018-09-27 14.644351  4.971319  0.17867842
## 2018-09-28  2.919708 -1.639344 -0.05946950
## 2018-10-01 -4.964539 -6.851852 -1.36821595
## 2018-10-02 -2.985075 -5.566600 -0.06031797
## 2018-10-03 -9.615385 -1.052632 -0.48278548

Plot daily stock returns:

stock_returns <- ror_dt[, 1:2]
st_returns <- dygraph(stock_returns, main = "Stock Daily Returns", ylab = "Percent (%)") %>% 
  dySeries("VGI_RoR", label = "VGI", color = "brown") %>%
  dySeries("SGT_RoR", label = "SGT", color = "steelblue") %>%
  dyOptions(drawGrid = FALSE, axisLabelFontSize = 11, drawAxesAtZero = TRUE)
st_returns

Plot daily benchmark returns:

benchmark_rt <- dygraph(ror_dt[,3], main = "Benchmark Daily Returns", ylab = "Percent (%)") %>% 
  dyOptions(colors = "orange", drawGrid = FALSE, axisLabelFontSize = 12, drawAxesAtZero = TRUE)
benchmark_rt

5. Excess Returns

This measures the relative performance of the two stocks versus VNM ETF benchmark. Excess returns = stock_returns - benchmark_rt (daily).

ror_dt$excess_rt_vgi <- ror_dt[, 1] - ror_dt[,3]
ror_dt$excess_rt_sgt <- ror_dt[, 2] - ror_dt[,3]

Visualize:

stock_excess <- dygraph(ror_dt[,4:5], main = "Stock Excess Returns", ylab = "Percent (%)") %>% 
  dySeries("excess_rt_vgi", label = "VGI", color = "brown") %>%
  dySeries("excess_rt_sgt", label = "SGT", color = "steelblue") %>%
  dyOptions(drawGrid = FALSE, axisLabelFontSize = 12, drawAxesAtZero = TRUE)
stock_excess

6. Average of Difference in Stock Returns

avg_exrt_vgi <- mean(ror_dt$excess_rt_vgi)
avg_exrt_sgt <- mean(ror_dt$excess_rt_sgt)

Visualize:

#Create dataframe for plotting
a1 <- c("Viettel Global Investment (VGI)", "Saigon Telecommunication (SGT)")
a2 <- c(avg_exrt_vgi, avg_exrt_sgt)
avg_re <- data.frame(a1, a2)
colnames(avg_re) <- c("Company", "avg_return_diff")
avg_re
##                           Company avg_return_diff
## 1 Viettel Global Investment (VGI)       0.0546441
## 2  Saigon Telecommunication (SGT)       0.2758362
#Compare avg differences in returns
plot_avg <- ggplot(avg_re, aes(Company, avg_return_diff, fill = Company)) +
  geom_bar(stat = "identity") +
  labs(title = "Average of Return Difference", subtitle = "SGT has significantly higher average returns than VGI", y = "Average (Unit)", x = "") +
  theme_bw() +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"), plot.subtitle = element_text(hjust = 0.5, size = 10), text = element_text(family = "serif"), panel.grid.major.x = element_blank()) +
  scale_fill_manual(labels = c("SGT", "VGI"), values = c("steelblue", "brown"))
plot_avg

7. Standard Deviation of Difference in Stock Returns

The standard deviation of excess_returns shows us the amount of risk an investment in the stocks implies as compared to an investment in the ETF fund.

sd_exrt_vgi <- sd(ror_dt$excess_rt_vgi)
sd_exrt_sgt <- sd(ror_dt$excess_rt_sgt)

Visualize:

#Create dataframe
r1 <- c("Viettel Global Investment (VGI)", "Saigon Telecommunication (SGT)")
r2 <- c(sd_exrt_vgi, sd_exrt_sgt)
risk_sd <- data.frame(r1, r2)
colnames(risk_sd) <- c("Company", "sd_return_diff")
risk_sd
##                           Company sd_return_diff
## 1 Viettel Global Investment (VGI)       3.030645
## 2  Saigon Telecommunication (SGT)       4.407414
#Compare std differences in returns
plot_risk <- ggplot(risk_sd, aes(Company, sd_return_diff, fill = Company)) +
  geom_bar(stat = "identity") +
  labs(title = "Standard Deviation of Return Difference", subtitle = "SGT is risker than VGI by 1 unit", y = "Standard Deviation (Unit)", x = "") +
  theme_bw() +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"), plot.subtitle = element_text(hjust = 0.5, size = 10), text = element_text(family = "serif"), panel.grid.major.x = element_blank()) +
  scale_fill_manual(labels = c("SGT", "VGI"), values = c("steelblue", "brown"))
plot_risk

8. Sharpe Ratio Calculation: Final Step

#Sharpe Ratio =  ratio of avg_excess_returns and sd_excess_returns
vgi_sharpe_ratio <- avg_exrt_vgi / sd_exrt_vgi
sgt_sharpe_ratio <- avg_exrt_sgt / sd_exrt_sgt

#annualized sharpe ratio
annual_factor = sqrt(nrow(ror_dt))
annualized_shratio_vgi <- vgi_sharpe_ratio * annual_factor
annualized_shratio_sgt <- sgt_sharpe_ratio * annual_factor

Visualize + Compare:

#create dataframe
c1 <- c("Viettel Global Investment (VGI)", "Saigon Telecommunication (SGT)")
c2 <- c(annualized_shratio_vgi, annualized_shratio_sgt)
annual_sratio <- data.frame(c1, c2)
colnames(annual_sratio) <- c("Company", "Sharpe_Ratio")
annual_sratio
##                           Company Sharpe_Ratio
## 1 Viettel Global Investment (VGI)    0.5048544
## 2  Saigon Telecommunication (SGT)    1.7523684
#Plot annualized sharpe ratios
plot_sratio <- ggplot(annual_sratio, aes(Company, Sharpe_Ratio, fill = Company)) +
  geom_bar(stat = "identity") +
  labs(title = "Annualized Sharpe Ratio", subtitle = "observed difference in ratio mostly driven by return rather than risk", y = "Sharpe Ratio", x = "") +
  theme_bw() +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"), plot.subtitle = element_text(hjust = 0.5, size = 10), text = element_text(family = "serif"), panel.grid.major.x = element_blank()) +
  scale_fill_manual(labels = c("SGT", "VGI"), values = c("steelblue", "brown"))
plot_sratio

From late 2018 to early 2022, SGT had a Sharpe ratio triple that of VGI’s. This means that an investment in SGT returned 3 times as much compared to the VGI for each unit of risk. In other words, in risk-adjusted terms, the investment in SGT would have been more attractive.

This difference was mostly driven by differences in return rather than risk between Viettel Global Investment and Saigon Telecom. The risk of choosing SGT over VGI (as measured by the standard deviation) was only higher by 1 unit whereas the average excess returns of SGI was 3 times that of VGI.Thus, the Sharpe ratio for SGT ends up higher mainly due to its higher average daily returns.


III. CONCLUSION

From the above risk-reward measures - Beta and Sharpe Ratio, we can conclude that Viettel Global Investment JSC (ticker: VGI) is a relatively safe investment with an overall beta < 1 and lower risk compared to other peers (i.e. SGT).


IV. LIMITATIONS

Critics argue that beta does not give enough information about the fundamentals of a company, and that beta is probably a better indicator of short-term rather than long-term risk.

The Sharpe ratio has several weaknesses, including an assumption that investment returns are normally distributed. This might not hold true for every stock.