An algorithmic trading strategy is created in this notebook for Tesla using the simple moving average strategy.
A “buy” or “sell” signal/decision will be generated and executed/traded by the algorithm.
Simple moving averages are calculated by taking the arithmetic mean of a stock price for the desired past n days. Here:
Tesla’s stock price from Yahoo Finance from 2017 Jan to 2022 May are used.
The outcome of this algo strategy yields the following results:
library(quantmod)
library(tidyverse)
library(TTR)
library(kableExtra)
library(PerformanceAnalytics)
library(reshape)
#Obtaining stock price data ONLY column 6. xts = extensible time series
TSLA <- getSymbols("TSLA", source="yahoo", auto.assign=FALSE,
return.class="xts")[,6]
#Calculate actual log returns
Actual_Ret <- diff(log(TSLA))
colnames(Actual_Ret) <- "TSLA"
#Choose start date & view price trend
TSLA <- round(TSLA["2017/"],2)
Actual_Ret <- round(Actual_Ret["2017/"],4)
plot(TSLA, main="TSLA Actual Stock Price (2017Jan - 2022May)")
The actual daily returns (difference in stock price from previous close) plotted below shows an average of 0%. This is a common expectation for the daily returns of a stock which are different from the annualized returns due to the compounding effect of the daily returns.
#Plot the returns
plot(Actual_Ret)
#Generate Simple Moving Averages (long-term & short-term)
SMA100 <- round(SMA(TSLA, 100),2) #Long-term moving average
SMA50 <- round(SMA(TSLA, 50),2) #Short-term moving average
#Put desired columns into a dataframe. "na.omit" to drop rows with n/a. "cbind" to combine desired columns.
Data <- na.omit(as.data.frame(cbind(TSLA, Actual_Ret, SMA50, SMA100)))
colnames(Data) <- c("TSLA_Price","Actual_Ret","SMA50","SMA100" )
#Condition for trend-following strategy (SMA)."UD" = price moving up or down
Data$UD <- ifelse(Data$SMA50 >= Data$SMA100, 1, 0)
#Generate "buy" (1) signal if short-term moving average > long-term moving average, else signal "Sell" (-1)
Data$Trade <- ifelse(Data$UD == 1, "BUY", "SELL")
#If "Position" is "Buy" show 1, else -1 (Sell)
Data$Position <- ifelse(Data$Trade == "BUY", 1, -1)
#Calculate returns based on this algorithm
Data$AlgoReturns_Daily <- round((Data$Actual_Ret * Data$Position),4)
#Annualized the Algo Returns
AnnualizedReturn <- ((mean(Data$AlgoReturns_Daily)+1)^252 - 1)
#Calculate the standard deviation for Sharpe ratio. Assume a risk-free rate (rf)
Standev <-StdDev.annualized(Data$AlgoReturns_Daily, scale = 252)
rf <- 0.02
SharpeRatio <- (AnnualizedReturn - rf)/Standev
#Actual Annualized Returns
AnnualizedReturn_Actual <- ((mean(Data$Actual_Ret)+1)^252 - 1)
#For the Actual Annualized Returns, calculate the standard deviation for Sharpe ratio. Assume a risk-free rate (rf)
Standev_Actual <- StdDev.annualized(Data$Actual_Ret, scale = 252)
rf <- 0.02
SharpeRatio_Actual <- (AnnualizedReturn_Actual - rf)/Standev_Actual
#Visualize Price vs Moving Averages
plot(Data$TSLA_Price, type = "l", col = "black", ylab = "", xlab = "", xaxt='n', cex.axis=1)
title(ylab = "Price", line=2.5, cex.lab=1.2)
title(xlab = 'Time', line=1.0, cex.lab=1.2)
title(xlab = '(2017Jan to 2022May)', line=2.3, cex.lab=0.8)
par(new = TRUE)
plot(Data$SMA50, type = "l" , col = "cyan", ylab = "", xlab = "", yaxt="n",xaxt='n')
par(new = TRUE)
plot(Data$SMA100, type = "l" , col = "blue", ylab = "", xlab = "",yaxt="n",xaxt='n')
legend("topleft",legend=c("Actual Price","SMA 50","SMA100"),col=c("black", "cyan","blue"),lty = 1, cex=0.8)
The table below displays a subset of the complete table of the algo’s output.
Essentially, based on the conditions set up above, either a “buy” or “sell” signal to trade/execute is generated (under “Trade” column):
If the “Actual_Ret” column is negative, and Algo signals a “Sell” (in the “Trade” column), then the “AlgoReturns_Daily” column registers a positive gain since the Algo correctly generated a decision on a falling stock price.
Conversely, If “Actual_Ret” is positive, but a “Sell” signal is generated, that means the Algo incorrectly predicted the direction of stock movement and the corresponding AlgoReturns_Daily for that trading day would then show a negative return.
#Sort results by latest date first
Data_latest_1st <-apply(Data, 2, rev) #1st calculate reverse (rows =2)
Data_latest_1st <-as.data.frame(Data_latest_1st) #Then, convert back into dataframe
# Show results for every 30 trading days
Data_sample = Data_latest_1st %>% slice(which(row_number() %% 30 == 1))
Data_sample %>%
kbl() %>%
kable_paper("hover", full_width = F)
| TSLA_Price | Actual_Ret | SMA50 | SMA100 | UD | Trade | Position | AlgoReturns_Daily | |
|---|---|---|---|---|---|---|---|---|
| 2022-05-06 | 865.65 | -0.0088 | 943.25 | 954.88 | 0 | SELL | -1 | 0.0088 |
| 2022-03-24 | 1013.92 | 0.0147 | 898.15 | 985.09 | 0 | SELL | -1 | -0.0147 |
| 2022-02-09 | 932.00 | 0.0108 | 1003.53 | 978.86 | 1 | BUY | 1 | 0.0108 |
| 2021-12-28 | 1088.47 | -0.0050 | 1048.24 | 896.85 | 1 | BUY | 1 | -0.0050 |
| 2021-11-12 | 1033.42 | -0.0287 | 884.19 | 784.34 | 1 | BUY | 1 | -0.0287 |
| 2021-10-01 | 775.22 | -0.0003 | 721.62 | 674.81 | 1 | BUY | 1 | -0.0003 |
| 2021-08-19 | 673.47 | -0.0228 | 665.10 | 660.04 | 1 | BUY | 1 | -0.0228 |
| 2021-07-08 | 652.81 | 0.0126 | 630.44 | 661.26 | 0 | SELL | -1 | -0.0126 |
| 2021-05-25 | 604.69 | -0.0029 | 663.70 | 720.12 | 0 | SELL | -1 | 0.0029 |
| 2021-04-13 | 762.32 | 0.0825 | 714.76 | 702.61 | 1 | BUY | 1 | 0.0825 |
| 2021-03-01 | 718.43 | 0.0616 | 777.16 | 630.32 | 1 | BUY | 1 | 0.0616 |
| 2021-01-14 | 845.00 | -0.0111 | 604.85 | 513.52 | 1 | BUY | 1 | -0.0111 |
| 2020-12-01 | 584.76 | 0.0298 | 443.29 | 400.86 | 1 | BUY | 1 | 0.0298 |
| 2020-10-19 | 430.83 | -0.0203 | 408.45 | 327.34 | 1 | BUY | 1 | -0.0203 |
| 2020-09-04 | 418.32 | 0.0274 | 326.87 | 248.20 | 1 | BUY | 1 | 0.0274 |
| 2020-07-24 | 283.40 | -0.0656 | 219.41 | 173.25 | 1 | BUY | 1 | -0.0656 |
| 2020-06-11 | 194.57 | -0.0523 | 152.90 | 141.86 | 1 | BUY | 1 | -0.0523 |
| 2020-04-29 | 160.10 | 0.0400 | 127.72 | 117.27 | 1 | BUY | 1 | 0.0400 |
| 2020-03-17 | 86.04 | -0.0340 | 131.67 | 101.29 | 1 | BUY | 1 | -0.0340 |
| 2020-02-03 | 156.00 | 0.1814 | 88.72 | 72.16 | 1 | BUY | 1 | 0.1814 |
| 2019-12-18 | 78.63 | 0.0367 | 64.18 | 55.31 | 1 | BUY | 1 | 0.0367 |
| 2019-11-05 | 63.44 | -0.0008 | 50.79 | 48.66 | 1 | BUY | 1 | -0.0008 |
| 2019-09-24 | 44.64 | -0.0776 | 46.88 | 45.41 | 1 | BUY | 1 | -0.0776 |
| 2019-08-12 | 45.80 | -0.0259 | 45.88 | 47.61 | 0 | SELL | -1 | 0.0259 |
| 2019-06-28 | 44.69 | 0.0028 | 44.52 | 50.88 | 0 | SELL | -1 | -0.0028 |
| 2019-05-16 | 45.67 | -0.0157 | 52.72 | 57.55 | 0 | SELL | -1 | 0.0157 |
| 2019-04-03 | 58.36 | 0.0205 | 58.53 | 63.27 | 0 | SELL | -1 | -0.0205 |
| 2019-02-20 | 60.51 | -0.0101 | 64.70 | 63.85 | 1 | BUY | 1 | -0.0101 |
| 2019-01-07 | 66.99 | 0.0529 | 67.89 | 63.13 | 1 | BUY | 1 | 0.0529 |
| 2018-11-20 | 69.50 | -0.0171 | 60.92 | 62.16 | 0 | SELL | -1 | 0.0171 |
| 2018-10-09 | 52.56 | 0.0477 | 61.57 | 62.45 | 0 | SELL | -1 | -0.0477 |
| 2018-08-27 | 63.85 | -0.0111 | 65.71 | 62.69 | 1 | BUY | 1 | -0.0111 |
| 2018-07-16 | 62.02 | -0.0279 | 62.83 | 62.13 | 1 | BUY | 1 | -0.0279 |
| 2018-06-01 | 58.36 | 0.0246 | 57.89 | 62.55 | 0 | SELL | -1 | -0.0246 |
| 2018-04-19 | 60.02 | 0.0227 | 62.92 | 64.45 | 0 | SELL | -1 | -0.0227 |
| 2018-03-07 | 66.46 | 0.0124 | 66.92 | 65.79 | 1 | BUY | 1 | 0.0124 |
| 2018-01-23 | 70.56 | 0.0035 | 64.66 | 66.91 | 0 | SELL | -1 | -0.0035 |
| 2017-12-07 | 62.25 | -0.0065 | 65.27 | 67.73 | 0 | SELL | -1 | 0.0065 |
| 2017-10-25 | 65.17 | -0.0347 | 70.56 | 70.22 | 1 | BUY | 1 | -0.0347 |
| 2017-09-13 | 73.25 | 0.0095 | 68.52 | 68.10 | 1 | BUY | 1 | 0.0095 |
| 2017-08-01 | 63.91 | -0.0121 | 68.74 | 63.78 | 1 | BUY | 1 | -0.0121 |
| 2017-06-19 | 73.96 | -0.0043 | 65.03 | 58.82 | 1 | BUY | 1 | -0.0043 |
#Print the results
cat(paste("Expected Annualized Return by this Algo: ", round(AnnualizedReturn*100, digits=2),"%\n",
" Standard deviation of Algo Return: ", round(Standev*100, digits=2),"%\n",
" Sharpe ratio of this Algo: ", round(SharpeRatio, digits=2),"(for a risk-free rate of", round((rf)*100,digits=2),"%)\n\n\n",
" Actual Annualized Return: ", round(AnnualizedReturn_Actual*100, digits=2),"%\n"," Standard deviation of Actual Return: ", round(Standev_Actual*100, digits=2),"%\n"," Sharpe ratio of Actual Return: ", round(SharpeRatio_Actual, digits=2),"(for a risk-free rate of", round((rf)*100,digits=2),"%)"))
## Expected Annualized Return by this Algo: 82.1 %
## Standard deviation of Algo Return: 62.38 %
## Sharpe ratio of this Algo: 1.28 (for a risk-free rate of 2 %)
##
##
## Actual Annualized Return: 70.26 %
## Standard deviation of Actual Return: 62.4 %
## Sharpe ratio of Actual Return: 1.09 (for a risk-free rate of 2 %)