Algorithmic trading applied to portfolio management

Momentum in stock returns is generally described as the continuation of those stocks that have performed well recently to do well over the subsequent 1–12 months. On the contrary, long-term reversals refer to the pattern of winning (loser) stocks tending to become losers (winners) in the long run, usually 3–5 years.

In general, momentum and reversals in stock prices is evaluated by sorting stocks into portfolios based on their past return performance and then evaluating the future performance of these portfolios. Although the process of forming winner and loser portfolios to assess momentum and reversals in stock prices is fairly standardized in the literature, the decisions of how to construct the winner and loser portfolios (equally weighted vs. value-weighted) and how to measure risk-adjusted return performance (CAPM, Fama-French 3-factor risk models) remain topics of debate.

A momentum (reversal) portfolio is then created by buying (shorting) the winner portfolio and shorting (buying) the loser portfolio and then evaluating the performance of this zero-cost, long-short portfolio over the next K months.

So, for example, a 6/6 momentum strategy is a momentum strategy that sorts stocks based on their past 6-month returns and creates a long-short portfolio by buying the winner portfolio and shorting the loser portfolio (as defined earlier) and holding the position for 6 months.

Cargamos librerías

For this example, we could use the Excel file or

df<-read.xlsx("Data.xlsx",detectDates = T)

The Api to download the tickers from yahoo finance

Podemos descargar la muestra desde la página de YaHoo!

# yf <- "https://finance.yahoo.com/quote/%5Emxx/components/"
# 
# html <- read_html(yf)
# 
# # To get the node a, wich contains characters
# node <- html_nodes(html,"a")
# 
# # To read the text in the node
# node<-html_text(node, trim=TRUE)
# 
# # To get the elements that have USD (the tickers). For the IPC tickers, replace "USD" with ".MX". For other tickers, print the node object and look for patterns or select by rows.
# tickers<-grep(pattern = ".MX", x = node, value = TRUE)
# 
# to eliminate tickers whiout information
# tickers<-tickers
# tickers<-tickers[-27]
# tickers<-tickers[-27]
# tickers<-tickers[-27]
# tickers<-tickers[-26]
# tickers<-tickers[-26]
# tickers

This code gets the current IPC tickers from yahoo finance. The stock composition could change over time, them if you want to replicate the example of this document, in the file data.xlsx you will find the sample close prices. T his code gets the current IPC tickers from yahoo finance. The stock composition could change over time, them if you want to replicate the example of this document, you will find the sample close prices.

tickers <- colnames(df)
tickers <- tickers[-1] 

This code gets the 1st and the 3rd tranches in one object. Also, it gets the daily returns for each stock, which we use to estimate the covariance matrix.

# después de mean, incluímos los argumentos para que excluya los na`s
# Para la media de una sola columna sería mean(ret[,1], na.rm=T)
ret_mean <- apply(ret,2,mean, na.rm=T) # El 2 señala que se tiene que aplicar a las columnas 
ret_mean <- data.frame(ret_mean)

Ordenamos los rendimientos

m <- dim(ret_mean_sort)[1] # Obtenemos el número de renglones 
n <- round(m / 3) # Tamaño de la muestra de cada tranche 

ret_mean_sort_top <- data.frame(ret_mean_sort[1:n,])
ret_mean_sort_low <- data.frame(ret_mean_sort[(m-n):m,]) 

# Esta podría ser otra opción para extraer un segmento de nuestro dataframe 

#ret_mean_sort_top <- ret_mean_sort %>% top_frac(0.33)
#ret_mean_sort_low <- ret_mean_sort %>% top_frac(-0.68) 

# Asignamos los nombres a los renglones con los tickers de las acciones que corresponden
rownames(ret_mean_sort_top) <- rownames(ret_mean_sort)[1:n]
rownames(ret_mean_sort_low) <- rownames(ret_mean_sort)[(m-n):m] 

# Renombramos las columnas con el mismo nombres para poder realizar los siguientes procedimientos donde debe de coincidir el nombre de la columna 

colnames(ret_mean_sort_top) <- "ret"
colnames(ret_mean_sort_low) <- "ret"
# Debemos tener el mismo nombre en las columnas para que coindida en rbind 
ret_mean_top_low <- data.frame(rbind(ret_mean_sort_top, ret_mean_sort_low))

#ret1<- ret[,rownames(reta1S)]
#ret2<- ret[,rownames(reta1L)] 

ret_top_low <- ret[,rownames(ret_mean_top_low)]

cov_ret_top_low <- cov(ret_top_low, use="complete.obs")

aleatorio = runif(n,0,1)
we_top <- aleatorio / sum(aleatorio) * 1.1 # vector de n pesos aleatorios

aleatorio = runif((m-n):m,0,1)
we_low <- (aleatorio / sum(aleatorio)) * - 0.1 # vector de n+1 (por m ser impar) pesos aleatorios 

we_top_low <- data.frame(c(we_top, we_low))

rownames(we_top_low) <- colnames(ret_top_low)
colnames(we_top_low) <- "we_top_low"

Desviación estándar del portafolio

# Revisamos las dimensiones de las variables para checar que coincida en la multiplicación 
dim(cov_ret_top_low)
## [1] 17 17
dim(we_top_low)
## [1] 17  1
# Varainza del portafolio 
var_port_top_low <- t(as.matrix(we_top_low)) %*% as.matrix(cov_ret_top_low) %*% as.matrix(we_top_low) 
var_port_top_low 
##              we_top_low
## we_top_low 9.269472e-05
# Desviación estándar 
desv_port_top_low <- var_port_top_low ^ 0.5
desv_port_top_low
##            we_top_low
## we_top_low 0.00962781
# Desviación estándar anualizada
desv_port_top_low_anual <- desv_port_top_low * (252) ^ (0.5)
desv_port_top_low_anual
##            we_top_low
## we_top_low  0.1528367

Media del portafolio

mean_port_top_low <- t(as.matrix(we_top_low)) %*% as.matrix(ret_mean_top_low)
mean_port_top_low
##                     ret
## we_top_low 0.0006583897
# Anualizamos 
mean_port_top_low_anual <- mean_port_top_low * 252
mean_port_top_low_anual 
##                  ret
## we_top_low 0.1659142

The next code creates 10,000 simulations of weights. The code generates aleatory numbers, limiting to invest 100% of the wealth in the stocks in the 1rst tranche (high return). Also, it limits investing 10% of the wealth in the 3rd tranche (low return), but in a short position (you could change that % in line 15, replacing the 0.1 by the % you want). In Table 2, the code prints the returns, standard deviation, Sharpe of the portfolio, and the set of simulated weights. Finally, it orders the results based on the highest Sharpe index. The risk-free rate for this example is 5% .

Note: Regarding the random seed(), students often get nervous when they do not get the same result as the professor because np.random.rand generate aleatory numbers. Then it is useful to take out the # before random seed and get the same result. After everyone gets the same results, insert the # again.

Realizamos lo anterior con múltiples simulaciones

sim <- 10000
nt <- length(rownames(ret_mean_top_low))
port <- matrix(0, sim, 3 + nt) 
rf <- .05

for (i in 1:sim) {
  
  weL <-runif(n, 0, 1)
#  s <- sum(weL)
  weL <- (weL/sum(weL)) *1.1
  weS <- runif((m-n):m, 0, 1) 
#  sS <- sum(weS)
  weS <- (weS/sum(weS)) * -.1
  weLS <- data.frame(c(weL,weS))
  rownames(weLS) <- rownames(ret_mean_top_low) #rownames(reta1LS)
  # colnames(weLS) <- "weLS"
  weLSm <- as.matrix(weLS)
  covt <- as.matrix(cov_ret_top_low) %*% as.matrix(weLSm) #covm%*%weLSm
  desv_anual <- ((t(as.matrix(weLSm))%*%covt)*252) ^.5 # desv anualizada
  reta1LSm <- as.matrix(ret_mean_top_low) # as.matrix(reta1LS)
  reta1LSf <-  (t(weLSm)%*%as.matrix(reta1LSm))*252 # (t(weLSm)%*%reta1LSm)*252 #rend esperado del 
  port[i,1] <- reta1LSf # Rendimiento 
  port[i,2] <- desv_anual  # Desv est 
  port[i,3] <- (reta1LSf-rf) / desv_anual  # sharpe 
  port[i,4:(3+nt)] <- weLSm[,1]
}

colnames(port)<-c("rend","desv_est","sharpe",rownames(ret_mean_top_low)) 
#port
plot(port[,2],port[,1], xlab = "Riesgo", ylab="Rendimiento", cex = 0.2, pch = 20, col = "blue") 

Appendix

Functions

An R function is created by using the keyword function. The basic syntax of an R function definition is as follows −

function_name <- function(arg_1, arg_2, ...) { Function body }

Create a function to print squares of numbers in sequence.

myfun <- function(a) {
   for(i in 1:a) {
      b <- i^2
      print(b)
   }
}   
myfun(5)
## [1] 1
## [1] 4
## [1] 9
## [1] 16
## [1] 25
# Create a function with arguments.
new.function <- function(a,b,c) {
   result <- a * b + c
   print(result)
}
new.function(5,3,11)
## [1] 26