This is my first attempt at creating an algorithmic trading strategy in R. The strategy is based on a simple trend analysis. More specifically, it is based on a Simple Moving Average (SMA) crossover. An SMA calculates the average of the asset price for a predefined number of days (formally known as the window). This algorithm will use a 20-day and 50-day SMA to create buy and sell signals. The logic of the strategy is simple:
The data is obtained using the tq_get() function from the tidyquant package. This function pulls historical stock information from Yahoo Finance. We then use the tidyquant package to calculate the simple moving averages and use the functions of the tidyverse package to wrangle, clean, and visualize the data.
AMC will be used as the underlying asset for this project. I will assume the portfolio consists of $1000 and will simulate the backtest over the closing adjusted prices from 2015 to the present (5/31/21).
library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.0 --
## v ggplot2 3.3.3 v purrr 0.3.4
## v tibble 3.1.0 v dplyr 1.0.3
## v tidyr 1.1.2 v stringr 1.4.0
## v readr 1.4.0 v forcats 0.5.1
## Warning: package 'tibble' was built under R version 4.0.4
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
library(tidyquant)
## Warning: package 'tidyquant' was built under R version 4.0.5
## Loading required package: lubridate
## Warning: package 'lubridate' was built under R version 4.0.4
##
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
## Loading required package: PerformanceAnalytics
## Warning: package 'PerformanceAnalytics' was built under R version 4.0.4
## Loading required package: 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
##
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
##
## legend
## Loading required package: quantmod
## Loading required package: TTR
## Warning: package 'TTR' was built under R version 4.0.5
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
## == Need to Learn tidyquant? ====================================================
## Business Science offers a 1-hour course - Learning Lab #9: Performance Analysis & Portfolio Optimization with tidyquant!
## </> Learn more at: https://university.business-science.io/p/learning-labs-pro </>
library(ggthemes)
## Warning: package 'ggthemes' was built under R version 4.0.4
Again, we will be obtaining the prices for AMC from 2015.
AMC <- tq_get(
"AMC",
get = "stock.prices",
from = "2000-01-01"
)
Next, we create the indicators for the SMA-20 and SMA-50.
AMC_SMA <- AMC %>%
mutate(
SMA_20 = SMA(close, n = 20),
SMA_50 = SMA(close, n = 50)
) %>%
na.omit()
To better visualize this data, we convert the tibble to a long format so that we are able to plot the values of the adjusted price, 20-day SMA, and 50-day SMA all on the same graph.
AMC_SMA %>%
select(date, adjusted, SMA_20, SMA_50) %>%
pivot_longer(adjusted:SMA_50, names_to = "Variable", values_to = "Price") %>%
ggplot(aes(date, Price, group = Variable, color = Variable)) +
geom_line(size = 1) +
theme_bw() +
labs(title = "AMC Stock with SMA-20 and SMA-50", x = "Date", y = "Price")
As stated earlier, we are going to use a crossover method. To reiterate the logic:
When implementing the algorithm, we need to be careful about how we code the signals and indicators. For this project, we are only going to buy/sell one position at a time. This means that if we already own some shares of AMC and SMA-50 > SMA-20, we are not going to buy an additional share. The same holds true for the converse.
AMC_Algo <- AMC_SMA %>%
mutate(
Signal = ifelse(
SMA_50 > SMA_20,
"Buy",
"Sell"
),
Previous_Signal = lag(Signal),
Decision = case_when(
Signal == Previous_Signal ~ "Hold",
TRUE ~ Signal
)
)
Using this dataframe, we can extract the periods for which the algorithm bought and sold AMC.
AMC_Decisions <- AMC_Algo %>%
filter(Decision != "Hold") %>%
select(date, high, low, close, adjusted, SMA_20, SMA_50, Decision)
AMC_Decisions %>%
head(10) %>%
knitr::kable()
| date | high | low | close | adjusted | SMA_20 | SMA_50 | Decision |
|---|---|---|---|---|---|---|---|
| 2014-03-03 | 23.60 | 22.77 | 22.95 | 16.50629 | 21.9505 | 21.0054 | Sell |
| 2014-04-25 | 23.24 | 21.95 | 22.09 | 15.88775 | 23.3305 | 23.3664 | Buy |
| 2014-06-12 | 24.23 | 23.68 | 24.05 | 17.45215 | 22.8205 | 22.8072 | Sell |
| 2014-07-28 | 23.15 | 22.58 | 23.08 | 16.74826 | 23.3070 | 23.3832 | Buy |
| 2014-09-02 | 23.99 | 23.49 | 23.94 | 17.37233 | 23.4410 | 23.4210 | Sell |
| 2014-10-10 | 22.37 | 21.62 | 21.76 | 15.92342 | 23.5195 | 23.6278 | Buy |
| 2014-11-10 | 25.79 | 25.00 | 25.60 | 18.73343 | 23.8730 | 23.7796 | Sell |
| 2015-04-27 | 33.25 | 31.59 | 32.04 | 23.76278 | 33.9815 | 34.0482 | Buy |
| 2015-07-07 | 31.00 | 30.05 | 30.85 | 23.04224 | 29.7780 | 29.6832 | Sell |
| 2015-08-19 | 31.20 | 30.19 | 31.00 | 23.15428 | 30.4455 | 30.5588 | Buy |
The backtest will allow us to calculate the gain/losses from this strategy. We start with $1000 USD and will buy at the mid-day price. Obviously, there are different ways to purchase the asset. The methods are arbitrarily chosen for simplicity.
usd <- 1000
shares <- 0
share_price <- 0
for(i in 1:nrow(AMC_Decisions)) {
share_price <- (AMC_Decisions[i,]$high + AMC_Decisions[i,]$low) / 2
if(shares > 0 && AMC_Decisions[i,]$Decision == "Sell"){
print(paste0("SELL: ", shares, " shares @ $", share_price, " USD"))
usd <- share_price * shares
print(paste0("USD IS NOW @ ", round(usd, 2)))
shares <- 0
} else if(usd > 0 && AMC_Decisions[i,]$Decision == "Buy"){
print(paste0("BUY: ", usd/share_price, " shares @ $", share_price, " USD"))
share_price <- AMC_Decisions[i,]$close
shares <- usd/share_price
usd <- 0
}
cat("\n")
}
##
## [1] "BUY: 44.257578131056 shares @ $22.5950005 USD"
##
## [1] "SELL: 45.2693526482571 shares @ $23.955 USD"
## [1] "USD IS NOW @ 1084.43"
##
## [1] "BUY: 47.4273930762738 shares @ $22.865 USD"
##
## [1] "SELL: 46.9855867716204 shares @ $23.74 USD"
## [1] "USD IS NOW @ 1115.44"
##
## [1] "BUY: 50.7132429754502 shares @ $21.995001 USD"
##
## [1] "SELL: 51.2609296855822 shares @ $25.3950005 USD"
## [1] "USD IS NOW @ 1301.77"
##
## [1] "BUY: 40.1533416099884 shares @ $32.42 USD"
##
## [1] "SELL: 40.6295659914563 shares @ $30.5249995 USD"
## [1] "USD IS NOW @ 1240.22"
##
## [1] "BUY: 40.40454279752 shares @ $30.695001 USD"
##
## [1] "SELL: 40.0070155346587 shares @ $26.125 USD"
## [1] "USD IS NOW @ 1045.18"
##
## [1] "BUY: 41.459074181413 shares @ $25.2100005 USD"
##
## [1] "SELL: 41.7238851324089 shares @ $25.455 USD"
## [1] "USD IS NOW @ 1062.08"
##
## [1] "BUY: 39.7485582399398 shares @ $26.7200005 USD"
##
## [1] "SELL: 39.5413796166824 shares @ $30.2800005 USD"
## [1] "USD IS NOW @ 1197.31"
##
## [1] "BUY: 38.4988094956408 shares @ $31.1000005 USD"
##
## [1] "SELL: 38.6229998246398 shares @ $30.4650005 USD"
## [1] "USD IS NOW @ 1176.65"
##
## [1] "BUY: 39.5845156858338 shares @ $29.7249995 USD"
##
## [1] "SELL: 40.0220989445289 shares @ $15.7749995 USD"
## [1] "USD IS NOW @ 631.35"
##
## [1] "BUY: 44.150251107615 shares @ $14.3 USD"
##
## [1] "SELL: 45.4207619308557 shares @ $15.025 USD"
## [1] "USD IS NOW @ 682.45"
##
## [1] "BUY: 51.0240708793351 shares @ $13.375 USD"
##
## [1] "SELL: 51.7005263644778 shares @ $15.365 USD"
## [1] "USD IS NOW @ 794.38"
##
## [1] "BUY: 53.6742288912299 shares @ $14.8 USD"
##
## [1] "SELL: 53.6742288912299 shares @ $17.0650005 USD"
## [1] "USD IS NOW @ 915.95"
##
## [1] "BUY: 49.8748035830849 shares @ $18.3649995 USD"
##
## [1] "SELL: 48.9551465430838 shares @ $14.145 USD"
## [1] "USD IS NOW @ 692.47"
##
## [1] "BUY: 51.6576313205461 shares @ $13.405 USD"
##
## [1] "SELL: 52.1046311400994 shares @ $11.185 USD"
## [1] "USD IS NOW @ 582.79"
##
## [1] "BUY: 54.0872667565672 shares @ $10.775 USD"
##
## [1] "SELL: 54.8766760171386 shares @ $5.65 USD"
## [1] "USD IS NOW @ 310.05"
##
## [1] "BUY: 70.627157060782 shares @ $4.39 USD"
##
## [1] "SELL: 67.8453434347556 shares @ $5.43 USD"
## [1] "USD IS NOW @ 368.4"
##
## [1] "BUY: 88.5577439545007 shares @ $4.16 USD"
##
## [1] "SELL: 90.7389691750549 shares @ $3.86 USD"
## [1] "USD IS NOW @ 350.25"
##
## [1] "BUY: 158.127503844565 shares @ $2.215 USD"
##
## [1] "SELL: 162.153898618385 shares @ $15.6850005 USD"
## [1] "USD IS NOW @ 2543.38"
##
## [1] "BUY: 240.736770554313 shares @ $10.565 USD"
##
## [1] "SELL: 249.35137067709 shares @ $14.12 USD"
## [1] "USD IS NOW @ 3520.84"
The total ROI is 35.2084135%! It is important to note that this is not a full algorithmic trading system (it’s way too simple). If transaction costs/fees were introduced to the model it could have wildly different results. The purpose of this project is to showcase the ability to run such a model.