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.

15 thoughts on “Nuts and Bolts of Quantstrat, Part V

  1. Thanks for the interesting post. I’ve taken your DataCamp course, and I recommend it. I blindly tried to run the script but it died on this statement:

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

    with the error message…

    "Error in get(signal$name) : invalid first argument"

    Thanks again for a great blog.

  2. Pingback: Nuts and Bolts of Quantstrat, Part V | A bunch of data

  3. Pingback: Nuts and Bolts of Quantstrat, Part V – Mubashir Qasim

  4. Pingback: Quantocracy's Daily Wrap for 04/13/2017 | Quantocracy

  5. Someone may have already asked this question, but anyway, I’d like to ask about it because you have already met with this situation, and I mean using the script “demoData.R” I changed the source on google to get data for the local stock market
    :
    library (quantstrat)
    initDate = “2000-01-01”
    from = “2010-01-01”
    to = “2016-12-31”
    options (width = 70)
    options (“getSymbols.warning4.0” = FALSE)
    rm (list = ls (.blotter), envir = .blotter)
    currency (‘PLN’)Sys.setenv (TZ = “UTC”)
    Symbols <- c ("WSE: AGO",
    "WSE: AMC",
    "WSE: PGD",
    "WSE: BDX",
    "WSE: KGH",
    "WSE: KTY",
    "WSE: RFK",
    "WSE: PGE",
    "WSE: TRK",
    "WSE: MSZ",
    "WSE: RMK",
    "WSE: TPE",
    "WSE: GPW",
    "WSE: PKO",
    "WSE: PKN")
    if (! "WSE: AGO"% in% ls ()) { (getSymbols (symbols, from = from,
    to = to, src = "google", auto.assign = TRUE))
    }

    However, the result is : "Error in naCheck (x, n): Series contains non-leading NAs".
    This is because the "WSE: RMK" symbol in the last column(volume) contains values equal to zero. What should be done to the values of zero in this column to be accepted and let the script carry on without the error ?

  6. Thanks for the detail post of “Nuts and Bolts of Quantstrat”. After reading this, I have a question:
    the examples in these post assume a static portfolio, what if the stock list for my portfolio is changeing and my money is distributed among the stock list equally. how can i do this job.
    Looking forward for your answer.

  7. Ilya, I’ve watched your lesson on DataCamp and I’m trying to set up a trading strategy with quanstrat but I’m receiving the following error when I try to run applyStrategy: Error in if (inherits(sret$indicators, “xts”) & nrow(mktdata) == nrow(sret$indicators)) { :
    argument is of length zero

    The code is below, can you tell me where I’m going wrong? Any help would be greatly appreciated.

    getSymbols(“AUD=X”,src=”yahoo”,from=”1975-01-02″)
    colnames(`AUD=X`) <- c("Open", "High", "Low", "Close", "Vol", "Adj")

    if (!exists('.blotter')) .blotter <- new.env()
    .strategy <- new.env()

    initdate = as.character("2006-05-15")
    from = as.character("2007-05-15")
    to = as.character("2018-06-01")
    Sys.setenv(TZ = "UTC")
    currency("USD")
    stock(`AUD=X`, currency="USD", multiplier = 1)

    tradesize <- 100000
    initeq <- 100000
    strategy.st <- portfolio.st <- account.st <- "firststrat"

    rm.strat(strategy.st)
    initPortf(portfolio.st, symbols = na.locf("AUD=X"), 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)

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

    add.indicator(strategy = strategy.st,
    name = "SMA",
    arguments = list(x = quote(na.omit(Cl(mktdata))), n = 50),
    label = "SMA50")

    test <- applyIndicators(strategy = strategy.st, mktdata = na.locf(Cl(`AUD=X`)))

    add.signal(strategy.st,
    name = "sigCrossover",
    arguments = list(columns = c("SMA.SMA50", "SMA.SMA200"),
    relationship = "gt"),
    label = "Crossover")

    add.signal(strategy.st,
    name = "sigComparison",
    arguments = list(columns = c("SMA.SMA50", "SMA.SMA200"),
    relationship = "lt"),
    label = "Compare")

    add.signal(strategy.st,
    name = "sigThreshold",
    arguments = list(column = "Close",
    threshold = 1.5,
    cross = FALSE,
    relationship = "lt"),
    label = "threshold_high")

    add.signal(strategy.st,
    name = "sigThreshold",
    arguments = list(column = "Close",
    threshold = 1,
    cross = FALSE,
    relationship = "gt"),
    label = "threshold_low")

    test2 <- applySignals(strategy = strategy.st, mktdata = test)
    tail(test2)
    add.rule(strategy.st, name = "ruleSignal",
    arguments = list(sigcol = "threshold_low", sigval = FALSE,
    orderqty = "all", ordertype = "market",
    orderside = "short", replace = FALSE,
    prefer = "Open"),
    type = "enter")

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

    applyStrategy(strategy = strategy.st, portfolios = portfolio.st)

    • Sorry, I don’t check individual code. I’d recommend checking for typos and using commands like applyIndicators and applySignals to see where you’re getting an error.

  8. Hi Ilya,

    I’m sure you’re busy but I thought I’d ask a question related to this post.

    Is it possible to apply indicators that operate on data from other indicators within quantstrat? Or is pre-preprocessing my only option?

    It seems that applyIndicators or applyStrategy passes the raw xts data object (from getSymbols, for example) through each indicator function, and the output is stored as a list that is cbinded to mktdata. Thus, pulling previous indicator output for use in custom functions throws an error, object “mktdata” not found. Perhaps there is a way to retrieve data from the mktdata while apply indicators? ApplySignal is obviously using mktdata to test for signals… but I’m struggling to see how this happens when I look at the source code. I appreciate any guidance you might have.

    Derrick

Leave a Reply

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

WordPress.com Logo

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

Facebook photo

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

Connecting to %s