Learn R Programming

PMwR (version 0.10-1)

btest: Backtesting Investment Strategies

Description

Testing trading and investment strategies.

Usage

btest(prices, signal,
      do.signal = TRUE, do.rebalance = TRUE,
      print.info = NULL, b = 1, fraction = 1,
      initial.position = 0, initial.cash = 0,
      final.position = FALSE,
      cashflow = NULL, tc = 0, …,
      add = FALSE, lag = 1, convert.weights = FALSE,
      trade.at.open = TRUE, tol = 1e-5, tol.p = NA,
      Globals = list(),
      prices0 = NULL,
      include.data = FALSE, include.timestamp = TRUE,
      timestamp, instrument,
      progressBar = FALSE,
      variations, variations.settings, replications)

Arguments

prices

For a single asset, a matrix of prices with four columns: open, high, low and close. For n assets, a list of length four: prices[[1]] is then a matrix with n columns containing the open prices for the assets; prices[[2]] is a matrix with the high prices, and so on. If only close prices are used, then for a single asset either a matrix of one column or a numeric vector; for multiple assets a list of length one, containing the matrix of close prices. For example, with 100 close prices of 5 assets, the prices should be arranged in a matrix p of size 100 times 5; and prices = list(p).

The series in prices are used both as transaction prices and for valuing open positions. If signals are to be based on other series, such other series should be passed via the … argument.

signal

A function that evaluates to the position in units of the instruments suggested by the trading rule. If convert.weights is TRUE, signal should return the suggested position as weights. If signal returns NULL, the current position is kept. See Details.

do.signal

Logical or numeric vector, or a function that evaluates to TRUE or FALSE.

When a logical vector, its length must match the number of observations in prices: do.signal then corresponds to the rows in prices at which a signal is computed. Alternatively, these rows may also be specified as integers. If a length-one TRUE or FALSE, the value is recycled to match the number of observations in prices. Default is TRUE: a signal is then computed in every period.

do.signal may also be the string “firstofmonth”, “lastofmonth”, “firstofquarter” or “lastofquarter”; in these cases, timestamp needs to specified and must be coercable to Date.

do.rebalance

Same as do.signal. Can also be the string "do.signal", in which case the value of do.signal is copied.

print.info

A function or NULL. If NULL, nothing is printed. See Details.

cashflow

A function or NULL (default).

b

burn-in (an integer). Defaults to 1.

fraction

amount of rebalancing to be done (a scalar between 0 and 1)

initial.position

a numeric vector: initial portfolio in units of instruments. If supplied, this will also be the initial suggested position.

initial.cash

a numeric vector of length 1. Defaults to 0.

final.position

logical

tc

transaction costs as a fraction of turnover (e.g., 0.001 means 0.1%). May also be a function that evaluates to such a fraction. More-complex computations may be specified with argument cashflow.

other named arguments. All functions (signal, do.signal, do.rebalance, print.info, cashflow) will have access to these arguments. See Details for reserved argument names.

add

Default is FALSE. TRUE is not implemented -- but would mean that signal should evaluate to changes in position, i.e. orders.

lag

default is 1

convert.weights

Default is FALSE. If TRUE, the value of signal will be considered a weight vector and automatically translated into (fractional) position sizes.

trade.at.open

A logical vector of length one; default is TRUE.

tol

A numeric vector of length one: only rebalance if the maximum absolute suggested change for at least one position is greater than tol. Default is 0.00001 (which practically means always rebalance).

tol.p

A numeric vector of length one: only rebalance those positions for which the relative suggested change is greater than tol.p. Default is NA: always rebalance.

Globals

A list of named elements. See Details.

prices0

A numeric vector (default is NULL). Only used if b is 0 and an initial portfolio (initial.position) is specified.

include.data

logical. If TRUE, all passed data are stored in final btest object. See Section Value. See also argument include.timestamp.

include.timestamp

logical. If TRUE, timestamp is stored in final btest object. If timestamp is missing, integers 1, 2, … are used. See Section Value. See also argument include.data.

timestamp

a vector of timestamps, along prices (optional; mainly used for print method and journal)

instrument

character vector of instrument names (optional; mainly used for print method and journal)

progressBar

logical: display txtProgressBar?

variations

a list

variations.settings

a list with the following defaults

replications

an integer

Value

A list with class attribute btest. The list comprises:

position

actual portfolio holdings

suggested.position

suggested holdings

cash

cash

wealth

time-series of total portfolio value (aka equity curve)

cum.tc

transaction costs

journal

journal of trades

initial.wealth

initial wealth

b

burn-in

final.position

final position if final.position is TRUE; otherwise NA

Globals

environment Globals

When include.timestamp is TRUE, the timestamp is included. If no timestamp was specified, integers 1, 2, ... are used instead. When include.data is TRUE, essentially all information (prices, instrument, the actual call and functions signal etc.) are stored in the list as well.

Details

The function provides infrastructure for testing trading rules. Essentially, btest does accounting: keep track of transactions and positions, value open positions, etc.

The basic ingredients are time series in the form of OHLC bars (which need not be equally spaced) and several functions that map these series and other pieces of information into positions.

btest runs a loop from b + 1 to NROW(prices). In iteration t, a signal can be computed based on information from periods prior to t. Trading then takes place at the opening price of t. For slow-to-compute signals this is reasonable if there is a time lag between close and open. For daily prices, for instance, signals could be computed overnight. For higher frequencies, such as every second, the signal function should be fast to compute; alternatively, it may be better to use a larger time offset (i.e. use information from periods << t).

If no ohlc bars are available, a single series per asset (assumed to be close prices) can be passed. We may then use information up to the close of t - 1, and trade at the close of t.

The ‘trade logic’ needs to be coded in the function signal; arguments to that function should be named and need to be passed with ....

Reserved argument names are currently these: Open, High, Low, Close, Wealth, Cash, Time, Timestamp, Portfolio, SuggestedPortfolio, Globals.

Globals is special: it is an environment, which can be used to persistently store data during the run of btest. Use the argument Globals to add initial objects.

variations.settings is a list with the following defaults

References

Schumann, E. (2018) Portfolio Management with R. http://enricoschumann.net/PMwR/; in particular the Chapter on backtesting: http://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#ch:backtesting

Examples

Run this code
# NOT RUN {
## For more examples, please see then Manual
## http://enricoschumann.net/R/packages/PMwR/manual/PMwR.html

## 1 - a simple rule
timestamp <- structure(c(16679L, 16680L, 16681L, 16682L, 
                         16685L, 16686L, 16687L, 16688L, 
                         16689L, 16692L, 16693L), 
                       class = "Date")
prices <- c(3182, 3205, 3272, 3185, 3201, 
            3236, 3272, 3224, 3194, 3188, 3213)
data.frame(timestamp, prices)


signal <- function()     ## buy when last price is 
    if (Close() < 3200)  ## below 3200, else sell
        1 else 0         ## (more precisely: build position of 1
                         ##  when price < 3200, else reduce
                         ##  position to 0)

solution <- btest(prices = prices, signal = signal)
journal(solution)


## with Date timestamps
solution <- btest(prices = prices, signal = signal,
                  timestamp = timestamp)
journal(solution)



## 2 - a simple MA model
# }
# NOT RUN {
library("PMwR")
library("NMOF")

dax <- DAX[[1]]

n <- 5
ma <- MA(dax, n, pad = NA)

ma_strat <-  function(ma) {
    if (Close() > ma[Time()])
        1
    else
        0
}


plot(as.Date(row.names(DAX)), dax, type = "l", xlab = "", ylab = "DAX")
lines(as.Date(row.names(DAX)), ma, type = "l")

res <- btest(dax, signal = ma_strat,
             b = n, ma = ma)

par(mfrow = c(3,1))
plot(as.Date(row.names(DAX)), dax, type = "l",
     xlab = "", ylab = "DAX")
plot(as.Date(row.names(DAX)), res$wealth, type = "l",
     xlab = "", ylab = "Equity")
plot(as.Date(row.names(DAX)), position(res), type = "s",
     xlab = "", ylab = "Position")
# }

Run the code above in your browser using DataLab