Title: | Automated Backtesting of Portfolios over Multiple Datasets |
---|---|
Description: | Automated backtesting of multiple portfolios over multiple datasets of stock prices in a rolling-window fashion. Intended for researchers and practitioners to backtest a set of different portfolios, as well as by a course instructor to assess the students in their portfolio design in a fully automated and convenient manner, with results conveniently formatted in tables and plots. Each portfolio design is easily defined as a function that takes as input a window of the stock prices and outputs the portfolio weights. Multiple portfolios can be easily specified as a list of functions or as files in a folder. Multiple datasets can be conveniently extracted randomly from different markets, different time periods, and different subsets of the stock universe. The results can be later assessed and ranked with tables based on a number of performance criteria (e.g., expected return, volatility, Sharpe ratio, drawdown, turnover rate, return on investment, computational time, etc.), as well as plotted in a number of ways with nice barplots and boxplots. |
Authors: | Daniel P. Palomar [cre, aut], Rui Zhou [aut] |
Maintainer: | Daniel P. Palomar <[email protected]> |
License: | GPL-3 |
Version: | 0.4.1 |
Built: | 2024-11-14 03:44:20 UTC |
Source: | https://github.com/dppalomar/portfoliobacktest |
Automated backtesting of multiple portfolios over multiple datasets of stock prices in a rolling-window fashion. Intended for researchers and practitioners to backtest a set of different portfolios, as well as by a course instructor to assess the students in their portfolio design in a fully automated and convenient manner, with results conveniently formatted in tables and plots. Each portfolio design is easily defined as a function that takes as input a window of the stock prices and outputs the portfolio weights. Multiple portfolios can be easily specified as a list of functions or as files in a folder. Multiple datasets can be conveniently extracted randomly from different markets, different time periods, and different subsets of the stock universe. The results can be later assessed and ranked with tables based on a number of performance criteria (e.g., expected return, volatility, Sharpe ratio, drawdown, turnover rate, return on investment, computational time, etc.), as well as plotted in a number of ways with nice barplots and boxplots.
stockDataDownload
, financialDataResample
,
portfolioBacktest
, backtestSelector
,
backtestTable
, backtestBoxPlot
, backtestLeaderboard
,
backtestChartCumReturn
, backtestChartDrawdown
, backtestChartStackedBar
backtestSummary
, summaryTable
, summaryBarPlot
For a quick help see the README file: GitHub-README.
For more details see the vignette: CRAN-vignette.
Daniel P. Palomar and Rui ZHOU
Add a new performance measure to backtests
add_performance(bt, name, fun, desired_direction = 1)
add_performance(bt, name, fun, desired_direction = 1)
bt |
Backtest results as produced by the function |
name |
String with name of new performance measure. |
fun |
Function to compute new performance measure from any element returned by
|
desired_direction |
Number indicating whether the new measure is desired to be larger (1), which is the default, or smaller (-1). |
List with the portfolio backtest results, see portfolioBacktest
.
Daniel P. Palomar
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function EWP_portfolio <- function(dataset, ...) { N <- ncol(dataset$adjusted) return(rep(1/N, N)) } # do backtest bt <- portfolioBacktest(list("EWP" = EWP_portfolio), dataset10) # add a new performance measure bt <- add_performance(bt, name = "SR arithmetic", fun = function(return, ...) PerformanceAnalytics::SharpeRatio.annualized(return, geometric = FALSE)) bt <- add_performance(bt, name = "avg leverage", desired_direction = -1, fun = function(w_bop, ...) if(anyNA(w_bop)) NA else mean(rowSums(abs(w_bop))))
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function EWP_portfolio <- function(dataset, ...) { N <- ncol(dataset$adjusted) return(rep(1/N, N)) } # do backtest bt <- portfolioBacktest(list("EWP" = EWP_portfolio), dataset10) # add a new performance measure bt <- add_performance(bt, name = "SR arithmetic", fun = function(return, ...) PerformanceAnalytics::SharpeRatio.annualized(return, geometric = FALSE)) bt <- add_performance(bt, name = "avg leverage", desired_direction = -1, fun = function(w_bop, ...) if(anyNA(w_bop)) NA else mean(rowSums(abs(w_bop))))
Create boxplot from a portfolio backtest obtained with the function
portfolioBacktest
. By default the boxplot is based on the
package ggplot2
(also plots a dot for each single backtest), but the user can also
specify a simple base plot.
backtestBoxPlot( bt, measure = "Sharpe ratio", ref_portfolio = NULL, type = c("ggplot2", "simple"), ... )
backtestBoxPlot( bt, measure = "Sharpe ratio", ref_portfolio = NULL, type = c("ggplot2", "simple"), ... )
bt |
Backtest results as produced by the function |
measure |
String to select a performane measure from
|
ref_portfolio |
Reference portfolio (whose measure will be subtracted). Default is |
type |
Type of plot. Valid options: |
... |
Additional parameters. For example:
|
Daniel P. Palomar and Rui Zhou
summaryBarPlot
, backtestChartCumReturn
,
backtestChartDrawdown
, backtestChartStackedBar
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can plot backtestBoxPlot(bt, "Sharpe ratio") backtestBoxPlot(bt, "Sharpe ratio", type = "simple")
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can plot backtestBoxPlot(bt, "Sharpe ratio") backtestBoxPlot(bt, "Sharpe ratio", type = "simple")
Create chart of the cumulative returns or wealth for a single backtest
obtained with the function portfolioBacktest
.
By default the chart is based on the package ggplot2
, but the user can also
specify a plot based on PerformanceAnalytics
.
backtestChartCumReturn( bt, portfolios = names(bt), dataset_num = 1, type = c("ggplot2", "simple"), ... )
backtestChartCumReturn( bt, portfolios = names(bt), dataset_num = 1, type = c("ggplot2", "simple"), ... )
bt |
Backtest results as produced by the function |
portfolios |
String with portfolio names to be charted. Default charts all portfolios in the backtest. |
dataset_num |
Dataset index to be charted. Default is |
type |
Type of plot. Valid options: |
... |
Additional parameters. |
Daniel P. Palomar and Rui Zhou
summaryBarPlot
, backtestBoxPlot
,
backtestChartDrawdown
, backtestChartStackedBar
, backtestChartSharpeRatio
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can chart backtestChartCumReturn(bt)
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can chart backtestChartCumReturn(bt)
Create chart of the drawdown for a single backtest
obtained with the function portfolioBacktest
.
By default the chart is based on the package ggplot2
, but the user can also
specify a plot based on PerformanceAnalytics
.
backtestChartDrawdown( bt, portfolios = names(bt), dataset_num = 1, type = c("ggplot2", "simple"), ... )
backtestChartDrawdown( bt, portfolios = names(bt), dataset_num = 1, type = c("ggplot2", "simple"), ... )
bt |
Backtest results as produced by the function |
portfolios |
String with portfolio names to be charted. Default charts all portfolios in the backtest. |
dataset_num |
Dataset index to be charted. Default is |
type |
Type of plot. Valid options: |
... |
Additional parameters. |
Daniel P. Palomar and Rui Zhou
summaryBarPlot
, backtestBoxPlot
,
backtestChartCumReturn
, backtestChartStackedBar
, backtestChartSharpeRatio
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can chart backtestChartDrawdown(bt)
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can chart backtestChartDrawdown(bt)
Create chart of the rolling Sharpe ratio over time for a single backtest
obtained with the function portfolioBacktest
.
By default the chart is based on the package ggplot2
, but the user can also
specify a plot based on PerformanceAnalytics
.
backtestChartSharpeRatio( bt, portfolios = names(bt), dataset_num = 1, lookback = 100, by = 1, gap = lookback, bars_per_year = 252, type = c("ggplot2", "simple"), ... )
backtestChartSharpeRatio( bt, portfolios = names(bt), dataset_num = 1, lookback = 100, by = 1, gap = lookback, bars_per_year = 252, type = c("ggplot2", "simple"), ... )
bt |
Backtest results as produced by the function |
portfolios |
String with portfolio names to be charted. Default charts all portfolios in the backtest. |
dataset_num |
Dataset index to be charted. Default is |
lookback |
Length of the lookback rolling window in periods (default is |
by |
Intervals at which the Sharpe ratio is to be calculated (default is equal to |
gap |
Initial number of periods to skip (default is equal to |
bars_per_year |
Number of bars/periods per year (default is |
type |
Type of plot. Valid options: |
... |
Additional parameters. |
Daniel P. Palomar and Rui Zhou
summaryBarPlot
, backtestBoxPlot
,
backtestChartCumReturn
, backtestChartStackedBar
, backtestChartDrawdown
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can chart backtestChartSharpeRatio(bt)
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can chart backtestChartSharpeRatio(bt)
Create chart of the weight allocation over time for a portfolio over a single
backtest obtained with the function portfolioBacktest
.
By default the chart is based on the package ggplot2
, but the user can also
specify a plot based on PerformanceAnalytics
.
backtestChartStackedBar( bt, portfolio = names(bt[1]), dataset_num = 1, num_bars = 100, type = c("ggplot2", "simple"), legend = FALSE )
backtestChartStackedBar( bt, portfolio = names(bt[1]), dataset_num = 1, num_bars = 100, type = c("ggplot2", "simple"), legend = FALSE )
bt |
Backtest results as produced by the function |
portfolio |
String with portfolio name to be charted. Default charts the first portfolio in the backtest. |
dataset_num |
Dataset index to be charted. Default is |
num_bars |
Number of bars shown over time (basically a downsample of the possibly long sequence). |
type |
Type of plot. Valid options: |
legend |
Boolean to choose whether legend is plotted or not. Default is |
Daniel P. Palomar and Rui Zhou
summaryBarPlot
, backtestBoxPlot
,
backtestChartCumReturn
, backtestChartDrawdown
library(portfolioBacktest) data(dataset10) # load dataset # for better illustration, let's use only the first 5 stocks dataset10_5stocks <- lapply(dataset10, function(x) {x$adjusted <- x$adjusted[, 1:5]; return(x)}) # define GMVP (with heuristic not to allow shorting) GMVP_portfolio_fun <- function(dataset, ...) { X <- diff(log(dataset$adjusted))[-1] # compute log returns Sigma <- cov(X) # compute SCM # design GMVP w <- solve(Sigma, rep(1, nrow(Sigma))) w <- abs(w)/sum(abs(w)) return(w) } # backtest bt <- portfolioBacktest(list("GMVP" = GMVP_portfolio_fun), dataset10_5stocks, rebalance_every = 20) # now we can chart backtestChartStackedBar(bt, "GMVP", type = "simple") backtestChartStackedBar(bt, "GMVP", type = "simple", legend = TRUE) backtestChartStackedBar(bt, "GMVP") backtestChartStackedBar(bt, "GMVP", legend = TRUE)
library(portfolioBacktest) data(dataset10) # load dataset # for better illustration, let's use only the first 5 stocks dataset10_5stocks <- lapply(dataset10, function(x) {x$adjusted <- x$adjusted[, 1:5]; return(x)}) # define GMVP (with heuristic not to allow shorting) GMVP_portfolio_fun <- function(dataset, ...) { X <- diff(log(dataset$adjusted))[-1] # compute log returns Sigma <- cov(X) # compute SCM # design GMVP w <- solve(Sigma, rep(1, nrow(Sigma))) w <- abs(w)/sum(abs(w)) return(w) } # backtest bt <- portfolioBacktest(list("GMVP" = GMVP_portfolio_fun), dataset10_5stocks, rebalance_every = 20) # now we can chart backtestChartStackedBar(bt, "GMVP", type = "simple") backtestChartStackedBar(bt, "GMVP", type = "simple", legend = TRUE) backtestChartStackedBar(bt, "GMVP") backtestChartStackedBar(bt, "GMVP", legend = TRUE)
Leaderboard of portfolios according to the backtesting results
and a ranking based on the combination of several performance criteria.
Since the different performance measures hava different ranges and distributions,
each is first transformed according to its empirical distribution function (along
the empirical distribution of the portfolios being ranked) to obtain percentile
scores. After that transformation, each of the measures has an empirical uniform
distribution in the interval [0, 100]
and can be weighted to obtain the final ranking.
backtestLeaderboard( bt = NA, weights = list(), summary_fun = median, show_benchmark = TRUE )
backtestLeaderboard( bt = NA, weights = list(), summary_fun = median, show_benchmark = TRUE )
bt |
Backtest results as produced by the function |
weights |
List of weights for the different performance measures as obtained
in |
summary_fun |
Summary function to be employed (e.g., |
show_benchmark |
Logical value indicating whether to include benchmarks in the summary (default is |
List with the following elements:
leaderboard_scores |
Matrix with the individual scores for the portfolios (as chosen in |
leaderboard_performance |
Matrix with all the performance measures for the portfolios. |
error_summary |
Error messages generated by each portfolio on each dataset. Useful for debugging and give feedback to the portfolio managers of the different portfolios. |
Daniel P. Palomar and Rui Zhou
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(quintile_portfolio, dataset10, benchmark = c("1/N", "index")) # see all performance measures available for the ranking backtestSummary(bt)$performance # show leaderboard leaderboard <- backtestLeaderboard(bt, weights = list("Sharpe ratio" = 6, "max drawdown" = 1, "ROT (bps)" = 1, "cpu time" = 1, "failure rate" = 1)) leaderboard$leaderboard_scores
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(quintile_portfolio, dataset10, benchmark = c("1/N", "index")) # see all performance measures available for the ranking backtestSummary(bt)$performance # show leaderboard leaderboard <- backtestLeaderboard(bt, weights = list("Sharpe ratio" = 6, "max drawdown" = 1, "ROT (bps)" = 1, "cpu time" = 1, "failure rate" = 1)) leaderboard$leaderboard_scores
Select the results from a portfolio backtest.
backtestSelector( bt, portfolio_index = NULL, portfolio_name = NULL, measures = NULL )
backtestSelector( bt, portfolio_index = NULL, portfolio_name = NULL, measures = NULL )
bt |
Backtest results as produced by the function |
portfolio_index |
Index number of a portfolio, e.g., |
portfolio_name |
String name of a portfolio, e.g., |
measures |
String vector to select performane measures (default is all) from
|
List with the following elements:
performance |
Performance measures selected by argument |
error |
Error status ( |
error_message |
Error messages generated by portfolio function over each dataset. Useful for debugging purposes. |
cpu time |
CPU usage by portfolio function over each dataset. |
portfolio |
Portfolio weights generated by portfolio function over each dataset. |
return |
Portfolio returns over each dataset. |
wealth |
Portfolio wealth (aka cumulative returns or cumulative P&L) over each dataset. |
Rui Zhou and Daniel P. Palomar
library(portfolioBacktest) data("dataset10") # load dataset # define your own portfolio function EWP_portfolio <- function(dataset, ...) { N <- ncol(dataset$adjusted) return(rep(1/N, N)) } # do backtest bt <- portfolioBacktest(list("EWP" = EWP_portfolio), dataset10) # extract your interested portfolio result bt_sel <- backtestSelector(bt, portfolio_name = "EWP") names(bt_sel)
library(portfolioBacktest) data("dataset10") # load dataset # define your own portfolio function EWP_portfolio <- function(dataset, ...) { N <- ncol(dataset$adjusted) return(rep(1/N, N)) } # do backtest bt <- portfolioBacktest(list("EWP" = EWP_portfolio), dataset10) # extract your interested portfolio result bt_sel <- backtestSelector(bt, portfolio_name = "EWP") names(bt_sel)
Summarize the results from a portfolio backtest.
backtestSummary( bt, portfolio_indexes = NA, portfolio_names = NA, summary_fun = median, show_benchmark = TRUE )
backtestSummary( bt, portfolio_indexes = NA, portfolio_names = NA, summary_fun = median, show_benchmark = TRUE )
bt |
Backtest results as produced by the function |
portfolio_indexes |
Numerical vector of portfolio indexes whose performance will be summarized,
e.g., |
portfolio_names |
String vector of portfolio names whose performance will be summarized,
e.g., |
summary_fun |
Summary function to be employed (e.g., |
show_benchmark |
Logical value indicating whether to include benchmarks in the summary (default is |
List with the following elements:
performance_summary |
Performance criteria:
|
error_message |
Error messages generated by each portfolio function over each dataset. Useful for debugging purposes. |
Rui Zhou and Daniel P. Palomar
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function EWP_portfolio <- function(dataset, ...) { N <- ncol(dataset$adjusted) return(rep(1/N, N)) } # do backtest bt <- portfolioBacktest(list("EWP" = EWP_portfolio), dataset10) # show the summary bt_sum <- backtestSummary(bt) names(bt_sum) bt_sum$performance_summary
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function EWP_portfolio <- function(dataset, ...) { N <- ncol(dataset$adjusted) return(rep(1/N, N)) } # do backtest bt <- portfolioBacktest(list("EWP" = EWP_portfolio), dataset10) # show the summary bt_sum <- backtestSummary(bt) names(bt_sum) bt_sum$performance_summary
Create table with the results from a portfolio backtest.
backtestTable( bt, portfolio_indexes = NA, portfolio_names = NA, show_benchmark = TRUE, measures = NULL )
backtestTable( bt, portfolio_indexes = NA, portfolio_names = NA, show_benchmark = TRUE, measures = NULL )
bt |
Backtest results as produced by the function |
portfolio_indexes |
Numerical vector of portfolio indexes whose performance will be summarized,
e.g., |
portfolio_names |
String vector of portfolio names whose performance will be summarized,
e.g., |
show_benchmark |
Logical value indicating whether to include benchmarks in the summary (default is |
measures |
String vector to select performane measures (default is all) from
|
List with the following elements:
<performance criterion> |
One item per performance measures as selected by argument |
error |
Error status ( |
cpu time |
CPU usage by each portfolio function over each dataset. |
error_message |
Error messages generated by each portfolio function over each dataset. Useful for debugging purposes. |
Rui Zhou and Daniel P. Palomar
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function EWP_portfolio <- function(dataset, ...) { N <- ncol(dataset$adjusted) return(rep(1/N, N)) } # do backtest bt <- portfolioBacktest(list("EWP" = EWP_portfolio), dataset10) # show the backtest results in table bt_tab <- backtestTable(bt) bt_tab[c("Sharpe ratio", "max drawdown")]
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function EWP_portfolio <- function(dataset, ...) { N <- ncol(dataset$adjusted) return(rep(1/N, N)) } # do backtest bt <- portfolioBacktest(list("EWP" = EWP_portfolio), dataset10) # show the backtest results in table bt_tab <- backtestTable(bt) bt_tab[c("Sharpe ratio", "max drawdown")]
Ten datasets of stock market data resampled from the S&P 500. Each resample contains a random selection of 50 stocks from the S&P 500 universe and a period of two years with a random initial point.
data(dataset10)
data(dataset10)
List of 10 datasets, each contains two xts
objects:
505 x 50 xts
with the adjusted prices of the 50 stocks
505 x 1 xts
with the market index prices
This function resamples the financial data (e.g., downloaded
with stockDataDownload
) to obtain many datasets for a
subsequent backtesting with portfolioBacktest
.
Given the original data, each resample is obtained by randomly
choosing a subset of the financial instruments and randomly choosing a
time period over the available long period.
financialDataResample( X, N_sample = 50, T_sample = 2 * 252, num_datasets = 10, rm_stocks_with_na = TRUE )
financialDataResample( X, N_sample = 50, T_sample = 2 * 252, num_datasets = 10, rm_stocks_with_na = TRUE )
X |
List of |
N_sample |
Desired number of financial instruments in each resample. |
T_sample |
Desired length of each resample (consecutive samples with a random initial time). |
num_datasets |
Number of resampled datasets (chosen randomly among the financial instrument universe). |
rm_stocks_with_na |
Logical value indicating whether to remove instruments with inner missing
values. Default is |
List of datasets resampled from X
.
Rui Zhou and Daniel P. Palomar
stockDataDownload
, portfolioBacktest
## Not run: library(portfolioBacktest) data(SP500_symbols) # download data from internet SP500_data <- stockDataDownload(stock_symbols = SP500_symbols, from = "2009-01-01", to = "2009-12-31") # generate 20 resamples from data, each with 10 stocks and one quarter continuous data my_dataset_list <- financialDataResample(SP500_data, N = 10, T = 252/4, num_datasets = 20) ## End(Not run)
## Not run: library(portfolioBacktest) data(SP500_symbols) # download data from internet SP500_data <- stockDataDownload(stock_symbols = SP500_symbols, from = "2009-01-01", to = "2009-12-31") # generate 20 resamples from data, each with 10 stocks and one quarter continuous data my_dataset_list <- financialDataResample(SP500_data, N = 10, T = 252/4, num_datasets = 20) ## End(Not run)
Portfolio functions usually contain some parameters that can be tuned.
This function creates multiple versions of a function with randomly chosen parameters.
After backtesting those portfolios, the plotting function plotPerformanceVsParams
can be used to show the performance vs parameters.
genRandomFuns(portfolio_fun, params_grid, name = "portfolio", N_funs = NULL)
genRandomFuns(portfolio_fun, params_grid, name = "portfolio", N_funs = NULL)
portfolio_fun |
Portfolio function with parameters unspecified. |
params_grid |
Named list containing for each parameter the possible values it can take. |
name |
String with the name of the portfolio function. |
N_funs |
Number of functions to be generated. |
Daniel P. Palomar and Rui Zhou
library(portfolioBacktest) # define GMVP with parameters "delay", "lookback", and "regularize" GMVP_portfolio_fun <- function(dataset, ...) { prices <- tail(lag(dataset$adjusted, delay), lookback) X <- diff(log(prices))[-1] Sigma <- cov(X) if (regularize) Sigma <- Sigma + 0.1 * mean(diag(Sigma)) * diag(ncol(Sigma)) # design GMVP w <- solve(Sigma, rep(1, ncol(Sigma))) return(w/sum(w)) } # generate the functions with random parameters portfolio_list <- genRandomFuns(portfolio_fun = GMVP_portfolio_fun, params_grid = list(lookback = c(100, 120, 140, 160), delay = c(0, 5, 10, 15, 20), regularize = c(FALSE, TRUE)), name = "GMVP", N_funs = 40) names(portfolio_list) portfolio_list[[1]] rlang::env_print(portfolio_list[[1]]) rlang::fn_env(portfolio_list[[1]])$lookback rlang::fn_env(portfolio_list[[1]])$delay rlang::fn_env(portfolio_list[[1]])$regularize
library(portfolioBacktest) # define GMVP with parameters "delay", "lookback", and "regularize" GMVP_portfolio_fun <- function(dataset, ...) { prices <- tail(lag(dataset$adjusted, delay), lookback) X <- diff(log(prices))[-1] Sigma <- cov(X) if (regularize) Sigma <- Sigma + 0.1 * mean(diag(Sigma)) * diag(ncol(Sigma)) # design GMVP w <- solve(Sigma, rep(1, ncol(Sigma))) return(w/sum(w)) } # generate the functions with random parameters portfolio_list <- genRandomFuns(portfolio_fun = GMVP_portfolio_fun, params_grid = list(lookback = c(100, 120, 140, 160), delay = c(0, 5, 10, 15, 20), regularize = c(FALSE, TRUE)), name = "GMVP", N_funs = 40) names(portfolio_list) portfolio_list[[1]] rlang::env_print(portfolio_list[[1]]) rlang::fn_env(portfolio_list[[1]])$lookback rlang::fn_env(portfolio_list[[1]])$delay rlang::fn_env(portfolio_list[[1]])$regularize
List portfolios with failures
listPortfoliosWithFailures(bt)
listPortfoliosWithFailures(bt)
bt |
Backtest results as produced by the function |
Daniel P. Palomar
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function portfolio_with_errors <- function(dataset, ...) { return(NA) } # do backtest bt <- portfolioBacktest(list("Portfolio with errors" = portfolio_with_errors), dataset10) listPortfoliosWithFailures(bt)
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function portfolio_with_errors <- function(dataset, ...) { return(NA) } # do backtest bt <- portfolioBacktest(list("Portfolio with errors" = portfolio_with_errors), dataset10) listPortfoliosWithFailures(bt)
Portfolio functions usually contain some parameters that can be tuned.
After generating multiple versions of a portfolio function with randomly chosen parameters
with the function genRandomFuns
and doing the backtesting, this function
can be used to plot the performance vs choice of parameters.
plotPerformanceVsParams( bt_all_portfolios, params_subset = NULL, name_performance = "Sharpe ratio", summary_fun = median )
plotPerformanceVsParams( bt_all_portfolios, params_subset = NULL, name_performance = "Sharpe ratio", summary_fun = median )
bt_all_portfolios |
Backtest results as produced by the function |
params_subset |
List of named parameters with a subset of the values to be considered. By default all the possible values will be considered. |
name_performance |
String with the name of the performance measure to be used. |
summary_fun |
Summary function to be employed (e.g., median or mean). Defult is median. |
Daniel P. Palomar and Rui Zhou
library(portfolioBacktest) # define GMVP with parameters "delay", "lookback", and "regularize" GMVP_portfolio_fun <- function(dataset, ...) { prices <- tail(lag(dataset$adjusted, delay), lookback) X <- diff(log(prices))[-1] Sigma <- cov(X) if (regularize) Sigma <- Sigma + 0.01*diag(ncol(Sigma)) # design GMVP w <- solve(Sigma, rep(1, ncol(Sigma))) return(w/sum(w)) } # generate the functions with random parameters portfolio_list <- genRandomFuns(portfolio_fun = GMVP_portfolio_fun, params_grid = list(lookback = c(100, 120, 140, 160), delay = c(0, 5, 10, 15, 20), regularize = c(FALSE, TRUE)), name = "GMVP", N_funs = 40) # backtest portfolios bt <- portfolioBacktest(portfolio_list, dataset10) # plot plotPerformanceVsParams(bt) plotPerformanceVsParams(bt, params_subset = list(regularize = TRUE)) plotPerformanceVsParams(bt, params_subset = list(delay = 5)) plotPerformanceVsParams(bt, params_subset = list(delay = 5, regularize = TRUE))
library(portfolioBacktest) # define GMVP with parameters "delay", "lookback", and "regularize" GMVP_portfolio_fun <- function(dataset, ...) { prices <- tail(lag(dataset$adjusted, delay), lookback) X <- diff(log(prices))[-1] Sigma <- cov(X) if (regularize) Sigma <- Sigma + 0.01*diag(ncol(Sigma)) # design GMVP w <- solve(Sigma, rep(1, ncol(Sigma))) return(w/sum(w)) } # generate the functions with random parameters portfolio_list <- genRandomFuns(portfolio_fun = GMVP_portfolio_fun, params_grid = list(lookback = c(100, 120, 140, 160), delay = c(0, 5, 10, 15, 20), regularize = c(FALSE, TRUE)), name = "GMVP", N_funs = 40) # backtest portfolios bt <- portfolioBacktest(portfolio_list, dataset10) # plot plotPerformanceVsParams(bt) plotPerformanceVsParams(bt, params_subset = list(regularize = TRUE)) plotPerformanceVsParams(bt, params_subset = list(delay = 5)) plotPerformanceVsParams(bt, params_subset = list(delay = 5, regularize = TRUE))
Automated backtesting of multiple portfolios over multiple
datasets of stock prices in a rolling-window fashion.
Each portfolio design is easily defined as a
function that takes as input a window of the stock prices and outputs the
portfolio weights. Multiple portfolios can be easily specified as a list
of functions or as files in a folder. Multiple datasets can be conveniently
obtained with the function financialDataResample
that resamples
the data downloaded with the function stockDataDownload
.
The results can be later assessed and arranged with tables and plots.
The backtesting can be highly time-consuming depending on the number of
portfolios and datasets can be performed with parallel computation over
multiple cores. Errors in functions are properly catched and handled so
that the execution of the overal backtesting is not stopped (error messages
are stored for debugging purposes). See
vignette
for a detailed explanation.
portfolioBacktest( portfolio_funs = NULL, dataset_list, folder_path = NULL, source_to_local = TRUE, price_name = NULL, paral_portfolios = 1, paral_datasets = 1, show_progress_bar = FALSE, benchmarks = NULL, shortselling = TRUE, leverage = Inf, lookback = 252, T_rolling_window = NULL, optimize_every = 20, rebalance_every = 1, bars_per_year = 252, execution = c("same period", "next period"), cost = list(buy = 0, sell = 0, short = 0, long_leverage = 0), cpu_time_limit = Inf, return_portfolio = TRUE, return_returns = TRUE )
portfolioBacktest( portfolio_funs = NULL, dataset_list, folder_path = NULL, source_to_local = TRUE, price_name = NULL, paral_portfolios = 1, paral_datasets = 1, show_progress_bar = FALSE, benchmarks = NULL, shortselling = TRUE, leverage = Inf, lookback = 252, T_rolling_window = NULL, optimize_every = 20, rebalance_every = 1, bars_per_year = 252, execution = c("same period", "next period"), cost = list(buy = 0, sell = 0, short = 0, long_leverage = 0), cpu_time_limit = Inf, return_portfolio = TRUE, return_returns = TRUE )
portfolio_funs |
List of functions (can also be a single function), each of them taking as input
a dataset containing a list of |
dataset_list |
List of datasets, each containing a list of |
folder_path |
If |
source_to_local |
Logical value indicating whether to source files to local environment (default is |
price_name |
Name of the |
paral_portfolios |
Interger indicating number of portfolios to be evaluated in parallel (default is |
paral_datasets |
Interger indicating number of datasets to be evaluated in parallel (default is |
show_progress_bar |
Logical value indicating whether to show progress bar (default is |
benchmarks |
String vector indicating the benchmark portfolios to be incorporated, currently supports:
|
shortselling |
Logical value indicating whether shortselling is allowed or not
(default is |
leverage |
Amount of leverage as in |
lookback |
Length of the lookback rolling window in periods (default is |
T_rolling_window |
Deprecated: use |
optimize_every |
How often the portfolio is to be optimized in periods (default is |
rebalance_every |
How often the portfolio is to be rebalanced in periods (default is |
bars_per_year |
Number of bars/periods per year. By default it will be calculated automatically (e.g., for daily data there are 252 bars/periods per year). |
execution |
String that can be either |
cost |
List containing four different types of transaction costs (common for all assets)
for buying, selling, shorting, and long leveraging. The default is
|
cpu_time_limit |
Time limit for executing each portfolio function over a single data set
(default is |
return_portfolio |
Logical value indicating whether to return the portfolios (default is |
return_returns |
Logical value indicating whether to return the portfolio returns (default is |
List with the portfolio backtest results, see
vignette-result-format
for details. It can be accessed directly, but we highly recommend the use of the package specific functions to extract
any required information, namely, backtestSelector
, backtestTable
,
backtestBoxPlot
, backtestLeaderboard
,
backtestSummary
, summaryTable
, summaryBarPlot
.
Daniel P. Palomar and Rui Zhou
stockDataDownload
, financialDataResample
,
backtestSelector
, backtestTable
,
backtestBoxPlot
, backtestLeaderboard
,
backtestSummary
, summaryTable
, summaryBarPlot
.
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function ewp_portfolio <- function(dataset, ...) { N <- ncol(dataset$adjusted) return(rep(1/N, N)) } # do backtest bt <- portfolioBacktest(list("EWP" = ewp_portfolio), dataset10) # check your result names(bt) backtestSelector(bt, portfolio_name = "EWP", measures = c("Sharpe ratio", "max drawdown")) backtestTable(bt, measures = c("Sharpe ratio", "max drawdown")) bt_summary <- backtestSummary(bt) summaryTable(bt_summary)
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function ewp_portfolio <- function(dataset, ...) { N <- ncol(dataset$adjusted) return(rep(1/N, N)) } # do backtest bt <- portfolioBacktest(list("EWP" = ewp_portfolio), dataset10) # check your result names(bt) backtestSelector(bt, portfolio_name = "EWP", measures = c("Sharpe ratio", "max drawdown")) backtestTable(bt, measures = c("Sharpe ratio", "max drawdown")) bt_summary <- backtestSummary(bt) summaryTable(bt_summary)
Stock symbols of the S&P 500 constituents
data(SP500_symbols)
data(SP500_symbols)
String vector of stock symbols of the S&P 500 constituents. The market index symbol is concluded as the attribute "index_symbol".
This function is basically a robust wrapper for
quantmod:getSymbols
to download stock
data from the internet. It will return 6 xts
objects of the same
dimensions named 'open', 'high', 'low', 'close', 'volume', 'adjusted'
and 'index'. Additionally, it can return an xts
object with an
index. If the download for some stock fails after a few attempts they
will be ignored and reported. Also, stocks with missing values can be
optionally removed.
stockDataDownload( stock_symbols, index_symbol = NULL, from, to, rm_stocks_with_na = TRUE, local_file_path = getwd(), ... )
stockDataDownload( stock_symbols, index_symbol = NULL, from, to, rm_stocks_with_na = TRUE, local_file_path = getwd(), ... )
stock_symbols |
String vector containing the symbols of the stocks to be downloaded. User can pass the market index symbol as its attribute 'index_symbol“ (only considered when argument 'index_symbol' is not passed). |
index_symbol |
String of the market index symbol. |
from |
String as the starting date, e.g., "2017-08-17". |
to |
String as the ending date (not included), e.g., "2017-09-17". |
rm_stocks_with_na |
Logical value indicating whether to remove stocks with missing values
(ignoring leading missing values). Default is |
local_file_path |
Path where the stock data will be saved after the first time is downloaded,
so that in future retrievals it will be locally loaded (if the same
arguments are used). Default is |
... |
Additional arguments to be passed to |
List of 7 xts
objects named 'open', 'high', 'low', 'close', 'volume',
'adjusted' and 'index'. Note that 'index' will only be returned when correct index symbols is passed.
Rui Zhou and Daniel P. Palomar
## Not run: library(portfolioBacktest) data(SP500_symbols) # download data from internet SP500_data <- stockDataDownload(stock_symbols = SP500_symbols, from = "2009-01-01", to = "2009-12-31") ## End(Not run)
## Not run: library(portfolioBacktest) data(SP500_symbols) # download data from internet SP500_data <- stockDataDownload(stock_symbols = SP500_symbols, from = "2009-01-01", to = "2009-12-31") ## End(Not run)
This function is deprecated. Use instead financialDataResample()
.
stockDataResample( X, N_sample = 50, T_sample = 2 * 252, num_datasets = 10, rm_stocks_with_na = TRUE )
stockDataResample( X, N_sample = 50, T_sample = 2 * 252, num_datasets = 10, rm_stocks_with_na = TRUE )
X |
List of |
N_sample |
Desired number of financial instruments in each resample. |
T_sample |
Desired length of each resample (consecutive samples with a random initial time). |
num_datasets |
Number of resampled datasets (chosen randomly among the financial instrument universe). |
rm_stocks_with_na |
Logical value indicating whether to remove instruments with inner missing
values. Default is |
After performing a backtest with portfolioBacktest
and obtaining a summary of the performance measures with
backtestSummary
, this function creates a barplot from the summary.
By default the plot is based on the package ggplot2
, but the user
can also specify a simple base plot.
summaryBarPlot(bt_summary, measures = NULL, type = c("ggplot2", "simple"), ...)
summaryBarPlot(bt_summary, measures = NULL, type = c("ggplot2", "simple"), ...)
bt_summary |
Backtest summary as obtained from the function |
measures |
String vector to select performane measures (default is all) from 'Sharpe ratio', 'max drawdown', 'annual return', 'annual volatility', 'Sterling ratio', 'Omega ratio', 'ROT bps', etc. |
type |
Type of plot. Valid options: |
... |
Additional parameters (only used for plot |
Daniel P. Palomar and Rui Zhou
summaryTable
, backtestBoxPlot
,
backtestChartCumReturn
, backtestChartDrawdown
,
backtestChartStackedBar
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can obtain the table bt_summary_median <- backtestSummary(bt) summaryBarPlot(bt_summary_median, measures = c("max drawdown", "annual volatility")) summaryBarPlot(bt_summary_median, measures = c("max drawdown", "annual volatility"), type = "simple")
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can obtain the table bt_summary_median <- backtestSummary(bt) summaryBarPlot(bt_summary_median, measures = c("max drawdown", "annual volatility")) summaryBarPlot(bt_summary_median, measures = c("max drawdown", "annual volatility"), type = "simple")
After performing a backtest with portfolioBacktest
and obtaining a summary of the performance measures with
backtestSummary
, this function creates a table from the summary.
By default the table is a simple matrix, but if the user has installed the
package DT
or grid.table
nicer tables can be generated.
summaryTable( bt_summary, measures = NULL, caption = "Performance table", type = c("simple", "DT", "kable", "grid.table"), digits = 2, order_col = NULL, order_dir = c("asc", "desc"), page_length = 10 )
summaryTable( bt_summary, measures = NULL, caption = "Performance table", type = c("simple", "DT", "kable", "grid.table"), digits = 2, order_col = NULL, order_dir = c("asc", "desc"), page_length = 10 )
bt_summary |
Backtest summary as obtained from the function |
measures |
String vector to select performane measures (default is all) from 'Sharpe ratio', 'max drawdown', 'annual return', 'annual volatility', 'Sterling ratio', 'Omega ratio', 'ROT bps', etc. |
caption |
Table caption (only works for |
type |
Type of table. Valid options: |
digits |
Integer indicating the number of decimal places when rounding (default is 2). |
order_col |
Column number or column name of the performance measure to be used to
sort the rows (only used for table |
order_dir |
Direction to be used to sort the rows (only used for table
|
page_length |
Page length for the table (only used for table |
Daniel P. Palomar and Rui Zhou
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can obtain the table bt_summary_median <- backtestSummary(bt) summaryTable(bt_summary_median, measures = c("max drawdown", "annual volatility")) summaryTable(bt_summary_median, measures = c("max drawdown", "annual volatility"), type = "DT") summaryTable(bt_summary_median, type = "kable") # this returned kable object can be combined with: " |> kableExtra::kable_styling()"
library(portfolioBacktest) data(dataset10) # load dataset # define your own portfolio function quintile_portfolio <- function(data, ...) { X <- diff(log(data$adjusted))[-1] N <- ncol(X) ranking <- sort(colMeans(X), decreasing = TRUE, index.return = TRUE)$ix w <- rep(0, N) w[ranking[1:round(N/5)]] <- 1/round(N/5) return(w) } # do backtest bt <- portfolioBacktest(list("Quintile" = quintile_portfolio), dataset10, benchmark = c("1/N", "index")) # now we can obtain the table bt_summary_median <- backtestSummary(bt) summaryTable(bt_summary_median, measures = c("max drawdown", "annual volatility")) summaryTable(bt_summary_median, measures = c("max drawdown", "annual volatility"), type = "DT") summaryTable(bt_summary_median, type = "kable") # this returned kable object can be combined with: " |> kableExtra::kable_styling()"