Nuts and Bolts of Quantstrat, Part V

This post will be about pre-processing custom indicators in quantstrat–that is, how to add values to your market data that do not arise from the market data itself.

The first four parts of my nuts and bolts of quantstrat were well received. They are even available as a datacamp course. For those that want to catch up to today’s post, I highly recommend the datacamp course.

To motivate this post, the idea is that say you’re using alternative data that isn’t simply derived from a transformation of the market data itself. I.E. you have a proprietary alternative data stream that may predict an asset’s price, you want to employ a cross-sectional ranking system, or any number of things. How do you do this within the context of quantstrat?

The answer is that it’s as simple as binding a new xts to your asset data, as this demonstration will show.

First, let’s get the setup out of the way.

require(quantstrat)
require(PerformanceAnalytics)

initDate="1990-01-01"
from="2003-01-01"
to="2012-12-31"
options(width=70)

options("getSymbols.warning4.0"=FALSE)

currency('USD')
Sys.setenv(TZ="UTC")

symbols <- 'SPY'
suppressMessages(getSymbols(symbols, from=from, to=to, src="yahoo", adjust=TRUE))  

stock(symbols, currency="USD", multiplier=1)

Now, we have our non-derived indicator. In this case, it’s a toy example–the value is 1 if the year is odd (I.E. 2003, 2005, 2007, 2009), and 0 if it’s even. We compute that and simply column-bind (cbind) it to the asset data.

nonDerivedIndicator <- as.numeric(as.character(substr(index(SPY), 1, 4)))%%2 == 1
nonDerivedIndicator <- xts(nonDerivedIndicator, order.by=index(SPY))

SPY <- cbind(SPY, nonDerivedIndicator)
colnames(SPY)[7] = "nonDerivedIndicator"

Next, we just have a very simple strategy–buy a share of SPY on odd years, sell on even years. That is, buy when the nonDerivedIndicator column crosses above 0.5 (from 0 to 1), and sell when the opposite occurs.

strategy.st <- portfolio.st <- account.st <- "nonDerivedData"
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD')
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

add.signal(strategy.st, name = sigThreshold, 
           arguments = list(column = "nonDerivedIndicator", threshold = 0.5, relationship = "gte", cross = TRUE),
           label = "longEntry")

add.signal(strategy.st, name = sigThreshold, 
           arguments = list(column = "nonDerivedIndicator", threshold = 0.5, relationship = "lte", cross = TRUE),
           label = "longExit")


tmp <- applySignals(strategy = strategy.st, mktdata=SPY)


add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open", orderqty = 1), 
         type="enter", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all", 
                        ordertype="market", orderside="long", 
                        replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)

#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)

#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)

And the result:

chart.Posn(portfolio.st, 'SPY')

In conclusion, you can create signals based off of any data in quantstrat. Whether that means volatility ratios, fundamental data, cross-sectional ranking, or whatever proprietary alternative data source you may have access to, this very simple process is how you can use quantstrat to add all of those things to your systematic trading backtest research.

Thanks for reading.

Note: I am always interested in full-time opportunities which may benefit from my skills. I have experience in data analytics, asset management, and systematic trading research. If you know of any such opportunities, do not hesitate to contact me on my LinkedIn, found here.

Review: Invoance’s TRAIDE application

This review will be about Inovance Tech’s TRAIDE system. It is an application geared towards letting retail investors apply proprietary machine learning algorithms to assist them in creating systematic trading strategies. Currently, my one-line review is that while I hope the company founders mean well, the application is still in an early stage, and so, should be checked out by potential users/venture capitalists as something with proof of potential, rather than a finished product ready for mass market. While this acts as a review, it’s also my thoughts as to how Inovance Tech can improve its product.

A bit of background: I have spoken several times to some of the company’s founders, who sound like individuals at about my age level (so, fellow millennials). Ultimately, the selling point is this:

Systematic trading is cool.
Machine learning is cool.
Therefore, applying machine learning to systematic trading is awesome! (And a surefire way to make profits, as Renaissance Technologies has shown.)

While this may sound a bit snarky, it’s also, in some ways, true. Machine learning has become the talk of the town, from IBM’s Watson (RenTec itself hired a bunch of speech recognition experts from IBM a couple of decades back), to Stanford’s self-driving car (invented by Sebastian Thrun, who now heads Udacity), to the Netflix prize, to god knows what Andrew Ng is doing with deep learning at Baidu. Considering how well machine learning has done at much more complex tasks than “create a half-decent systematic trading algorithm”, it shouldn’t be too much to ask this powerful field at the intersection of computer science and statistics to help the retail investor glued to watching charts generate a lot more return on his or her investments than through discretionary chart-watching and noise trading. To my understanding from conversations with Inovance Tech’s founders, this is explicitly their mission.

(Note: Dr. Wes Gray and Alpha Architect, in their book DIY Financial Advisor, have already established that listening to pundits, and trying to succeed at discretionary trading, is on a whole, a loser’s game)

However, I am not sure that Inovance’s TRAIDE application actually accomplishes this mission in its current state.

Here’s how it works:

Users select one asset at a time, and select a date range (data going back to Dec. 31, 2009). Assets are currently limited to highly liquid currency pairs, and can take the following settings: 1 hour, 2 hour, 4 hour, 6 hour, or daily bar time frames.

Users then select from a variety of indicators, ranging from technical (moving averages, oscillators, volume calculations, etc. Mostly an assortment of 20th century indicators, though the occasional adaptive moving average has managed to sneak in–namely KAMA–see my DSTrading package, and MAMA–aka the Mesa Adaptive Moving Average, from John Ehlers) to more esoteric ones such as some sentiment indicators. Here’s where things start to head south for me, however. Namely, that while it’s easy to add as many indicators as a user would like, there is basically no documentation on any of them, with no links to reference, etc., so users will have to bear the onus of actually understanding what each and every one of the indicators they select actually does, and whether or not those indicators are useful. The TRAIDE application makes zero effort (thus far) to actually get users acquainted with the purpose of these indicators, what their theoretical objective is (measure conviction in a trend, detect a trend, oscillator type indicator, etc.)

Furthermore, regarding indicator selections, users also specify one parameter setting for each indicator per strategy. E.G. if I had an EMA crossover, I’d have to create a new strategy for a 20/100 crossover, a 21/100 crossover, rather than specifying something like this:

short EMA: 20-60
long EMA: 80-200

Quantstrat itself has this functionality, and while I don’t recall covering parameter robustness checks/optimization (in other words, testing multiple parameter sets–whether one uses them for optimization or robustness is up to the user, not the functionality) in quantstrat on this blog specifically, this information very much exists in what I deem “the official quantstrat manual”, found here. In my opinion, the option of covering a range of values is mandatory so as to demonstrate that any given parameter setting is not a random fluke. Outside of quantstrat, I have demonstrated this methodology in my Hypothesis Driven Development posts, and in coming up for parameter selection for volatility trading.

Where TRAIDE may do something interesting, however, is that after the user specifies his indicators and parameters, its “proprietary machine learning” algorithms (WARNING: COMPLETELY BLACK BOX) determine for what range of values of the indicators in question generated the best results within the backtest, and assign them bullishness and bearishness scores. In other words, “looking backwards, these were the indicator values that did best over the course of the sample”. While there is definite value to exploring the relationships between indicators and future returns, I think that TRAIDE needs to do more in this area, such as reporting P-values, conviction, and so on.

For instance, if you combine enough indicators, your “rule” is a market order that’s simply the intersection of all of the ranges of your indicators. For instance, TRAIDE may tell a user that the strongest bullish signal when the difference of the moving averages is between 1 and 2, the ADX is between 20 and 25, the ATR is between 0.5 and 1, and so on. Each setting the user selects further narrows down the number of trades the simulation makes. In my opinion, there are more ways to explore the interplay of indicators than simply one giant AND statement, such as an “OR” statement, of some sort. (E.G. select all values, put on a trade when 3 out of 5 indicators fall into the selected bullish range in order to place more trades). While it may be wise to filter down trades to very rare instances if trading a massive amount of instruments, such that of several thousand possible instruments, only several are trading at any given time, with TRAIDE, a user selects only *one* asset class (currently, one currency pair) at a time, so I’m hoping to see TRAIDE create more functionality in terms of what constitutes a trading rule.

After the user selects both a long and a short rule (by simply filtering on indicator ranges that TRAIDE’s machine learning algorithms have said are good), TRAIDE turns that into a backtest with a long equity curve, short equity curve, total equity curve, and trade statistics for aggregate, long, and short trades. For instance, in quantstrat, one only receives aggregate trade statistics. Whether long or short, all that matters to quantstrat is whether or not the trade made or lost money. For sophisticated users, it’s trivial enough to turn one set of rules on or off, but TRAIDE does more to hold the user’s hand in that regard.

Lastly, TRAIDE then generates MetaTrader4 code for a user to download.

And that’s the process.

In my opinion, while what Inovance Tech has set out to do with TRAIDE is interesting, I wouldn’t recommend it in its current state. For sophisticated individuals that know how to go through a proper research process, TRAIDE is too stringent in terms of parameter settings (one at a time), pre-coded indicators (its target audience probably can’t program too well), and asset classes (again, one at a time). However, for retail investors, my issue with TRAIDE is this:

There is a whole assortment of undocumented indicators, which then move to black-box machine learning algorithms. The result is that the user has very little understanding of what the underlying algorithms actually do, and why the logic he or she is presented with is the output. While TRAIDE makes it trivially easy to generate any one given trading system, as multiple individuals have stated in slightly different ways before, writing a strategy is the easy part. Doing the work to understand if that strategy actually has an edge is much harder. Namely, checking its robustness, its predictive power, its sensitivity to various regimes, and so on. Given TRAIDE’s rather short data history (2010 onwards), and coupled with the opaqueness that the user operates under, my analogy would be this:

It’s like giving an inexperienced driver the keys to a sports car in a thick fog on a winding road. Nobody disputes that a sports car is awesome. However, the true burden of the work lies in making sure that the user doesn’t wind up smashing into a tree.

Overall, I like the TRAIDE application’s mission, and I think it may have potential as something for the retail investors that don’t intend to learn the ins-and-outs of coding a trading system in R (despite me demonstrating many times over how to put such systems together). I just think that there needs to be more work put into making sure that the results a user sees are indicative of an edge, rather than open the possibility of highly-flexible machine learning algorithms chasing ghosts in one of the noisiest and most dynamic data sets one can possibly find.

My recommendations are these:

1) Multiple asset classes
2) Allow parameter ranges, and cap the number of trials at any given point (E.G. 4 indicators with ten settings each = 10,000 possible trading systems = blow up the servers). To narrow down the number of trial runs, use techniques from experimental design to arrive at decent combinations. (I wish I remembered my response surface methodology techniques from my master’s degree about now!)
3) Allow modifications of order sizing (E.G. volatility targeting, stop losses), such as I wrote about in my hypothesis-driven development posts.
4) Provide *some* sort of documentation for the indicators, even if it’s as simple as a link to investopedia (preferably a lot more).
5) Far more output is necessary, especially for users who don’t program. Namely, to distinguish whether or not there is a legitimate edge, or if there are too few observations to reject the null hypothesis of random noise.
6) Far longer data histories. 2010 onwards just seems too short of a time-frame to be sure of a strategy’s efficacy, at least on daily data (may not be true for hourly).
7) Factor in transaction costs. Trading on an hourly time frame will mean far less P&L per trade than on a daily resolution. If MT4 charges a fixed ticket price, users need to know how this factors into their strategy.
8) Lastly, dogfooding. When I spoke last time with Inovance Tech’s founders, they claimed they were using their own algorithms to create a forex strategy, which was doing well in live trading. By the time more of these suggestions are implemented, it’d be interesting to see if they have a track record as a fund, in addition to as a software provider.

If all of these things are accounted for and automated, the product will hopefully accomplish its mission of bringing systematic trading and machine learning to more people. I think TRAIDE has potential, and I’m hoping that its staff will realize that potential.

Thanks for reading.

NOTE: I am currently contracting in downtown Chicago, and am always interested in networking with professionals in the systematic trading and systematic asset management/allocation spaces. Find my LinkedIn here.

EDIT: Today in my email (Dec. 3, 2015), I received a notice that Inovance was making TRAIDE completely free. Perhaps they want a bunch more feedback on it?

Why Backtesting On Individual Legs In A Spread Is A BAD Idea

So after reading the last post, the author of quantstrat had mostly critical feedback, mostly of the philosophy that prompted its writing in the first place. Basically, the reason I wrote it, as I stated before, is that I’ve seen many retail users of quantstrat constantly ask “how do I model individual spread instruments”, and otherwise try to look like they’re sophisticated by trading spreads.

The truth is that real professionals use industrial-strength tools to determine their intraday hedge ratios (such a tool is called a spreader). The purpose of quantstrat is not to be an execution modeling system, but to be a *strategy* modeling system. Basically, the purpose of your backtest isn’t to look at individual instruments, since in the last post, the aggregate trade statistics told us absolutely nothing about how our actual spread trading strategy performed. The backtest was a mess as far as the analytics were concerned, and thus rendering it more or less useless. So this post, by request of the author of quantstrat, is about how to do the analysis better, and looking at what matters more–the actual performance of the strategy on the actual relationship being traded–namely, the *spread*, rather than the two components.

So, without further ado, let’s look at the revised code:

require(quantmod)
require(quantstrat)
require(IKTrading)

getSymbols("UNG", from="1990-01-01")
getSymbols("DGAZ", from="1990-01-01")
getSymbols("UGAZ", from="1990-01-01")
UNG <- UNG["2012-02-22::"]
UGAZ <- UGAZ["2012-02-22::"]

spread <- 3*OHLC(UNG) - OHLC(UGAZ)

initDate='1990-01-01'
currency('USD')
Sys.setenv(TZ="UTC")
symbols <- c("spread")
stock(symbols, currency="USD", multiplier=1)

strategy.st <- portfolio.st <- account.st <-"spread_strategy_done_better"
rm.strat(portfolio.st)
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD')
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

#### paramters

nEMA = 20

### indicator

add.indicator(strategy.st, name="EMA",
              arguments=list(x=quote(Cl(mktdata)), n=nEMA),
              label="ema")

### signals

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "EMA.ema"), relationship="gt"),
           label="longEntry")

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "EMA.ema"), relationship="lt"),
           label="longExit")

### rules

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open", orderqty=1), 
         type="enter", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all", ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)

#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)

In this case, things are a LOT simpler. Rather than jumping through the hoops of pre-computing an indicator, along with the shenanigans of separate rules for both the long and the short end, we simply have a spread as it’s theoretically supposed to work–three of an unleveraged ETF against the 3x leveraged ETF, and we can go long the spread, or short the spread. In this case, the dynamic seems to be on the up, and we want to capture that.

So how did we do?

#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)

#trade statistics
tStats <- tradeStats(Portfolios = portfolio.st, use="trades", inclZeroDays=FALSE)
tStats[,4:ncol(tStats)] <- round(tStats[,4:ncol(tStats)], 2)
print(data.frame(t(tStats[,-c(1,2)])))
(aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
(aggCorrect <- mean(tStats$Percent.Positive))
(numTrades <- sum(tStats$Num.Trades))
(meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))

And here’s the output:

> print(data.frame(t(tStats[,-c(1,2)])))
                   spread
Num.Txns            76.00
Num.Trades          38.00
Net.Trading.PL       9.87
Avg.Trade.PL         0.26
Med.Trade.PL        -0.10
Largest.Winner       7.76
Largest.Loser       -1.06
Gross.Profits       21.16
Gross.Losses       -11.29
Std.Dev.Trade.PL     1.68
Percent.Positive    39.47
Percent.Negative    60.53
Profit.Factor        1.87
Avg.Win.Trade        1.41
Med.Win.Trade        0.36
Avg.Losing.Trade    -0.49
Med.Losing.Trade    -0.46
Avg.Daily.PL         0.26
Med.Daily.PL        -0.10
Std.Dev.Daily.PL     1.68
Ann.Sharpe           2.45
Max.Drawdown        -4.02
Profit.To.Max.Draw   2.46
Avg.WinLoss.Ratio    2.87
Med.WinLoss.Ratio    0.78
Max.Equity          13.47
Min.Equity          -1.96
End.Equity           9.87
> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
[1] 1.874225
> (aggCorrect <- mean(tStats$Percent.Positive))
[1] 39.47
> (numTrades <- sum(tStats$Num.Trades))
[1] 38
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))
[1] 2.87

In other words, the typical profile for a trend follower, rather than the uninformative analytics from the last post. Furthermore, the position sizing and equity curve chart actually make sense now. Here they are.

To conclude, while it’s possible to model spreads using individual legs, it makes far more sense in terms of analytics to actually examine the performance of the strategy on the actual relationship being traded, which is the spread itself. Furthermore, after constructing the spread as a synthetic instrument, it can be treated like any other regular instrument in the context of analysis in quantstrat.

Thanks for reading.

NOTE: I am a freelance consultant in quantitative analysis on topics related to this blog. If you have contract or full time roles available for proprietary research that could benefit from my skills, please contact me through my LinkedIn here.

A Way To Model Execution On Individual Legs Of A Spread In Quantstrat

In this post, I’ll attempt to address a question I’ve seen tossed around time and again regarding quantstrat.

“How do I model executions on individual underlying instruments in spread trading?”

First off, a disclaimer: this method is a bit of a kludge, and in using it, you’ll lose out on quantstrat’s inbuilt optimization functionality. Essentially, it builds upon the pre-computed signal methodology I described in a previous post.

Essentially, by appending a column with the same name but with different values to two separate instruments, I can “trick” quantstrat into providing me desired behavior by modeling trading on two underlying instruments.

SO here’s the strategy:

Go long 3 shares of the UNG (natural gas) ETF against 1 share of UGAZ (3x bull) when the spread crosses above its 20-day exponential moving average, otherwise, do nothing. Here’s the reasoning as to why:

require(quantmod)
require(quantstrat)
require(IKTrading)

getSymbols("UNG", from="1990-01-01")
getSymbols("DGAZ", from="1990-01-01")
getSymbols("UGAZ", from="1990-01-01")
UNG <- UNG["2012-02-22::"]
UGAZ <- UGAZ["2012-02-22::"]

spread <- 3*OHLC(UNG) - OHLC(UGAZ)

nEMA=20

chart_Series(spread)
add_TA(EMA(Cl(spread), n=nEMA), on=1, col="blue", lwd=1.5)
legend(x=5, y=50, legend=c("EMA 20"),
       fill=c("blue"), bty="n")

With the corresponding plot:

So, as you can see, we have a spread that drifts upward (something to do with the nature of the leveraged ETF)? So, let’s try and capture that with a strategy.

The way I’m going to do that is to precompute a signal–whether or not the spread’s close is above its EMA20, and append that signal to UNG, with the negative of said signal appended to UGAZ, and then encapsulate it in a quantstrat strategy. In this case, there’s no ATR order sizing function or initial equity–just a simple 3 UNG to 1 UGAZ trade.

signal <- Cl(spread) > EMA(Cl(spread), n=nEMA)
UNG$precomputedSig <- signal
UGAZ$precomputedSig <- signal*-1

initDate='1990-01-01'
currency('USD')
Sys.setenv(TZ="UTC")
symbols <- c("UNG", "UGAZ")
stock(symbols, currency="USD", multiplier=1)

strategy.st <- portfolio.st <- account.st <-"spread_strategy"

rm.strat(portfolio.st)
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD')
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

#long rules
add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="precomputedSig", threshold=.5, 
                          relationship="gt", cross=TRUE),
           label="longEntry")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="precomputedSig", threshold=.5, 
                          relationship="lt", cross=TRUE),
           label="longExit")

#short rules
add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="precomputedSig", threshold=-.5, 
                          relationship="lt", cross=TRUE),
           label="shortEntry")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="precomputedSig", threshold=-.5, 
                          relationship="gt", cross=TRUE),
           label="shortExit")

#buy 3
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open", orderqty=3), 
         type="enter", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all", ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)

#short 1
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortEntry", sigval=TRUE, ordertype="market", 
                        orderside="short", replace=FALSE, prefer="Open", orderqty=-1), 
         type="enter", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortExit", sigval=TRUE, orderqty="all", ordertype="market", 
                        orderside="short", replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)

t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)

#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)

So, did our spread trade work?

#trade statistics
tStats <- tradeStats(Portfolios = portfolio.st, use="trades", inclZeroDays=FALSE)
tStats[,4:ncol(tStats)] <- round(tStats[,4:ncol(tStats)], 2)
print(data.frame(t(tStats[,-c(1,2)])))
(aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
(aggCorrect <- mean(tStats$Percent.Positive))
(numTrades <- sum(tStats$Num.Trades))
(meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))

> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
[1] 1.101303
> (aggCorrect <- mean(tStats$Percent.Positive))
[1] 51.315
> (numTrades <- sum(tStats$Num.Trades))
[1] 76
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))
[1] 1.065

Sort of. However, when you think about it–looking at the statistics on a per-instrument basis in a spread trade is a bit of a red herring. After all, outside of a small spread, what one instrument makes, another will lose, so the aggregate numbers should be only slightly north of 1 or 50% in most cases, which is what we see here.

A better way of looking at whether or not the strategy performs is to look at the cumulative sum of the daily P&L.

#portfolio cash PL
portString <- paste0("portfolio.", portfolio.st)
portPL <- .blotter[[portString]]$summary$Net.Trading.PL
portPL <- portPL[-1,] #remove initialization date
plot(cumsum(portPL))

With the following equity curve:

Is this the greatest equity curve? Probably not. In fact, after playing around with the strategy a little bit, it’s better to actually get in at the close of the next day than the open (apparently there’s some intraday mean-reversion).

Furthermore, one thing to be careful of is that in this backtest, I made sure that for UNG, my precomputedSig would only take values 1 and 0, and vice versa for the UGAZ variant, such that I could write the rules I did. If it took the values 1, 0, and -1, or 1 and -1, the results would not make sense.

In conclusion, the method I showed was essentially a method building on a previous technique of pre-computing signals. Doing this will disallow users to use quantstrat’s built-in optimization functionality, but will allow users to backtest individual leg execution.

To answer one last question, if one wanted to short the spread as well, the thing to do using this methodology would be to pre-compute a second column called, say, precomputedSig2, that behaved the opposite way.

Thanks for reading.

NOTE: I am a freelance consultant in quantitative analysis on topics related to this blog. If you have contract or full time roles available for proprietary research that could benefit from my skills, please contact me through my LinkedIn here.

Predicting High Yield with SPY–a Two Part Post

This post will cover ideas from two individuals: David Varadi of CSS Analytics with whom I am currently collaborating on some volatility trading strategies (the extent of which I hope will end up as a workable trading strategy–my current replica of some of VolatilityMadeSimple’s publicly displayed “example” strategies (note, from other blogs, not to be confused with their proprietary strategy) are something that I think is too risky to be traded as-is), and Cesar Alvarez, of Alvarez Quant Trading. If his name sounds familiar to some of you, that’s because it should. He used to collaborate (still does?) with Larry Connors of TradingMarkets.com, and I’m pretty sure that sometime in the future, I’ll cover those strategies as well.

The strategy for this post is simple, and taken from this post from CSS Analytics.

Pretty straightforward–compute a 20-day SMA on the SPY (I use unadjusted since that’s what the data would have actually been). When the SPY’s close crosses above the 20-day SMA, buy the high-yield bond index, either VWEHX or HYG, and when the converse happens, move to the cash-substitute security, either VFISX or SHY.

Now, while the above paragraph may make it seem that VWEHX and HYG are perfect substitutes, well, they aren’t, as no two instruments are exactly alike, which, as could be noted from my last post, is a detail that one should be mindful of. Even creating a synthetic “equivalent” is never exactly perfect. Even though I try my best to iron out such issues, over the course of generally illustrating an idea, the numbers won’t line up exactly (though hopefully, they’ll be close). In any case, it’s best to leave an asterisk whenever one is forced to use synthetics for the sake of a prolonged backtest.

The other elephant/gorilla in the room (depending on your preference for metaphorical animals), is whether or not to use adjusted data. The upside to that is that dividends are taken into account. The *downside* to that is that the data isn’t the real data, and also assumes a continuous reinvestment of dividends. Unfortunately, shares of a security are not continuous quantities–they are discrete quantities made so by their unit price, so the implicit assumptions in adjusted prices can be optimistic.

For this particular topic, Cesar Alvarez covered it exceptionally well on his blog post, and I highly recommend readers give that post a read, in addition to following his blog in general. However, just to illustrate the effect, let’s jump into the script.


getSymbols("VWEHX", from="1950-01-01")
getSymbols("SPY", from="1900-01-01")
getSymbols("HYG", from="1990-01-01")
getSymbols("SHY", from="1990-01-01")
getSymbols("VFISX", from="1990-01-01")

spySma20Cl <- SMA(Cl(SPY), n=20)
clSig <- Cl(SPY) > spySma20Cl
clSig <- lag(clSig, 1)

vwehxCloseRets <- Return.calculate(Cl(VWEHX))
vfisxCloseRets <- Return.calculate(Cl(VFISX))
vwehxAdjustRets <- Return.calculate(Ad(VWEHX))
vfisxAdjustRets <- Return.calculate(Ad(VFISX))

hygCloseRets <- Return.calculate(Cl(HYG))
shyCloseRets <- Return.calculate(Cl(SHY))
hygAdjustRets <- Return.calculate(Ad(HYG))
shyAdjustRets <- Return.calculate(Ad(SHY))

mutualAdRets <- vwehxAdjustRets*clSig + vfisxAdjustRets*(1-clSig)
mutualClRets <- vwehxCloseRets*clSig + vfisxCloseRets*(1-clSig)

etfAdRets <- hygAdjustRets*clSig + shyAdjustRets*(1-clSig)
etfClRets <- hygCloseRets*clSig + shyCloseRets*(1-clSig)

Here are the results:


mutualFundBacktest <- merge(mutualAdRets, mutualClRets, join='inner')
charts.PerformanceSummary(mutualFundBacktest)
data.frame(t(rbind(Return.annualized(mutualFundBacktest)*100, 
                   maxDrawdown(mutualFundBacktest)*100,
                   SharpeRatio.annualized(mutualFundBacktest))))

Which produces the following equity curves:

As can be seen, the choice to adjust or not can be pretty enormous. Here are the corresponding three statistics:

               Annualized.Return Worst.Drawdown Annualized.Sharpe.Ratio..Rf.0..
VWEHX.Adjusted         14.675379       2.954519                        3.979383
VWEHX.Close             7.794086       4.637520                        3.034225

Even without the adjustment, the strategy itself is…very very good, at least from this angle. Let’s look at the ETF variant now.

etfBacktest <- merge(etfAdRets, etfClRets, join='inner')
charts.PerformanceSummary(etfBacktest)
data.frame(t(rbind(Return.annualized(etfBacktest)*100, 
                   maxDrawdown(etfBacktest)*100,
                   SharpeRatio.annualized(etfBacktest))))

The resultant equity curve:

With the corresponding statistics:

             Annualized.Return Worst.Drawdown Annualized.Sharpe.Ratio..Rf.0..
HYG.Adjusted         11.546005       6.344801                       1.4674301
HYG.Close             5.530951       9.454754                       0.6840059

Again, another stark difference. Let’s combine all four variants.

fundsAndETFs <- merge(mutualFundBacktest, etfBacktest, join='inner')
charts.PerformanceSummary(fundsAndETFs)
data.frame(t(rbind(Return.annualized(fundsAndETFs)*100, 
                   maxDrawdown(fundsAndETFs)*100,
                   SharpeRatio.annualized(fundsAndETFs))))

The equity curve:

With the resulting statistics:

               Annualized.Return Worst.Drawdown Annualized.Sharpe.Ratio..Rf.0..
VWEHX.Adjusted         17.424070       2.787889                       4.7521579
VWEHX.Close            11.739849       3.169040                       3.8715923
HYG.Adjusted           11.546005       6.344801                       1.4674301
HYG.Close               5.530951       9.454754                       0.6840059

In short, while the strategy itself seems strong, the particular similar (but not identical) instruments used to implement the strategy make a large difference. So, when backtesting, make sure to understand what taking liberties with the data means. In this case, by turning two levers, the Sharpe Ratio varied from less than 1 to above 4.

Next, I’d like to demonstrate a little trick in quantstrat. Although plenty of examples of trading strategies only derive indicators (along with signals and rules) from the market data itself, there are also many strategies that incorporate data from outside simply the price action of the particular security at hand. Such examples would be many SPY strategies that incorporate VIX information, or off-instrument signal strategies like this one.

The way to incorporate off-instrument information into quantstrat simply requires understanding what the mktdata object is, which is nothing more than an xts type object. By default, a security may originally have just the OHLCV and open interest columns. Most demos in the public space generally use data only from the instruments themselves. However, it is very much possible to actually pre-compute signals.

Here’s a continuation of the script to demonstrate, with a demo of the unadjusted HYG leg of this trade:

####### BOILERPLATE FROM HERE
require(quantstrat)

currency('USD')
Sys.setenv(TZ="UTC")
symbols <- "HYG"
stock(symbols, currency="USD", multiplier=1)
initDate="1990-01-01"

strategy.st <- portfolio.st <- account.st <- "preCalc"
rm.strat(portfolio.st)
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD')
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)
######### TO HERE

clSig <- Cl(SPY) > SMA(Cl(SPY), n=20)

HYG <- merge(HYG, clSig, join='inner')
names(HYG)[7] <- "precomputed_signal"

#no parameters or indicators--we precalculated our signal

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="precomputed_signal", threshold=.5, 
                          relationship="gt", cross=TRUE),
           label="longEntry")


add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="precomputed_signal", threshold=.5, 
                          relationship="lt", cross=TRUE),
           label="longExit")

add.rule(strategy.st, name="ruleSignal",
         arguments=list(sigcol="longEntry", sigval=TRUE, orderqty=1, ordertype="market",
                        orderside="long", replace=FALSE, prefer="Open"),
         type="exit", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all", ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)

#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)

#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)

As you can see, no indicators computed from the actual market data, because the strategy used a pre-computed value to work off of. The lowest-hanging fruit of applying this methodology, of course, would be to append the VIX index as an indicator for trading strategies on the SPY.

And here are the results, trading a unit quantity:

> data.frame(round(t(tradeStats(portfolio.st)[-c(1,2)]),2))
                      HYG
Num.Txns           217.00
Num.Trades         106.00
Net.Trading.PL      36.76
Avg.Trade.PL         0.35
Med.Trade.PL         0.01
Largest.Winner       9.83
Largest.Loser       -2.71
Gross.Profits       67.07
Gross.Losses       -29.87
Std.Dev.Trade.PL     1.67
Percent.Positive    50.00
Percent.Negative    50.00
Profit.Factor        2.25
Avg.Win.Trade        1.27
Med.Win.Trade        0.65
Avg.Losing.Trade    -0.56
Med.Losing.Trade    -0.39
Avg.Daily.PL         0.35
Med.Daily.PL         0.01
Std.Dev.Daily.PL     1.67
Ann.Sharpe           3.33
Max.Drawdown        -7.24
Profit.To.Max.Draw   5.08
Avg.WinLoss.Ratio    2.25
Med.WinLoss.Ratio    1.67
Max.Equity          43.78
Min.Equity          -1.88
End.Equity          36.76

And the corresponding position chart:

Lastly, here are the vanguard links for VWEHX and VFISX. Apparently, neither charge a redemption fee. I’m not sure if this means that they can be freely traded in a systematic fashion, however.

In conclusion, hopefully this post showed a potentially viable strategy, understanding the nature of the data you’re working with, and how to pre-compute values in quantstrat.

Thanks for reading.

Note: I am a freelance consultant in quantitative analysis on topics related to this blog. If you have contract or full time roles available for proprietary research that could benefit from my skills, please contact me through my LinkedIn here.

Nuts and Bolts of Quantstrat, Part IV

This post will provide an introduction to the way that rules work in quantstrat. It will detail market orders along with order-sizing functions (limit orders will be saved for a later date). After this post, readers should be able to understand the strategies written in my blog posts, and should be able to write their own. Unlike indicators and signals, rules usually call one function, which is called “ruleSignal” (there is a function that is specifically designed for rebalancing strategies, but it’s possible to do that outside the bounds of quantstrat). For all intents and purposes, this one function handles all rule executions. However, that isn’t to say that rules cannot be customized, as the ruleSignal function has many different arguments that can take in one of several values, though not all permutations will be explored in this post. Let’s take a look at some rules:

#rules
if(atrOrder) {
  
  add.rule(strategy.st, name="ruleSignal", 
           arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
                          orderside="long", replace=FALSE, prefer="Open", 
                          osFUN=osDollarATR, tradeSize=tradeSize, 
                          pctATR=pctATR, atrMod="X"), 
           type="enter", path.dep=TRUE)
} else { 
  add.rule(strategy.st, name="ruleSignal", 
           arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
                          orderside="long", replace=FALSE, prefer="Open", 
                          osFUN=osMaxDollar, tradeSize=tradeSize, maxSize=tradeSize), 
           type="enter", path.dep=TRUE)
}

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all", 
                        ordertype="market", orderside="long", 
                        replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="filterExit", sigval=TRUE, orderqty="all", 
                        ordertype="market", orderside="long", replace=FALSE, 
                        prefer="Open"), 
         type="exit", path.dep=TRUE)

In this case, the first thing to note is that as quantstrat is an R library, it can also incorporate basic programming concepts into the actual strategy formulation. In this case, depending on a meta-parameter (that is, a parameter not found in the argument of any indicator, signal, or rule) called atrOrder (a boolean), I can choose which rule I wish to add to the strategy configuration.

Next, here’s the format for adding a rule:

1) The call to add.rule
2) The name of the strategy (strategy.st)
3) The name of the strategy function (this is usually “ruleSignal”)
4) The arguments to ruleSignal:

a) The signal column (sigCol)
b) the value that signals a trigger (sigVal)
c) the order type (ordertype)
d) the order side (orderside)
e) to replace any other open signal (replace)
f) The order quantity (orderqty) is no order-sizing function is used.
g) the preferred price (prefer, defaults to Close, but as quantstrat is a next-bar system, I use the open)
h) the order sizing function (osFUN)
i) the arguments to the order-sizing function.
j) There are other arguments to different order types, but we’ll focus on market orders for this post.

5) The rule type (type), which will comprise either “enter” or “exit” for most demos
6) The path.dep argument, which is always TRUE
7) (Not shown) the label for the rule. If you’re interested in writing your demos as quickly as possible, these are not necessary if your entry and exit rules are your absolute final points of logic in your backtest. However, if you wish to look at your orders in detail, or use stop-losses/take-profit orders, then the rules need labels, as well.

While most of the logic to adding your basic rule is almost always boilerplate outside the arguments to ruleSignal, it’s the arguments to ruleSignal that allow users to customize rules.

The sigCol argument is a string that has the exact name of the signal column that you wish to use to generate your entries (or exits) from. This is the same string that went into the label argument of your add.signal function calls. In quantstrat, labels effectively act as logical links between indicators, signals, rules, and more.

The sigVal argument is what value to use to trigger rule logic. Since signal output (so far) is comprised of ones (TRUE) and zeroes (FALSE), I set my sigVal to TRUE. It is possible, however, to make a sigSum rule and then allow the sigVal argument to take other values.

The ordertype argument is the order type. For most of my demos that I’ve presented thus far, I’ve mostly used “market” type orders, which are the simplest. Market orders execute at the next bar after receiving the signal. They do not execute on the signal bar, but the bar after the signal bar. On daily data, this might cause some P/L due to gaps, but on intraday data, the open of the next bar should be very similar to the close of current bar. One thing to note is that using monthly data, quantstrat uses current-bar execution.

The orderside argument takes one of two values–“long” or “short”. This separates rule executions into two bins, such that long sells won’t work on short positions and vice versa. It also serves to add clarity and readability to strategy specifications.

The replace argument functions in the following way: if TRUE, it overrides any other signal on the same day. Generally, I avoid ever setting this to true, as order sets (not shown in this post) exist deliberately to control order replacement. However, for some reason, it defaults to TRUE in quantstrat, so make sure to set it to FALSE whenever you write a strategy.

The orderqty argument applies only when there’s no osFUN specified. It can take a flat value (E.G. 1, 2, 100, etc.), or, when the rule type is “exit”, a quantity of “all”, to flatten a position. In all the sell rules I use in my demos, my strategies do not scale out of positions, but merely flatten them out.

The prefer argument exists for specifying what aspect of a bar a trade will get in on. Quantstrat by default executes at the close of the next bar. I set this argument to “Open” instead to minimize the effect of the next bar transaction.

The osFUN specifies the order-sizing function to use. Unlike the functions passed into the name arguments in quantstrat (for indicators, signals, or rules), the osFUN argument is actually a function object (that is, it’s the actual function, rather than its name) that gets passed in as an argument. Furthermore, and this is critical: all arguments *to* the order-sizing function must be passed into the arguments for ruleSignal. They are covered through the ellipsis functionality that most R functions include. The ellipsis means that additional arguments can be passed in, and these additional arguments usually correspond to functions used inside the original function that’s called. This, of course, has the potential to violate the black-box modular programming paradigm by assuming users know the inner-workings of pre-existing code, but it offers additional flexibility in instances such as these. So, to give an example, in my entry rule that uses the osDollarATR order-sizing function, arguments such as pctATR and tradeSize are not arguments to the ruleSignal function, but to the osDollarATR function. Nevertheless, the point to pass them in when constructing a quantstrat strategy is in the arguments to ruleSignal.

If you do not wish to use an osFUN, simply use a flat quantity, such as 100, or if using exit type orders, use “all” to flatten a position.

Moving outside the arguments to ruleSignal, we have several other arguments:

The type argument takes one of several values–but “enter” and “exit” are the most basic. They do exactly as they state. There are other rule types, such as “chain” (for stop-losses), which have their own mechanics, but for now, know that “enter” and “exit” are the two basic rules you need to get off the ground.

The path.dep argument should always be TRUE for the ruleSignal function.

Finally, add.rule also contains a label argument that I do not often use in my demos, as usually, my rules are the last point of my logic. However, if one wants to do deeper strategy analysis using the order book, then using these labels is critical.

After adding rules, you can simply call applyStrategy and run your backtest. Here’s an explanation of how that’s done:

#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)

#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)

As an explanation, I enclose the applyStrategy call in some code to print how much time the backtest took. Generally, on these twelve years of daily data, a single market may take between several seconds to thirty seconds (if a strategy has hundreds of trades per market).

The next four lines essentially update the objects initialized in order of dependency: first the portfolio, then the account for a given date range (the duration of the backtest), and then compute the end equity.

This concludes the basic nuts and bolts of creating a basic nuts and bolts strategy in quantstrat. On this blog, when I make more use of other features, I’ll dedicate other nuts and bolts sections so that readers can use all of quantstrat’s features more efficiently.

Thanks for reading.

Nuts and Bolts of Quantstrat, Part III

This post will focus on signals in quantstrat.

In comparison to indicators, signals in quantstrat are far more cut-and-dry, as they describe the interaction of indicators with each other–whether that indicator is simply the close price (“Close”), or a computed indicator, there are only so many ways indicators can interact, and the point of signals is to provide the user with a way of describing these relationships–is one greater than another, is the concern only when the cross occurs, does the indicator pass above or below a certain number, etc.

Here’s the code that will provide the example for the demonstration, from the atrDollarComparison strategy:

#signals
add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("Close", "sma"), relationship="gt"),
           label="filter")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=buyThresh, 
                          relationship="lt", cross=FALSE),
           label="rsiLtThresh")

add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("filter", "rsiLtThresh"), cross=TRUE),
           label="longEntry")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=sellThresh,
                          relationship="gt", cross=TRUE),
           label="longExit")

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "sma"), relationship="lt"),
           label="filterExit")

Adding signals to a strategy has a very similar format to adding indicators. The structure is very similar:

1) The call to add.signal
2) The name of the strategy (again, strategy.st makes this very simple)
3) The name of the signal function (the majority of which are on display in the preceding block of code)
4) The arguments to said signal function, passed in the same way they are to indicators (that is, arguments=list(args)), but which are far more similar compared to indicators
5) The label for the signal column, which is highly similar to the labeling for indicator columns.

The first two steps are identical to the add.indicator step, except with add.signal instead of add.indicator. This is cut and dry.

Beyond this, all of the signal functions I use are presented above. They are:

sigComparison, sigThreshold, sigAND, and sigCrossover.

The arguments for all four are very similar. They contain some measure of columns, a threshold, a relationship between the first and second column (or between the first column and the threshold), and whether the signal should return TRUE for the entire duration of the relationship being true, or only on the first day, with the cross argument.

Relationships are specified with a two or three character identifier: “gt” stands for greater than (E.G. SMA50 > SMA200), “gte” stands for greater than or equal to, “lt” and “lte” work similarly, and “eq” stands for equal to (which may be useful for certain logic statements such as “stock makes a new seven-day low”, which can be programmed by comparing the close to the running seven-day min, and checking for equality).

Here’s an explanation of all four sig functions:

The sigComparison function compares two columns, and will return TRUE (aka 1) so long as the specified relationship comparing the first column to the second holds. E.G. it will return 1 if you specify SMA50 > SMA200 for every timestamp (aka bar, for those using OHLC data) that the 50-day SMA is greater than the 200-day SMA. The sigComparison function is best used for setting up filters (EG the classic Close > SMA200 formation). This function takes two columns, and a relationship comparing the first to the second columns.

The sigCrossover is identical to the above, except only returns TRUE on the timestamp (bar) that the relationship moves from FALSE to TRUE. E.G. going with the above example, you would only see TRUE the day that the SMA50 first crossed over the SMA200. The sigCrossover is useful for setting up buy or sell orders in trend-following strategies.

The sigThreshold signal is identical to the two above signals (depending on whether cross is TRUE or FALSE), but instead uses a fixed quantity to compare one indicator to, passed in via the threshold argument. For instance, one can create a contrived example of an RSI buy order with a sigCrossover signal with an RSI indicator and an indicator that’s nothing but the same identical buy threshold all the way down, or one can use the sigThreshold function wherever oscillator-type indicators or uniform-value type indicators (E.G. indicators transformed with a percent rank), wherever all such indicators are involved.

Lastly, the sigAND signal function, to be pedantic, can also be called colloquially as sigIntersect. It’s a signal function I wrote (from my IKTrading package) that checks if multiple signals (whether two or more) are true at the same time, and like the sigThreshold function, can be set to either return all times that the condition holds, or the first day only. I wrote sigAND so that users would be able to structurally tie up multiple signals, such as an RSI threshold cross coupled with a moving-average filter. While quantstrat does have a function called sigFormula, it involves quoted code evaluation, which I wish to minimize as much as possible. Furthermore, using sigAND allows users to escalate the cross clause, meaning that the signals that are used as columns can be written as comparisons, rather than as crosses. E.G. in this RSI 20/80 filtered on SMA200 strategy, I can simply compare if the RSI is less than 20, and only generate a buy rule at the timestamp after both RSI is less than 20 AND the close is greater than its SMA200. It doesn’t matter whether the close is above SMA200 and the RSI crosses under 20, or if the RSI was under 20, and the close crossed above its SMA200. Either combination will trigger the signal.

One thing to note regarding columns passed as arguments to the signals: quantstrat will do its best to “take an educated guess” regarding which column the user attempts to refer to. For instance, when using daily data, the format may often be along the lines of XYZ.Open XYZ.High XYZ.Low XYZ.Close, so when “Close” is one of the arguments, quantstrat will make its best guess that the user means the XYZ.Close column. This is also, why, once again, I stress that reserved keywords (OHLC keywords, analogous tick data keywords) should not be used in labeling. Furthermore, unlike indicators, whose output will usually be something along the lines of FUNCTION_NAME.userLabel, labels for signals are as-is, so what one passes into the label argument is what one gets.

To put it together, here is the chunk of code again, and the English description of what the signals in the chunk of code do:

#signals
add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("Close", "sma"), relationship="gt"),
           label="filter")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=buyThresh, 
                          relationship="lt", cross=FALSE),
           label="rsiLtThresh")

add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("filter", "rsiLtThresh"), cross=TRUE),
           label="longEntry")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=sellThresh,
                          relationship="gt", cross=TRUE),
           label="longExit")

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "sma"), relationship="lt"),
           label="filterExit")

1) The first signal checks to see if the “Close” column is greater than (“gt”) the “sma” column (which had a setting of 200), and is labeled “filter”.
2) The second signal checks to see if the “rsi” column is less than (“lt”) the threshold of buyThresh (which was defined earlier as 20), and is labeled as “rsiLtThresh”.
3) The third signal checks when both of the above signals became TRUE for the first time, until one or the other condition becomes false, and is labeled as “longEntry”. NB: the signals themselves do not place the order–I just like to use the label “longEntry” as this allows code in the rules logic to be reused quicker.
4) The fourth signal checks if the “rsi” column crossed over the sell threshold (80), and is labeled as “longExit”.
5) The fifth signal checks if the “Close” column crossed under the “sma” column, and is labeled “filterExit”.

In quantstrat, it’s quite feasible to have multiple signals generate entry orders, and multiple signals generate exit orders. However, make sure that the labels are unique.

The next post will cover rules.

Thanks for reading.

Nuts and Bolts of Quantstrat, Part II

Last week, I covered the boilerplate code in quantstrat.

This post will cover parameters and adding indicators to strategies in quantstrat.

Let’s look at a the code I’m referring to for this walkthrough:

#parameters
pctATR <- .02
period <- 10
atrOrder <- TRUE

nRSI <- 2
buyThresh <- 20
sellThresh <- 80
nSMA <- 200

#indicators
add.indicator(strategy.st, name="lagATR", 
              arguments=list(HLC=quote(HLC(mktdata)), n=period), 
              label="atrX")

add.indicator(strategy.st, name="RSI",
              arguments=list(price=quote(Cl(mktdata)), n=nRSI),
              label="rsi")

add.indicator(strategy.st, name="SMA",
              arguments=list(x=quote(Cl(mktdata)), n=nSMA),
              label="sma")

This code contains two separate chunks–parameters and indicators. The parameters chunk is simply a place to store values in one area, and then call them as arguments to the add.indicator and add.signal functions. Parameters are simply variables assigned to values that can be updated when a user wishes to run a demo (or in other ways, when running optimization processes).

Indicators are constructs computed from market data, and some parameters that dictate the settings of the function used to compute them. Most well-known indicators, such as the SMA (simple moving average), EMA, and so on, usually have one important component, such as the lookback period (aka the ubiquitous n). These are the parameters I store in the parameters chunk of code.

Adding an indicator in quantstrat has five parts to it. They are:

1) The add.indicator function call
2) The name of the strategy to add the indicator to (which I always call strategy.st, standing for strategy string)
3) The name of the indicator function, in quotes (E.G. such as “SMA”, “RSI”, etc.)
4) The arguments to the above indicator function, which are the INPUTS in this statement arguments=list(INPUTS)
5) The label that signals and possibly rules will use–which is the column name in the mktdata object.

Notice that the market data (mktdata) input to the indicators has a more unique input style, as it’s wrapped in a quote() function call. This quote function call essentially tells the strategy that the strategy will obtain the object referred to in the quotes later. The mktdata object is initially the OHLCV(adjusted) price time series one originally obtains from yahoo (or elsewhere), as far as my demos will demonstrate for the foreseeable future. However, the mktdata object will later come to contain all of the indicators and signals added within the strategy. So because of this, here are some functions that one should familiarize themselves with regarding some time series data munging:

Op: returns all columns in the mktdata object containing the term “Open”
Hi: returns all columns in the mktdata object containing the term “High”
Lo: returns all columns in the mktdata object containing the term “Low”
Cl: returns all columns in the mktdata object containing the term “Close”
Vo: returns all columns in the mktdata object containing the term “Volume”
HLC: returns all columns in the mktdata object containing “High”, “Low”, or “Close”.
OHLC: same as above, but includes “Open”.

These all ignore case.

For these reasons, please avoid using these “reserved” terms when labeling (that is, column naming in step 5) your indicators/signals/rules. One particularly easy mistake to make is using the word “slow”. For instance, a naive labeling convention may be to use “maFast” and “maSlow” as labels for, say, a 50-day and 200-day SMA, respectively, and then maybe implement an indicator that uses an HLC for an argument, such as ATR. This may create errors down the line when more than one column has the name “Low”. In the old (CRAN) version of TTR–that is, the version that gets installed if one simply types in

install.packages("TTR")

as opposed to

install.packages("TTR",  repos="http://R-Forge.R-project.org")

the SMA function will still append the term “Close” to the output. I’m sure some of you have seen some obscure error when calling applyStrategy. It might look something like this:

length of 'dimnames' [2] not equal to array extent

This arises as the result of bad labeling. The CRAN version of TTR runs into this from time to time, and if you’re stuck on that version, a kludge to work around this is instead of using

x=quote(Cl(mktdata))

to use

quote(Cl(mktdata)[,1])

instead. That [,1] specifies only the first column in which the term “Close” appears. However, I simply recommend upgrading to a newer version of TTR from R-forge. On Windows, this means using R 3.0.3 rather than 3.1.1, due to R-forge’s lack of binaries for Windows for the most recent version of TTR (only source is available), at least as of the time of this writing.

On a whole, however, I highly recommend avoiding reserved market data keywords (open, high, low, close, volume, and analogous keywords for tick data) for labels.

One other aspect to note about labeling indicators is that the indicator column name is not merely the argument to “label”, but rather, the label you provide is appended onto the output of the function. In DSTrading and IKTrading, for instance, all of the indicators (such as FRAMA) come with output column headings. So, when computing the FRAMA of a time series, you may get something like this:

> test <- FRAMA(SPY)
> head(test)
           FRAMA trigger
2000-01-06    NA      NA
2000-01-07    NA      NA
2000-01-10    NA      NA
2000-01-11    NA      NA
2000-01-12    NA      NA
2000-01-13    NA      NA

When adding indicators, the user-provided label will come after a period following the initial column name output, and the column name will be along the lines of “FunctionOutput.userLabel”.

Beyond pitfalls and explanations of labeling, the other salient aspect of indicators is the actual indicator function that’s called, and how its arguments function.

When adding indicators, I use the following format:

add.indicator(strategy.st, name="INDICATOR_FUNCTION",
              arguments=list(x=quote(Cl(mktdata)), OTHERINPUTS),
              label="LABEL")

This is how these two aspects work:

The INDICATOR_FUNCTION is an actual R function that should take in some variant of an OHLC object (whether one column–most likely close, HLC, or whatever else). Functions such as RSI, SMA, and lagATR (from my IKTrading library) are all examples of such functions. To note, there is nothing “official” as opposed to “custom” about the functions I use for indicators. Indicators are merely R functions (that can be written by any R user) that take in a price series as one of the arguments.

The inputs to these functions are enclosed in the arguments input to the add.indicator function. That is, the part of the syntax that looks like this:

arguments=list(x=quote(Cl(mktdata)), n=nSMA)

These arguments are the inputs to the function. For instance, if one would write:

args(SMA)

One would see:

function (x, n = 10, ...) 

In this case, x is a time series based on the market data (that is, the mktdata object), and n is a parameter. As pointed out earlier, the syntax for the mktdata involves the use of the quote function. However, all other parameters to the SMA (or any other) function call are static, at least per individual backtest (these can vary when doing optimization/parameter exploration). Thus, for the classic 200-day simple moving average, the appropriate syntax would contain:

add.indicator(strategy.st, "SMA",
              arguments=list(x=quote(Cl(mktdata)), n=200),
              label="sma")

In my backtests, I store the argument to n above the add.indicator call in my parameters chunk of code for ease of location. The reason for this is that when adding multiple indicators, signals, and rules, it’s fairly easy to lose track of a hard-coded value among the interspersed code, so I prefer to keep my numerical values collected in one place and reference them in the actual indicator, signal, and rule syntax.

Lastly, one final piece of advice is that when constructing a strategy, one need not have all the signals and rules implemented just to check how the indicators will be added to the mktdata object. Instead, try this, after running the code through the add.indicator syntax and no further if you’re ever unsure what your mktdata object will look like. Signals (at least in my demos) will start off with a commented

#signals

bit of syntax. If you see that line, you know that there are no more indicators to add. In any case, the following is a quick way of inspecting indicator output.

test <- applyIndicators(strategy.st, mktdata=OHLC(SOME_DATA_HERE))

For example, using XLB:

test <- applyIndicators(strategy.st, mktdata=OHLC(XLB))
head(test, 12)

Which would give the output:

> head(test, 12)
           XLB.Open XLB.High  XLB.Low XLB.Close atr.atrX  EMA.rsi SMA.sma
2003-01-02 15.83335 16.09407 15.68323  16.08617       NA       NA      NA
2003-01-03 16.03877 16.05457 15.91235  15.99926       NA       NA      NA
2003-01-06 16.10988 16.41011 16.10988  16.30740       NA 78.00000      NA
2003-01-07 16.38641 16.38641 16.18098  16.25209       NA 60.93750      NA
2003-01-08 16.19679 16.19679 15.80964  15.83335       NA 14.13043      NA
2003-01-09 15.95186 16.12568 15.92026  16.07827       NA 54.77099      NA
2003-01-10 15.94396 16.27579 15.94396  16.25209       NA 72.94521      NA
2003-01-13 16.35480 16.37061 16.12568  16.18098       NA 54.89691      NA
2003-01-14 16.12568 16.25999 16.12568  16.25999       NA 70.89800      NA
2003-01-15 16.17308 16.17308 15.90445  15.99136       NA 20.77648      NA
2003-01-16 15.95976 16.18098 15.95976  16.07827       NA 45.64200      NA
2003-01-17 15.97556 16.13358 15.92816  15.95186 0.281271 23.85808      NA

This allows a user to see how the indicators will be appended to the mktdata object in the backtest. If the call to applyIndicators fails, it means that there most likely is an issue with labeling (column naming).

Next week, I’ll discuss signals, which are a bit more defined in scope.

Thanks for reading.

Nuts and Bolts of Quantstrat, Part I

Recently, I gave a webinar on some introductory quantstrat. Here’s the link.

So to follow up on it, I’m going to do a multi-week series of posts delving into trying to explain the details of parts of my demos, so as to be sure that everyone has a chance to learn and follow along with my methodologies, what I do, and so on. To keep things simple, I’ll be using the usual RSI 20/80 filtered on SMA 200 demo. This post will deal with the initial setup of any demo–code which will be largely similar from demo to demo.

Let’s examine this code:

require(IKTrading)
require(quantstrat)
require(PerformanceAnalytics)

initDate="1990-01-01"
from="2003-01-01"
to="2012-12-31"
options(width=70)

source("demoData.R")

The first three lines load the libraries I use in my demos. In R, libraries are loaded with a single line. However, installation procedures may vary from operating system to operating system. Windows systems are the least straightforward, while macs can use unix functionality to function in identical ways to linux machines. It’s often good practice to place functions used repeatedly into a package, which is R’s own version of encapsulation and information hiding. Packages don’t always have to be open-sourced to the internet, and in many cases, some are used just as local repositories. My IKTrading package started off as such a case; it’s simply a toolbox that contains functionality that isn’t thematically attributable in other places.

The next three lines, dealing with dates, all have separate purposes.

The initDate variable needs a date that must occur before the start of data in a backtest. If this isn’t the case, the portfolio will demonstrate a massive drawdown on the initialization date, and many of the backtest statistics will be misleading or nonsensical.

The from and to variables are endpoints on the data that the demoData.R script will use to fetch from yahoo (or elsewhere). The format is yyyy-mm-dd, which means four digit year, two digit month (E.G. January is “01”), and two digit day, in that order.

In some cases, I may write the code:

to=as.character(Sys.Date())

This just sets the current to date to the time that I run the demonstration. Although it may affect the replication of the results, thanks to some of the yearly metrics I’ve come to utilize, those wishing to see the exact day of the end of the data would be able to. However, in cases that I use data to the present, it’s often simply an exploration of the indicator as opposed to trying to construct a fully-fledged trading system.

The options(width=70) line simply controls the width of output to my R console.

The source line is a way to execute other files in the specified directory. Sourcing files works in similar ways to specifying a file path. So if, from your current directory, there’s a file you want to source called someFile, you may write a command such as source(“someFile.R”), but if said file is in a different directory, you would want to use the standard unix file navigation notation to execute it. For instance, if my directory was in “IKTrading”, rather than “IKTrading/demo”, I would write source(“demo/demoData.R”). Note that this notation is relative to my current directory. To see your current working directory, type:

getwd()

To navigate among your working directories, use the setwd command, such as:

setwd("/home/myAccount/someDirectory/NextDirectory/")

In order to obtain the data, let’s look at the demoData.R file once again.

options("getSymbols.warning4.0"=FALSE)

currency('USD')
Sys.setenv(TZ="UTC")

symbols <- c("XLB", #SPDR Materials sector
             "XLE", #SPDR Energy sector
             "XLF", #SPDR Financial sector
             "XLP", #SPDR Consumer staples sector
             "XLI", #SPDR Industrial sector
             "XLU", #SPDR Utilities sector
             "XLV", #SPDR Healthcare sector
             "XLK", #SPDR Tech sector
             "XLY", #SPDR Consumer discretionary sector
             "RWR", #SPDR Dow Jones REIT ETF
             
             "EWJ", #iShares Japan
             "EWG", #iShares Germany
             "EWU", #iShares UK
             "EWC", #iShares Canada
             "EWY", #iShares South Korea
             "EWA", #iShares Australia
             "EWH", #iShares Hong Kong
             "EWS", #iShares Singapore
             "IYZ", #iShares U.S. Telecom
             "EZU", #iShares MSCI EMU ETF
             "IYR", #iShares U.S. Real Estate
             "EWT", #iShares Taiwan
             "EWZ", #iShares Brazil
             "EFA", #iShares EAFE
             "IGE", #iShares North American Natural Resources
             "EPP", #iShares Pacific Ex Japan
             "LQD", #iShares Investment Grade Corporate Bonds
             "SHY", #iShares 1-3 year TBonds
             "IEF", #iShares 3-7 year TBonds
             "TLT" #iShares 20+ year Bonds
)

#SPDR ETFs first, iShares ETFs afterwards
if(!"XLB" %in% ls()) { 
  suppressMessages(getSymbols(symbols, from=from, to=to, src="yahoo", adjust=TRUE))  
}

stock(symbols, currency="USD", multiplier=1)

The getSymbols.warning4.0=FALSE line is simply to remove the initial warning that comes from getting symbols from yahoo. It makes no difference to how the demo runs.

The next two lines are critical.

currency('USD')
Sys.setenv(TZ="UTC")

Currency must be initialized for every demo. Thus far, I’ve yet to see it set to anything besides USD (U.S. Dollars), however, the accounting analytics back-end systems need to know what currency the prices are listed in. So the currency line cannot be skipped, or the demo will not work.

Next, the Sys.setenv(TZ=”UTC”) line is necessary because if you look at, say, the data of XLB, and look at the class of its index, here’s what you see:

> head(XLB)
           XLB.Open XLB.High  XLB.Low XLB.Close XLB.Volume XLB.Adjusted
2003-01-02 15.83335 16.09407 15.68323  16.08617  401095.00        15.58
2003-01-03 16.03877 16.05457 15.91235  15.99926   79105.20        15.50
2003-01-06 16.10988 16.41011 16.10988  16.30740  377806.43        15.80
2003-01-07 16.38641 16.38641 16.18098  16.25209  390463.27        15.75
2003-01-08 16.19679 16.19679 15.80964  15.83335  201496.76        15.34
2003-01-09 15.95186 16.12568 15.92026  16.07827   82522.54        15.58

class(index(XLB))

> class(index(XLB))
[1] "Date"

Since the index of the data is a Date type object, in order for certain orders to work, such as chain rules (which contain stop losses and take profits), the timezone has to be set as UTC, since that’s the time zone for a “Date” class object. If the demo uses the system’s default timezone instead, the timestamps will not match, and so, there will be order failures.

The symbols assignment is simply one long string vector. Here it is, once again:


symbols <- c("XLB", #SPDR Materials sector
             "XLE", #SPDR Energy sector
             "XLF", #SPDR Financial sector
             "XLP", #SPDR Consumer staples sector
             "XLI", #SPDR Industrial sector
             "XLU", #SPDR Utilities sector
             "XLV", #SPDR Healthcare sector
             "XLK", #SPDR Tech sector
             "XLY", #SPDR Consumer discretionary sector
             "RWR", #SPDR Dow Jones REIT ETF
             
             "EWJ", #iShares Japan
             "EWG", #iShares Germany
             "EWU", #iShares UK
             "EWC", #iShares Canada
             "EWY", #iShares South Korea
             "EWA", #iShares Australia
             "EWH", #iShares Hong Kong
             "EWS", #iShares Singapore
             "IYZ", #iShares U.S. Telecom
             "EZU", #iShares MSCI EMU ETF
             "IYR", #iShares U.S. Real Estate
             "EWT", #iShares Taiwan
             "EWZ", #iShares Brazil
             "EFA", #iShares EAFE
             "IGE", #iShares North American Natural Resources
             "EPP", #iShares Pacific Ex Japan
             "LQD", #iShares Investment Grade Corporate Bonds
             "SHY", #iShares 1-3 year TBonds
             "IEF", #iShares 3-7 year TBonds
             "TLT" #iShares 20+ year Bonds
)

There is nothing particularly unique about it. However, I structured the vector so as to be able to comment with the description of each ETF next to its ticker string for the purposes of clarity.

From there, the file gets the symbols from yahoo. The extra verbosity around the command is simply to suppress any output to the screen. Here’s the line of code that does this:

#SPDR ETFs first, iShares ETFs afterwards
if(!"XLB" %in% ls()) { 
  suppressMessages(getSymbols(symbols, from=from, to=to, src="yahoo", adjust=TRUE))  
}

I can control whether or not to rerun the data-gathering process by removing XLB from my current working environment. This isn’t the most general way of controlling the data cache (a more general boolean would be better general style), but it works for the examples I use. If I keep XLB in my working environment, then this line is skipped altogether, to speed up the backtest.

Lastly, the backtest needs the instrument specifications. This is the line of code to do so:

stock(symbols, currency="USD", multiplier=1)

Although it looks fairly trivial at the moment, once a backtest would start dealing with futures, contract multiplier specifications, and other instrument-specific properties, this line becomes far less trivial than it looks.

Moving back to the main scripts, here is the rest of the initialization boilerplate:

#trade sizing and initial equity settings
tradeSize <- 100000
initEq <- tradeSize*length(symbols)

strategy.st <- portfolio.st <- account.st <- "DollarVsATRos"
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

The tradeSize and initEq variables are necessary in order to compute returns at the end of a backtest. Furthermore, tradeSize is necessary for the osDollarATR order-sizing function.

Next, I name the strategy, portfolio, and account–all with the same name. The x <- y <- z <- "xyz" format is a multi-assignment syntax that should be used only for assigning several objects that are all initially (or permanently) identical to one another, such as initializing multiple xts objects of the same length.

Next, the removal of the strategy is necessary for rerunning the strategy. If the strategy object exists, and a user attempts to rerun the demo, the demo will crash. Always make sure to remove the strategy.

Next, we have the three initialization steps. Due to the dependencies between the portfolio, account, and orders, the portfolio must be initialized before the account, and it also must be initialized before the orders.

To initialize the portfolio, one needs to name the portfolio something, have a vector of character strings that represent the symbols passed in as the symbols argument, an initial date (initDate), and a currency. This currency was defined earlier in the demoData.R file.

The account initialization replaces the symbols argument with a portfolios argument, and adds an initEq argument, from which to compute returns.

Lastly, the orders initialization needs only a portfolio to reference, and a date from which to begin transactions (initDate, which is earlier than the beginning of the data).

Lastly, we initialize the strategy in the form of the line:

strategy(strategy.st, store=TRUE)

This is where we put all our indicators, signals, and rules. Without supplying this line to the demo, none of the indicators, signals, and rules will know what strategy object to look for, and the demo will crash.

This concludes the initial boilerplate walkthrough. Next: parameters and indicators.

Thanks for reading.

The Limit of ATR Order Sizing

Before beginning this post, I’d like to notify readers that I have a webcast tomorrow (Wednesday, Sep. 3) at 4:30 EST for Big Mike’s Trading. Those that can follow the code and the analytics on this blog will see nothing new, but for those that effectively “nod and wait for the punchline” in the form of the equity curve, I’ll demonstrate how to build a strategy “in real time”.

Here’s the link.

Now onto the post:

While the last post showed how ATR did a better job than raw dollar positions of equalizing risk in the form of standard deviations across instruments, it isn’t the be-all, end-all method of order sizing. Something I learned about recently was portfolio component expected shortfall (along with portfolio component standard deviation). The rabbit hole on these methods runs very deep, including to a paper in the Journal of Risk. To give a quick summary of this computation, it’s one that takes into account not just the well-known mean and covariance, but also interactions between higher order moments, such as co-skewness, and co-kurtosis. The actual details of the math behind this is quite extensive, but luckily, it’s already programmed into the PerformanceAnalytics package, so computing it is as simple as calling a pre-programmed procedure. This demo will, along the way of making yet another comparison between ATR and dollar order sizes, demonstrate one way of doing this.

For those that are unfamiliar with the terminology, expected shortfall is also known as conditional value-at-risk (aka CVaR), which is a coherent risk measure, while regular value at risk is not (for instance, take the example of two bonds each with a default probability of less than 5%, say, 4.95% — the 5% VaR of either of them is 0, but the 5% VaR of the two bond portfolio is greater than zero (or less, depending on how you express the quantity–as a portfolio value, or loss value)).

In any case, here’s the code:

require(IKTrading)
require(quantstrat)
require(PerformanceAnalytics)

initDate="1990-01-01"
from="2003-01-01"
to="2012-12-31"
options(width=70)

source("demoData.R")

#trade sizing and initial equity settings
tradeSize <- 100000
initEq <- tradeSize*length(symbols)

strategy.st <- portfolio.st <- account.st <- "DollarVsATRos"
rm.strat(portfolio.st)
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

#parameters
pctATR=.02
period=10
atrOrder <- TRUE

nRSI <- 2
buyThresh <- 20
sellThresh <- 80
nSMA <- 200

add.indicator(strategy.st, name="lagATR", 
              arguments=list(HLC=quote(HLC(mktdata)), n=period), 
              label="atrX")

add.indicator(strategy.st, name="RSI",
              arguments=list(price=quote(Cl(mktdata)), n=nRSI),
              label="rsi")

add.indicator(strategy.st, name="SMA",
              arguments=list(x=quote(Cl(mktdata)), n=nSMA),
              label="sma")

#signals
add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("Close", "sma"), relationship="gt"),
           label="filter")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=buyThresh, 
                          relationship="lt", cross=FALSE),
           label="rsiLtThresh")

add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("filter", "rsiLtThresh"), cross=TRUE),
           label="longEntry")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=sellThresh,
                          relationship="gt", cross=TRUE),
           label="longExit")

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "sma"), relationship="lt"),
           label="filterExit")

#rules
if(atrOrder) {
  
  add.rule(strategy.st, name="ruleSignal", 
           arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
                          orderside="long", replace=FALSE, prefer="Open", osFUN=osDollarATR,
                          tradeSize=tradeSize, pctATR=pctATR, atrMod="X"), 
           type="enter", path.dep=TRUE)
} else { 
  add.rule(strategy.st, name="ruleSignal", 
           arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
                          orderside="long", replace=FALSE, prefer="Open", osFUN=osMaxDollar,
                          tradeSize=tradeSize, maxSize=tradeSize), 
           type="enter", path.dep=TRUE)
}


add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all", ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="filterExit", sigval=TRUE, orderqty="all", ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)

#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)

#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)


#Portfolio comparisons to SPY
instRets <- PortfReturns(account.st)

if(atrOrder) {  
  atrSd <- StdDev(na.omit(instRets), portfolio_method="component")
  t1  <- Sys.time()
  atrES <- ES(na.omit(instRets), portfolio_method="component")
  t2 <- Sys.time()
  print(t2-t1)
} else {
  dollarSd <- StdDev(na.omit(instRets), portfolio_method="component")
  t1  <- Sys.time()
  dollarES <- ES(na.omit(instRets), portfolio_method="component")
  t2 <- Sys.time()
  print(t2-t1)
}

if(("atrSd" %in% ls()) & ("dollarSd" %in% ls())){  
  boxPlotFrame <- cbind(atrSd$pct_contrib_StdDev, 
                        atrES$pct_contrib_MES,
                        dollarSd$pct_contrib_StdDev,
                        dollarES$pct_contrib_MES)
  colnames(boxPlotFrame) <- c("atrSd", "atrES", "dollarSd", "dollarES")
  boxplot(boxPlotFrame)
}

rownames(boxPlotFrame) <- gsub(".DailyEndEq", "", rownames(boxPlotFrame))

This is the resulting image:

And the corresponding data which was used to generate the box plot:

> boxPlotFrame
          atrSd       atrES     dollarSd      dollarES
EFA 0.049378400 0.032829510 0.0412678995  0.0414725387
EPP 0.053737115 0.061714539 0.0509966059  0.0701035651
EWA 0.044784653 0.064515175 0.0510242998  0.0728805941
EWC 0.036487179 0.019929189 0.0439868019  0.0422535671
EWG 0.043172662 0.028698773 0.0455195612  0.0505451367
EWH 0.039433573 0.038858046 0.0449196754  0.0362996367
EWJ 0.030736566 0.037979817 0.0257488355  0.0257318748
EWS 0.041005553 0.016891054 0.0453268353  0.0240447623
EWT 0.029300684 0.058477463 0.0378874688  0.0599281716
EWU 0.043907517 0.025657106 0.0403600522  0.0366783005
EWY 0.039628602 0.028507044 0.0550089098  0.0370010120
EWZ 0.039586224 0.057278661 0.0721037108  0.0867982223
EZU 0.042050678 0.026106675 0.0412176610  0.0288231226
IEF 0.008465791 0.027757444 0.0008097627  0.0046390295
IGE 0.038329663 0.062596052 0.0487744244  0.0797431561
IYR 0.029283546 0.009068168 0.0299819881 -0.0088073708
IYZ 0.034378964 0.042472847 0.0265562205  0.0285952033
LQD 0.008845486 0.020600278 0.0013406959  0.0007059662
RWR 0.027775214 0.014710031 0.0301350434 -0.0063985075
SHY 0.007692137 0.026727026 0.0001618876  0.0009506367
TLT 0.008471822 0.015863044 0.0025502684  0.0031127512
XLB 0.037847498 0.069639008 0.0407311722  0.0788174726
XLE 0.034833476 0.040059687 0.0463448900  0.0602607692
XLF 0.036103344 0.031322842 0.0306424885  0.0337304540
XLI 0.036248065 0.003602306 0.0324606725  0.0018744509
XLK 0.039230708 0.022105404 0.0330037953  0.0276545899
XLP 0.029559398 0.006160133 0.0161629448 -0.0067567464
XLU 0.024703361 0.047065790 0.0191419001  0.0393038102
XLV 0.028872857 0.027564044 0.0168619029  0.0173169198
XLY 0.036149264 0.035242843 0.0289716256  0.0326969108

As can be seen in the image, which is a box plot of the various ways of computing the percentage of portfolio component risk for the two order types, ATR order sizing still does a better job than raw dollar order sizing in terms of controlling risk. However, as evidenced by the atrES box plot, there still is a somewhat wide distribution in terms of contributions to portfolio risk between the various instruments. However, even in this instance of portfolio component risk, it’s readily visible how the ATR order sizing improves on dollar order sizing. However, this also demonstrates how ATR order sizing isn’t the be-all, end-all method of portfolio allocations.

For future note, the application of portfolio component risk metrics is to optimize them in one of two ways–by minimizing the difference between them, or by striving to set them as close to equal to each other as possible (that is, portfolio component risk balance). The PortfolioAnalytics package provides methods on how to do that, which I’ll visit in the future.

Thanks for reading.