Chapter 13 Parallel computing: An application in Finance
Parallel computing is about reducing computing time by simply dividing tasks and allocating each task to a core of your server or computer.
We will use R here and write parallel code within the context of the “apply” function. We will use the parallel library.
# Generate data and computation on a single core
data <- 1:1e9
data_list <- list("1" = data,
"2" = data,
"3" = data,
"4" = data)# Single core
time_benchmark <- system.time(
lapply(data_list, mean)
)
This calculation took 23.2 seconds. Below, the same computation is implemented using the multi-core equivalent of lapply (parLapply) from the parallel library:
library(parallel) # Detect the number of available cores and create cluster
cl <- parallel::makeCluster(detectCores())# Run parallel computation
time_parallel <- system.time(
parallel::parLapply(cl,
data_list,
mean)
)# Close cluster
parallel::stopCluster(cl)
This calculation took 5.83 seconds.
Another way to parallelize is to use the “foreach” function. To be noted, foreach can be modified to run on a single core by changing %dopar% to %do% :
## Loading required package: foreach
## Loading required package: iterators
library(parallel)
library(foreach)# Detect the number of available cores and create cluster
cl <- parallel::makeCluster(detectCores())# Activate cluster for foreach library
doParallel::registerDoParallel(cl)
time_foreach <- system.time({
r <- foreach::foreach(i = 1:length(data_list),
.combine = rbind) %dopar% {
mean(data_list[[i]])
}
})
time_foreach[3]# Stop cluster to free up resources
## elapsed
## 5.899
At 5.83 seconds, the computation time is not significantly different to that of the parLapply implementation.
13.1 An application to portfolio optimisation
# Load parallelisation libraries
library(doParallel)
library(foreach)
library(parallel)# Load finance and optimization libraries
library(PerformanceAnalytics)
## Loading required package: xts
## Loading required package: zoo
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
##
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
##
## legend
## Loading required package: ROI
## Registered S3 method overwritten by 'ROI':
## method from
## print.constraint PortfolioAnalytics
## ROI: R Optimization Infrastructure
## Registered solver plugins: nlminb, glpk, quadprog.
## Default solver: auto.
##
## Attaching package: 'ROI'
## The following objects are masked from 'package:PortfolioAnalytics':
##
## is.constraint, objective
## Loading required package: ROI.plugin.glpk
## Loading required package: ROI.plugin.quadprog
## Loading required package: quadprog
# Load sample return data from the PerformanceAnalytics library
lookback <- 120 # lookback in months
returns <- tail(PerformanceAnalytics::edhec, lookback)
# Create portfolio object
names_funds <- colnames(returns)
port.obj <- PortfolioAnalytics::portfolio.spec(assets = names_funds)
port.obj <- PortfolioAnalytics::add.constraint(portfolio = port.obj,
type = "full_weight")
port.obj <- PortfolioAnalytics::add.constraint(portfolio = port.obj,
type="long_only")
port.obj <- add.objective(portfolio=port.obj,
type='risk',
name='ETL',
arguments=list(p=0.95))
# Define maximum achievable return
ER_assets <- colMeans(returns)
ER_assets_max <- max(ER_assets)# Calculation of return of minimum risk portfolio
weights_ES_min <- PortfolioAnalytics::optimize.portfolio(
R = returns,
portfolio = port.obj,
optimize_method = "ROI",
trace = FALSE)$weights
ER_ES_min <- sum(weights_ES_min * ER_assets)# Vector of return targets
n_portfolios <- 500
return_targets <- seq(ER_ES_min,
ER_assets_max,
length.out = n_portfolios)
# Write optimization function that returns exactly what we need.
optimise <- function(port.obj,
return_target) {
port.obj <- PortfolioAnalytics::add.constraint(
portfolio = port.obj,
type="return",
return_target = return_target)
out <- PortfolioAnalytics::optimize.portfolio(
R = returns,
portfolio = port.obj,
optimize_method = "ROI",
trace = FALSE)
return(c(out$weights, out$objective_measures$ETL))
}
# Activate cluster for foreach library and pass libraries
cl <- parallel::makeCluster(detectCores())
doParallel::registerDoParallel(cl)
data_frontier_par <- foreach::foreach(
i = 1:n_portfolios,
.combine = rbind,
.packages = c("PortfolioAnalytics")) %dopar% {
optimise(port.obj, return_targets[i])
}
parallel::stopCluster(cl)