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).

Libraries

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

Obtaining the Historical Data

Again, we will be obtaining the prices for AMC from 2015.

AMC <- tq_get(
  "AMC",
  get = "stock.prices",
  from = "2000-01-01"
)

Simple Moving Average Indicators

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")

Creating the Buy and Sell Signals

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

Backtesting the Algorithm

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"

Final Result

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.