1
$\begingroup$

I need a fast package for backtesting in R. I'm going to optimize a lot so I'll be running my strategies many millions of times. I know about packages like quantstrat or SIT but they are terribly slow for my purposes, I have one strategy, I'm not going to model 20 portfolios at the same time and such. Which package can you recommend?

UPD=========

Yes, I implemented something very simple like

signals <- sample(c(-1,0,1),30,replace = T)

-1 open sale

1 open buy

0 do nothing and close any position

prices <- cumsum(rnorm(30))+100

count_balance <- function(prices,signals){
  p <- c(0,diff(prices))
  s <- c(0,signals[-length(signals)])
  return(   cumsum(p*s)   )
}

count_balance(p = prices,sig = signals)

or equivalent in Rcpp even faster 30 times

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
NumericVector count_balance_cpp(NumericVector prices, NumericVector signals) {
  int n = prices.size();
  NumericVector result(n);
  result[0] = 0;
  for (int i = 1; i < n; ++i) {
    result[i] = result[i-1] + signals[i-1] * (prices[i] - prices[i-1]);
  }
  return result;
}

But I would like a little more

  1. take into account the commission
  2. have a trade log so that I know the ratio of profitable losing trades

In princepe I can implement this too, but I will not be completely sure that I did everything right, since I am a very bad programmer. I'm not even sure about the functions that I posted above)

That's why I was looking for a simple, fast, ready-made and most importantly proven library

$\endgroup$
1
  • $\begingroup$ Hi: As far as I know, there is no generalized backtester available in R. I could be wrong of course because it's been a while since I looked. Hopefully, I'm wrong. At the same time, I suggest writing your own because then it's not a black box. $\endgroup$
    – mark leeds
    Jun 3, 2023 at 1:01

1 Answer 1

4
$\begingroup$

To pick up Mark's suggestion on writing your own code: the raw P/L computation from a vector of signals in R can be quite fast.

P <- c(100, 99, 104, 103, 105, 104)  ## price series
S <- c(  0,  1,   1,   0,   1,   0)  ## position to be held
dS <- c(0, diff(S)) ## change in position ==> trades
## [1]  0  1  0 -1  1 -1

portfolio.value <- S*P - cumsum(dS*P)
## [1] 0 0 5 4 4 3

But backtesting can mean different things to different people: from bare-bones accounting for P/L, via computation/reporting of statistics, to actual connectivity to live trading. And strategies come in many flavours, too. You might get better answers if you provided more details about your strategy, and how/what you intent to optimize.


Response to update:

If you only need a mapping between signals and P/L, it will be hard to beat a raw implementation, in particular written in C. I am not aware of a package that offers this, but maybe there is. (You might also want to ask this question also on R-SIG-Finance, where at least in the past the maintainers of the packages you mentioned have answered questions.)

That being said, you'll need to decide how much speed you need. Transactions costs are easy to add to the snippet above:

## with 10bp proportional fees
dSP <- dS*P
portfolio.value <- S*P - cumsum(dSP) - cumsum(abs(dSP)*0.001)
## [1]  0.000 -0.099  4.901  3.798  3.693  2.589

As for a trade log: you have it in the vector dS. Here is a sketch how you could handle the trades, using package PMwR (which I maintain).

## trades
library("PMwR")
J <- journal(amount = dS, price = P, timestamp = seq_along(P))
J
##    timestamp  amount  price
## 1          1       0    100
## 2          2       1     99
## 3          3       0    104
## 4          4      -1    103
## 5          5       1    105
## 6          6      -1    104
## 
## 6 transactions  

pl(J)
## P/L total         3
## average buy     102
## average sell  103.5
## cum. volume       4
## 
## ‘P/L total’ is in units of instrument;
## ‘volume’ is sum of /absolute/ amounts.

pl will give the total P/L. But you can easily split up the trades.

trade <- dS != 0
trades <- split_trades(amount = dS[trade],
                       price = P[trade],
                       timestamp = seq_along(P)[trade])
## [[1]]
## [[1]]$amount
## [1]  1 -1
## 
## [[1]]$price
## [1]  99 103
## 
## [[1]]$timestamp
## [1] 2 4
## 
## 
## [[2]]
## [[2]]$amount
## [1]  1 -1
## 
## [[2]]$price
## [1] 105 104
## 
## [[2]]$timestamp
## [1] 5 6

Call pl for each trade.

sapply(trades,
       function(x) pl(as.journal(x), pl.only = TRUE))
## [1]  4 -1
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.