Friday, May 1, 2009

RSI(2) with Position Sizing

Though it's more than two weeks later, here's the second post in the series that will demonstrate how to build, test, and implement a trading strategy with R. You can find the first post here.

The first post replicated this simple RSI(2) strategy from the MarketSci Blog. This second post will demonstrate how to replicate this strategy that scales in/out of RSI(2).

A couple notes before moving to the code:
  1. The rsi2pos() function isn't necessary, but it provides an example of how to define a function. Plus, it enables us to test several ideas with much greater speed and flexibility.

  2. The ifelse() function works on entire vectors at once, avoiding costly loops (loops are costly in R because it's an interpreted language). Since we can potentially modify the entire 'size' vector, we must be mindful of the order of the tests.

On to the code!
# Attach the quantmod and TTR packages.
# You can install packages via:
# install.packages(c("quantmod","TTR"))
library(quantmod)
library(TTR)

# Pull S&P500 index data from Yahoo! Finance
getSymbols("^GSPC", from="2000-01-01", to="2008-12-07")

# Calculate the RSI indicator
rsi <- RSI(Cl(GSPC),2)

# Calculate Close-to-Close returns
# (this assumes we open/close our positions
# at each day's close)
ret <- ROC(Cl(GSPC))
ret[1] <- 0

# Define our position-sizing function
rsi2pos <- function(ind, indIncr=5, posIncr=0.25) {
# Inputs:
# ind : indicator vector
# indIncr : indicator value increments/breakpoints
# posIncr : position value increments/breakpoints

# Initialize result vector
size <- rep(0,NROW(ind))

# Long
size <- ifelse(ind < 4*indIncr, (1-posIncr*3), size)
size <- ifelse(ind < 3*indIncr, (1-posIncr*2), size)
size <- ifelse(ind < 2*indIncr, (1-posIncr*1), size)
size <- ifelse(ind < 1*indIncr, (1-posIncr*0), size)

# Short
size <- ifelse(ind > 100-4*indIncr, 3*posIncr-1, size)
size <- ifelse(ind > 100-3*indIncr, 2*posIncr-1, size)
size <- ifelse(ind > 100-2*indIncr, 1*posIncr-1, size)
size <- ifelse(ind > 100-1*indIncr, 0*posIncr-1, size)

# Today's position ('size') is based on today's
# indicator, but we need to apply today's position
# to the Close-to-Close return at tomorrow's close.
size <- lag(size)

# Replace missing signals with no position
# (generally just at beginning of series)
size[is.na(size)] <- 0

# Return results
return(size)
}

# Calculate signals using the 'rsi2pos()' function
sig <- rsi2pos(rsi, 5, 0.25)

# Break out the long (up) and short (dn) signals
sigup <- ifelse(sig > 0, sig, 0)
sigdn <- ifelse(sig < 0, sig, 0)

# Calculate equity curves
eq_up <- exp(cumsum(ret*sigup)
)
eq_dn <- exp(cumsum(ret*sigdn))
eq_all <- exp(cumsum(ret*sig))

# Replicate Michael's nice chart (again)
png(filename="20090430_rsi2_replication.png")
plot.zoo( cbind(eq_up, eq_dn), plot.type="single",
ylab=c("Long","Short"), col=c("green","red"),
main="RSI(2) Strategy, with Position Scaling:\n 2000-01-03 through 2008-12-07" )
dev.off()


# Calculate signals using the 'rsi2pos()' function
# with new values
sig <- rsi2pos(rsi, 10, 0.3)

# Break out the long (up) and short (dn) signals
sigup <- ifelse(sig > 0, sig, 0)
sigdn <- ifelse(sig < 0, sig, 0)

# Calculate equity curves
eq_up <- exp(cumsum(ret*sigup)
)
eq_dn <- exp(cumsum(ret*sigdn))
eq_all <- exp(cumsum(ret*sig))

# Re-plot equity curves using updated values
png(filename="20090501_rsi2_updated.png")
plot.zoo( cbind(eq_up, eq_dn), plot.type="single",
ylab=c("Long","Short"), col=c("green","red"),
main="Updated RSI(2) Strategy, with Position Scaling:\n 2000-01-03 through 2008-12-07" )
dev.off()


Visual inspection of the charts seems to indicate the updated RSI(2) strategy has slightly higher returns, but more volatility and larger drawdowns.

The PerformanceAnalytics package can evaluate the volatility, drawdowns, and related metrics for investing and trading strategies.  We'll use it in the next post to evaluate RSI(2).

6 comments:

Autore del blog said...

i look forward to it -- never could get my head around performance metrics..

Damian said...

This is awesome - very cool. I also was curious if you could put together a simple example of the following:

- Take four ETFs (SPY,IWM,QQQQ,TLT) and rank them by their 12m ROC.
- Buy the top one on the first day of the month.
- Rerank at the beginning of each month, sell the fund if it has fallen from the top position and buy the new top position.

Thanks much!

Joshua Ulrich said...

Hi Damian,

I'll keep that in mind for when we start showing some more complicated trading systems and portfolio management using another package, 'blotter'.

You can see a demo of the blotter package by installing it, attaching it (via 'library()'), and running 'demo(turtles)'.

Please do note that the blotter package is still in heavy development, so many things will probably change. Such is life on the bleeding edge. :)

Sc00per said...

Thank you very much for the informative post. I was wondering if you knew of any limitations this code would have when we do not use the getSymbols quantmod function and instead use imported Bloomberg data.

I have found that the results from Bloomberg data differ highly from the results you've posted.

I think that this problem may be due to how RSI and ROC calculate the signals and returns respectively.

Any advice or suggestions would be awesome. Thanks in advance.

Sean

Joshua Ulrich said...

Hi Sean,

It's really hard to say, without more information. Here are the things I would expect to cause differences:

- actual data differences between Yahoo and Bloomberg

- data object differences (i.e. getSymbols returns an xts object, the function you use to pull Bloomberg data may return a data.frame, matrix, etc.)

Sc00per said...

Thanks Josh, I found that the problem lies in how the lag operator works. I posted a similar problem to the R Finance Community and the error should be corrected in the new version of quantmod.

To quote Jeff:
"lag.zoo was designed by Achim and Kurt to map as closely as possible to R's ts class behavior, which by all rational interpretations is simply inconsistent with the common understanding of lagged series. Zoo's goal was a better ts; xts is a time-based zoo.

That said, lag.xts makes a break from this R lag "design" and implements a different set of default behavior, more consistent with user expectations IMO.

Take a look at ?lag.xts

Obviously the downside is management of this difference amongst time classes."

Thanks again