Implementing Technical Indicators: Tools

Programming Technical Indicators R Plotting

Introduction

This article is the start of my series on Implementing Technical Indicators. The tools provided here will make our lives easier when implementing actual technical indicators in the remaining articles of this series. All related code is in R.

For an introduction to technical indicators including example plots see my article Technical Indicators: An Introduction.

DISCLAIMER: None of the below is intended to be considered as any kind of investment advice. All examples serve as illustrative material only.

Calculation Tools

When evaluating technical indicators several operators occur repeatedly. It is considered best practice to wrap these operations in functions made available to all indicator implementations. I came up with the following relevant functions:

  1. checkWindow
  2. checkWindows
  3. checkVectors
  4. isDataFrame
  5. dateMatch
  6. movingAverage
  7. runningMax
  8. runningMin
  9. standardDeviation
Let us briefly see what these do. The three check-something functions allow to see whether certain variables are consistent in terms of size: checkWindow tests whether an input vector is long enough to apply operations on sub intervals of a specific length to it, checkWindows validates that short and long window length are ordered appropriately while checkVectors ensures that up to four vectors are equal in size. The function isDataFrame throws an error if the input is not of type dataframe. dateMatch tests whether the size of a vector and the number of rows in a dataframe match. The remaining functions perform actual calculations: movingAverage calculates a simple moving average of specified window length. Similarly, runningMax and runningMin take an input series which is then converted into a series where each value is the maximum respectively minimum over the previous i_window days of the input vector. standardDeviation finally calculates the i_window days rolling standard deviation over the input vector.

I personally store the collection of all these functions as listed below in a file Indicators/TechnicalIndicators.R. You will find this file being included via source() into my indicator implementation source files.


#
# helper functions in the context of calculating technical indicators
#

# check function arguments for consistency
checkWindow = function(v_values, i_window)
{
  if (i_window > length(v_values)) {
    stop("window length > number of values!")
  }
}

checkWindows = function(i_window_short, i_window_long)
{
  if (i_window_short > i_window_long) {
    stop("short window length > long window length!")
  }
}

checkVectors = function(v_vec1, v_vec2, v_vec3=c(), v_vec4=c())
{
  b_is_error = FALSE
  
  if (length(v_vec1) != length(v_vec2)) {
    b_is_error = TRUE
  } else if (!is.null(v_vec3)) {
    if (length(v_vec1) != length(v_vec3)) {
      b_is_error = TRUE
    }
  } else if (!is.null(v_vec4)) {
    if (length(v_vec1) != length(v_vec4)) {
      b_is_error = TRUE
    }
  }
  
  if (b_is_error) {
    stop("vector size mismatch!")
  }
}

isDataFrame = function(df)
{
  if (!is.data.frame(df)) {
    stop("target is not of type 'data frame'!")
  }
}

dateMatch = function(v_date, df)
{
  isDataFrame(df)
  if (length(v_date) != nrow(df)) {
    stop("number of dates and rows of data frame do not match!")
  }
}

# moving average
movingAverage = function(v_values, i_window)
{
  i_length = length(v_values)
  
  v_ma = rep(0, i_length)
  
  d_cur_ma = v_values[1]
  v_ma[1] = d_cur_ma
  
  for (i in 2 : i_length) {
    if (i <= i_window) {
      d_cur_ma = d_cur_ma * (i - 1)
      d_cur_ma = d_cur_ma + v_values[i]
      d_cur_ma = d_cur_ma / i
    } else {
      d_cur_ma = d_cur_ma - v_values[i-i_window] / i_window
      d_cur_ma = d_cur_ma + v_values[i] / i_window
    }
    v_ma[i] = d_cur_ma
  }
  
  return(v_ma)
}

# running maximum
runningMax = function(v_values, i_window)
{
  i_length = length(v_values)
  
  v_rmax = rep(0, i_length)
  
  d_cur_max = v_values[1]
  v_rmax[1] = d_cur_max
  
  for (i in 2 : i_length) {
    if (i < i_window) {
      d_cur_max = max(d_cur_max, v_values[i]);
    } else {
      d_cur_max = max(v_values[(i-i_window+1):i]);
    }
    v_rmax[i] = d_cur_max;
  }
  
  return(v_rmax);
}

# running minimum
runningMin = function(v_values, i_window)
{
  i_length = length(v_values)
  
  v_rmin = rep(0, i_length)
  
  d_cur_min = v_values[1]
  v_rmin[1] = d_cur_min
  
  for (i in 2 : i_length) {
    if (i < i_window) {
      d_cur_min = min(d_cur_min, v_values[i]);
    } else {
      d_cur_min = min(v_values[(i-i_window+1):i]);
    }
    v_rmin[i] = d_cur_min;
  }
  
  return(v_rmin);
}

# lagged standard deviation
standardDeviation = function(v_values, i_window)
{
  i_length = length(v_values)
  
  v_sdv = rep(0, i_length)
  v_sdv[1] = 0
  
  for (i in 2 : i_length) {
    if (i < i_window) {
      v_sdv[i] = sd(v_values[1:i])
    } else {
      v_sdv[i] = sd(v_values[(i-i_window+1):i])
    }
  }
  
  return(v_sdv)
}

				

Plotting Tools

Although R comes with fantastic plotting capabilities it always takes me some time to make thinks look as demanded. To me common issues are coloured backgrounds, date axes, legend positioning and so on. While many R packages offering solutions to these tasks exist I prefer to set things up from first principles on. In the context of plotting technical indicators and visualising trading strategies I came up with the following relevant functions:

  1. plotIndicator
  2. plotIndicator2
  3. plotStock
The functions plotIndicator and plotIndicator2 both generate a jpeg file with the plot of the indicator of interest over time. Especially the date axis formatting and legend positioning are being taken care of. In addition, plotIndicator colours the plot's background according to the v_signals trading signal input vector, where times during which the strategy positions long are coloured greenish, times during which the position is short are coloured reddish and the remainder is left white. The function plotStock produces a straightforward jpeg plot of a stocks historical price series.

Similarly to the calculation tools script I store the plotting tools in a file Indicators/IndicatorPlottingFunctions.R.


#
# helper functions in the context of plotting technical indicators
#

# define constants
COLOR_LONG_POSITION    = rgb(186/255, 255/255, 179/255, alpha=0.5)
COLOR_SHORT_POSITION   = rgb(255/255, 179/255, 186/255, alpha=0.3)
COLOR_NEUTRAL_POSITION = "white"

# plot indicator (trading strategy)
plotIndicator = function(v_date, v_signals, df_data, v_colors, v_legend, s_path)
{
  jpeg(s_path, width=1200, height=700)
  
  v_x = seq(1:nrow(df_data))
  
  # determine x axis date labels
  v_ticks      = c(v_x[1])
  s_prev_date  = format(as.Date(v_date[1]), "%m-%Y")
  v_tick_dates = c(s_prev_date)
  for (i in 2 : length(v_x)) {
    s_cur_date = format(as.Date(v_date[i]), "%m-%Y")
    if (s_cur_date != s_prev_date) {
      v_ticks      = c(v_ticks, v_x[i])
      v_tick_dates = c(v_tick_dates, s_cur_date)
      s_prev_date  = s_cur_date
    }
  }
  
  # determine start and end coordinates for signal boxes
  v_long_start    = c()
  v_long_end      = c()
  v_neutral_start = c()
  v_neutral_end   = c()
  v_short_start   = c()
  v_short_end     = c()
  i_start = 1
  i_end   = 1
  for (i in 2 : length(v_signals)) {
    i_signal = v_signals[i]
    if (i_signal == v_signals[i-1]) {
      i_end = i
    } else {
      if (v_signals[i-1] == 1) {
        v_long_start = c(v_long_start, i_start)
        v_long_end   = c(v_long_end, i_end+1)
      } else if (v_signals[i-1] == -1) {
        v_short_start = c(v_short_start, i_start)
        v_short_end   = c(v_short_end, i_end+1)
      } else if (v_signals[i-1] == 0) {
        v_neutral_start = c(v_neutral_start, i_start)
        v_neutral_end   = c(v_neutral_end, i_end+1)
      }
      i_start = i
      i_end   = i
    }
  }
  if (i_signal == 1) {
    v_long_start = c(v_long_start, i_start)
    v_long_end   = c(v_long_end, i)
  } else if (i_signal == -1) {
    v_short_start = c(v_short_start, i_start)
    v_short_end   = c(v_short_end, i)
  } else if (i_signal == 0) {
    v_neutral_start = c(v_neutral_start, i_start)
    v_neutral_end   = c(v_neutral_end, i)
  }
  
  # create plot
  d_max     = max(df_data)
  d_min     = min(df_data)
  v_y_ticks = pretty(seq(d_min, d_max))
  
  par(xpd=TRUE, mar=par("mar")+c(0, 0, 0, 7))
  
  plot(range(v_x), c(d_min, d_max), type="n", xaxt="n", yaxt="n", ylab="", xlab="", xaxs="i")
  if (length(v_long_start) > 0) {
    rect(xleft=v_long_start, xright=v_long_end, ybottom=par("usr")[3],
	     ytop=par("usr")[4], border="transparent", col=COLOR_LONG_POSITION)
  }
  if (length(v_short_start) > 0) {
    rect(xleft=v_short_start, xright=v_short_end, ybottom=par("usr")[3],
	     ytop=par("usr")[4], border="transparent", col=COLOR_SHORT_POSITION)
  }
  if (length(v_neutral_start) > 0) {
    rect(xleft=v_neutral_start, xright=v_neutral_end, ybottom=par("usr")[3],
	     ytop=par("usr")[4], border="transparent", col=COLOR_NEUTRAL_POSITION)
  }
  legend(nrow(df_data)+3, d_max, legend=v_legend, col=v_colors, lty=rep(1, length(v_colors)), lwd=1.5)
  par(xpd=FALSE)
  axis(1, at=v_ticks, labels=v_tick_dates)
  axis(2, at=v_y_ticks, labels=v_y_ticks)
  abline(h=v_y_ticks, v=v_ticks, col="gray", lty=3)
  for (i in 1 : ncol(df_data)) {
    lines(df_data[, i], type="l", col=v_colors[i])
  }
  box()
  
  dev.off()
}

# plot indicator
plotIndicator2 = function(v_date, v_indicator, df_data, v_colors, v_legend, s_path)
{
  jpeg(s_path, width=1200, height=700)
  
  v_x = seq(1:nrow(df_data))
  
  # determine x axis date labels
  v_ticks      = c(v_x[1])
  s_prev_date  = format(as.Date(v_date[1]), "%m-%Y")
  v_tick_dates = c(s_prev_date)
  for (i in 2 : length(v_x)) {
    s_cur_date = format(as.Date(v_date[i]), "%m-%Y")
    if (s_cur_date != s_prev_date) {
      v_ticks      = c(v_ticks, v_x[i])
      v_tick_dates = c(v_tick_dates, s_cur_date)
      s_prev_date  = s_cur_date
    }
  }
  
  # create plot
  d_max     = max(df_data)
  d_min     = min(df_data)
  v_y_ticks = pretty(seq(d_min, d_max))
  
  par(xpd=TRUE, mar=par("mar")+c(0, 0, 0, 8))
  
  plot(range(v_x), c(d_min, d_max), type="n", xaxt="n", yaxt="n", ylab="", xlab="", xaxs="i")
  legend(nrow(df_data)+8, d_max, legend=v_legend, col=v_colors, lty=rep(1, length(v_colors)),
         pch=c(1, rep(NA, length(v_colors)-1)), lwd=1.5)
  par(xpd=FALSE)
  axis(1, at=v_ticks, labels=v_tick_dates)
  axis(2, at=v_y_ticks, labels=v_y_ticks)
  abline(h=v_y_ticks, v=v_ticks, col="gray", lty=3)
  for (i in 1 : ncol(df_data)) {
    lines(df_data[, i], type="l", col=v_colors[i+1])
  }
  par(new=TRUE)
  plot(v_x, v_indicator, type="o", col=v_colors[1], axes=FALSE, bty="n", xlab="", ylab="", xaxs="i")
  axis(4, at=pretty(range(v_indicator)))
  box()
  
  dev.off()
}

# plot indicator (trading strategy)
plotStock = function(v_date, v_price, s_path)
{
  jpeg(s_path, width=1200, height=700)
  
  v_x = seq(1:length(v_price))
  
  # determine x axis date labels
  v_ticks      = c(v_x[1])
  s_prev_date  = format(as.Date(v_date[1]), "%m-%Y")
  v_tick_dates = c(s_prev_date)
  for (i in 2 : length(v_x)) {
    s_cur_date = format(as.Date(v_date[i]), "%m-%Y")
    if (s_cur_date != s_prev_date) {
      v_ticks      = c(v_ticks, v_x[i])
      v_tick_dates = c(v_tick_dates, s_cur_date)
      s_prev_date  = s_cur_date
    }
  }
  
  # create plot
  d_max     = max(v_price)
  d_min     = min(v_price)
  v_y_ticks = pretty(seq(d_min, d_max))
  
  plot(v_x, v_price, type="l", xaxt="n", yaxt="n", ylab="", xlab="", xaxs="i")
  
  axis(1, at=v_ticks, labels=v_tick_dates)
  axis(2, at=v_y_ticks, labels=v_y_ticks)
  abline(h=v_y_ticks, v=v_ticks, col="gray", lty=3)

  box()
  
  dev.off()
}