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:
checkWindow
checkWindows
checkVectors
isDataFrame
dateMatch
movingAverage
runningMax
runningMin
standardDeviation
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)
}
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:
plotIndicator
plotIndicator2
plotStock
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()
}