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.
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:
checkWindowcheckWindowscheckVectorsisDataFramedateMatchmovingAveragerunningMaxrunningMinstandardDeviationcheckWindow 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)
}
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:
plotIndicatorplotIndicator2plotStockplotIndicator 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()
}