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:

if(atrOrder) {
  add.rule(, 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(, 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(, 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(, name="ruleSignal", 
         arguments=list(sigcol="filterExit", sigval=TRUE, orderqty="all", 
                        ordertype="market", orderside="long", replace=FALSE, 
         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 (
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(,
t2 <- Sys.time()

#set up analytics
dateRange <- time(getPortfolio($summary)[-1]

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.


44 thoughts on “Nuts and Bolts of Quantstrat, Part IV

  1. Pingback: The Whole Street’s Daily Wrap for 9/24/2014 | The Whole Street

    • Paul,

      By default, quantstrat buys at next bar close after the signal. That is, you can’t observe the signal (EG Close cross SMA 200) and buy at that exact signal. You have to wait 1 bar.

  2. Thansk, so “prefer=Close” argument in ruleSignal simply says “execute signal at next days close” if I have daily data? Best Regards

  3. Hi Ilya, I have a question on transaction fees. How can I set the level of transaction fees of 0.02 cents per share or 0.05% per share for backtesting? I want the net price to be Price +/- Commissions (+ for buys, – for sells), where Commissions = 0.02c or Commissions = Price * 0.05%
    Best Regards

  4. Amazing post..this is so helpful..
    I went through your Youtube video on Larry Connor’s strategy that you call as RSI_10_6 and was simultaneously trying on my machine. I was able to make it almost till the end when I ran into an error while using applyStrategy

    out <- applyStrategy(,
    Error in inherits(x, "xts") : argument "x" is missing, with no default

    Not sure where in the code did you initialize mktdata ? Following is a traceback to the function call applyStrategy

    9: inherits(x, "xts")
    8: is.xts(x)
    7: try.xts(x, error = as.matrix)
    6: runSum(x, n)
    5: runMean(x, n)
    4: SMA(x = , n = 5, price = Cl(mktdata))
    3:$name, .formals)
    2: applyIndicators(strategy = strategy, mktdata = mktdata, parameters = parameters,
    1: applyStrategy(strategy =, portfolios =

  5. Hi Ilya, thanks for all the great posts! I understand much better how Quantstrat works.

    I am quite new to Quantstrat, so apologies if this question seems too basic: order sizing function – is there a function that dynamically adjusts your position size on a daily basis?

    For example, suppose I have a strategy to buy S&P futures when the Close crosses over the 200 MA. I use the signal function ‘sigCrossover’ so that the signal is only TRUE on the day when the cross-over happens, and FALSE on other days. Suppose at end of day T, the signal is TRUE. So on T+1 open (apparently I cannot buy at end of day T), based on some sizing principle (say ATR) I bought 100 lots. At end of day T+1, the re-computed ATR tells me that I should only hold 95 lots. Therefore at end of day T+1, I will sell 5 lots. At end of day T+2, after re-computation I should be holding 110 lots, so I will buy 15 lots at end of day T+2, etc.

    • No. The ATR order size is computed only once, and that’s at the moment you place the order. Since the order is placed at the open of T+1, you don’t know the ATR of T+1 since it requires the close. So if the ATR order sizing function at time T tells you to buy 95, you’ll buy 95 at the open of T+1 (or, for that matter, the close), as it doesn’t take into account T+1 at all.

      Also, when you *sell*, I have all of my exits set to the quantstrat-reserved quantity “all”, so whatever your position is, it will flatten it, no questions asked.

      • I would like to write an OS function that adjusts the order size daily. Based on my understanding, the OS function is only triggered when there is a signal. Therefore, suppose the S&P Close crosses the 200 MA from below for day 1 and stays above the 200 MA for 5 days. So I need my signal function to generate [1,1,1,1,1,…] instead of [1,0,0,0,0,…]. So it seems like I should use the signal function ‘sigComparison’ and go from there…

      • Yes, that’d be the case. Your code will also throw off a LOT of signals, and so, will run extremely slowly, as the finite-state-machine loop (that is, the loop that controls your orders) is O(signals), so the more signals, the longer your backtests will take. If you have a strategy that maintains positions for short times (EG a mean reverting RSI-based strategy), then you don’t really need to rebalance your functions. But if you want to do rebalancing, you can also do an endpoints type function, and use sigAND with sigComparison to see A) if your buy conditions hold and B) if it’s a rebalancing day.

      • Thanks Ilya, this is really insightful. Will try out both daily and weekly/ monthly rebalancing options.

  6. Hi Ilya,

    your blog is awesome. Found really loads of useful stuff here which helped me to understand quanstrat better.

    I’m currently testing some really really simple strategy on bitcoin data to learn more about qunastrat. However, I’m quite struggling with OCO orders and chain of orders. My strategy is designed that for every entry order there should be two protective orders, one take profit and second stop-loss order. The first entry order is the parent for the latter two. My impression was that the two OCO orders will be only linked to their parent, however, in case that I have two positions opened at the same time, the second entry order simply cancels the oco orders of the first market order. Is it a mistake in my code or in the design of quanstrat?

    I psoted this question on

    but nobody answered it yet. I would be very thankful for any help or suggestion.


  7. Hi Ilya, great work on Nuts&Bolts!

    One question on debugging/analyzing a strategy:

    How can I follow what my strategy is doing trade by trade, i.e. (doesnt have to be exactly below, but just to illustrate what I am after):

    DATE: 01JAN2016
    BOUGHT/SOLD: 100
    PRICE: 20
    Position now: 200
    MySignalStatus: AboveMA

    I’ve trade the order book, but that doesnt give me enough depth:

    ob <- getOrderBook("XYZ")$XYZ$VNQ
    ob.df <- data.frame(Date=time(ob),coredata(ob),stringsAsFactors=FALSE)

    Your help is very much appreciated!


  8. Hi, i am new to R. I tried to replicate your code and managed to run them with no errors. But when I try to call for the results (output) at the end, I get NULL. I suspect that it has to do with the arguments not passed properly. I see that you have tried to explain the common pitfalls with labeling. Can you advise me how I can trouble shoot and fix this? When I run test<-applyIndicators(…), the labels are shown as "SMA.sma".

    • Odds are, you’re doing something incorrect, Jenny. Are you using my exact code, or trying your own naming conventions? If so, be more descriptive with your indicator/signal names.

  9. Hi Ilya. Just a generic quantstrat tool question. Is there support to get reports based on several runs of backtest. E.g. to take the tradestats results per each column and have it merged into a report with say, 100 tradestats column results. (my guess is that the user merged this info together).

  10. Hi Ilya. Could you please explain what is “rebal” accountable for the fun”osDollarATR” ?why it has
    default value in False ? Is it a blotter argument and is mandatory ?

  11. Hi,

    It’s a very nice tutorial, thanks for putting it together!
    One (beginner) question: is it possible to apply an indicator to all symbols in s portfolio, and how do yiu differenciate between them, would you need a separate column for each or can it be done more elegantly (apply once on full portfolio)?


  12. Hi Ilya, just to understand better testing portfolios with QuantStrat: I can see when applied to a basked to instruments, each instrument is taken sequencially and signals generated. The sequencial process may be quite daunting when the basked is larger than trivial small numbers … is there an optimization / a different way to work with portfolios?
    I mean, say for example you want to apply an SMA indicator on all the symbols simultanously … or apply a signal simultaneously on 3 stocks, but generate orders only on one of them depending on signal scoring. If one wants to get creative in this direction, looping through symbols sequencially when strategy is applied, might be very ineficient … am I missing something?

  13. Great post Ilya , I am trying to test my strategy on SMA . It seems to be working fine for long position entry and exits . But goes erratic with short positions. Here is my order placing function :

    SMA_str <- add.rule(SMA_str , name = "ruleSignal" , arguments = list(sigcol = "ClgtSMA" , sigval = TRUE , prefer = "Open" , orderqty = 1 , ordertype = "market" , orderside = "long" , threshold = NULL , osFun = osMaxPos) , type = "enter" , path.dep = TRUE)

    SMA_str <- add.rule(SMA_str , name = "ruleSignal" , arguments = list(sigcol = "ClltSMA" , sigval = TRUE , prefer = "Open" , orderqty = "all" , ordertype = "market" , orderside = "long" , threshold = NULL , osFun = osMaxPos) , type = "exit" , path.dep = TRUE)

    I would expect my Pnl to change its sign while maintaining the same magnitude upon changing "long" to "short" , but it doesn't happen . Also the position is not closed when it should be.

      • require(quantstrat)

        nifty <- as.xts(read.zoo("NIFTY1.csv" , sep ="," ,header = TRUE , format = "%H:%M" , tz = ""))
        NSEI <- nifty
        colnames(NSEI) = c("Open","High","Low","Close")


        stock.str <- "NSEI"
        stock(stock.str , currency = "INR" , multiplier = 1)

        initEq = 1
        initDate = index(NSEI[1]) = 'SMA_X' = 'SMA_X'

        initPortf( , symbols = stock.str , initDate = initDate)
        initAcct( , , initDate = initDate)
        initOrders( , initdDate = initDate)

        addPosLimit( , stock.str , initDate ,1 ,1)
        SMA_str <- strategy('SMA_X',store = TRUE)

        SMA_str <- add.indicator(strategy = SMA_str , name = "SMA" , arguments = list(x = quote(Cl(mktdata)) , n = 150 ) , label = "SMA")
        SMA_str <- add.signal(SMA_str , name = "sigCrossover" , arguments = list(columns = c("Close","SMA") , relationship = "gt"), label = "ClgtSMA")
        SMA_str <- add.signal(SMA_str , name = "sigCrossover" , arguments = list(columns = c("Close","SMA") , relationship = "lt"), label = "ClltSMA")
        SMA_str <- add.rule(SMA_str , name = "ruleSignal" , arguments = list(sigcol = "ClgtSMA" , sigval = TRUE , prefer = "Open" , orderqty = 1 , ordertype = "market" , orderside = "short" , pricemethod='market', threshold = NULL , osFun = osMaxPos) , type = "enter" , path.dep = TRUE)
        SMA_str <- add.rule(SMA_str , name = "ruleSignal" , arguments = list(sigcol = "ClltSMA" , sigval = TRUE , prefer = "Open" , orderqty = "all" , ordertype = "market" , orderside = "short" , pricemethod='market' , threshold = NULL , osFun = osMaxPos) , type = "exit" , path.dep = TRUE)

        a <- applyIndicators(SMA_str , Cl(mktdata))
        b <- applySignals(SMA_str , a)

        startt <- Sys.time()
        out <- try(applyStrategy(strategy = SMA_str , portfolio = "SMA_X"))
        end_t <- Sys.time()
        chart.Posn(Portfolio = 'SMA_X' , Symbol = "NSEI")

        It is unable to treat short as short , it shows me a green triangle on the plot while it should be red. Also it doesn't exit the position when the exit rule is triggered . Probably I am doing something wrong with my order function.

  14. Dear Ilya:

    I ran the code for the entire demo. However, I receive an error when I get to this part of the code under #apply strategy:

    out <- applyStrategy(,

    The error I get is:

    Error in get(symbol) : object 'EFA' not found

    I see that the ticker is in demoData.R. Any ideas why I am getting this? Thank you.

  15. It is in there. Am I missing something?



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

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s