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")]


Friday, August 12, 2011

Introduction to quantstrat

quantstrat provides a generic infrastructure to model and backtest signal-based quantitative strategies.  It is a high-level abstraction layer (built on xts, FinancialInstrument, blotter, etc.) that allows you to build and test strategies in very few lines of code.  quantstrat is still under heavy development but is being used every day on real portfolios.  We encourage you to send contributions and test cases to the project forums.

This post is a joint effort between me and Brian Peterson.  It will describe the underlying philosophy of quantstrat and how quantstrat implements that philosophy.  You may have seen some of this in Brian's Quantitative Strategy Development in R lightning talk at R/Finance 2011.

Generic Signal-Based Strategy Modeling

A signal-based strategy model first generates indicators.  Indicators are quantitative values derived from market data (e.g. moving averages, RSI, volatility bands, channels, momentum, etc.).  Indicators should be applied to market data in a vectorized (for fast backtesting) or streaming (for live execution) fashion, and are assumed to be path-independent (i.e. they do not depend on account / portfolio characteristics, current positions, or trades).

The interaction between indicators and market data are used to generate signals (e.g. crossovers, thresholds, multiples, etc.).  These signals are points in time at which you may want to take some action, even though you may not be able to.  Like indicators, signals may be applied in a vectorized or streaming fashion, and are assumed to be path-independent.

Rules use market data, indicators, signals, and current account / portfolio characteristics to generate orders.  Notice that rules about position sizing, fill simulation, order generation / management, etc. are separate from the indicator and signal generation process.  Unlike indicators and signals, rules are generally evaluated in a path-dependent fashion (path-independent rules are supported but are rare in real life) and are aware of all prior market data and current positions at the time of evaluation.  Rules may either generate new or modify existing orders (e.g. risk management, fill, rebalance, entry, exit).

How quantstrat Models Strategies

quantstrat uses FinancialInstrument to specify instruments (including their currencies) and uses blotter to keep track of transactions, valuations, and P&amp;amp;L across portfolios and accounts.

Indicators are often standard technical analysis functions like those found in TTR; and signals are often specified by the quantstrat sig* functions (i.e. sigComparison, sigCrossover, sigFormula, sigPeak, sigThreshold).  Rules are typically specified with the quantstrat ruleSignal function.

The functions used to specify indicators, signals, and rules are not limited to those mentioned previously.  The name parameter to add.indicator, add.signal, and add.rule can be any R function.  Because the supporting toolchain is built using xts objects, custom functions will integrate most easily if they return xts objects.

The strategy model is created in layers and makes use of delayed execution.  This means strategies can be applied--unmodified--to several different portfolios.  Before execution, quantstrat strategy objects do not know what instruments they will be applied to or what parameters will be passed to them.

For example, indicator parameters such as moving average periods or thresholds are likely to affect strategy performance.  Default values for parameters may (optionally) be set in the strategy object, or set at call-time via the parameters argument of applyStrategy (parameters is a named list, used like the arguments lists).

quantstrat models orders, which may or may not become transactions.  This provides a lot of extra ability to evaluate how the strategy is actually working, not working, or could be improved.  For example, performance strategies are often affected by how often resting limit orders are changed / replaced / canceled.  An order book allows the quantitative strategist to examine market conditions at the time these decisions are made. Also, the order history allows for easy computation of things that are important for many strategies, like order-to-fill ratios.

What's next?
  • Examples!  You can run some demos while you wait:
      demo(package="quantstrat")
  • Strategy Evaluation
  • Parameter Evaluation