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.

63 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(strategy=strategy.st,portfolios=portfolio.st)
    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

    traceback()
    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: do.call(indicator$name, .formals)
    2: applyIndicators(strategy = strategy, mktdata = mktdata, parameters = parameters,
    …)
    1: applyStrategy(strategy = strategy.st, portfolios = portfolio.st)

  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 https://stackoverflow.com/questions/28136625/quanstrat-chain-and-oco-orders

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

    Steef

  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
    INSTUMENT: IBM STOCK
    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)
    print(data.frame(Date=time(ob),coredata(ob),stringsAsFactors=FALSE))

    Your help is very much appreciated!

    Thanks
    Peter

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

    Thanks

  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)
        require(quantmod)
        require(blotter)
        require(ggplot2)

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

        rm.strat('SMA_X')

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

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

        account.st = 'SMA_X'
        portfolio.st = 'SMA_X'

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

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

        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"))
        getOrderBook('SMA_X')
        end_t <- Sys.time()
        updatePortf('SMA_X',stock.str)
        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(strategy=strategy.st,portfolios=portfolio.st)

    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.

      • Hi,

        I have the same issue when I was trying to backtest some of the stocks in HK market. (e.g. 1398.hk)

        library(quantstrat)
        library(quantmod)
        #’ Basic Symbols
        #’
        #’ IWM, QQQ, SPY and TLT for basic analysis
        #’
        #’ @return a list of basic stock symbols
        #’ @export
        #’
        basic_symbols <- function() {
        symbols <- c(
        "0700.hk"
        # iShares Russell 2000 Index ETF

        )
        }

        #' Enhanced Symbols
        #'
        #' Includes SPDR ETFs
        #'
        #' @return a list of stock symbols including basic_symbols() and sector SPDRs
        #' @export
        #'
        enhanced_symbols <- function() {
        symbols <- c(
        "IWM", # iShares Russell 2000 Index ETF
        "QQQ", # PowerShares QQQ TRust, Series 1 ETF
        "SPY", # SPDR S&P 500 ETF Trust
        "TLT", # iShares Barclays 20+ Yr Treas. Bond ETF
        "XLB", # Materials Select Sector SPDR ETF
        "XLE", # Energy Select Sector SPDR ETF
        "XLF", # Financial Select Sector SPDR ETF
        "XLI", # Industrials Select Sector SPDR ETF
        "XLK", # Technology Select Sector SPDR ETF
        "XLP", # Consumer Staples Select Sector SPDR ETF
        "XLU", # Utilities Select Sector SPDR ETF
        "XLV", # Health Care Select Sector SPDR ETF
        "XLY" # Consumer Discretionary Select Sector SPDR ETF
        )
        }

        #' Global Symbols
        #'
        #' @return a list of global stock symbols
        #' @export
        #'
        global_symbols <- function() {
        symbols <- c(
        "EFA", # iShares EAFE
        "EPP", # iShares Pacific Ex Japan
        "EWA", # iShares Australia
        "EWC", # iShares Canada
        "EWG", # iShares Germany
        "EWH", # iShares Hong Kong
        "EWJ", # iShares Japan
        "EWS", # iShares Singapore
        "EWT", # iShares Taiwan
        "EWU", # iShares UK
        "EWY", # iShares South Korea
        "EWZ", # iShares Brazil
        "EZU", # iShares MSCI EMU ETF
        "IGE", # iShares North American Natural Resources
        "IWM", # iShares Russell 2000 Index ETF
        "IYR", # iShares U.S. Real Estate
        "IYZ", # iShares U.S. Telecom
        "LQD", # iShares Investment Grade Corporate Bonds
        "QQQ", # PowerShares QQQ TRust, Series 1 ETF
        "SHY", # iShares 42372 year TBonds
        "SPY", # SPDR S&P 500 ETF Trust
        "TLT", # iShares Barclays 20+ Yr Treas. Bond ETF
        "XLB", # Materials Select Sector SPDR ETF
        "XLE", # Energy Select Sector SPDR ETF
        "XLF", # Financial Select Sector SPDR ETF
        "XLI", # Industrials Select Sector SPDR ETF
        "XLK", # Technology Select Sector SPDR ETF
        "XLP", # Consumer Staples Select Sector SPDR ETF
        "XLU", # Utilities Select Sector SPDR ETF
        "XLV", # Health Care Select Sector SPDR ETF
        "XLY" # Consumer Discretionary Select Sector SPDR ETF
        )
        }

        #' Copyright (C) 2011-2014 Guy Yollin
        #' License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
        #' http://www.r-programming.org/papers
        checkBlotterUpdate <- function(port.st = portfolio.st,
        account.st = account.st,
        verbose = TRUE) {

        ok <- TRUE
        p <- getPortfolio(port.st)
        a <- getAccount(account.st)
        syms <- names(p$symbols)
        port.tot <- sum(
        sapply(
        syms,
        FUN = function(x) eval(
        parse(
        text = paste("sum(p$symbols",
        x,
        "posPL.USD$Net.Trading.PL)",
        sep = "$")))))

        port.sum.tot <- sum(p$summary$Net.Trading.PL)

        if(!isTRUE(all.equal(port.tot, port.sum.tot))) {
        ok <- FALSE
        if(verbose) print("portfolio P&L doesn't match sum of symbols P&L")
        }

        initEq <- as.numeric(first(a$summary$End.Eq))
        endEq <- as.numeric(last(a$summary$End.Eq))

        if(!isTRUE(all.equal(port.tot, endEq – initEq)) ) {
        ok <- FALSE
        if(verbose) print("portfolio P&L doesn't match account P&L")
        }

        if(sum(duplicated(index(p$summary)))) {
        ok <- FALSE
        if(verbose)print("duplicate timestamps in portfolio summary")

        }

        if(sum(duplicated(index(a$summary)))) {
        ok <- FALSE
        if(verbose) print("duplicate timestamps in account summary")
        }
        return(ok)
        }

        #' Copyright (C) 2011-2014 Guy Yollin
        #' License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
        #' http://www.r-programming.org/papers
        osFixedDollar <- function(timestamp, orderqty, portfolio, symbol, ruletype, …) {
        if(!exists("trade_size")) stop("You must set trade_size")
        ClosePrice <- as.numeric(Cl(mktdata[timestamp,]))
        orderqty <- round(trade_size/ClosePrice,-2)
        return(orderqty)
        }
        #Define the parameters used in the trading portfolio
        portfolio.st <- "Port.Luxor"
        account.st <- "Acct.Luxor"
        strategy.st <- "Strat.Luxor"
        init_date <- "2014-12-31"
        start_date <- "2015-01-01"
        end_date <- "2017-05-31"
        adjustment <- TRUE
        init_equity <- 1e4 # $10,000

        Sys.setenv(TZ = "UTC")

        currency('USD')

        symbols <- basic_symbols()

        getSymbols("2800.hk",
        src = "yahoo",
        from = start_date,
        to = end_date)

        getSymbols(symbols,
        src = "yahoo",
        from = start_date,
        to = end_date,
        adjust = adjustment)

        names(stock_values)<-c("stock1.open","stock1.High","stock1.Low","stock1.Close",
        "stock1.Volume","stock1.Adjusted")

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

        rm.strat(portfolio.st)
        rm.strat(account.st)

        initPortf(name = portfolio.st,
        symbols = symbols,
        initDate = init_date)

        initAcct(name = account.st,
        portfolios = portfolio.st,
        initDate = init_date,
        initEq = init_equity)

        initOrders(portfolio = portfolio.st,
        symbols = symbols,
        initDate = init_date)

        strategy(strategy.st, store = TRUE)

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

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

        add.signal(strategy = strategy.st,
        name="sigCrossover",
        arguments = list(columns = c("nFast", "nSlow"),
        relationship = "gte"),
        label = "long")

        add.signal(strategy = strategy.st,
        name="sigCrossover",
        arguments = list(columns = c("nFast", "nSlow"),
        relationship = "lt"),
        label = "short")

        add.rule(strategy = strategy.st,
        name = "ruleSignal",
        arguments = list(sigcol = "long",
        sigval = TRUE,
        orderqty = 100,
        ordertype = "market",
        orderside = "long",
        prefer = "High",
        TxnFees = -10,
        replace = FALSE),
        type = "enter",
        label = "EnterLONG")

        add.rule(strategy.st,
        name = "ruleSignal",
        arguments = list(sigcol = "short",
        sigval = TRUE,
        orderside = "long",
        ordertype = "market",
        orderqty = "all",
        TxnFees = -10,
        replace = TRUE),
        type = "exit",
        label = "Exit2SHORT")

        cwd <- getwd()
        setwd("D:/R_Working_Directory/Data")
        results_file <- paste("results", strategy.st, "RData", sep = ".")
        if( file.exists(results_file) ) {
        load(results_file)
        } else {
        results <- applyStrategy(strategy.st, portfolios = portfolio.st, verbose = TRUE)
        updatePortf(portfolio.st)
        updateAcct(account.st)
        updateEndEq(account.st)
        if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) {
        save.strategy(strategy.st)
        }
        }
        setwd(cwd)

        tstats <- tradeStats(portfolio.st)

        When I ran the statement applyStrategy I got the following error message:

        Error in get(symbol) : object '0700.hk' not found

        Any idea of how I could solve the problem here. The script works fine with US stocks but it seems it did not work in HK stocks.

        Thanks for the help here.

        BR,

        Victor

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

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

  16. Pingback: Nuts and Bolts of Quantstrat, Part V | QuantStrat TradeR

  17. Hi IIya,

    Actually I have already got the stocks in HK market with quantmod but the problem is that there is error message still showing the object (“0700.hk”) not found.

    Regarding your comment “You need to actually have the symbol somewhere in your session.” could you help to indicate how I could resolve the issue here ? (based on the script that I have sent you below)

    Note that in quantmod I need to use “0700.hk” to fetch one of the stocks in HK market and “0700.hk” is the symbol that I used in quantmod in order to fetch the stock with quantmod.

    I have also resent the script which I have taken out some of those unnecessary stuff so as to better illustrate the case clearly:

    Still the problem happened when it tried to execute the applyStrategy function.

    R SCRIPT:

    library(quantstrat)
    library(quantmod)
    #’ Basic Symbols
    #’
    #’ IWM, QQQ, SPY and TLT for basic analysis
    #’
    #’ @return a list of basic stock symbols
    #’ @export
    #’
    basic_symbols <- function() {
    symbols <- c(
    "0700.hk"

    )
    }

    #' Enhanced Symbols
    #'
    #' Includes SPDR ETFs
    #'
    #' @return a list of stock symbols including basic_symbols()
    #' @export
    #'

    checkBlotterUpdate <- function(port.st = portfolio.st,
    account.st = account.st,
    verbose = TRUE) {

    ok <- TRUE
    p <- getPortfolio(port.st)
    a <- getAccount(account.st)
    syms <- names(p$symbols)
    port.tot <- sum(
    sapply(
    syms,
    FUN = function(x) eval(
    parse(
    text = paste("sum(p$symbols",
    x,
    "posPL.USD$Net.Trading.PL)",
    sep = "$")))))

    port.sum.tot <- sum(p$summary$Net.Trading.PL)

    if(!isTRUE(all.equal(port.tot, port.sum.tot))) {
    ok <- FALSE
    if(verbose) print("portfolio P&L doesn't match sum of symbols P&L")
    }

    initEq <- as.numeric(first(a$summary$End.Eq))
    endEq <- as.numeric(last(a$summary$End.Eq))

    if(!isTRUE(all.equal(port.tot, endEq – initEq)) ) {
    ok <- FALSE
    if(verbose) print("portfolio P&L doesn't match account P&L")
    }

    if(sum(duplicated(index(p$summary)))) {
    ok <- FALSE
    if(verbose)print("duplicate timestamps in portfolio summary")

    }

    if(sum(duplicated(index(a$summary)))) {
    ok <- FALSE
    if(verbose) print("duplicate timestamps in account summary")
    }
    return(ok)
    }

    #' Copyright (C) 2011-2014 Guy Yollin
    #' License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
    #' http://www.r-programming.org/papers
    osFixedDollar <- function(timestamp, orderqty, portfolio, symbol, ruletype, …) {
    if(!exists("trade_size")) stop("You must set trade_size")
    ClosePrice <- as.numeric(Cl(mktdata[timestamp,]))
    orderqty <- round(trade_size/ClosePrice,-2)
    return(orderqty)
    }
    #Define the parameters used in the trading portfolio
    portfolio.st <- "Port.Luxor"
    account.st <- "Acct.Luxor"
    strategy.st <- "Strat.Luxor"
    init_date <- "2014-12-31"
    start_date <- "2015-01-01"
    end_date <- "2017-05-31"
    adjustment <- TRUE
    init_equity <- 1e4 # $10,000

    Sys.setenv(TZ = "UTC")

    currency('USD')

    symbols <- basic_symbols()

    getSymbols(symbols,
    src = "yahoo",
    from = start_date,
    to = end_date,
    adjust = adjustment)

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

    rm.strat(portfolio.st)
    rm.strat(account.st)

    initPortf(name = portfolio.st,
    symbols = symbols,
    initDate = init_date)

    initAcct(name = account.st,
    portfolios = portfolio.st,
    initDate = init_date,
    initEq = init_equity)

    initOrders(portfolio = portfolio.st,
    symbols = symbols,
    initDate = init_date)

    strategy(strategy.st, store = TRUE)

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

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

    add.signal(strategy = strategy.st,
    name="sigCrossover",
    arguments = list(columns = c("nFast", "nSlow"),
    relationship = "gte"),
    label = "long")

    add.signal(strategy = strategy.st,
    name="sigCrossover",
    arguments = list(columns = c("nFast", "nSlow"),
    relationship = "lt"),
    label = "short")

    add.rule(strategy = strategy.st,
    name = "ruleSignal",
    arguments = list(sigcol = "long",
    sigval = TRUE,
    orderqty = 100,
    ordertype = "market",
    orderside = "long",
    prefer = "High",
    TxnFees = -10,
    replace = FALSE),
    type = "enter",
    label = "EnterLONG")

    add.rule(strategy.st,
    name = "ruleSignal",
    arguments = list(sigcol = "short",
    sigval = TRUE,
    orderside = "long",
    ordertype = "market",
    orderqty = "all",
    TxnFees = -10,
    replace = TRUE),
    type = "exit",
    label = "Exit2SHORT")

    cwd <- getwd()
    setwd("D:/R_Working_Directory/Data")
    results_file <- paste("results", strategy.st, "RData", sep = ".")
    if( file.exists(results_file) ) {
    load(results_file)
    } else {
    results <- applyStrategy(strategy.st, portfolios = portfolio.st, verbose = TRUE)
    updatePortf(portfolio.st)
    updateAcct(account.st)
    updateEndEq(account.st)
    if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) {
    save.strategy(strategy.st)
    }
    }
    setwd(cwd)

    tstats <- tradeStats(portfolio.st)
    library(knitr)
    kable(t(tstats))

    for(symbol in symbols) {
    pts <- perTradeStats(portfolio.st, Symbol = symbol)
    kable(pts, booktabs = TRUE, caption = symbol)
    }
    kable(pts)

    head(mktdata)

    mktdata['2008-11']

    getOrderBook(portfolio.st)

    Thanks for the support and I am looking forward to hearing from you soon.

    BR,

    Victor

    • When you tell quantstrat what symbols the strategy uses, it must be an object in your environment. I.E. I can make an object called “abcd” and so long as it has an OHLC format, quantstrat will pick it up.

      • Hi llya,

        Yes it works now after I have assign a variable to the object that I have downloaded with Quantmod.

        Besides I am trying to install quantstrat in Mac and found that I could not find any source file of the blotter package from R-forge. Since Quantsrat needs to have the blotter installed and without the successful installation of blotter I am not able to install quantstrat in my Mac :-(

        Thanks for your quick help again and I am looking forward to hearing more from you soon.

        BR,

        Victor

  18. I have a question, while using sigAND

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

    Sys.setenv(TZ = “UTC”)
    currency(‘USD’)
    init_date <- "2015-12-31"
    start_date <- "2016-01-01"
    end_date <- "2016-12-31"
    init_equity <- 1e4
    adjustment <- TRUE

    buythresh <- 70
    sellthresh <- 30

    basic_symbols <- function() {
    symbols <- c(
    "nasd1h_xts",
    )
    }
    stock("nasd1h_xts",
    currency = "USD",
    multiplier = 1)

    strat.name <- "Bollinger.Band"

    rm.strat(strat.name)

    initPortf(strat.name,
    symbols = "nasd1h_xts",
    initDate = init_date)

    initAcct(strat.name,
    portfolios = strat.name,
    initDate = init_date,
    initEq = init_equity)

    initOrders(portfolio = strat.name,
    symbols = "nasd1h_xts",
    initDate = init_date)

    addPosLimit(portfolio = strat.name,
    symbol = "nasd1h_xts",
    timestamp = start_date,
    maxpos = 100,
    longlevels = 3)

    strategy(strat.name, store = TRUE)

    strat <- getStrategy(strat.name)

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

    add.indicator(strategy = strat.name,
    name = "BBands",
    arguments = list(HLC = quote(HLC(mktdata)),
    n = 20,
    maType = "SMA",
    sd = 2),
    label = "BB.20.2")

    test <- applyIndicators(strat.name, mktdata = OHLC(nasd1h_xts))
    head(test)

    add.signal(strat.name,
    name="sigCrossover",
    arguments = list(columns = c("close", "up"),
    relationship = "gt"),
    label="VerkaufsignalBB")

    add.signal(strat.name,
    name = "sigCrossover",
    arguments = list(columns = c("close", "dn"),
    relationship = "lt"),
    label = "KaufsignalBB")

    add.signal(strat.name,
    name = "sigCrossover",
    arguments = list(columns = c("high", "low", "mavg"),
    relationship = "op"),
    label = "Cross.Mid")

    add.signal(strat.name,
    name = "sigThreshold",
    arguments = list(columns= c("rsi"), threshold=buythresh,
    relationship="lt", cross=FALSE),
    label="rsiltThresh")

    add.signal(strat.name,
    name = "sigThreshold",
    arguments = list(columns= c("rsi"), threshold=buythresh,
    relationship="gt", cross=TRUE),
    label="rsigtThresh")

    add.signal(strat.name, name="sigAND",
    arguments = list(columns=c("KaufsignalBB", "rsiltThresh"), cross=TRUE),
    label="longEntry")

    add.signal(strat.name, name="sigAND",
    arguments = list(columns=c("VerkaufsignalBB", "rsigtThresh"), cross=TRUE),
    label="shortEntry")

    add.rule(strategy = strat.name,
    name = "ruleSignal",
    arguments = list(sigcol = "shortEntry",
    sigval = TRUE,
    orderqty = -100,
    ordertype = "market",
    orderside = NULL,
    threshold = NULL,
    osFUN = osMaxPos),
    type = "enter")

    add.rule(strategy = strat.name,
    name = "ruleSignal",
    arguments = list(sigcol = "longEntry",
    sigval = TRUE,
    orderqty = 100,
    ordertype = "market",
    orderside = NULL,
    threshold = NULL,
    osFUN = osMaxPos),
    type = "enter")

    add.rule(strategy = strat.name,
    name = "ruleSignal",
    arguments = list(sigcol = "Cross.Mid",
    sigval = TRUE,
    orderqty = "all",
    ordertype = "market",
    orderside = NULL,
    threshold = NULL,
    osFUN = osMaxPos),
    label = "exitMid",
    type = "exit")

    applyStrategy(strategy = strat.name,
    portfolios = strat.name)

    I always get the error: Error in match.names(column, colnames(data)) :
    argument "column" is missing, with no default

    When just do it whit the BB strategy without RSI it work fine.

    Thanks for your help!

  19. Nice post! Could you explain the signal relationship “op” pls?? How it works?? Searching in the web don’t found clear information how to use this relationship but in every “bollinger band” strategy was used this relationship as the signal that trigger the order to close a open position…
    Any help will be great!! Tks a lot :)

  20. Hello Ilya

    Wonderful posts. Ive done your course on DataCamp as well and its helped my understanding of backtesting significantly.

    I am trying to get stoplimit orders to work by comparing to the close of the day rather than to the low (for stop losses for long positions) or the high(for short positions). I am not really sure how to go about this. Any help would be appreciated!

      • Hi Ilya,

        Thank you for your suggestion. I did post this to the R-Sig Finance Mailing list. The responses thus far seem to indicate that I need to create a signal when the price goes below the stop loss price that triggers a market order the next day.

        Let us say that an initial execution was done at price P. I want to observe closing prices after the execution is done and if the closing price on any day falls below 0.95*P [5% is my loss tolerance] then this triggers a signal on that day so that on the next day I want to fire a market order to sell at open.

        The trouble as I see it is that creating a signal as suggested needs awareness of the execution price of the last position taken, and hence a path dependence. As I understand it from the documentation, signals are not designed to be path dependent. Is there any way that signals can be written in a position dependent fashion in quantstrat?

        Or is there a simpler way and I am overthinking the issue?

        Thanks again.

  21. Pingback: Nuts and Bolts of Quantstrat, Part V - 区块链投研

  22. Hi Ilya,

    Thank you for your tutorials. They are really helpful and clear.
    I have a problem.
    I am trying to implement a custom indicator in quantstrat but the I get the error that the argument “RUT” (the market data I want to use) is not specified and does not have a predefined value.
    I do not understand where the error is, could you please help me?

    The indicator I want to use is CNOwma.

    wma <- function(RUT) { WMA(Cl(RUT), n=4, wts=c(1:4)) }
    wmamaxt <- rollmaxr(wma(Cl(RUT)), n, fill = NA)
    wmamint <- – rollmaxr(- wma(Cl(RUT)), n, fill = NA)
    CNOwma <- function (RUT) {(wma(Cl(RUT)) – wmamint) / (wmamaxt – wmamint)}

    I get the error when I try to write the following code:

    out <- applyStrategy(strategy = strategy.st, portfolios = portfolio.st)

    The traceback of the error is:

    Error in has.Cl(x) :
    l'argomento "RUT" non è specificato e non ha un valore predefinito
    13.
    has.Cl(x)
    12.
    Cl(RUT)
    11.
    has.Cl(x)
    10.
    Cl(RUT)
    9.
    inherits(x, "xts")
    8.
    is.xts(x)
    7.
    try.xts(x, error = as.matrix)
    6.
    WMA(Cl(RUT), n = 4, wts = c(1:4))
    5.
    wma(Cl(RUT))
    4.
    (function (RUT)
    {
    (wma(Cl(RUT)) – wmamint)/(wmamaxt – wmamint)
    })(RUT = )
    3.
    do.call(indFun, .formals)
    2.
    applyIndicators(strategy = strategy, mktdata = mktdata, parameters = parameters,
    …)
    1.
    applyStrategy(strategy = strategy.st, portfolios = portfolio.st)

    Thank you if you find the time for helping me.

    Best regards,

    Pietro Fabbro

    • Seems like you need to make sure you define RUT (see part 1), and also, you should make your functions general–I.E. to take in any arbitrary data, say, x, and compute functions on that. Trying to specify to one particular instrument at a time the way you’re doing is not a way I’d recommend.

      • Thank you Ilya for your answer and your suggestion in making the function general. I will do it.
        I don’t get what I am doing wrong with defining RUT.

        If I write:

        initdate <- "2011-01-01"
        from <- "2012-01-01" #start of backtest
        to <- "2017-01-31" #end of backtest

        Sys.setenv(TZ= "UTC") #Set up time zone environment for timestamps

        currency("USD") #Set up environment for currency to be used

        symbols <- c("RUT", "IXIC") #symbols used in our backtest
        if(!"RUT" %in% ls()) {
        suppressMessages(getSymbols(symbols, from=from, to=to, src="yahoo", adjust=TRUE))
        }

        I get the error message that the download of both indexes failed.

        instead, if I write this:

        symbols <- c("RUT", "IXIC") #symbols used in our backtest

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

        getSymbols("^IXIC",src="yahoo", from="2012-01-01", to="2017-12-31", periodicity="daily")
        getSymbols("^RUT",src="yahoo", from="2012-01-01", to="2017-12-31", periodicity="daily")

        It works but I get the error message that I reported above.

      • I am stuck, do you have any suggestion to solve the problem?
        I managed to download RUT. Maybe the problem is that, since my rule has a 30 days look back period, the first 30 values of the indicator are missing. Could this be the cause of the problem?

Leave a reply to Ilya Kipnis Cancel reply