Search code examples
rfinancestocktrendlinetidyquant

Find stock market support trend lines more efficiently in R Tidyquant


I've developed some R code to find and draw trend lines on stock market data. However, the approach I'm using involves brute-force use of processing power and can take a long time, especially if I want to draw trend lines over more than a year's worth of price data. So, I'd love if someone could help me find a more efficient way to do this.

Basically, my current method involves generating all possible pairs of two daily lows in the data set, generating all possible trend lines that pass through a pair of points, and then testing each line to see if any daily low in the data set falls below the line. We keep all lines for which this is FALSE.

The processing time required increases exponentially as you increase the time frame over which you're trying to generate trend lines. To cut down on processing time, I've been filtering the data set for lows that are below the simple moving average. This gets rid of about half the data and generally preserves the most relevant data points. However, it doesn't fully solve the problem. When analyzing long time frames over more than one ticker symbol, running this code can still take a long time.

Here's what I've got:

# Load libraries for tidy stock data analysis
library(tidyverse)
library(tidyquant)

# Retrieve 1 year's worth of Apple stock price data from Yahoo! Finance
ticker <- "AAPL"
start <- Sys.Date() %m-% years(1)
prices <- tq_get(ticker, from = start) %>%
  mutate(open = round(open,digits=2),
         high = round(high,digits=2),
         low = round(low,digits=2),
         close = round(close,digits=2)) %>%
  select(symbol,date,open,high,low,close)

# Filter prices data for lows that are below the simple moving average
lows <- prices %>%
  filter(low < SMA(close),date<max(date))

# Find all unique possible combinations of two lows
# (and all unique possible combinations of their associated dates)
all_lowcombos <- bind_cols(as.data.frame(t(combn(lows$date,m=2,simplify=TRUE))),as.data.frame(t(combn(lows$low,m=2,simplify=TRUE))))
colnames(all_lowcombos) <- c("X1","X2","Y1","Y2")

# Generate a trendline for every combination of points
n <- seq(1:nrow(all_lowcombos))
low_trendfinder <- function(n,all_lowcombos){
  model <- lm(c(all_lowcombos$Y1[n],all_lowcombos$Y2[n])~c(all_lowcombos$X1[n],all_lowcombos$X2[n]))
  data.frame(intercept = model$coefficients[1],slope = model$coefficients[2])
}
low_trendlines <- map_dfr(n,low_trendfinder,all_lowcombos = all_lowcombos)

  # For each low_trendline, check if any low in the prices dataframe falls below the line
  # Keep only trendlines for which this is FALSE
  # Also make sure the trendline wouldn't be less than half the current price for today's date; I only want lines that might be tradeable in the next week
  low_trendline_test <- function(x,y,prices){
    !any(x*as.numeric(prices$date) + y > prices$low + 0.01) & !(x*as.numeric(Sys.Date())+y < 0.5*prices$close[nrow(prices)])
  }
  none_below <- map2(.x = low_trendlines$slope,.y = low_trendlines$intercept,.f = low_trendline_test,prices = prices)
  none_below <- unlist(none_below)
  low_trendlines <- low_trendlines[none_below,]

# Chart support trendlines on a candlestick chart
prices %>% ggplot(aes(x = date, y = close)) + 
  geom_candlestick(aes(open = open, high = high, low = low, close = close)) + 
  geom_abline(intercept=low_trendlines$intercept,slope=low_trendlines$slope) + 
  labs(title = paste(ticker,"Trendline Chart"), 
       y = "Price", 
       x = "Date", 
       caption = paste("Price data courtesy of Yahoo! Finance. Accessed ",
                       Sys.Date(),
                       ".",
                       sep="")) + 
  theme_tq()

AAPL support trendline chart


Solution

  • Here's an implementation of the solution from the comments.

    # Load libraries for tidy stock data analysis
    library(tidyverse)
    library(tidyquant)
    
    # Retrieve 1 year's worth of Apple stock price data from Yahoo! Finance
    ticker <- "AAPL"
    start <- Sys.Date() %m-% years(2)
    prices <- tq_get(ticker, from = start) %>%
      mutate(open = round(open,digits=2),
             high = round(high,digits=2),
             low = round(low,digits=2),
             close = round(close,digits=2)) %>%
      select(symbol,date,open,high,low,close)
    
    # Filter prices data for lows that fall on the convex hull
    lows <- prices[chull(prices[c("date", "low")]),] %>%
      filter(date<max(date))
    
    # Find all unique possible combinations of two lows
    # (and all unique possible combinations of their associated dates)
    all_lowcombos <- bind_cols(as.data.frame(t(combn(lows$date,m=2,simplify=TRUE))),as.data.frame(t(combn(lows$low,m=2,simplify=TRUE))))
    colnames(all_lowcombos) <- c("X1","X2","Y1","Y2")
    
    # Generate a trend line for every combination of points
    n <- seq(1:nrow(all_lowcombos))
    low_trendfinder <- function(n,all_lowcombos){
      model <- lm(c(all_lowcombos$Y1[n],all_lowcombos$Y2[n])~c(all_lowcombos$X1[n],all_lowcombos$X2[n]))
      data.frame(intercept = model$coefficients[1],slope = model$coefficients[2])
    }
    low_trendlines <- map_dfr(n,low_trendfinder,all_lowcombos = all_lowcombos)
    
    # For each low_trendline, check if any low in the prices dataframe falls below the line
    # Keep only trendlines for which this is FALSE
    # Also make sure the trendline wouldn't be less than half the current price for today's date
    low_trendline_test <- function(x,y,prices){
      !any(x*as.numeric(prices$date) + y > prices$low + 0.01) & !(x*as.numeric(Sys.Date())+y < 0.5*prices$close[nrow(prices)])
    }
    none_below <- map2(.x = low_trendlines$slope,.y = low_trendlines$intercept,.f = low_trendline_test,prices = prices)
    none_below <- unlist(none_below)
    low_trendlines <- low_trendlines[none_below,]
    
    # Chart support and resistance trendlines and this week's price targets
    prices %>%
      ggplot(aes(x = date, y = close)) +
      geom_candlestick(aes(open = open, high = high, low = low, close = close)) +
      geom_abline(intercept=low_trendlines$intercept,slope=low_trendlines$slope) +
      labs(title = paste(ticker,"Trendline Chart"), y = "Price", x = "Date", caption = paste("Price data courtesy of Yahoo! Finance. Accessed ",Sys.Date(),".",sep="")) +
      theme_tq()
    

    AAPL support trend lines example