Generating O’Shaughnessy’s Trending Value w/R

This is my code for creating O’Shaughnessy’s Trending Value screen. Not my best code since it was origionally for my personal and private consumption. It uses AAII Stock Investor Pro's exported data. I figured that it might not be bad to share.

Note

I am just an amateur investor sharing my experiences. You should do your own due diligence.

TrendValue.R (Source)

# Trend Value
#https://www.valuesignals.com/Screens/Details/OShaughnessy_Trending_Value

#fix dplr undef13
if(!require("pacman")){
  install.packages("pacman")
}

rm(list = ls(all = TRUE))

# library(dplyr)
# library(quantmod)
# library(xlsx)

pacman::p_load(bindrcpp,dplyr, quantmod, xlsx, rio)

#Momentum will be Regression Relative Strength instead. 126 == 6 month


# 21,42,63,126,189
trenddays <- 126
SIGMA <- 0

ifelse(dir.exists("C:/Users/msghe/OneDrive/Stocks/R"),
       setwd("C:/Users/msghe/OneDrive/Stocks/R"), setwd("C:/Users/michael/SkyDrive/Stocks/R"))


stockdata <-
  read.csv("data/WEEKLY.TXT",
           header = FALSE,
           stringsAsFactors = FALSE,
           na.strings = '-99999999.990')
stocknames <-
  read.csv(
    "data/WEEKLY_KEY.TXT",
    header = FALSE,
    stringsAsFactors = FALSE,
    na.strings = '-99999999.990'
  )
stocknames[, 1]
names(stockdata) <- stocknames[, 1]

#clean ticker name for yahoo finance
stockdata$TICKER <- gsub('.', '-', stockdata$TICKER, fixed = TRUE)
# Remove bad tickers


#NA bad data
# stockdata[stockdata == -99999999.990] <- NA

stockdata <- stockdata[complete.cases(stockdata$TICKER),]

#Create all stock universe


# All stocks is 150 million in  1995 $$. It is adjusted to todays dollors. All
# stocks exclude over the counter.

# min.mark.cap <- quantile(stockdata$MKTCAP, na.rm = TRUE)[[2]]



#find inflation Market Cap 150 @ 1995
getSymbols("CPIAUCSL", src = "FRED")
deflator  <-
  last(Cl(to.yearly(CPIAUCSL)))[[1]] / Cl(to.yearly(CPIAUCSL))['1995'][[1]]

tvminmarketcap <- 150 * deflator

allstock <- stockdata[stockdata$MKTCAP >  tvminmarketcap,]

# stockdata[stockdata$MKTCAP > quantile(stockdata$MKTCAP, na.rm = TRUE)[[4]],]

#Minimum market cap for later sanity


#I will not invest in over the counter
condition <- c("N - New York", "A - American", "M - Nasdaq")
allstock <- filter(allstock, EXCHG_DESC %in% condition)
# min.mark.cap <- median(allstock$MKTCAP, na.rm = TRUE)
# min.mark.cap <- quantile(allstock$MKTCAP, na.rm = TRUE)[[2]]
#add market cap requirement
# allstock <- filter(allstock, MKTCAP > min.mark.cap)
#Minimum Market Cap
min(allstock$MKTCAP)

#Start Ranking the stocks



#Calculate VC2
#subtract ntile from 101 to reverse (correct) Order. So Small is big
allstock$PBVPS.Rank <- 101 - ntile(allstock$PBVPS, 100)
allstock$PE.Rank <- 101 - ntile(allstock$PE, 100)
allstock$PSPS.Rank <- 101 - ntile(allstock$PSPS, 100)
allstock$EVEDA_12M.Rank <- 101 - ntile(allstock$EVEDA_12M, 100)
allstock$PCFPS.Rank <- 101 - ntile(allstock$PCFPS, 100)
allstock$SHY.Rank <- ntile(allstock$SHY, 100)

#Stocks with no rank get 50
allstock$PBVPS.Rank[is.na(allstock$PBVPS.Rank)] <- 50
allstock$PE.Rank[is.na(allstock$PE.Rank)] <- 50
allstock$PSPS.Rank[is.na(allstock$PSPS.Rank)] <- 50
allstock$EVEDA_12M.Rank[is.na(allstock$EVEDA_12M.Rank)] <- 50
allstock$PCFPS.Rank[is.na(allstock$PCFPS.Rank)] <- 50
allstock$SHY.Rank[is.na(allstock$SHY.Rank)] <- 50

#Sum the Ranks
allstock$SumRank <-
  allstock$PBVPS.Rank + allstock$PE.Rank + allstock$PSPS.Rank + allstock$EVEDA_12M.Rank + allstock$PCFPS.Rank + allstock$SHY.Rank

# Calculate VC2
allstock$VC2 <- ntile(allstock$SumRank, 100)

#Get top 10% of allstock

tvstocks <- filter(allstock, VC2 > 70)

#I like Fscore >= 5, Z score >=3
# However, just use Fscore >= 7

#Clean data
tvstocks <- tvstocks[complete.cases(tvstocks$FSCORE_12M),]
# tvstocks <- tvstocks[complete.cases(tvstocks$ZSCORE_Q1),]
#Zscore Prime
# tvstocks <- tvstocks[complete.cases(tvstocks$UDEF13),]

#weed out must sells
# tvstocks <- filter(tvstocks, FSCORE_12M >= 4, ZSCORE_Q1 >= 1.8)
tvstocks <- filter(tvstocks, FSCORE_12M >= 7)
#My Market Cap Needs
# tvstocks <- filter(tvstocks, MKTCAP > min.mark.cap)
# tvstocks <- filter(, YIELD > 0)



#Final Cleaning
# tvstocks <- tvstocks[complete.cases(tvstocks$RPS_6M),]
# tvstocks <- tvstocks[complete.cases(tvstocks$RS_26W),]
# tvstocks <- filter(tvstocks,RS_26W > 0)
# tvstocks <- filter(tvstocks,RS_26W >= median(allstock$PRCHG_26W, na.rm = TRUE))
tvstocks <- filter(tvstocks,PRCHG_13W >= median(allstock$PRCHG_13W, na.rm = TRUE))

# head((screen[order(as.numeric(screen$SCORE), decreasing =  TRUE),]),25) #[1]
# head((screen[order(as.numeric(screen$SCORE), decreasing =  TRUE),]),25) #[1]

# names(stockdata) <- stocknames$V1
# screen.rpt <- left_join(screen, stockdata, by = "TICKER")
# screen.rpt <- arrange(screen.rpt, desc(as.numeric(SCORE)))
# screen.rpt$SCORE <- round(as.numeric(screen.rpt$SCORE), 4)
# # screen.rpt <- arrange(screen.rpt, desc(as.numeric(PRCHG_26W)))
# nrow(screen.rpt)


# stockenv <- new.env()
# getSymbols(tvstocks$TICKER, env = stockenv, adjust=TRUE)
# getSymbols(tvstocks$TICKER, env = stockenv)

# rm(screen)
# Keep it simple, stop using RRS
# tvstocks[,"krs26"] <- NA
# for (i in ls(stockenv)) {
#     print(i)
#     # tvstocks[tvstocks$TICKER == i,"krs26"] <- last(Ad(stockenv[[i]]),1)[[1]]/last(SMA(Ad(stockenv[[i]]),126),1)[[1]]
#     tvstocks[tvstocks$TICKER == i,"krs26"] <- last(Ad(stockenv[[i]]),1)[[1]]/ mean(last(Ad(stockenv[[i]]),126))
# }

screen.rpt <- arrange(tvstocks,desc(as.numeric(PRCHG_26W)))
# screen.rpt <- arrange(tvstocks,desc(as.numeric(RPS_6M)))
# screen.rpt <- arrange(tvstocks,desc(as.numeric(krs26)))

screen.rpt <-
  select(screen.rpt, TICKER, COMPANY, SMG_DESC, PRCHG_26W, MKTCAP,FSCORE_12M,VC2)

head(screen.rpt,25)

write.xlsx(
  head(screen.rpt, 25),
  "MoneyPicks.xlsx",
  sheetName = "Money",
  append = FALSE,
  row.names = FALSE
)

rio::export(head(screen.rpt, 25),"MoneyPicks.csv")

Comments

Comments powered by Disqus