This website uses cookies to collect usage information in order to offer a better browsing experience. By browsing this site or by clicking on the "ACCEPT COOKIES" button you accept our Cookie Policy.

Optimising the rsims package for Fast Backtesting in R – Part II

Robot Wealth

Contributor:
Robot Wealth
Visit: Robot Wealth

Get ready-to-use code for profiling with the package profvis from here.

This version of cash_backtest takes a long data frame of prices and weights by date, which is a very convenient format for data analysis. We can make such a data frame of randomly generated prices and weights:

library(tidyverse)
# function for generating prices from GBM process
gbm_sim <- function(nsim = 100, t = 25, mu = 0, sigma = 0.1, S0 = 100, dt = 1./365) {
  # matrix of random draws - one for each day for each simulation
  epsilon <- matrix(rnorm(t*nsim), ncol = nsim, nrow = t)
  # get GBM paths
  gbm <- exp((mu - sigma * sigma / 2) * dt + sigma * epsilon * sqrt(dt)) - 1
  # convert to price paths
  gbm <- apply(rbind(rep(S0, nsim), gbm + 1), 2, cumprod)
  gbm
}
# generate prices and weights
years <- 20
universe <- 100
x <- 1
tickers <- vector() 
repeat{
  tickers[[x]] <- paste0(sample(LETTERS, 5, replace = TRUE), collapse = "")
  x <- x + 1
  if(x == universe + 1) 
    break
}
stopifnot(n_distinct(tickers) == universe)
date <- seq(as.numeric(as.Date("1980-01-01")), as.numeric(as.Date("1980-01-01"))+(years*365))
prices <- cbind(date, gbm_sim(nsim = universe, t = years*365, mu = 0.1, sigma = 0.1))
colnames(prices) <- c("date", tickers)
weights <- cbind(date, rbind(rep(0, universe), matrix(rnorm(years*365*universe), nrow = years*365)))
colnames(weights) <- c("date", tickers)
backtest_df_long <- prices %>% 
  as.data.frame() %>% 
  mutate(date = as.Date(date, origin ="1970-01-01")) %>% 
  pivot_longer(-date, names_to = "ticker", values_to = "price") %>% 
  left_join(
    weights %>% 
      as.data.frame() %>% 
      mutate(date = as.Date(date, origin ="1970-01-01")) %>% 
      pivot_longer(-date, names_to = "ticker", values_to = "theo_weight"),
    by = c("date", "ticker")
  )
head(backtest_df_long)
#> # A tibble: 6 x 4
#>   date       ticker price theo_weight
#>   <date>     <chr>  <dbl>       <dbl>
#> 1 1980-01-01 TVEZI    100           0
#> 2 1980-01-01 XIERO    100           0
#> 3 1980-01-01 XGYMU    100           0
#> 4 1980-01-01 PMVPF    100           0
#> 5 1980-01-01 KNCIP    100           0
#> 6 1980-01-01 JEBOY    100           0

backtest_df_long has prices and weights for 100 tickers over 7301 days:

backtest_df_long %>% 
  summarise(
    num_days = n_distinct(date),
    num_tickers = n_distinct(ticker)
  )
#> # A tibble: 1 x 2
#>   num_days num_tickers
#>      <int>       <int>
#> 1     7301         100

To get some insight into how quickly the backtest runs and where the bottlenecks are, profvis is your friend:

library(profvis)
profvis({cash_backtest_original(backtest_df_long)}, interval = 0.01)

(To get deeper insight, you can extract the function logic as a series of expressions and pass these directly to profvis – but this shortcut is fine for our purposes)

profvis tells us that cash_backtest_original took about 7.5 seconds to run and that most of the time was spent messing around with data frames:

Optimising the rsims package for Fast Backtesting in R

Stay tuned for the next installment in which Kris will discuss how we can benefit from switching our data frames to matrixes.

Visit Robot Wealth to download the complete R script: https://robotwealth.com/optimising-the-rsims-package-for-fast-backtesting-in-r/.

Disclosure: Interactive Brokers

Information posted on IBKR Traders’ Insight that is provided by third-parties and not by Interactive Brokers does NOT constitute a recommendation by Interactive Brokers that you should contract for the services of that third party. Third-party participants who contribute to IBKR Traders’ Insight are independent of Interactive Brokers and Interactive Brokers does not make any representations or warranties concerning the services offered, their past or future performance, or the accuracy of the information provided by the third party. Past performance is no guarantee of future results.

This material is from Robot Wealth and is being posted with permission from Robot Wealth. The views expressed in this material are solely those of the author and/or Robot Wealth and IBKR is not endorsing or recommending any investment or trading discussed in the material. This material is not and should not be construed as an offer to sell or the solicitation of an offer to buy any security. To the extent that this material discusses general market activity, industry or sector trends or other broad based economic or political conditions, it should not be construed as research or investment advice. To the extent that it includes references to specific securities, commodities, currencies, or other instruments, those references do not constitute a recommendation to buy, sell or hold such security. This material does not and is not intended to take into account the particular financial conditions, investment objectives or requirements of individual customers. Before acting on this material, you should consider whether it is suitable for your particular circumstances and, as necessary, seek professional advice.

In accordance with EU regulation: The statements in this document shall not be considered as an objective or independent explanation of the matters. Please note that this document (a) has not been prepared in accordance with legal requirements designed to promote the independence of investment research, and (b) is not subject to any prohibition on dealing ahead of the dissemination or publication of investment research.

Any trading symbols displayed are for illustrative purposes only and are not intended to portray recommendations.

trading top