#Import libraries
library(quantmod)
## 필요한 패키지를 로딩중입니다: xts
## 필요한 패키지를 로딩중입니다: zoo
##
## 다음의 패키지를 부착합니다: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
## 필요한 패키지를 로딩중입니다: TTR
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
library(TTR)
library(highcharter)
library(ggplot2)
library(DT)
library(plotly)
##
## 다음의 패키지를 부착합니다: '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
#Import MSFT Stock Price
getSymbols("MSFT", src = "yahoo", from = "2022-01-01")
## [1] "MSFT"
#Add Bollingerband
MSFT$BBands <- BBands(Cl(MSFT), n = 20, sd = 2)
#Visualization
chartSeries(MSFT)
addRSI(n=14, maType = "EMA", wilder = TRUE)
addBBands(n=20, sd=2, maType="SMA")
#Use different visualization method
hchart(MSFT) %>% #Chart NVDA stock price
hc_add_series(MSFT$mavg, color = 'red',name='SMA(20)') %>% #Chart SMA(20)
hc_add_series(MSFT$dn, color = 'cornflowerblue')%>% #Chart downside BB
hc_add_series(MSFT$up, color = 'cornflowerblue') # Chart uppersider BB
#Add trade signal
MSFT$signal <- ifelse(Cl(MSFT) < MSFT$dn, 1, #If the close prices is below the band, it's a buy signal
ifelse(Cl(MSFT) > MSFT$up, -1, 0)) #If the close prices is above the band, it's a sell signal
#Create trade signal using bollingerband strategy
trade <- ifelse(MSFT$signal == 1,"buy",
ifelse(MSFT$signal == -1,"sell",""))
#Create new dataframe for trading strategy
df <- data.frame(Date=index(MSFT), Close=Cl(MSFT), BBands_dn = MSFT$dn, BBands_up = MSFT$up,Trade=trade)
#Change the column names
colnames(df) <- c('Date','Close','BBands_dn','BBands_up','Trade')
datatable(df)
#Visualize BB and trading point
ggplot(df, aes(x=Date, y=Close)) +
geom_line(size=0.8,color="cornflowerblue") +
geom_ribbon(aes(ymin=BBands_dn, ymax=BBands_up), alpha=0.2, fill="black") +
geom_point(data=df[df$Trade=="buy",], aes(x=Date, y=Close, color="buy"), size=2) +
geom_point(data=df[df$Trade=="sell",], aes(x=Date, y=Close, color="sell"), size=2) +
scale_color_manual(name="Trade", values=c("buy"="grey30", "sell"="orangered")) +
labs(title="Bollinger Band Buy&Sell Signal for MSFT", y="Price", x="Date")+
theme(legend.position = "top") +
scale_x_date(date_breaks = '2 months',
date_labels = '%Y-%m')
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## i Please use `linewidth` instead.
## Warning: Removed 19 rows containing missing values (`geom_point()`).
## Removed 19 rows containing missing values (`geom_point()`).
There are going to be 2 buy & sell trading using this strategy. First, we will buy the stocks at the black points starting from February and sell it at the first red point around August.
For the second trading, we will buy the stocks at the black points after the first red point (around September 2022) and then sell it at the next red point (around March 2023).
Thus, the return from the trade would be the difference between the average price of the black points and the first red point after the black point.
#Identify the first buying point
first_buy_index <- which(df$Trade=="buy")[1]
#Identify the first selling point
first_sell_index <- which(df$Trade=="sell" & index(df) > index(df)[first_buy_index])[1]
#Identify the second buying point
second_buy_index <- which(df$Trade=="buy" & index(df) > index(df)[first_sell_index])[1]
#Identify the second selling point
second_sell_index <- which(df$Trade=="sell" & index(df) > index(df)[second_buy_index])[1]
# Calculate average price and return for first buy-sell pair
if(!is.na(first_buy_index) & !is.na(first_sell_index)){
buy_prices_1 <- df[which(df$Trade == "buy" & index(df) <= index(df)[first_sell_index]), "Close"]
buy_avg_price_1 <- mean(buy_prices_1, na.rm = TRUE)
sell_price_1 <- df[first_sell_index, "Close"]
return_1 <- (sell_price_1 - buy_avg_price_1) / buy_avg_price_1
} else {
return_1 <- NA
}
# Calculate average price and return for second buy-sell pair
if(!is.na(second_buy_index) & !is.na(second_sell_index)){
buy_prices_2 <- df[which(df$Trade == "buy" & index(df) > index(df)[first_sell_index] &
index(df) < index(df)[second_sell_index]), "Close"]
buy_avg_price_2 <- mean(buy_prices_2, na.rm = TRUE)
sell_price_2 <- df[second_sell_index, "Close"]
return_2 <- (sell_price_2 - buy_avg_price_2) / buy_avg_price_2
} else {
return_2<- NA
}
#First trade buying prices
buy_prices_1
## [1] 280.27 278.91 275.85 285.26 282.06 279.83 264.58 260.55 255.35 242.26
#First trade average buying price
buy_avg_price_1
## [1] 270.492
#First trade selling price
sell_price_1
## [1] 276.41
#First trade return
return_1
## [1] 0.02187868
#Second trade buying prices
buy_prices_2
## [1] 268.09 265.23 262.97 229.25 225.41 214.25 229.10 222.31
#Second trade average buying price
buy_avg_price_2
## [1] 239.5763
#Second trade selling price
sell_price_2
## [1] 264.6
#Second trade return
return_2
## [1] 0.1044501
#Calculate total return
total_return <- (1 + return_1) * (1 + return_2) - 1
buy_hold_return <- (df[300,'Close']-df[1,'Close'])/df[1,'Close']
#Make a dataframe to plot the return graph
returns_df <- data.frame(
Trade = c("1st Trade", "2nd Trade","Trading Profit","Buy&Hold"),
Return = c(return_1, return_2,total_return,buy_hold_return)
)
# Define the order of the trades
trade_order <- c("1st Trade", "2nd Trade","Trading Profit", "Buy&Hold")
returns_df$Trade <- factor(returns_df$Trade, levels = trade_order)
# plot the returns using a bar chart
ggplot(returns_df, aes(x=Trade, y=Return,color=Trade,fill=Trade)) +
geom_bar(stat="identity",color='black' ) +
labs(title="Returns from Bollinger Band Trading Strategy for MSFT", y="Return", x="Trade") +
scale_y_continuous(labels = scales::percent)+
geom_text(aes(label=scales::percent(Return)), vjust=-0.2, size=4)+
geom_text(data = filter(returns_df, Return < 0), aes(label=scales::percent(Return), y=Return - 0.01), size=4)+
theme(legend.position = "top")
We can see that the cumulative return from 2022-01-01 to current period using this strategy was roughly 12%. However, we want to backtest whether this strategy was valid on a different time period. Since we tested post-COVID period, we will test whether this strategy worked during the COVID and before the COVID.
getSymbols("MSFT", src = "yahoo", from = "2020-01-01", to = "2021-12-31")
## [1] "MSFT"
MSFT$BBands <- BBands(Cl(MSFT), n = 20, sd = 2)
hchart(MSFT) %>% #Chart NVDA stock price
hc_add_series(MSFT$mavg, color = 'red',name='SMA(20)') %>% #Chart SMA(20)
hc_add_series(MSFT$dn, color = 'cornflowerblue')%>% #Chart downside BB
hc_add_series(MSFT$up, color = 'cornflowerblue') # Chart uppersider BB
#Add trade signal
MSFT$signal <- ifelse(Cl(MSFT) < MSFT$dn, 1, #If the close prices is below the band, it's a buy signal
ifelse(Cl(MSFT) > MSFT$up, -1, 0)) #If the close prices is above the band, it's a sell signal
#Create trade signal using bollingerband strategy
trade <- ifelse(MSFT$signal == 1,"buy",
ifelse(MSFT$signal == -1,"sell",""))
#Create new dataframe for trading strategy
df <- data.frame(Date=index(MSFT), Close=Cl(MSFT), BBands_dn = MSFT$dn, BBands_up = MSFT$up,Trade=trade)
#Change the column names
colnames(df) <- c('Date','Close','BBands_dn','BBands_up','Trade')
datatable(df)
#Visualize BB and trading point
ggplot(df, aes(x=Date, y=Close)) +
geom_line(size=0.8,color="cornflowerblue") +
geom_ribbon(aes(ymin=BBands_dn, ymax=BBands_up), alpha=0.2, fill="black") +
geom_point(data=df[df$Trade=="buy",], aes(x=Date, y=Close, color="buy"), size=2) +
geom_point(data=df[df$Trade=="sell",], aes(x=Date, y=Close, color="sell"), size=2) +
scale_color_manual(name="Trade", values=c("buy"="grey30", "sell"="orangered")) +
labs(title="Bollinger Band Buy&Sell Signal for MSFT(COVID)", y="Price", x="Date")+
theme(legend.position = "top") +
scale_x_date(date_breaks = '2 months',
date_labels = '%Y-%m')
## Warning: Removed 19 rows containing missing values (`geom_point()`).
## Removed 19 rows containing missing values (`geom_point()`).
According to the graph we can see that there are going to be 5 returns
from this trading. Repeat the same process above.
#Identify the first trade
first_buy_index <- which(df$Trade=="buy")[1]
first_sell_index <- which(df$Trade=="sell" & index(df) > index(df)[first_buy_index])[1]
#Identify the second trade
second_buy_index <- which(df$Trade=="buy" & index(df) > index(df)[first_sell_index])[1]
second_sell_index <- which(df$Trade=="sell" & index(df) > index(df)[second_buy_index])[1]
#Identify the third trade
third_buy_index <- which(df$Trade =='buy' & index(df) > index(df)[second_sell_index])[1]
third_sell_index <- which(df$Trade=='sell'& index(df)> index(df)[third_buy_index])[1]
#Identify the fourth trade
fourth_buy_index <- which(df$Trade =='buy' & index(df) > index(df)[third_sell_index])[1]
fourth_sell_index <- which(df$Trade=='sell'& index(df)> index(df)[fourth_buy_index])[1]
#Identify the fifth trade
fifth_buy_index <- which(df$Trade =='buy' & index(df) > index(df)[fourth_sell_index])[1]
fifth_sell_index <- which(df$Trade=='sell'& index(df)> index(df)[fifth_buy_index])[1]
# Calculate average price and return for first buy-sell pair
if(!is.na(first_buy_index) & !is.na(first_sell_index)){
buy_prices_1 <- df[which(df$Trade == "buy" & index(df) <= index(df)[first_sell_index]), "Close"]
buy_avg_price_1 <- mean(buy_prices_1, na.rm = TRUE)
sell_price_1 <- df[first_sell_index, "Close"]
return_1 <- (sell_price_1 - buy_avg_price_1) / buy_avg_price_1
} else {
return_1 <- NA
}
# Calculate average price and return for second buy-sell pair
if(!is.na(second_buy_index) & !is.na(second_sell_index)){
buy_prices_2 <- df[which(df$Trade == "buy" & index(df) > index(df)[first_sell_index] &
index(df) < index(df)[second_sell_index]), "Close"]
buy_avg_price_2 <- mean(buy_prices_2, na.rm = TRUE)
sell_price_2 <- df[second_sell_index, "Close"]
return_2 <- (sell_price_2 - buy_avg_price_2) / buy_avg_price_2
} else {
return_2<- NA
}
# Calculate average price and return for third buy-sell pair
if(!is.na(third_buy_index) & !is.na(third_sell_index)){
buy_prices_3 <- df[which(df$Trade == "buy" & index(df) > index(df)[second_sell_index] &
index(df) < index(df)[third_sell_index]), "Close"]
buy_avg_price_3 <- mean(buy_prices_3, na.rm = TRUE)
sell_price_3 <- df[third_sell_index, "Close"]
return_3 <- (sell_price_3 - buy_avg_price_3) / buy_avg_price_3
} else {
return_3<- NA
}
# Calculate average price and return for fourth buy-sell pair
if(!is.na(fourth_buy_index) & !is.na(fourth_sell_index)){
buy_prices_4 <- df[which(df$Trade == "buy" & index(df) > index(df)[third_sell_index] &
index(df) < index(df)[fourth_sell_index]), "Close"]
buy_avg_price_4 <- mean(buy_prices_4, na.rm = TRUE)
sell_price_4 <- df[fourth_sell_index, "Close"]
return_4 <- (sell_price_4 - buy_avg_price_4) / buy_avg_price_4
} else {
return_4<- NA
}
# Calculate average price and return for fourth buy-sell pair
if(!is.na(fifth_buy_index) & !is.na(fifth_sell_index)){
buy_prices_5 <- df[which(df$Trade == "buy" & index(df) > index(df)[fourth_sell_index] &
index(df) < index(df)[fifth_sell_index]), "Close"]
buy_avg_price_5 <- mean(buy_prices_5, na.rm = TRUE)
sell_price_5 <- df[fifth_sell_index, "Close"]
return_5 <- (sell_price_5 - buy_avg_price_5) / buy_avg_price_5
} else {
return_5<- NA
}
#Calculate total return
total_return <- (1 + return_1) * (1 + return_2) * (1 + return_3) * (1 + return_4) *(1 + return_5) - 1
buy_hold_return <- (df[504,'Close']-df[1,'Close'])/df[1,'Close']
#Make a dataframe to plot the return graph
returns_df <- data.frame(
Trade = c("1st Trade", "2nd Trade", "3rd Trade", "4th Trade", "5th Trade","Trading Profit",'Buy&Hold'),
Return = c(return_1, return_2, return_3, return_4, return_5,total_return,buy_hold_return)
)
# Define the order of the trades
trade_order <- c("1st Trade", "2nd Trade", "3rd Trade", "4th Trade", "5th Trade", "Trading Profit", "Buy&Hold")
returns_df$Trade <- factor(returns_df$Trade, levels = trade_order)
# plot the returns using a bar chart
ggplot(returns_df, aes(x=Trade, y=Return,color=Trade,fill=Trade)) +
geom_bar(stat="identity",color='black' ) +
labs(title="Returns from Bollinger Band Trading Strategy for MSFT (COVID)", y="Return", x="Trade") +
scale_y_continuous(labels = scales::percent)+
geom_text(aes(label=scales::percent(Return)), vjust=-0.2, size=4)+
geom_text(data = filter(returns_df, Return < 0), aes(label=scales::percent(Return), y=Return - 0.01), size=4)+
theme(legend.position = "top")
Although we discovered that the strategy worked during the COVID, we discovered that the buy&hold is a better strategy than implementing bollinger band trade strategy in a bull market. Finally, we want to verify whether this strategy could work in a bear market, especially during financial crisis.
getSymbols("MSFT", src = "yahoo", from = "2008-01-01", to = "2009-04-01")
## [1] "MSFT"
MSFT$BBands <- BBands(Cl(MSFT), n = 20, sd = 2)
hchart(MSFT) %>% #Chart NVDA stock price
hc_add_series(MSFT$mavg, color = 'red',name='SMA(20)') %>% #Chart SMA(20)
hc_add_series(MSFT$dn, color = 'cornflowerblue')%>% #Chart downside BB
hc_add_series(MSFT$up, color = 'cornflowerblue') # Chart uppersider BB
#Add trade signal
MSFT$signal <- ifelse(Cl(MSFT) < MSFT$dn, 1, #If the close prices is below the band, it's a buy signal
ifelse(Cl(MSFT) > MSFT$up, -1, 0)) #If the close prices is above the band, it's a sell signal
#Create trade signal using bollingerband strategy
trade <- ifelse(MSFT$signal == 1,"buy",
ifelse(MSFT$signal == -1,"sell",""))
#Create new dataframe for trading strategy
df <- data.frame(Date=index(MSFT), Close=Cl(MSFT), BBands_dn = MSFT$dn, BBands_up = MSFT$up,Trade=trade)
#Change the column names
colnames(df) <- c('Date','Close','BBands_dn','BBands_up','Trade')
datatable(df)
#Visualize BB and trading point
ggplot(df, aes(x=Date, y=Close)) +
geom_line(size=0.8,color="cornflowerblue") +
geom_ribbon(aes(ymin=BBands_dn, ymax=BBands_up), alpha=0.2, fill="black") +
geom_point(data=df[df$Trade=="buy",], aes(x=Date, y=Close, color="buy"), size=2) +
geom_point(data=df[df$Trade=="sell",], aes(x=Date, y=Close, color="sell"), size=2) +
scale_color_manual(name="Trade", values=c("buy"="grey30", "sell"="orangered")) +
labs(title="Bollinger Band Buy&Sell Signal for MSFT (Financial Crisis)", y="Price", x="Date")+
theme(legend.position = "top") +
scale_x_date(date_breaks = '2 months',
date_labels = '%Y-%m')
## Warning: Removed 19 rows containing missing values (`geom_point()`).
## Removed 19 rows containing missing values (`geom_point()`).
We defined that there are going to be 3 returns from this trade. Repeat the same process
#Identify the first trade
first_buy_index <- which(df$Trade=="buy")[1]
first_sell_index <- which(df$Trade=="sell" & index(df) > index(df)[first_buy_index])[1]
#Identify the second trade
second_buy_index <- which(df$Trade=="buy" & index(df) > index(df)[first_sell_index])[1]
second_sell_index <- which(df$Trade=="sell" & index(df) > index(df)[second_buy_index])[1]
#Identify the third trade
third_buy_index <- which(df$Trade =='buy' & index(df) > index(df)[second_sell_index])[1]
third_sell_index <- which(df$Trade=='sell'& index(df)> index(df)[third_buy_index])[1]
# Calculate average price and return for first buy-sell pair
if(!is.na(first_buy_index) & !is.na(first_sell_index)){
buy_prices_1 <- df[which(df$Trade == "buy" & index(df) <= index(df)[first_sell_index]), "Close"]
buy_avg_price_1 <- mean(buy_prices_1, na.rm = TRUE)
sell_price_1 <- df[first_sell_index, "Close"]
return_1 <- (sell_price_1 - buy_avg_price_1) / buy_avg_price_1
} else {
return_1 <- NA
}
# Calculate average price and return for second buy-sell pair
if(!is.na(second_buy_index) & !is.na(second_sell_index)){
buy_prices_2 <- df[which(df$Trade == "buy" & index(df) > index(df)[first_sell_index] &
index(df) < index(df)[second_sell_index]), "Close"]
buy_avg_price_2 <- mean(buy_prices_2, na.rm = TRUE)
sell_price_2 <- df[second_sell_index, "Close"]
return_2 <- (sell_price_2 - buy_avg_price_2) / buy_avg_price_2
} else {
return_2<- NA
}
# Calculate average price and return for third buy-sell pair
if(!is.na(third_buy_index) & !is.na(third_sell_index)){
buy_prices_3 <- df[which(df$Trade == "buy" & index(df) > index(df)[second_sell_index] &
index(df) < index(df)[third_sell_index]), "Close"]
buy_avg_price_3 <- mean(buy_prices_3, na.rm = TRUE)
sell_price_3 <- df[third_sell_index, "Close"]
return_3 <- (sell_price_3 - buy_avg_price_3) / buy_avg_price_3
} else {
return_3<- NA
}
#Calculate total return
total_return <- (1 + return_1) * (1 + return_2) * (1 + return_3) - 1
buy_hold_return <- (df[314,'Close']-df[1,'Close'])/df[1,'Close']
#Make a dataframe to plot the return graph
returns_df <- data.frame(
Trade = c("1st Trade", "2nd Trade", "3rd Trade","Trading Profit",'Buy&Hold'),
Return = c(return_1, return_2, return_3,total_return,buy_hold_return)
)
# Define the order of the trades
trade_order <- c("1st Trade", "2nd Trade", "3rd Trade", "Trading Profit", "Buy&Hold")
returns_df$Trade <- factor(returns_df$Trade, levels = trade_order)
ggplot(returns_df, aes(x=Trade, y=Return,color=Trade,fill=Trade)) +
geom_bar(stat="identity",color='black' ) +
labs(title="Returns from Bollinger Band Trading Strategy for MSFT (Financial Crisis)", y="Return", x="Trade") +
scale_y_continuous(labels = scales::percent)+
geom_text(aes(label=scales::percent(Return)), vjust=-0.2, size=4)+
geom_text(data = filter(returns_df, Return < 0), aes(label=scales::percent(Return), y=Return - 0.01), size=4)+
theme(legend.position = "top")
We can see that we were able to make profit using this strategy even during the financial crisis. Although the Trading profit was negative, it is meaningful that it created better outcome than just holding the stock