Tuesday, August 23, 2011

Tactical asset allocation using quantstrat

As promised in the introduction to quantstrat, here is an example strategy.  I thought I'd start with the obligatory tactial asset allocation (TAA) strategy.  This post will replicate the strategy in the post, tactical asset allocation using blotter.

The "faber" demo in the quanstrat package contains a TAA strategy but it uses a slightly different approach than the code we're trying to replicate.  There are two major differences:
  1. The blotter TAA code initiates a position at the first observation where the close is above the SMA.  The demo only initiates a position when the close crosses the SMA to the upside.

    For example, assume the close is above the SMA at the beginning of the sample.  The demo has to wait for the close to drop below the SMA and then cross above it before taking a position; the blotter TAA code initiates a position on the first observation.

  2. The blotter TAA code calculates order size based on total account equity (as stored in the UnitSize object).  The demo always uses an order size of 1,000 shares, regardless of total account value (in case you're wondering, yes, capital/equity-aware order sizing is on the to-do list).
We need to make a few changes to the demo(faber) code to make it work more like the blotter TAA post.  First, we change the add.signal calls to use sigCrossover instead of sigComparison.  This allows us to create an order on the first observation, rather than wait for a crossover.

Next, we need to change the first call to add.rule (the entry rule).  sigComparison will be TRUE for every period where the close is above the SMA and we don't want to buy 1000 shares every period, so we need to tell ruleSignal to use the max position order sizing function.  We do this by adding osFUN=osMaxPos to the list of arguments passed to ruleSignal.

Finally, we need to set the position limits for each instrument.  We do this via two calls to addPosLimit (one for each symbol) and we set the maximum position to 1000 shares and the minimum position to 0 shares.

The modified code is below and even includes some simple evaluation of the results at no extra charge.  The tradeStats function has a ton more columns; I've only selected a few to make the output more readable.  Feel free to tinker.

NOTE: I wrote this code using the latest quantmod, xts, and zoo from CRAN; and the latest blotter, FinancialInstrument, and quantstrat from R-Forge.

# This code is a slight modification of the quantstrat "faber" demo, intended to replicate 
# http://blog.fosstrading.com/2009/11/tactical-asset-allocation-using-blotter.html 
# Uncomment the line below to install the latest packages on R-Forge:
#install.packages(c("quantstrat","blotter","FinancialInstrument"), repos="http://r-forge.r-project.org")
require(quantstrat)
require(PerformanceAnalytics)

# Set initial values
initDate <- "2002-07-31"
endDate <- "2009-10-31"
initEq <- 100000

# Pull Yahoo Finance data
symbols <- c("IEF", "SPY")
getSymbols(symbols, from=initDate, to=endDate, index.class=c("POSIXt","POSIXct"))

# adjust for splits/dividends (comment to replicate blotter example)
#IEF <- adjustOHLC(IEF, use.Adjusted=TRUE)
#SPY <- adjustOHLC(SPY, use.Adjusted=TRUE)

# convert to monthly
IEF <- to.monthly(IEF, indexAt="endof")
SPY <- to.monthly(SPY, indexAt="endof")

# Set up instruments with FinancialInstruments package
currency("USD")
for(symbol in symbols) {
  stock(symbol, currency="USD", multiplier=1)
}

# Delete portfolio, account, and order book if they already exist
suppressWarnings(rm("account.faber","portfolio.faber",pos=.blotter))
suppressWarnings(rm("order_book.faber",pos=.strategy))

# Initialize portfolio and account
initPortf("faber", symbols=symbols, initDate=initDate)
initAcct("faber", portfolios="faber", initDate=initDate, initEq=initEq)
initOrders(portfolio="faber", initDate=initDate)

# Initialize a strategy object
stratFaber <- strategy("faber")

# Add the 10-month SMA indicator
stratFaber <- add.indicator(strategy=stratFaber, name="SMA",
  arguments=list(x=quote(Cl(mktdata)), n=10), label="SMA10")

# There are two signals:
# The first is when monthly price crosses over the 10-month SMA
stratFaber <- add.signal(stratFaber, name="sigComparison",
  arguments=list(columns=c("Close","SMA10"),relationship="gte"), label="Cl.gt.SMA")
# The second is when the monthly price crosses under the 10-month SMA
stratFaber <- add.signal(stratFaber, name="sigComparison",
  arguments=list(columns=c("Close","SMA10"),relationship="lt"), label="Cl.lt.SMA")

# There are two rules:
# The first is to buy when the price crosses above the SMA
stratFaber <- add.rule(stratFaber, name="ruleSignal",
  arguments=list(sigcol="Cl.gt.SMA", sigval=TRUE, orderqty=1000, ordertype="market",
  orderside="long", pricemethod="market", TxnFees=-5, osFUN=osMaxPos), type="enter", path.dep=TRUE)
# The second is to sell when the price crosses below the SMA
stratFaber <- add.rule(stratFaber, name="ruleSignal",
  arguments=list(sigcol="Cl.lt.SMA", sigval=TRUE, orderqty="all", ordertype="market",
  orderside="long", pricemethod="market", TxnFees=-5), type="exit", path.dep=TRUE)

# Set position limits so we don't add to the position every month Close > SMA10
addPosLimit("faber", "SPY", timestamp=initDate, maxpos=1000, minpos=0)
addPosLimit("faber", "IEF", timestamp=initDate, maxpos=1000, minpos=0)

# Process the indicators and generate trades
out <- try(applyStrategy(strategy=stratFaber, portfolios="faber"))
updatePortf("faber")

# Evaluate results
portRet <- PortfReturns("faber")
portRet$Total <- rowSums(portRet, na.rm=TRUE)
charts.PerformanceSummary(portRet$Total)
tradeStats("faber")[,c("Symbol","Num.Trades","Net.Trading.PL","Max.Drawdown")]


10 comments:

Lemao said...

I am trying to run this script in R-Studio on OSX but I am getting the following error:

> currency("USD")
Error in assign(primary_id, currency_temp, envir = as.environment(.instrument)) :
object '.instrument' not found

I have installed the dependencies: zoo, xts, TTR, RTAQ, quantstrat, quantmod, Performanceanalytics, FInancialInstrument, Defaults and blotter.

Any ideas of what is going wrong here? (note I am new to R)

Lemao said...

I looked at the sources and found out that .instrument is initialized when the package namespace is loaded. The issue is that I clicked on Workspace->Clear All clears it in RStudio, which wiped out everything including this variable.

As I said, I am new to R, but I am curious as to whether this initialization indeed be done on package load?

Steve said...

> install.packages(c("quantstrat","blotter","FinancialInstrument"), repos="http://r-forge.r-project.org")
Installing package(s) into (... )
trying URL 'http://r-forge.r-project.org/bin/windows/contrib/2.13/quantstrat_0.5.2.zip'
Error in download.file(url, destfile, method, mode = "wb", ...) :
cannot open URL 'http://r-forge.r-project.org/bin/windows/contrib/2.13/quantstrat_0.5.2.zip'
In addition: Warning message:
In download.file(url, destfile, method, mode = "wb", ...) :
cannot open: HTTP status was '404 Not Found'
Warning in download.packages(pkgs, destdir = tmpd, available = available, :
download of package 'quantstrat' failed (... )

Steve said...

But it's working today...

> install.packages(c("quantstrat","blotter","FinancialInstrument"),repos="http://r-forge.r-project.org")
(... )
package 'quantstrat' successfully
(... )

Brian G. Peterson said...

Lemao: You need an instrument environment to store instrument metadata. When the FinancialInstrument package loads, it will create this environment for you.

Generally speaking, it's a really bad idea to clear out everything in a workspace, as you can lose important things along with all the junk. Generally accepted 'best practices' in R are to create a new workspace for each project you are working on, so that you can store history, state, working variables, etc separately for each project.

Speaking Freely said...

Change last line to:

tradeStats("faber")[c("Symbol","Num.Trades","Net.Trading.PL","Max.Drawdown")]

Max.Drawdown instead of maxDrawdown

Mikkel said...

Hi, thanks for the post. I am somewhat new to R and I also use Rapidminer which I am also pretty new at.

I have a large dataset of historical prices which I did not pull from yahoo, but it looks somewhat similar in setup. I have calculated and selected some indicators which I have added to my dataset and want to test a strategy with the quantstrat package.

When you add the indicator I am not sure I understand the arguments variable. What is going on there, could I add my already calculated indicator by typing something like?

stratFaber <- add.indicator(strategy=stratFaber, name="SMA", arguments=list(data$SMA10)
)

Thanks, Mikkel

Joshua Ulrich said...

Mikkel,

arguments should be a named list to be passed to the function given via the name argument. Note in the example arguments=list(x=quote(Cl(mktdata)), n=10) provides the two arguments to the SMA function. x is quoted to delay evaluation (since mktdata only exists inside of applyStrategy.

You generally don't add previously-calculated indicators to strategy objects. It defeats the purpose of the strategy being able to run on multiple sets of data. Instead, specify your custom function as the name argument to add.indicator and pass any relevant arguments to it via the arguments argument.

Best,
Josh

Mikkel said...

Thank you Josh, I understand it now, pretty smart that I can create any function to use as an indicator. I like the flexibility of R, but the learning curve can be pretty steep sometimes.

I am running through this example to try to learn the quantstrat package. Everything runs fine in R, except when I replace the data from yahoo with my own dataset.

I use this to initiate my dataset to R

SBO <-read.csv("C:/......./MABODay.csv", header=T)

SBO <-xts(SBO[,-1],as.Date(SBO[,1],"%Y/%m/%d"))

Dataset is then consisted of

- Open High Low Close Volume
YYYY-mm-dd nr nr nr nr nr

Then I skip the steps where you pull data from yahoo, adjust for dividends and convert it to monthly. I wanted a 10 day SMA instead.

When I setup the currency I use
# Set symbols
symbols <- c("SBO")

# Set up instruments with FinancialInstruments package
currency("USc/Lbs")
for(symbol in symbols) {
stock(symbol, currency="USc/Lbs", multiplier=1)
}

everything from then on is pretty much copy paste from your example.

When I run

out <- try(applyStrategy(strategy=stratSBO, portfolios="mySBO"))
updatePortf("mySBO")

I get the error

Error in if ((orderqty + pos) < PosLimit[, "MaxPos"]) { :
argument is of length zero

and

Error in if (length(c(year, month, day, hour, min, sec)) == 6 && c(year, :
missing value where TRUE/FALSE needed
In addition: Warning message:
In as_numeric(YYYY) : NAs introduced by coercion

I interpret the first error maybe because no orders were placed?

Second warning something with the date. NA values on time maybe?

I just want to learn and I don't expect you to give me a finished code that I can copy paste, but if you can point me in the right direction?

Thanks a lot

Best Regards
Mikkel

Mikkel said...

Hi again

Just want to mention that I solved my errors. The first error was simply a typo on my part in the date.

The second error I reinstalled blotter and it worked.

Now everything works like a charm.

Thanks

Best Regards
Mikkel